Switch to Inter, round the corners softly and make the nav switch tabs

Inter replaces General Sans (Inter Display for titles and big numbers).
Corners come back from before the squaring commit, scaled to a soft set
(16 cards, 14 buttons, 12 rows, 8 chips) and applied to the screens built
since. The nav is now a cluster of round frosted buttons that switches
Today, Routines, Goals, Groups and Progress in place inside Home: no
pushed pages, no back arrows, and Android back returns to Today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
alvocool
2026-11-09 07:08:46 +03:00
parent add94f621c
commit d3431ca5de
54 changed files with 736 additions and 518 deletions

View File

@@ -111,7 +111,8 @@ status field. That's what makes honest history and excuse analysis possible.
See `design.md`. In short: sage-to-cream ambient backdrop, frosted glass
controls, painted `SceneArt` instead of photos, image-led cards, two-tone
greetings, square night buttons with a lime spark, no rounded corners.
greetings, night buttons with a lime spark, softly rounded corners (never
pill-soft) and a floating round-button nav that switches tabs in place.
- Screens are built from `Sheet` (`designs/Shell.dart`); Home, Routines and
the routine player compose `AmbientBackground` + glass directly.
@@ -121,7 +122,7 @@ greetings, square night buttons with a lime spark, no rounded corners.
- Standing owns a colour used consistently everywhere (chrome, chip, meter).
- One primary button per screen; everything else outlined or text.
Font: **General Sans** (Fontshare, FFL — see `fonts/LICENSE-GeneralSans.txt`),
Font: **Inter** (SIL OFL — see `fonts/LICENSE-Inter.txt`; Light is Inter Display),
wrapped as `TextType.{Bold,Medium,Regular,Light}`.
---

View File

@@ -30,7 +30,11 @@ build their own layouts from the same pieces.
The sections below describe the original record-style type scale, which still
applies to labels and metrics.
## Type — General Sans
## Type — Inter
`TextType.Light` is Inter Display Regular (the display cut, for titles and
big numbers); Regular, Medium and Bold are Inter Regular, Medium and
SemiBold.
| Role | Family | Size | Use |
|---|---|---|---|
@@ -45,7 +49,8 @@ directly above a large dark value. Used everywhere numbers appear.
## Geometry
- No rounded corners anywhere: sheets, cards, chips, buttons, fields, tiles and portraits are all square (`BorderRadius.zero`). Only small status dots, glows, the sun, progress rings and the breathing circle stay round.
- Softly rounded, never pill-soft: large cards and image cards `16`, buttons `14`, rows, tiles and fields `12`, small controls `10`, chips and badges `8`, bars `4`. Sheets round their top at `20`.
- Portraits, the nav buttons, status dots, rings and the breathing circle are full circles.
- Screen padding `20` horizontal. Card padding `16`. Section gap `28`.
- Hairlines at `colorDivider` (black 7%), borders at `colorBorder` (black 8%).
- One soft shadow only: `0 4 18 rgba(0,0,0,0.03)`. No coloured glows.
@@ -66,6 +71,14 @@ Near-black ink (`colorPrimaryDark #141414`) on warm off-white
The tier colour is used consistently — header, chip, meter, queue accent — so
the standing is legible without reading the word.
## Navigation
The nav is a floating cluster of round frosted buttons, icons only; the
open tab is a raised white disc. It switches tabs inside Home (Today,
Routines, Goals, Groups, Progress) in place: nothing is pushed, a tab has
no back arrow (`tab: true`), and Android back returns to Today. Tabs are
built on first open and then kept.
## Structure
Every screen is: black `headerBar` (back, title, action) → white sheet with a

View File

@@ -1,57 +0,0 @@
Fontshare EULA
---—---------------------------------—------------------------------
Free Font - End User License Agreement (FF EULA)
---—---------------------------------—------------------------------
Notice to User
Indian Type Foundry designs, produces and distributes font software as digital fonts to end users worldwide. In addition to commercial fonts that are available for a fee, ITF also offers several fonts which can be used free of charge. The free fonts are distributed through a dedicated platform called www.fontshare.com (“Fontshare”) to end users worldwide. These free fonts are subject to this legally binding EULA between the Indian Type Foundry (“Indian Type Foundry” or “Licensor”) and you (“Licensee”). 
You acknowledge that the Font Software and designs embodied therein are protected by the copyright, other intellectual property rights and industrial property rights and by international treaties. They are and remain at all times the intellectual property of the Indian Type Foundry.
In addition to direct download, Fontshare also offers these free fonts via Fonthsare API using a code. In this case, the Font Software is delivered directly from the servers used by Indian Type Foundry to the Licensee's website, without the Licensee having to download the Font Software.
By downloading, accessing the API, installing, storing, copying or using one of any Font Software, you agree to the following terms. 
Definitions
“Font Software” refers to the set of computer files or programs released under this license that instructs your computer to display and/or print each letters, characters, typographic designs, ornament and so forth. Font Software includes all bitmap and vector representations of fonts and typographic representations and embellishments created by or derived from the Font Software. 
“Original Version” refers to the Font Software as distributed by the Indian Type Foundry as the copyright holder. 
“Derivative Work” refers to the pictorial representation of the font created by the Font Software, including typographic characters such as letters, numerals, ornaments, symbols, or punctuation and special characters.
01. Grant of License
You are hereby granted a non-exclusive, non-assignable, non-transferrable, terminable license to access, download and use the Font Software for your personal or commercial use for an unlimited period of time for free of charge. 
You may use the font Software in any media (including Print, Web, Mobile, Digital, Apps, ePub, Broadcasting and OEM) at any scale, at any location worldwide. 
You may use the Font Software to create logos and other graphic elements, images on any surface, vector files or other scalable drawings and static images. 
You may use the Font Software on any number of devices (computer, tablet, phone). The number of output devices (Printers) is not restricted. 
You may make only such reasonable number of back-up copies suitable to your permitted use. 
You may but are not required to identify Indian Type Foundry Fonts in your work credits. 
02. Limitations of usage
You may not modify, edit, adapt, translate, reverse engineer, decompile or disassemble, alter or otherwise copy the Font Software or the designs embodied therein in whole or in part, without the prior written consent of the Licensor. 
The Fonts may not - beyond the permitted copies and the uses defined herein - be distributed, duplicated, loaned, resold or licensed in any way, whether by lending, donating or give otherwise to a person or entity. This includes the distribution of the Fonts by e-mail, on USB sticks, CD-ROMs, or other media, uploading them in a public server or making the fonts available on peer-to-peer networks. A passing on to external designers or service providers (design agencies, repro studios, printers, etc.) is also not permitted. 
You are not allowed to transmit the Font Software over the Internet in font serving or for font replacement by means of technologies such as but not limited to EOT, Cufon, sIFR or similar technologies that may be developed in the future without the prior written consent of the Licensor. 
03. Embedding
You may embed the Font Software in PDF and other digital documents provided that is done in a secured, read-only mode. It must be ensured beyond doubt that the recipient cannot use the Font Software to edit or to create new documents. The design data (PDFs) created in this way and under these created design data (PDFs) may be distributed in any number. 
The extraction of the Font Software in whole or in part is prohibited. 
04. Third party use, Commercial print service provider
You may include the Font Software in a non-editable electronic document solely for printing and display purposes and provide that electronic document to the commercial print service provider for the purpose of printing. If the print service needs to install the fonts, they too need to download the Font Software from the Licensor's website.
05. Derivative Work
You are allowed to make derivative works as far as you use them for your personal or commercial use. However, you cannot modify, make changes or reverse engineer the original font software provided to you. Any derivative works are the exclusive property of the Licensor and shall be subject to the terms and conditions of this EULA. Derivative works may not be sub-licensed, sold, leased, rented, loaned, or given away without the express written permission of the Licensor. 
06. Warranty and Liability
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, INDIAN TYPE FOUNDRY MAKES NO WARRANTIES, EXPRESS OR IMPLIED AS TO THE MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR OTHERWISE. THE FONT SOFTWARE WAS NOT MANUFACTURED FOR USE IN MANUFACTURING CONTROL DEVICES OR NAVIGATION DEVICES OR IN CIRCUMSTANCES THAT COULD RESULT IN ENVIRONMENTAL DAMAGE OR PERSONAL INJURY. WITHOUT LIMITING THE FOREGOING, INDIAN TYPE FOUNDRY SHALL IN NO EVENT BE LIABLE TO THE LICENSED USER OR ANY OTHER THIRD PARTY FOR ANY DIRECT, CONSEQUENTIAL OR INCIDENTAL DAMAGES, INCLUDING DAMAGES FROM LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION NOR FOR LOST PROFITS OR SAVINGS ARISING OUT OF THE USE OR INABILITY TO USE THE PRODUCT EVEN IF NOTIFIED IN ADVANCE, UNDER NO CIRCUMSTANCES SHALL INDIAN TYPE FOUNDRY’S LIABILITY EXCEED THE REPLACEMENT COST OF THE SOFTWARE. 
IF LICENSEE CHOOSES TO ACCESS THE FONT SOFTWARE THROUGH A CODE (API), IT MAY HAVE A DIRECT IMPACT ON LICENSEE'S WEBSITE OR APPLICATIONS. INDIAN TYPE FOUNDRY IS NOT RESPONSIBLE OR LIABLE FOR ANY INTERRUPTION, MALFUNCTION, DOWNTIME OR OTHER FAILURE OF THE WEBSITE OR ITS API.
07. Updates, Maintenance and Support Services
Licensor will not provide you with any support services for the Software under this Agreement.
08. Termination 
Any breach of the terms of this agreement shall be a cause for termination, provided that such breach is notified in writing to the Licensee by the Licensor and the Licensee failed to rectify the breach within 30 days of the receipt of such notification. 
In the event of termination and without limitation of any remedies under law or equity, you must delete the Font Software and all copies thereof. Proof of this must be provided upon request of the Licensor.  
We reserve the right to claim damages for the violation of the conditions. 
09. Final Provisions
If individual provisions of this agreement are or become invalid, the validity of the remaining provisions shall remain unaffected. Invalid provisions shall be replaced by mutual agreement by such provisions that are suitable to achieve the desired economic purpose, taking into account the interests of both parties. The same shall apply mutatis mutandis to the filling of any gaps which may arise in this agreement.
This contract is subject to laws of the Republic of India. Place of performance and exclusive place of jurisdiction for all disputes between the parties arising out of or in connection with this contract is, as far as legally permissible, Ahmedabad, India.
- 
Last Updated on 22 March 2021
Copyright 2021 Indian Type Foundry. All rights reserved. 

View File

@@ -0,0 +1,92 @@
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -48,7 +48,7 @@ class TickBox extends StatelessWidget {
height: size,
decoration: BoxDecoration(
color: checked ? tone : Colors.transparent,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: checked ? tone : colorGrey.withValues(alpha: 0.55),
width: 1.6,
@@ -202,7 +202,7 @@ Widget addTaskAffordance(
alignment: Alignment.center,
decoration: BoxDecoration(
color: tone.withValues(alpha: 0.12),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: tone.withValues(alpha: 0.25), width: 1),
),
child: Icon(Icons.add_rounded, size: 22, color: tone),

View File

@@ -5,7 +5,7 @@ import '../utils/Colors.dart';
import 'text/Text.dart';
/// The font families, wrapped so no screen ever names a family directly.
/// General Sans — see fonts/LICENSE-GeneralSans.txt.
/// Inter (SIL OFL) — see fonts/LICENSE-Inter.txt.
String getTextType(TextType type) {
switch (type) {
case TextType.Bold:
@@ -32,7 +32,7 @@ Widget pill(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: background,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: foreground.withValues(alpha: 0.20), width: 1),
),
child: Row(
@@ -61,7 +61,7 @@ Widget card({
EdgeInsets margin = EdgeInsets.zero,
Color? background,
Color? borderColor,
double radius = 22,
double radius = 16,
VoidCallback? onTap,
}) {
final Widget body = Container(
@@ -70,7 +70,7 @@ Widget card({
margin: margin,
decoration: BoxDecoration(
color: background ?? colorCard,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(radius),
border: Border.all(color: borderColor ?? colorGlassBorder, width: 1),
boxShadow: const [
BoxShadow(
@@ -159,6 +159,7 @@ Widget emptyState(
height: 76,
decoration: BoxDecoration(
color: tone.withValues(alpha: 0.08),
shape: BoxShape.circle,
),
child: Icon(icon, size: 32, color: tone),
),
@@ -190,7 +191,7 @@ Widget meter(
: fraction;
return ClipRRect(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
child: LinearProgressIndicator(
value: clamped,
minHeight: height,

View File

@@ -1,125 +1,100 @@
import 'package:flutter/material.dart';
import '../about/internal/application/TextType.dart';
import '../utils/Colors.dart';
import 'GlassSurface.dart';
import 'text/Text.dart';
typedef NavEntry = ({IconData icon, String label, VoidCallback onTap});
/// The floating tab bar: the selected tab is a filled night block, the rest
/// sit quietly on white. An optional dark action button can sit beside it.
/// The floating tab bar: a cluster of round frosted buttons, icons only.
/// The open tab rises out of the row as a solid white disc. It switches tabs
/// in place; nothing here pushes a page.
class FloatingNav extends StatelessWidget {
final List<NavEntry> entries;
final int selected;
final IconData? actionIcon;
final VoidCallback? onAction;
const FloatingNav({
super.key,
required this.entries,
required this.selected,
this.actionIcon,
this.onAction,
});
static const double _size = 58;
@override
Widget build(BuildContext context) {
return SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 14),
child: Row(
children: [
Expanded(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Center(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
boxShadow: const [
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(_size)),
boxShadow: [
BoxShadow(
color: Color(0x1A1A1F1C),
color: Color(0x1F1A1F1C),
blurRadius: 30,
offset: Offset(0, 10),
offset: Offset(0, 12),
),
],
),
child: GlassSurface(
radius: BorderRadius.zero,
tint: colorGlassStrong,
padding: const EdgeInsets.all(6),
radius: const BorderRadius.all(Radius.circular(_size)),
tint: colorGlass,
padding: const EdgeInsets.all(5),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
for (int index = 0; index < entries.length; index++)
Expanded(child: _tab(entries[index], index == selected)),
],
),
),
),
),
if (onAction != null) ...[
const SizedBox(width: 10),
GestureDetector(
onTap: onAction,
child: Container(
width: 72,
height: 72,
decoration: BoxDecoration(
gradient: const RadialGradient(
center: Alignment(-0.3, -0.4),
colors: <Color>[colorNightGlow, colorNight],
),
border: Border.all(color: colorWhite, width: 3),
boxShadow: const [
BoxShadow(
color: Color(0x331A1F1C),
blurRadius: 24,
offset: Offset(0, 10),
),
],
),
child: Icon(actionIcon, size: 24, color: colorLime),
),
),
for (int index = 0; index < entries.length; index++) ...[
if (index > 0) const SizedBox(width: 4),
_button(entries[index], index == selected),
],
],
),
),
),
),
),
);
}
Widget _tab(NavEntry entry, bool active) {
return GestureDetector(
Widget _button(NavEntry entry, bool active) {
return Semantics(
button: true,
selected: active,
label: entry.label,
child: Tooltip(
message: entry.label,
child: GestureDetector(
onTap: entry.onTap,
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 220),
curve: Curves.easeOutCubic,
padding: const EdgeInsets.symmetric(vertical: 10),
width: _size,
height: _size,
alignment: Alignment.center,
decoration: BoxDecoration(
color: active ? colorHeaderBottom : colorWhite.withValues(alpha: 0.0),
borderRadius: BorderRadius.zero,
shape: BoxShape.circle,
color: active ? colorWhite : colorWhite.withValues(alpha: 0.35),
border: Border.all(
color: colorWhite.withValues(alpha: active ? 1 : 0.6),
width: 1,
),
boxShadow: active
? const [
BoxShadow(
color: Color(0x141A1F1C),
blurRadius: 12,
offset: Offset(0, 4),
color: Color(0x261A1F1C),
blurRadius: 16,
offset: Offset(0, 6),
),
]
: const [],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(entry.icon,
size: 23, color: active ? colorWhite : colorInkSoft),
const SizedBox(height: 4),
text(entry.label, 10, active ? TextType.Medium : TextType.Regular,
color: active ? colorWhite : colorInkSoft, maxLines: 1),
],
child: Icon(entry.icon,
size: 22, color: active ? colorInk : colorInkSoft),
),
),
),
);

View File

@@ -23,7 +23,7 @@ class GlassSurface extends StatelessWidget {
const GlassSurface({
super.key,
required this.child,
this.radius = const BorderRadius.all(Radius.zero),
this.radius = const BorderRadius.all(Radius.circular(14)),
this.padding = EdgeInsets.zero,
this.tint,
this.onDark = false,
@@ -73,7 +73,7 @@ Widget glassCircle(
children: [
GlassSurface(
onDark: onDark,
radius: BorderRadius.zero,
radius: BorderRadius.circular(size),
child: SizedBox(
width: size,
height: size,

View File

@@ -32,8 +32,8 @@ Future<Goal?> showGoalPicker(
decoration: const BoxDecoration(
color: colorSheetBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: ListView(
@@ -93,7 +93,7 @@ Widget _goalOption(
padding: const EdgeInsets.fromLTRB(14, 13, 14, 13),
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: selected ? accent.withValues(alpha: 0.5) : colorBorder,
width: selected ? 1.5 : 1,

View File

@@ -6,7 +6,7 @@ import '../utils/CommonUtils.dart';
import 'scene/SceneArt.dart';
import 'text/Text.dart';
/// A member's square portrait: a landscape picked from their name with the
/// A member's round portrait: a landscape picked from their name with the
/// initial on it, so everyone is recognisable without a photo.
Widget memberAvatar(String name, double size, {Color? border}) {
const List<List<Color>> palettes = <List<Color>>[
@@ -21,17 +21,22 @@ Widget memberAvatar(String name, double size, {Color? border}) {
return Container(
width: size,
height: size,
decoration:
BoxDecoration(border: Border.all(color: border ?? colorWhite, width: 2)),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: border ?? colorWhite, width: 2),
),
child: ClipOval(
child: Stack(
fit: StackFit.expand,
children: [
SceneArt(palette: palettes[seed % palettes.length], seed: seed),
Center(
child: text(initial, size * 0.42, TextType.Medium, color: colorWhite),
child:
text(initial, size * 0.42, TextType.Medium, color: colorWhite),
),
],
),
),
);
}
@@ -61,6 +66,7 @@ Widget avatarStack(List<String> names, double size,
height: size,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colorNight,
border: Border.all(color: border ?? colorWhite, width: 2),
),

View File

@@ -27,6 +27,9 @@ class Sheet extends StatelessWidget {
/// Optional trailing control in the chrome.
final Widget? action;
/// Space under the content; tabs leave room for the floating nav.
final double bottomSpace;
/// Stage colour. Leaving it null (with no banner) gives the ambient tone.
final Color? chrome;
@@ -53,6 +56,7 @@ class Sheet extends StatelessWidget {
this.scrollable = true,
this.scene,
this.sceneSeed = 0,
this.bottomSpace = 40,
});
bool get _light => chrome == null && banner == null && scene == null;
@@ -62,8 +66,7 @@ class Sheet extends StatelessWidget {
final bool light = _light;
final Color stage = chrome ?? colorPrimaryDark;
final Color ink = light ? colorInk : colorWhite;
final Color soft =
light ? colorInkSoft : colorWhite.withValues(alpha: 0.5);
final Color soft = light ? colorInkSoft : colorWhite.withValues(alpha: 0.5);
final Widget header = SafeArea(
bottom: false,
@@ -80,8 +83,7 @@ class Sheet extends StatelessWidget {
? null
: Align(
alignment: Alignment.centerLeft,
child: glassCircle(
CupertinoIcons.arrow_left, onBack!,
child: glassCircle(CupertinoIcons.arrow_left, onBack!,
onDark: !light),
),
),
@@ -136,14 +138,14 @@ class Sheet extends StatelessWidget {
decoration: BoxDecoration(
color: light ? colorFrost : colorPrimaryLight,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
clipBehavior: Clip.antiAlias,
child: scrollable
? SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 28, 20, 40),
padding: EdgeInsets.fromLTRB(20, 28, 20, bottomSpace),
child: child,
)
: Padding(

View File

@@ -13,7 +13,7 @@ Widget roundedCornerButton(
Color? foreground,
IconData? icon,
bool enabled = true,
double radius = 99,
double radius = 14,
double verticalPadding = 16,
}) {
final Color bg = enabled ? (background ?? colorNight) : colorGrey;
@@ -27,7 +27,7 @@ Widget roundedCornerButton(
disabledBackgroundColor: colorGrey.withValues(alpha: 0.4),
padding: EdgeInsets.symmetric(vertical: verticalPadding),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(radius),
),
),
child: Row(
@@ -55,7 +55,7 @@ Widget outlinedActionButton(
Color? foreground,
IconData? icon,
bool enabled = true,
double radius = 99,
double radius = 14,
}) {
final Color fg = enabled ? (foreground ?? colorPrimaryDark) : colorGrey;
@@ -65,7 +65,7 @@ Widget outlinedActionButton(
side: BorderSide(color: fg.withValues(alpha: 0.35), width: 1),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(radius),
),
),
child: Row(
@@ -112,7 +112,7 @@ Widget iconButton(
alignment: Alignment.center,
decoration: BoxDecoration(
color: background ?? Colors.transparent,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(radius),
border: bordered
? Border.all(color: borderColor ?? colorBorder, width: 1)
: null,
@@ -161,7 +161,7 @@ Widget segmentedSelector<T>({
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: isSelected ? active : colorWhite,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isSelected ? active : colorBorder,
width: 1,

View File

@@ -17,7 +17,7 @@ Widget countStepper(
padding: const EdgeInsets.fromLTRB(14, 10, 8, 10),
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: colorBorder, width: 1),
),
child: Row(
@@ -54,7 +54,7 @@ Widget _stepButton(IconData icon, VoidCallback? onTap) {
alignment: Alignment.center,
decoration: BoxDecoration(
color: enabled ? colorSecondary : colorInset,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon,
size: 14, color: enabled ? colorPrimaryDark : colorGrey),

View File

@@ -54,19 +54,19 @@ Widget inputField(
contentPadding:
const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorBorder, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorPrimaryDark, width: 1.4),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorNegative, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorNegative, width: 1.4),
),
errorStyle: TextStyle(
@@ -163,19 +163,19 @@ Widget excuseField(
fillColor: colorWhite,
contentPadding: const EdgeInsets.all(14),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorBorder, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorPrimaryDark, width: 1.4),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorNegative, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: colorNegative, width: 1.4),
),
errorStyle: TextStyle(
@@ -212,7 +212,7 @@ Widget selectField(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 16),
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: colorBorder, width: 1),
),
child: Row(

View File

@@ -443,8 +443,8 @@ class NewCommitmentState extends State<NewCommitment>
decoration: BoxDecoration(
color: colorSheetBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: SingleChildScrollView(
@@ -460,7 +460,7 @@ class NewCommitmentState extends State<NewCommitment>
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: colorGrey.withValues(alpha: 0.35),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
),
),

View File

@@ -142,7 +142,7 @@ class ExcuseReportState extends State<ExcuseReport>
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: colorInset,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: colorBorder, width: 1),
),
child: Row(

View File

@@ -242,7 +242,7 @@ class GoalDetailState extends State<GoalDetail>
padding: const EdgeInsets.fromLTRB(16, 14, 16, 14),
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.08),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -405,7 +405,7 @@ class GoalDetailState extends State<GoalDetail>
width: 58,
height: 58,
child: ClipRRect(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
child: SceneArt(
palette: scenePalette(routine.kind),
seed: sceneSeed(routine.title),
@@ -454,6 +454,7 @@ class GoalDetailState extends State<GoalDetail>
color: routine.kind == RoutineKind.MustDo
? colorPrimaryDark
: tone,
shape: BoxShape.circle,
),
child: Icon(CupertinoIcons.play_fill,
size: 15, color: colorWhite),

View File

@@ -282,7 +282,7 @@ class GoalEditorState extends State<GoalEditor> implements ConnectGoalEditor {
return Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -338,6 +338,7 @@ class GoalEditorState extends State<GoalEditor> implements ConnectGoalEditor {
alignment: Alignment.center,
decoration: BoxDecoration(
color: colour,
shape: BoxShape.circle,
border: Border.all(
color: selected ? colorWhite : Colors.transparent,
width: 3,

View File

@@ -3,7 +3,11 @@ import 'package:flutter/material.dart';
import 'GoalsState.dart';
class Goals extends StatefulWidget {
const Goals({super.key});
/// Shown as a tab inside Home: no back arrow, and room at the bottom
/// for the nav.
final bool tab;
const Goals({super.key, this.tab = false});
@override
State<Goals> createState() => GoalsState();

View File

@@ -74,7 +74,8 @@ class GoalsState extends State<Goals> implements ConnectGoals {
return Sheet(
eyebrow: "Grounded",
title: "Goals",
onBack: _onBack,
onBack: widget.tab ? null : _onBack,
bottomSpace: widget.tab ? 130 : 40,
action: chromeAction(CupertinoIcons.add, _onNewGoal),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -120,7 +121,7 @@ class GoalsState extends State<Goals> implements ConnectGoals {
height: 260,
margin: const EdgeInsets.only(bottom: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: Color(0x262A3A30),
@@ -130,7 +131,7 @@ class GoalsState extends State<Goals> implements ConnectGoals {
],
),
child: ClipRRect(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(16),
child: Stack(
fit: StackFit.expand,
children: [
@@ -151,7 +152,7 @@ class GoalsState extends State<Goals> implements ConnectGoals {
children: [
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
child: text(

View File

@@ -119,8 +119,8 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
final GroupMessage? older = index + 1 < newestFirst.length
? newestFirst[index + 1]
: null;
final bool newDay = older == null ||
!_sameDay(older.sentAt, message.sentAt);
final bool newDay =
older == null || !_sameDay(older.sentAt, message.sentAt);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -145,6 +145,7 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
return Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(12)),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -198,9 +199,12 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.7),
child: text(_dayName(moment), 11, TextType.Medium,
color: colorInkSoft),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child:
text(_dayName(moment), 11, TextType.Medium, color: colorInkSoft),
),
),
);
@@ -241,7 +245,10 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
alignment: Alignment.centerRight,
child: Container(
padding: const EdgeInsets.fromLTRB(14, 11, 14, 10),
decoration: BoxDecoration(
color: colorNight,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
@@ -273,6 +280,7 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
child: Container(
padding: const EdgeInsets.fromLTRB(14, 10, 14, 10),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(12)),
color: colorWhite,
border: Border.all(color: colorGlassBorder),
),
@@ -308,6 +316,7 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
child: Container(
padding: const EdgeInsets.fromLTRB(12, 12, 14, 12),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(12)),
color: checkIn ? colorStandingGoodBg : colorOverdueWarmEnd,
border: Border(
left: BorderSide(
@@ -357,7 +366,10 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
Widget _composer() {
return Container(
decoration: BoxDecoration(
color: colorWhite,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
padding: EdgeInsets.fromLTRB(
0, 10, 0, MediaQuery.of(context).padding.bottom + 10),
child: Column(
@@ -375,7 +387,10 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14),
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorAmbientMid,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: text(_quickReplies[index], 13, TextType.Regular,
color: colorInk),
),
@@ -396,7 +411,10 @@ class GroupChatState extends State<GroupChat> implements ConnectGroupChat {
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: colorNight,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Icon(CupertinoIcons.paperplane_fill,
size: 20, color: colorLime),
),

View File

@@ -137,8 +137,8 @@ class GroupDetailState extends State<GroupDetail>
_amount.text = formatAmount(value).replaceAll(",", "");
}
void _onSubmitLog(BuildContext sheetContext, GroupGoal goal,
GroupObjective objective) {
void _onSubmitLog(
BuildContext sheetContext, GroupGoal goal, GroupObjective objective) {
final double amount = double.tryParse(_amount.text.trim()) ?? 0;
if (amount <= 0) {
return;
@@ -258,7 +258,10 @@ class GroupDetailState extends State<GroupDetail>
behavior: HitTestBehavior.opaque,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: colorAmbientMid,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
@@ -292,6 +295,7 @@ class GroupDetailState extends State<GroupDetail>
child: Container(
padding: const EdgeInsets.fromLTRB(20, 18, 16, 18),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -317,16 +321,18 @@ class GroupDetailState extends State<GroupDetail>
Icon(CupertinoIcons.chat_bubble_2_fill,
size: 16, color: colorLime),
const SizedBox(width: 8),
text("Group chat", 14, TextType.Medium,
color: colorLime),
text("Group chat", 14, TextType.Medium, color: colorLime),
if (_group.unread > 0) ...[
const SizedBox(width: 10),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 7, vertical: 3),
decoration: BoxDecoration(
color: colorLime,
child: text("${_group.unread} new", 10,
TextType.Medium,
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: text(
"${_group.unread} new", 10, TextType.Medium,
color: colorNight),
),
],
@@ -351,9 +357,12 @@ class GroupDetailState extends State<GroupDetail>
Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.12),
child: Icon(CupertinoIcons.arrow_right,
size: 20, color: colorWhite),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child:
Icon(CupertinoIcons.arrow_right, size: 20, color: colorWhite),
),
],
),
@@ -409,7 +418,9 @@ class GroupDetailState extends State<GroupDetail>
),
const SizedBox(height: 12),
meter(goal.progress,
fill: tone, track: tone.withValues(alpha: 0.14), height: 6),
fill: tone,
track: tone.withValues(alpha: 0.14),
height: 6),
const SizedBox(height: 8),
],
),
@@ -426,6 +437,7 @@ class GroupDetailState extends State<GroupDetail>
return Container(
padding: const EdgeInsets.fromLTRB(20, 14, 14, 16),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(12)),
border: Border(top: BorderSide(color: colorBorder)),
),
child: Row(
@@ -472,7 +484,10 @@ class GroupDetailState extends State<GroupDetail>
onTap: () => _onLog(goal, objective),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: colorNight,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -560,7 +575,10 @@ class GroupDetailState extends State<GroupDetail>
child: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: nudged ? colorAmbientMid : colorNight,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Icon(
nudged
? CupertinoIcons.checkmark
@@ -579,13 +597,13 @@ class GroupDetailState extends State<GroupDetail>
/// Log progress: an amount (with one-tap presets that suit the unit) and
/// an optional note that rides along into the chat.
Widget _logSheet(BuildContext sheetContext, GroupGoal goal,
GroupObjective objective) {
Widget _logSheet(
BuildContext sheetContext, GroupGoal goal, GroupObjective objective) {
final List<double> presets = _presets(objective.unit);
return Padding(
padding: EdgeInsets.fromLTRB(20, 16, 20,
MediaQuery.of(sheetContext).viewInsets.bottom + 28),
padding: EdgeInsets.fromLTRB(
20, 16, 20, MediaQuery.of(sheetContext).viewInsets.bottom + 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
@@ -612,9 +630,12 @@ class GroupDetailState extends State<GroupDetail>
child: Container(
padding: const EdgeInsets.symmetric(vertical: 14),
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorAmbientMid,
child: text("+${formatAmount(preset)}", 15,
TextType.Medium,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: text(
"+${formatAmount(preset)}", 15, TextType.Medium,
color: colorInk),
),
),
@@ -636,8 +657,8 @@ class GroupDetailState extends State<GroupDetail>
const SizedBox(height: 14),
inputField("Note", _note, hint: "Optional: posted with the check-in"),
const SizedBox(height: 22),
roundedCornerButton("Log it",
() => _onSubmitLog(sheetContext, goal, objective),
roundedCornerButton(
"Log it", () => _onSubmitLog(sheetContext, goal, objective),
icon: CupertinoIcons.checkmark),
],
),

View File

@@ -99,7 +99,8 @@ class GroupEditorState extends State<GroupEditor>
}
if (_joining) {
_model?.join(JoinGroupRequest(inviteCode: _code.text.trim().toUpperCase()));
_model
?.join(JoinGroupRequest(inviteCode: _code.text.trim().toUpperCase()));
} else {
_model?.create(GroupRequest(
name: _name.text.trim(),
@@ -130,9 +131,8 @@ class GroupEditorState extends State<GroupEditor>
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
displayTitle(_joining
? "Got a code?"
: "Who's keeping\nyou honest?"),
displayTitle(
_joining ? "Got a code?" : "Who's keeping\nyou honest?"),
const SizedBox(height: 22),
segmentedSelector<String>(
options: const <String>["Create", "Join"],
@@ -221,6 +221,7 @@ class GroupEditorState extends State<GroupEditor>
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(12)),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -237,8 +238,7 @@ class GroupEditorState extends State<GroupEditor>
size: 16, color: colorWhite.withValues(alpha: 0.7)),
const SizedBox(width: 8),
text("1 MEMBER", 9, TextType.Bold,
color: colorWhite.withValues(alpha: 0.6),
letterSpacing: 1.2),
color: colorWhite.withValues(alpha: 0.6), letterSpacing: 1.2),
],
),
const SizedBox(height: 14),
@@ -266,6 +266,7 @@ class GroupEditorState extends State<GroupEditor>
height: 40,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(12)),
color: colour,
border: Border.all(
color: selected ? colorWhite : Colors.transparent,

View File

@@ -195,8 +195,7 @@ class GroupGoalEditorState extends State<GroupGoalEditor>
maxLines: 2,
),
const SizedBox(height: 28),
sectionBreak("Objectives",
caption: "what everyone logs toward"),
sectionBreak("Objectives", caption: "what everyone logs toward"),
text(
"Break the goal into numbers people can add to: kilometres, sessions, pages, hours. Each member logs their share and the totals add up.",
12,
@@ -288,7 +287,8 @@ class GroupGoalEditorState extends State<GroupGoalEditor>
keyboard: const TextInputType.numberWithOptions(
decimal: true),
formatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r"[0-9.]")),
FilteringTextInputFormatter.allow(
RegExp(r"[0-9.]")),
],
),
),

View File

@@ -3,7 +3,11 @@ import 'package:flutter/material.dart';
import 'GroupsState.dart';
class Groups extends StatefulWidget {
const Groups({super.key});
/// Shown as a tab inside Home: no back arrow, and room at the bottom
/// for the nav.
final bool tab;
const Groups({super.key, this.tab = false});
@override
State<Groups> createState() => GroupsState();

View File

@@ -86,7 +86,8 @@ class GroupsState extends State<Groups> implements ConnectGroups {
return Sheet(
eyebrow: "Grounded",
title: "Groups",
onBack: _onBack,
onBack: widget.tab ? null : _onBack,
bottomSpace: widget.tab ? 130 : 40,
action: chromeAction(CupertinoIcons.add, _onNewGroup),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -133,6 +134,7 @@ class GroupsState extends State<Groups> implements ConnectGroups {
height: 250,
margin: const EdgeInsets.only(bottom: 14),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
boxShadow: [
BoxShadow(
color: Color(0x262A3A30),
@@ -141,6 +143,7 @@ class GroupsState extends State<Groups> implements ConnectGroups {
),
],
),
clipBehavior: Clip.antiAlias,
child: Stack(
fit: StackFit.expand,
children: [
@@ -175,7 +178,10 @@ class GroupsState extends State<Groups> implements ConnectGroups {
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: colorLime,
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -23,7 +23,8 @@ class ViewGroupChat extends ParentViewModel {
connection.onMessagesLoaded(getGroupMessageList(response.data));
} catch (e) {
handleError(e, () => loadMessages(groupId), () => dismissError(), "Retry");
handleError(
e, () => loadMessages(groupId), () => dismissError(), "Retry");
}
}

View File

@@ -42,8 +42,8 @@ class ViewGroupDetail extends ParentViewModel {
connection.onProgressLogged(Group.fromJson(response.data), summary);
} catch (e) {
handleError(e, () => logProgress(request, summary),
() => dismissError(), "Retry");
handleError(e, () => logProgress(request, summary), () => dismissError(),
"Retry");
}
}

View File

@@ -81,6 +81,10 @@ class HomeState extends State<Home> implements ConnectHome {
bool _weekly = false;
/// The open tab, and the tabs built so far.
int _tab = 0;
final Set<int> _opened = <int>{0};
Standing _standing = Standing.Good;
@@ -183,28 +187,14 @@ class HomeState extends State<Home> implements ConnectHome {
_model?.tick(item);
}
void _onOpenProgress() {
GroundedNavigation().navigateToPageWithData(const Progress(), context);
}
void _onOpenReportCard() {
GroundedNavigation().navigateToPage(
NavigatorType.justOpen, const ReportCardScreen(), context);
}
void _onOpenGoals() async {
final result = await GroundedNavigation()
.navigateToPageWithData(const Goals(), context);
void _onOpenGoals() => _onNavGoals();
if (result == true) {
_model?.loadPlan();
}
}
void _onOpenGroups() async {
await GroundedNavigation().navigateToPageWithData(const Groups(), context);
_model?.loadGroups();
}
void _onOpenGroups() => _onNavGroups();
void _onOpenGroupChat(Group group) async {
await GroundedNavigation()
@@ -224,11 +214,7 @@ class HomeState extends State<Home> implements ConnectHome {
.navigateToPage(NavigatorType.justOpen, const Training(), context);
}
void _onOpenRoutines() async {
await GroundedNavigation()
.navigateToPageWithData(const Routines(), context);
_model?.loadRoutines();
}
void _onOpenRoutines() => _onNavRoutines();
void _onOpenKind(RoutineKind kind) async {
await GroundedNavigation()
@@ -260,7 +246,39 @@ class HomeState extends State<Home> implements ConnectHome {
.navigateToPage(NavigatorType.justOpen, const ExcuseReport(), context);
}
void _onNavToday() {}
/// The nav switches tabs in place; nothing is pushed. A tab is built the
/// first time it is opened and then kept, so it holds its scroll and data.
void _onSelectTab(int index) {
if (index == _tab) {
return;
}
setState(() {
_tab = index;
_opened.add(index);
});
// Back on Today: pick up whatever changed in the other tabs.
if (index == 0) {
_model?.loadRoutines();
_model?.loadGroups();
}
}
void _onNavToday() => _onSelectTab(0);
void _onNavRoutines() => _onSelectTab(1);
void _onNavGoals() => _onSelectTab(2);
void _onNavGroups() => _onSelectTab(3);
void _onNavProgress() => _onSelectTab(4);
void _onBackToToday(bool didPop, Object? result) {
if (!didPop) {
_onSelectTab(0);
}
}
void _onShowToday() {
setState(() {
@@ -288,8 +306,8 @@ class HomeState extends State<Home> implements ConnectHome {
return FadeTransition(
opacity: curve,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.12), end: Offset.zero)
position:
Tween<Offset>(begin: const Offset(0, 0.12), end: Offset.zero)
.animate(curve),
child: child,
),
@@ -389,14 +407,86 @@ class HomeState extends State<Home> implements ConnectHome {
final bool queueIsHome =
StandingEngine.showsOverdueQueueAsHome(_standing) && !_distressed;
return AnnotatedRegion<SystemUiOverlayStyle>(
// Back on another tab returns to Today rather than leaving the app.
return PopScope(
canPop: _tab == 0,
onPopInvokedWithResult: _onBackToToday,
child: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light,
child: Scaffold(
backgroundColor: colorAmbientMid,
body: AmbientBackground(
child: Stack(
children: [
SingleChildScrollView(
IndexedStack(
index: _tab,
children: <Widget>[
_todayView(queueIsHome),
_opened.contains(1)
? const Routines(tab: true)
: const SizedBox.shrink(),
_opened.contains(2)
? const Goals(tab: true)
: const SizedBox.shrink(),
_opened.contains(3)
? const Groups(tab: true)
: const SizedBox.shrink(),
_opened.contains(4)
? const Progress(tab: true)
: const SizedBox.shrink(),
],
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: FloatingNav(
selected: _tab,
entries: <NavEntry>[
(
icon: CupertinoIcons.house_fill,
label: "Today",
onTap: _onNavToday
),
(
icon: CupertinoIcons.square_stack_3d_up_fill,
label: "Routines",
onTap: _onNavRoutines
),
(
icon: CupertinoIcons.flag_fill,
label: "Goals",
onTap: _onNavGoals
),
(
icon: CupertinoIcons.person_3_fill,
label: "Groups",
onTap: _onNavGroups
),
(
icon: CupertinoIcons.chart_bar_alt_fill,
label: "Progress",
onTap: _onNavProgress
),
],
).animate().fadeIn(duration: 400.ms, delay: 250.ms).slideY(
begin: 0.4,
end: 0,
duration: 500.ms,
delay: 250.ms,
curve: Curves.easeOutCubic),
),
],
),
),
),
),
);
}
/// The Today tab: the forest header and the day under it.
Widget _todayView(bool queueIsHome) {
return SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 130),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -420,51 +510,6 @@ class HomeState extends State<Home> implements ConnectHome {
),
],
),
),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: FloatingNav(
selected: 0,
entries: <NavEntry>[
(
icon: CupertinoIcons.house_fill,
label: "Today",
onTap: _onNavToday
),
(
icon: CupertinoIcons.square_stack_3d_up_fill,
label: "Routines",
onTap: _onOpenRoutines
),
(
icon: CupertinoIcons.flag_fill,
label: "Goals",
onTap: _onOpenGoals
),
(
icon: CupertinoIcons.person_3_fill,
label: "Groups",
onTap: _onOpenGroups
),
(
icon: CupertinoIcons.chart_bar_alt_fill,
label: "Progress",
onTap: _onOpenProgress
),
],
).animate().fadeIn(duration: 400.ms, delay: 250.ms).slideY(
begin: 0.4,
end: 0,
duration: 500.ms,
delay: 250.ms,
curve: Curves.easeOutCubic),
),
],
),
),
),
);
}
@@ -475,6 +520,7 @@ class HomeState extends State<Home> implements ConnectHome {
Widget _header() {
return Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(20)),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
@@ -568,6 +614,7 @@ class HomeState extends State<Home> implements ConnectHome {
return Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: colorHeaderPanel,
border: Border.all(color: colorHeaderLine),
),
@@ -589,7 +636,12 @@ class HomeState extends State<Home> implements ConnectHome {
curve: Curves.easeOutCubic,
padding: const EdgeInsets.symmetric(vertical: 13),
alignment: Alignment.center,
color: active ? colorHeaderSelected : colorHeaderSelected.withValues(alpha: 0),
decoration: BoxDecoration(
color: active
? colorHeaderSelected
: colorHeaderSelected.withValues(alpha: 0),
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: text(label, 16, active ? TextType.Medium : TextType.Regular,
color: active ? colorWhite : colorHeaderMuted),
),
@@ -603,12 +655,19 @@ class HomeState extends State<Home> implements ConnectHome {
final DateTime first = DateTime(now.year, now.month, now.day)
.subtract(const Duration(days: 6));
const List<String> days = <String>[
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
];
return Container(
padding: const EdgeInsets.fromLTRB(18, 16, 18, 18),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: colorHeaderPanel,
border: Border.all(color: colorHeaderLine),
),
@@ -621,8 +680,7 @@ class HomeState extends State<Home> implements ConnectHome {
children: [
text("${_streak()}", 28, TextType.Medium, color: colorWhite),
const SizedBox(width: 8),
text("day streak", 16, TextType.Regular,
color: colorHeaderMuted),
text("day streak", 16, TextType.Regular, color: colorHeaderMuted),
const Spacer(),
Icon(Icons.eco, size: 32, color: colorLime),
],
@@ -733,10 +791,10 @@ class HomeState extends State<Home> implements ConnectHome {
/// The week in numbers: the average kept, a bar per day, and the classes.
Widget _weeklyNumbers() {
final Map<int, double> byDay = _week?.completionByWeekday ?? <int, double>{};
final double average = byDay.isEmpty
? 0
: byDay.values.reduce((a, b) => a + b) / byDay.length;
final Map<int, double> byDay =
_week?.completionByWeekday ?? <int, double>{};
final double average =
byDay.isEmpty ? 0 : byDay.values.reduce((a, b) => a + b) / byDay.length;
const List<String> letters = <String>["M", "T", "W", "T", "F", "S", "S"];
final DateTime now = DateTime.now();
final List<int> order = <int>[
@@ -781,12 +839,17 @@ class HomeState extends State<Home> implements ConnectHome {
duration: const Duration(milliseconds: 400),
curve: Curves.easeOutCubic,
height: 90 * (byDay[day] ?? 0).clamp(0.04, 1.0),
decoration: BoxDecoration(
color: (byDay[day] ?? 0) >= _keptFloor
? colorBarElective
: colorHeaderLine,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
const SizedBox(height: 8),
text(letters[day - 1], 13,
text(
letters[day - 1],
13,
day == now.weekday
? TextType.Medium
: TextType.Regular,
@@ -834,7 +897,9 @@ class HomeState extends State<Home> implements ConnectHome {
return Container(
width: 74,
height: 74,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
border: Border.all(color: colorWhite, width: 2),
),
child: Opacity(
@@ -899,7 +964,10 @@ class HomeState extends State<Home> implements ConnectHome {
const SizedBox(height: 9),
Container(
height: 7,
decoration: BoxDecoration(
color: colorHeaderLine,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
alignment: Alignment.centerLeft,
child: FractionallySizedBox(
widthFactor: fraction.clamp(0.0, 1.0),
@@ -978,23 +1046,29 @@ class HomeState extends State<Home> implements ConnectHome {
child: Container(
width: 54,
height: 54,
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: Icon(CupertinoIcons.xmark, size: 22, color: colorInk),
),
),
const SizedBox(height: 16),
Container(
width: double.infinity,
decoration: BoxDecoration(
color: colorWhite,
padding: EdgeInsets.fromLTRB(
18, 14, 18, MediaQuery.of(sheetContext).padding.bottom + 22),
borderRadius: BorderRadius.all(Radius.circular(12)),
),
padding: EdgeInsets.fromLTRB(18, 14, 18,
MediaQuery.of(sheetContext).padding.bottom + 22),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Container(
width: 40, height: 4, color: colorGrabber),
child:
Container(width: 40, height: 4, color: colorGrabber),
),
const SizedBox(height: 18),
text("Add to today", 24, TextType.Medium,
@@ -1023,12 +1097,12 @@ class HomeState extends State<Home> implements ConnectHome {
"Must-do", _onAddPickMustDo)),
const SizedBox(width: 10),
Expanded(
child: _addTile(CupertinoIcons.flag,
"Goal", _onAddPickGoal)),
child: _addTile(
CupertinoIcons.flag, "Goal", _onAddPickGoal)),
const SizedBox(width: 10),
Expanded(
child: _addTile(CupertinoIcons.person_3,
"Group", _onAddPickGroup)),
child: _addTile(CupertinoIcons.person_3, "Group",
_onAddPickGroup)),
],
),
const SizedBox(height: 10),
@@ -1049,7 +1123,10 @@ class HomeState extends State<Home> implements ConnectHome {
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 22),
decoration: BoxDecoration(
color: colorAmbientMid,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -1067,7 +1144,10 @@ class HomeState extends State<Home> implements ConnectHome {
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 22),
decoration: BoxDecoration(
color: colorAmbientMid,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@@ -1093,7 +1173,9 @@ class HomeState extends State<Home> implements ConnectHome {
return Container(
width: size,
height: size,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: colorWhite, width: 2),
),
child: Stack(
@@ -1101,8 +1183,8 @@ class HomeState extends State<Home> implements ConnectHome {
children: [
SceneArt(palette: sceneWorkout, seed: sceneSeed(_user.name)),
Center(
child: text(initial, size * 0.4, TextType.Medium,
color: colorWhite),
child:
text(initial, size * 0.4, TextType.Medium, color: colorWhite),
),
],
),
@@ -1195,7 +1277,7 @@ class HomeState extends State<Home> implements ConnectHome {
padding: const EdgeInsets.fromLTRB(20, 24, 20, 24),
decoration: BoxDecoration(
color: colorFrost,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.all(Radius.circular(16)),
border: Border.all(color: colorGlassBorder),
),
child: child,
@@ -1300,12 +1382,12 @@ class HomeState extends State<Home> implements ConnectHome {
children: [
_category(sceneWorkout, kindIcon(RoutineKind.Workout), "Workout", 3,
() => _onOpenKind(RoutineKind.Workout)),
_category(sceneMeditation, kindIcon(RoutineKind.Meditation),
"Meditate", 5, () => _onOpenKind(RoutineKind.Meditation)),
_category(sceneMeditation, kindIcon(RoutineKind.Meditation), "Meditate",
5, () => _onOpenKind(RoutineKind.Meditation)),
_category(sceneMustDo, kindIcon(RoutineKind.MustDo), "Must-do", 8,
() => _onOpenKind(RoutineKind.MustDo)),
_category(goalScene(goalAccents[4]), CupertinoIcons.flag_fill,
"Goals", 13, _onOpenGoals),
_category(goalScene(goalAccents[4]), CupertinoIcons.flag_fill, "Goals",
13, _onOpenGoals),
_category(goalScene(goalAccents[2]), CupertinoIcons.flame_fill,
"Training", 21, _onOpenTraining),
],
@@ -1324,6 +1406,7 @@ class HomeState extends State<Home> implements ConnectHome {
width: 74,
height: 74,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: colorWhite, width: 2.5),
boxShadow: const [
BoxShadow(
@@ -1333,7 +1416,7 @@ class HomeState extends State<Home> implements ConnectHome {
),
],
),
child: ClipRect(
child: ClipOval(
child: SceneArt(
palette: palette,
seed: seed,
@@ -1376,7 +1459,7 @@ class HomeState extends State<Home> implements ConnectHome {
child: Container(
width: 244,
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.all(Radius.circular(16)),
boxShadow: const [
BoxShadow(
color: Color(0x262A3A30),
@@ -1386,7 +1469,7 @@ class HomeState extends State<Home> implements ConnectHome {
],
),
child: ClipRRect(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.all(Radius.circular(16)),
child: Stack(
fit: StackFit.expand,
children: [
@@ -1407,7 +1490,7 @@ class HomeState extends State<Home> implements ConnectHome {
children: [
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.all(Radius.circular(14)),
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
child: text(
@@ -1515,7 +1598,9 @@ class HomeState extends State<Home> implements ConnectHome {
onTap: () => _onOpenGroupChat(group),
child: Container(
width: 250,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: colorWhite,
boxShadow: const [
BoxShadow(
@@ -1550,9 +1635,13 @@ class HomeState extends State<Home> implements ConnectHome {
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: colorLime,
child: text("${group.unread} new", 10,
TextType.Medium,
borderRadius:
BorderRadius.all(Radius.circular(12)),
),
child: text(
"${group.unread} new", 10, TextType.Medium,
color: colorNight),
),
const Spacer(),
@@ -1618,9 +1707,8 @@ class HomeState extends State<Home> implements ConnectHome {
sectionBreak(
"To-do",
caption: "${open.length} open, no debt",
trailing: done.isEmpty
? null
: _viewAll("Clear done", _onClearDoneTodos),
trailing:
done.isEmpty ? null : _viewAll("Clear done", _onClearDoneTodos),
),
GlassSurface(
tint: colorGlassStrong,
@@ -1645,7 +1733,10 @@ class HomeState extends State<Home> implements ConnectHome {
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: colorNight,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
alignment: Alignment.center,
child: Icon(CupertinoIcons.add,
size: 22, color: colorLime),
@@ -1691,6 +1782,7 @@ class HomeState extends State<Home> implements ConnectHome {
width: 30,
height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8)),
color: item.done ? colorPrimary : colorWhite,
border: Border.all(
color: item.done
@@ -1794,11 +1886,14 @@ class HomeState extends State<Home> implements ConnectHome {
"Your standing will recover as the debt decays.",
)
else
..._overdue.take(_standing == Standing.Lockdown ? 1 : _overdue.length)
..._overdue
.take(_standing == Standing.Lockdown ? 1 : _overdue.length)
.map(_commitmentRow),
const SizedBox(height: 24),
roundedCornerButton(
_standing == Standing.Lockdown ? "Deal with this one" : "Open the queue",
_standing == Standing.Lockdown
? "Deal with this one"
: "Open the queue",
_onOpenOverdue,
background: standingColor(_standing),
icon: CupertinoIcons.arrow_right,
@@ -1853,8 +1948,8 @@ class HomeState extends State<Home> implements ConnectHome {
const SizedBox(height: 24),
sectionBreak("Three things", caption: "that actually matter"),
..._plan
.where((item) =>
item.commitmentClass == CommitmentClass.NonNegotiable)
.where(
(item) => item.commitmentClass == CommitmentClass.NonNegotiable)
.take(3)
.map(_commitmentRow),
const SizedBox(height: 24),
@@ -1871,7 +1966,7 @@ class HomeState extends State<Home> implements ConnectHome {
onTap: _onOpenOverdue,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.all(Radius.circular(16)),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -1894,6 +1989,7 @@ class HomeState extends State<Home> implements ConnectHome {
height: 58,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const RadialGradient(
center: Alignment(-0.3, -0.4),
colors: <Color>[colorOverdueGlow, colorStandingGrounded],
@@ -1943,7 +2039,7 @@ class HomeState extends State<Home> implements ConnectHome {
child: Container(
padding: const EdgeInsets.fromLTRB(22, 22, 22, 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.all(Radius.circular(16)),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -1967,8 +2063,7 @@ class HomeState extends State<Home> implements ConnectHome {
const SizedBox(width: 6),
text("Pattern spotted", 12, TextType.Medium, color: colorLime),
const Spacer(),
text("${cluster.occurrences}× this month", 11,
TextType.Regular,
text("${cluster.occurrences}× this month", 11, TextType.Regular,
color: colorWhite.withValues(alpha: 0.5)),
],
),
@@ -2017,7 +2112,7 @@ class HomeState extends State<Home> implements ConnectHome {
width: 88,
height: 88,
child: ClipRRect(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.all(Radius.circular(12)),
child: Opacity(
opacity: ticked ? 0.45 : 1,
child: SceneArt(
@@ -2059,8 +2154,7 @@ class HomeState extends State<Home> implements ConnectHome {
const SizedBox(height: 5),
Row(
children: [
Icon(CupertinoIcons.clock, size: 11,
color: colorInkSoft),
Icon(CupertinoIcons.clock, size: 11, color: colorInkSoft),
const SizedBox(width: 4),
Flexible(
child: text(formatWindow(item), 11, TextType.Regular,
@@ -2069,11 +2163,9 @@ class HomeState extends State<Home> implements ConnectHome {
overflow: TextOverflow.ellipsis),
),
const SizedBox(width: 10),
Icon(CupertinoIcons.timer, size: 11,
color: colorInkSoft),
Icon(CupertinoIcons.timer, size: 11, color: colorInkSoft),
const SizedBox(width: 4),
text(formatMinutes(item.estMinutes), 11,
TextType.Regular,
text(formatMinutes(item.estMinutes), 11, TextType.Regular,
color: colorInkSoft),
],
),
@@ -2101,6 +2193,7 @@ class HomeState extends State<Home> implements ConnectHome {
width: 44,
height: 44,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: checked ? colorPrimary : colorWhite,
border: Border.all(
color: checked ? colorPrimary : accent.withValues(alpha: 0.35),

View File

@@ -184,8 +184,8 @@ class LiveTaskState extends State<LiveTask>
decoration: BoxDecoration(
color: colorSheetBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: SingleChildScrollView(

View File

@@ -107,8 +107,8 @@ class LoginState extends State<Login> implements ConnectLogin {
decoration: BoxDecoration(
color: colorPrimaryLight,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
clipBehavior: Clip.antiAlias,

View File

@@ -147,8 +147,8 @@ class OverdueQueueState extends State<OverdueQueue>
decoration: BoxDecoration(
color: colorSheetBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: SingleChildScrollView(
@@ -166,7 +166,7 @@ class OverdueQueueState extends State<OverdueQueue>
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: colorGrey.withValues(alpha: 0.35),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
),
),
@@ -242,8 +242,8 @@ class OverdueQueueState extends State<OverdueQueue>
decoration: BoxDecoration(
color: colorSheetBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: SingleChildScrollView(
@@ -261,7 +261,7 @@ class OverdueQueueState extends State<OverdueQueue>
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: colorGrey.withValues(alpha: 0.35),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
),
),
@@ -343,8 +343,8 @@ class OverdueQueueState extends State<OverdueQueue>
decoration: BoxDecoration(
color: colorSheetBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: SingleChildScrollView(
@@ -360,7 +360,7 @@ class OverdueQueueState extends State<OverdueQueue>
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: colorGrey.withValues(alpha: 0.35),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
),
),
@@ -504,7 +504,7 @@ class OverdueQueueState extends State<OverdueQueue>
padding: const EdgeInsets.fromLTRB(16, 14, 16, 14),
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.10),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [

View File

@@ -152,7 +152,7 @@ class ParentViewModel extends ChangeNotifier {
height: 64,
decoration: BoxDecoration(
color: colorPrimaryDark,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
child: Icon(CupertinoIcons.wifi_slash,
size: 26, color: colorWhite),
@@ -396,7 +396,7 @@ class ParentViewModel extends ChangeNotifier {
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorStandingGroundedBg,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
),
child: Icon(
CupertinoIcons.exclamationmark_triangle_fill,
@@ -544,8 +544,8 @@ class ParentViewModel extends ChangeNotifier {
decoration: BoxDecoration(
color: colorSheetBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Column(
@@ -559,7 +559,7 @@ class ParentViewModel extends ChangeNotifier {
margin: const EdgeInsets.only(top: 12, bottom: 8),
decoration: BoxDecoration(
color: colorGrey.withValues(alpha: 0.35),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
),
),
@@ -579,7 +579,7 @@ class ParentViewModel extends ChangeNotifier {
alignment: Alignment.center,
decoration: BoxDecoration(
color: actionBgColor,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(15),
),
child: Icon(actionIconData,
size: 24, color: actionColor),

View File

@@ -3,7 +3,11 @@ import 'package:flutter/material.dart';
import 'ProgressState.dart';
class Progress extends StatefulWidget {
const Progress({super.key});
/// Shown as a tab inside Home: no back arrow, and room at the bottom
/// for the nav.
final bool tab;
const Progress({super.key, this.tab = false});
@override
State<Progress> createState() => ProgressState();

View File

@@ -33,7 +33,13 @@ class ProgressState extends State<Progress> implements ConnectProgress {
bool _loaded = false;
static const List<String> _weekLetters = <String>[
"M", "T", "W", "T", "F", "S", "S"
"M",
"T",
"W",
"T",
"F",
"S",
"S"
];
@override
@@ -130,7 +136,8 @@ class ProgressState extends State<Progress> implements ConnectProgress {
return Sheet(
eyebrow: "Over time",
title: "Progress",
onBack: _onBack,
onBack: widget.tab ? null : _onBack,
bottomSpace: widget.tab ? 130 : 40,
action: chromeAction(CupertinoIcons.slider_horizontal_3, _onOpenTargets),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -175,7 +182,6 @@ class ProgressState extends State<Progress> implements ConnectProgress {
"Weekly report card",
_onOpenReportCard,
icon: CupertinoIcons.doc_text,
radius: 0,
),
const SizedBox(height: 12),
],
@@ -190,7 +196,10 @@ class ProgressState extends State<Progress> implements ConnectProgress {
onTap: _onOpenTargets,
child: Container(
padding: const EdgeInsets.fromLTRB(18, 16, 14, 16),
decoration: BoxDecoration(
color: colorNight,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Row(
children: [
Icon(CupertinoIcons.scope, size: 22, color: colorLime),
@@ -226,8 +235,7 @@ class ProgressState extends State<Progress> implements ConnectProgress {
/// and the bars underneath.
Widget _headline() {
final int held = ProgressEngine.held(_current);
final int target =
ProgressEngine.routineTarget(_targets, _period, _start);
final int target = ProgressEngine.routineTarget(_targets, _period, _start);
final int delta = held - ProgressEngine.held(_previous);
return card(
@@ -270,14 +278,16 @@ class ProgressState extends State<Progress> implements ConnectProgress {
}
Widget _deltaChip(int delta) {
final Color tone = delta > 0
? colorPositive
: (delta < 0 ? colorNegative : colorInkSoft);
final Color tone =
delta > 0 ? colorPositive : (delta < 0 ? colorNegative : colorInkSoft);
final String sign = delta > 0 ? "+" : "";
return Container(
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 5),
decoration: BoxDecoration(
color: tone.withValues(alpha: 0.12),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: text("$sign$delta vs $_previousNoun", 11, TextType.Medium,
color: tone),
);
@@ -319,9 +329,12 @@ class ProgressState extends State<Progress> implements ConnectProgress {
const SizedBox(height: 4),
Container(
height: future ? 4 : 6 + 84 * fraction,
decoration: BoxDecoration(
color: isCurrent
? colorNight
: (future ? colorMuted : colorChartGrid),
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
),
const SizedBox(height: 8),
text(
@@ -343,8 +356,7 @@ class ProgressState extends State<Progress> implements ConnectProgress {
Widget _activeTile() {
final int minutes = ProgressEngine.minutes(_current);
final int target =
ProgressEngine.minuteTarget(_targets, _period, _start);
final int target = ProgressEngine.minuteTarget(_targets, _period, _start);
return _tile(
CupertinoIcons.clock,
@@ -396,14 +408,20 @@ class ProgressState extends State<Progress> implements ConnectProgress {
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.fromLTRB(14, 12, 16, 12),
decoration: BoxDecoration(
color: colorCard,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Row(
children: [
Container(
width: 42,
height: 42,
alignment: Alignment.center,
decoration: BoxDecoration(
color: kindColor(kind).withValues(alpha: 0.14),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Icon(kindIcon(kind), size: 20, color: kindColor(kind)),
),
const SizedBox(width: 14),

View File

@@ -166,7 +166,6 @@ class TargetSetupState extends State<TargetSetup>
"Save targets",
_onSave,
icon: CupertinoIcons.checkmark,
radius: 0,
),
const SizedBox(height: 12),
],
@@ -225,8 +224,8 @@ class TargetSetupState extends State<TargetSetup>
if (_scheduled != _targets.routinesPerWeek)
GestureDetector(
onTap: _onMatchSchedule,
child: text("Match it", 12, TextType.Medium,
color: colorInk),
child:
text("Match it", 12, TextType.Medium, color: colorInk),
),
],
),
@@ -243,7 +242,10 @@ class TargetSetupState extends State<TargetSetup>
width: 48,
height: 48,
alignment: Alignment.center,
decoration: BoxDecoration(
color: enabled ? colorNight : colorMuted,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Icon(icon, size: 20, color: enabled ? colorLime : colorGrey),
),
);

View File

@@ -17,11 +17,9 @@ class ViewProgress extends ParentViewModel {
/// The runs and targets are on the device, so there is no network guard
/// or spinner. Until a target is saved, the schedule stands in for it.
void initialise() async {
final List<RoutineRunRecord> records =
await getDataManager().getRunLog();
final List<RoutineRunRecord> records = await getDataManager().getRunLog();
final ProgressTargets targets =
await getDataManager().getProgressTargets();
final ProgressTargets targets = await getDataManager().getProgressTargets();
if (!targets.set) {
try {
@@ -31,8 +29,8 @@ class ViewProgress extends ParentViewModel {
page: Pageable(0, 0, 50, 0),
),
));
final int scheduled = ProgressEngine.scheduledPerWeek(
getRoutineChainList(response.data));
final int scheduled =
ProgressEngine.scheduledPerWeek(getRoutineChainList(response.data));
if (scheduled > 0) {
targets.routinesPerWeek = scheduled;
}

View File

@@ -20,8 +20,7 @@ class ViewTargetSetup extends ParentViewModel {
/// Targets are on the device; the routines are only needed to count the
/// schedule and to place reminders, so their failure is quiet.
void initialise() async {
final ProgressTargets targets =
await getDataManager().getProgressTargets();
final ProgressTargets targets = await getDataManager().getProgressTargets();
try {
final response = await getDataManager().getMyRoutines(HistoryRequest(

View File

@@ -99,7 +99,7 @@ class ReportCardScreenState extends State<ReportCardScreen>
padding: const EdgeInsets.fromLTRB(16, 14, 16, 14),
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.08),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
@@ -128,7 +128,7 @@ class ReportCardScreenState extends State<ReportCardScreen>
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
),
child: text(_report.grade, 26, TextType.Light,
color: colorPrimaryDark),

View File

@@ -402,7 +402,7 @@ class RoutineBuilderState extends State<RoutineBuilder>
height: 124,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
gradient: selected
? LinearGradient(
begin: Alignment.topLeft,
@@ -424,6 +424,7 @@ class RoutineBuilderState extends State<RoutineBuilder>
color: selected
? colorWhite.withValues(alpha: 0.16)
: kindBackground(kind),
shape: BoxShape.circle,
),
child: Icon(kindIcon(kind),
size: 17, color: selected ? colorWhite : tone),
@@ -469,7 +470,7 @@ class RoutineBuilderState extends State<RoutineBuilder>
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
decoration: BoxDecoration(
color: applied ? kindBackground(_routine.kind) : colorWhite,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: applied
? kindColor(_routine.kind).withValues(alpha: 0.4)
@@ -514,6 +515,7 @@ class RoutineBuilderState extends State<RoutineBuilder>
alignment: Alignment.center,
decoration: BoxDecoration(
color: on ? tone : colorWhite,
shape: BoxShape.circle,
border: Border.all(color: on ? tone : colorBorder),
),
child: text(weekdayInitial(day), 13,
@@ -574,7 +576,7 @@ class RoutineBuilderState extends State<RoutineBuilder>
alignment: Alignment.center,
decoration: BoxDecoration(
color: kindBackground(_routine.kind),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
),
child: text("${index + 1}", 12, TextType.Bold, color: tone),
),

View File

@@ -462,7 +462,7 @@ class RoutineRunnerState extends State<RoutineRunner>
Widget _totalPill() {
return GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
child: Row(
mainAxisSize: MainAxisSize.min,
@@ -501,7 +501,7 @@ class RoutineRunnerState extends State<RoutineRunner>
track: colorWhite.withValues(alpha: 0.22),
child: GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
child: SizedBox(
width: 150,
height: 150,
@@ -557,7 +557,7 @@ class RoutineRunnerState extends State<RoutineRunner>
),
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(200),
child: SizedBox(
width: 120 + 50 * t,
height: 120 + 50 * t,
@@ -593,7 +593,7 @@ class RoutineRunnerState extends State<RoutineRunner>
children: [
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
child: Row(
mainAxisSize: MainAxisSize.min,
@@ -615,7 +615,7 @@ class RoutineRunnerState extends State<RoutineRunner>
const SizedBox(width: 8),
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
child: text("Left app ${_run.backgroundedCount}×", 11,
TextType.Medium,
@@ -732,6 +732,7 @@ class RoutineRunnerState extends State<RoutineRunner>
height: size,
padding: const EdgeInsets.all(2.5),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isCurrent ? colorNight : colorWhite,
boxShadow: const [
BoxShadow(
@@ -741,7 +742,7 @@ class RoutineRunnerState extends State<RoutineRunner>
),
],
),
child: ClipRect(
child: ClipOval(
child: Stack(
fit: StackFit.expand,
children: [
@@ -798,7 +799,7 @@ class RoutineRunnerState extends State<RoutineRunner>
margin: const EdgeInsets.only(right: 4),
decoration: BoxDecoration(
color: filled || active ? tone : tone.withValues(alpha: 0.15),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
);
}),
@@ -898,7 +899,7 @@ class RoutineRunnerState extends State<RoutineRunner>
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorNight,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
boxShadow: const [
BoxShadow(
color: Color(0x331A1F1C),
@@ -960,8 +961,8 @@ class RoutineRunnerState extends State<RoutineRunner>
decoration: const BoxDecoration(
color: colorSheetBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: ListView(
@@ -989,7 +990,7 @@ class RoutineRunnerState extends State<RoutineRunner>
padding: const EdgeInsets.fromLTRB(12, 12, 14, 12),
decoration: BoxDecoration(
color: current ? kindBackground(_run.chain.kind) : colorWhite,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: current ? tone.withValues(alpha: 0.35) : colorBorder,
),
@@ -1002,6 +1003,7 @@ class RoutineRunnerState extends State<RoutineRunner>
alignment: Alignment.center,
decoration: BoxDecoration(
color: step.completed ? tone : colorSecondary,
shape: BoxShape.circle,
),
child: step.completed
? Icon(Icons.check_rounded,
@@ -1075,7 +1077,7 @@ class RoutineRunnerState extends State<RoutineRunner>
children: [
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 7),
child: text(
@@ -1099,7 +1101,7 @@ class RoutineRunnerState extends State<RoutineRunner>
track: colorWhite.withValues(alpha: 0.3),
child: GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
child: SizedBox(
width: 158,
height: 158,
@@ -1131,7 +1133,7 @@ class RoutineRunnerState extends State<RoutineRunner>
),
const SizedBox(height: 22),
GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(16),
tint: colorGlassStrong,
padding: const EdgeInsets.fromLTRB(18, 16, 18, 16),
child: Row(
@@ -1200,7 +1202,7 @@ class RoutineRunnerState extends State<RoutineRunner>
final int streak = ProgressEngine.streak(withThis, now);
return GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(16),
tint: colorGlassStrong,
padding: const EdgeInsets.fromLTRB(18, 14, 18, 14),
child: Row(

View File

@@ -7,7 +7,11 @@ class Routines extends StatefulWidget {
/// Opens pre-filtered to one kind; null shows the whole library.
final RoutineKind? kind;
const Routines({super.key, this.kind});
/// Shown as a tab inside Home: no back arrow, and room at the bottom
/// for the nav.
final bool tab;
const Routines({super.key, this.kind, this.tab = false});
@override
State<Routines> createState() => RoutinesState();

View File

@@ -158,7 +158,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
backgroundColor: colorAmbientMid,
body: AmbientBackground(
child: SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 40),
padding: EdgeInsets.only(bottom: widget.tab ? 130 : 40),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
@@ -265,6 +265,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
padding: const EdgeInsets.fromLTRB(16, 6, 16, 0),
child: Row(
children: [
if (!widget.tab)
glassCircle(CupertinoIcons.arrow_left, _onBack),
const Spacer(),
glassCircle(CupertinoIcons.add, _onBuild),
@@ -330,7 +331,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
..sort((a, b) => a.startMinute.compareTo(b.startMinute));
return GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(16),
tint: colorGlassStrong,
padding: const EdgeInsets.fromLTRB(18, 16, 18, 16),
child: Row(
@@ -402,6 +403,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
height: 78,
padding: const EdgeInsets.all(2.5),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selected ? colorNight : colorWhite,
boxShadow: const [
BoxShadow(
@@ -411,7 +413,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
),
],
),
child: ClipRect(
child: ClipOval(
child: kind == null
? Container(
color: colorWhite,
@@ -471,7 +473,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
child: Container(
width: 244,
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: Color(0x262A3A30),
@@ -481,7 +483,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
],
),
child: ClipRRect(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(16),
child: Stack(
fit: StackFit.expand,
children: [
@@ -502,7 +504,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
children: [
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
child: text(
@@ -613,7 +615,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
width: 90,
height: 90,
child: ClipRRect(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
child: SceneArt(
palette: scenePalette(routine.kind),
seed: sceneSeed(routine.title),
@@ -664,6 +666,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
alignment: Alignment.center,
decoration: BoxDecoration(
color: tone,
shape: BoxShape.circle,
),
child: Icon(Icons.check_rounded,
size: 22, color: colorWhite),
@@ -715,8 +718,8 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
decoration: const BoxDecoration(
color: colorAmbientMid,
borderRadius: BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
clipBehavior: Clip.antiAlias,
@@ -753,7 +756,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
height: 4,
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.7),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
),
),
@@ -770,7 +773,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
children: [
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
child: text(kindLabel(routine.kind), 10,
@@ -780,7 +783,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
const SizedBox(width: 6),
GlassSurface(
onDark: true,
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
child: text(
@@ -814,7 +817,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
const SizedBox(height: 16),
],
GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
tint: colorGlassStrong,
padding: const EdgeInsets.fromLTRB(16, 14, 16, 14),
child: Row(
@@ -893,7 +896,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
padding: const EdgeInsets.fromLTRB(10, 10, 14, 10),
decoration: BoxDecoration(
color: colorWhite,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
child: Row(
children: [
@@ -903,6 +906,7 @@ class RoutinesState extends State<Routines> implements ConnectRoutines {
alignment: Alignment.center,
decoration: BoxDecoration(
color: tone.withValues(alpha: 0.10),
shape: BoxShape.circle,
),
child: text("${index + 1}", 12, TextType.Medium, color: tone),
),

View File

@@ -67,8 +67,8 @@ class StepEditorState extends State<StepEditor> {
decoration: const BoxDecoration(
color: colorSheetBackground,
borderRadius: BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: SingleChildScrollView(
@@ -234,7 +234,7 @@ class StepEditorState extends State<StepEditor> {
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: kindBackground(widget.kind),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(10),
),
child: Row(
children: [

View File

@@ -99,8 +99,8 @@ class SettingsState extends State<Settings> implements ConnectSettings {
decoration: BoxDecoration(
color: colorSheetBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: SingleChildScrollView(
@@ -118,7 +118,7 @@ class SettingsState extends State<Settings> implements ConnectSettings {
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: colorGrey.withValues(alpha: 0.35),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
),
),
@@ -228,7 +228,7 @@ class SettingsState extends State<Settings> implements ConnectSettings {
color: _amnestyRemaining > 0
? colorStandingGoodBg
: colorMuted,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
CupertinoIcons.checkmark_shield_fill,

View File

@@ -239,7 +239,7 @@ class SplashState extends State<Splash> implements ConnectSplash {
],
),
GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
child: Row(
mainAxisSize: MainAxisSize.min,
@@ -299,11 +299,11 @@ class SplashState extends State<Splash> implements ConnectSplash {
Widget _mark() {
return GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(16),
padding: const EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(16),
boxShadow: <BoxShadow>[
BoxShadow(
color: colorPrimary.withValues(alpha: 0.4),
@@ -342,7 +342,7 @@ class SplashState extends State<Splash> implements ConnectSplash {
alignment: alignment,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
boxShadow: const [
BoxShadow(
color: Color(0x142A3A30),
@@ -352,7 +352,7 @@ class SplashState extends State<Splash> implements ConnectSplash {
],
),
child: GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
tint: colorGlassStrong,
padding: const EdgeInsets.fromLTRB(4, 4, 12, 4),
child: Row(
@@ -424,7 +424,7 @@ class SplashState extends State<Splash> implements ConnectSplash {
/// The whole idea in three beats, on glass over the valley.
Widget _howItWorks() {
return GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(16),
tint: colorGlassStrong,
padding: const EdgeInsets.fromLTRB(14, 14, 14, 14),
child: Row(
@@ -499,7 +499,7 @@ class SplashState extends State<Splash> implements ConnectSplash {
return Column(
children: [
GlassSurface(
radius: BorderRadius.zero,
radius: BorderRadius.circular(14),
padding: const EdgeInsets.all(3),
child: SizedBox(
width: 150,
@@ -510,7 +510,7 @@ class SplashState extends State<Splash> implements ConnectSplash {
width: 150,
decoration: BoxDecoration(
color: colorNight,
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
).animate().scaleX(
begin: 0.04,

View File

@@ -71,7 +71,7 @@ class SessionExpiredState extends State<SessionExpired>
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.10),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
child: Icon(CupertinoIcons.lock_fill,
size: 24, color: colorWhite),

View File

@@ -68,7 +68,7 @@ class UpdateMeState extends State<UpdateMe> implements ConnectUpdateMe {
alignment: Alignment.center,
decoration: BoxDecoration(
color: colorWhite.withValues(alpha: 0.10),
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
child: Icon(CupertinoIcons.arrow_up_circle_fill,
size: 24, color: colorWhite),

View File

@@ -48,12 +48,13 @@ class GroundedApp extends StatelessWidget {
fontSize: 17,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadius.circular(14),
),
),
// Material 3 rounds sheet tops by default; the app is square.
bottomSheetTheme: const BottomSheetThemeData(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.zero),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
),
),
home: const Splash(),

View File

@@ -82,7 +82,7 @@ flutter:
- assets/images/
- .env
# General Sans (Fontshare, FFL) — see fonts/LICENSE-GeneralSans.txt
# Inter (SIL OFL) — see fonts/LICENSE-Inter.txt. Light is Inter Display Regular.
fonts:
- family: GroundedBold