diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index f98408b..f649f61 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -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}`. --- diff --git a/frontend/design.md b/frontend/design.md index c71542e..61c89c5 100644 --- a/frontend/design.md +++ b/frontend/design.md @@ -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 diff --git a/frontend/fonts/LICENSE-GeneralSans.txt b/frontend/fonts/LICENSE-GeneralSans.txt deleted file mode 100644 index f14ebba..0000000 --- a/frontend/fonts/LICENSE-GeneralSans.txt +++ /dev/null @@ -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.  \ No newline at end of file diff --git a/frontend/fonts/LICENSE-Inter.txt b/frontend/fonts/LICENSE-Inter.txt new file mode 100644 index 0000000..9b2ca37 --- /dev/null +++ b/frontend/fonts/LICENSE-Inter.txt @@ -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. diff --git a/frontend/fonts/bold.ttf b/frontend/fonts/bold.ttf index 8fcaeca..47f8ab1 100644 Binary files a/frontend/fonts/bold.ttf and b/frontend/fonts/bold.ttf differ diff --git a/frontend/fonts/light.ttf b/frontend/fonts/light.ttf index 9cfd75d..956be38 100644 Binary files a/frontend/fonts/light.ttf and b/frontend/fonts/light.ttf differ diff --git a/frontend/fonts/medium.ttf b/frontend/fonts/medium.ttf index 1b25d91..458cd06 100644 Binary files a/frontend/fonts/medium.ttf and b/frontend/fonts/medium.ttf differ diff --git a/frontend/fonts/regular.ttf b/frontend/fonts/regular.ttf index efa0664..b7aaca8 100644 Binary files a/frontend/fonts/regular.ttf and b/frontend/fonts/regular.ttf differ diff --git a/frontend/lib/Grounded/designs/Checkbox.dart b/frontend/lib/Grounded/designs/Checkbox.dart index f1d93e4..8bd50d6 100644 --- a/frontend/lib/Grounded/designs/Checkbox.dart +++ b/frontend/lib/Grounded/designs/Checkbox.dart @@ -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), diff --git a/frontend/lib/Grounded/designs/Component.dart b/frontend/lib/Grounded/designs/Component.dart index a5dbcf1..b1178c7 100644 --- a/frontend/lib/Grounded/designs/Component.dart +++ b/frontend/lib/Grounded/designs/Component.dart @@ -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, diff --git a/frontend/lib/Grounded/designs/FloatingNav.dart b/frontend/lib/Grounded/designs/FloatingNav.dart index 319ae0d..5e47148 100644 --- a/frontend/lib/Grounded/designs/FloatingNav.dart +++ b/frontend/lib/Grounded/designs/FloatingNav.dart @@ -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 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( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.zero, - boxShadow: const [ - BoxShadow( - color: Color(0x1A1A1F1C), - blurRadius: 30, - offset: Offset(0, 10), - ), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + child: Center( + child: Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(_size)), + boxShadow: [ + BoxShadow( + color: Color(0x1F1A1F1C), + blurRadius: 30, + offset: Offset(0, 12), + ), + ], + ), + child: GlassSurface( + 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++) ...[ + if (index > 0) const SizedBox(width: 4), + _button(entries[index], index == selected), ], - ), - child: GlassSurface( - radius: BorderRadius.zero, - tint: colorGlassStrong, - padding: const EdgeInsets.all(6), - child: Row( - 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: [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), - ), - ), - ], - ], + ), ), ), ); } - Widget _tab(NavEntry entry, bool active) { - return GestureDetector( - onTap: entry.onTap, - behavior: HitTestBehavior.opaque, - child: AnimatedContainer( - duration: const Duration(milliseconds: 220), - curve: Curves.easeOutCubic, - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - color: active ? colorHeaderBottom : colorWhite.withValues(alpha: 0.0), - borderRadius: BorderRadius.zero, - boxShadow: active - ? const [ - BoxShadow( - color: Color(0x141A1F1C), - blurRadius: 12, - offset: Offset(0, 4), - ), - ] - : 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), - ], + 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, + width: _size, + height: _size, + alignment: Alignment.center, + decoration: BoxDecoration( + 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(0x261A1F1C), + blurRadius: 16, + offset: Offset(0, 6), + ), + ] + : const [], + ), + child: Icon(entry.icon, + size: 22, color: active ? colorInk : colorInkSoft), + ), ), ), ); diff --git a/frontend/lib/Grounded/designs/GlassSurface.dart b/frontend/lib/Grounded/designs/GlassSurface.dart index 376e3c5..a80efa1 100644 --- a/frontend/lib/Grounded/designs/GlassSurface.dart +++ b/frontend/lib/Grounded/designs/GlassSurface.dart @@ -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, diff --git a/frontend/lib/Grounded/designs/GoalPicker.dart b/frontend/lib/Grounded/designs/GoalPicker.dart index 16a33c6..caf07ca 100644 --- a/frontend/lib/Grounded/designs/GoalPicker.dart +++ b/frontend/lib/Grounded/designs/GoalPicker.dart @@ -32,8 +32,8 @@ Future 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, diff --git a/frontend/lib/Grounded/designs/MemberAvatar.dart b/frontend/lib/Grounded/designs/MemberAvatar.dart index 7ef9aa1..8a2cd86 100644 --- a/frontend/lib/Grounded/designs/MemberAvatar.dart +++ b/frontend/lib/Grounded/designs/MemberAvatar.dart @@ -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> palettes = >[ @@ -21,16 +21,21 @@ Widget memberAvatar(String name, double size, {Color? border}) { return Container( width: size, height: size, - decoration: - BoxDecoration(border: Border.all(color: border ?? colorWhite, width: 2)), - 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), - ), - ], + 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), + ), + ], + ), ), ); } @@ -61,6 +66,7 @@ Widget avatarStack(List names, double size, height: size, alignment: Alignment.center, decoration: BoxDecoration( + shape: BoxShape.circle, color: colorNight, border: Border.all(color: border ?? colorWhite, width: 2), ), diff --git a/frontend/lib/Grounded/designs/Shell.dart b/frontend/lib/Grounded/designs/Shell.dart index eb404cc..193b091 100644 --- a/frontend/lib/Grounded/designs/Shell.dart +++ b/frontend/lib/Grounded/designs/Shell.dart @@ -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( diff --git a/frontend/lib/Grounded/designs/buttons/Buttons.dart b/frontend/lib/Grounded/designs/buttons/Buttons.dart index 3c656e3..65d1a6d 100644 --- a/frontend/lib/Grounded/designs/buttons/Buttons.dart +++ b/frontend/lib/Grounded/designs/buttons/Buttons.dart @@ -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({ 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, diff --git a/frontend/lib/Grounded/designs/input/CountStepper.dart b/frontend/lib/Grounded/designs/input/CountStepper.dart index 6c7b82b..b14577c 100644 --- a/frontend/lib/Grounded/designs/input/CountStepper.dart +++ b/frontend/lib/Grounded/designs/input/CountStepper.dart @@ -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), diff --git a/frontend/lib/Grounded/designs/input/InputFields.dart b/frontend/lib/Grounded/designs/input/InputFields.dart index 46043d9..7753708 100644 --- a/frontend/lib/Grounded/designs/input/InputFields.dart +++ b/frontend/lib/Grounded/designs/input/InputFields.dart @@ -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( diff --git a/frontend/lib/Grounded/see/commitment/NewCommitmentState.dart b/frontend/lib/Grounded/see/commitment/NewCommitmentState.dart index 2f0f7c8..039b4da 100644 --- a/frontend/lib/Grounded/see/commitment/NewCommitmentState.dart +++ b/frontend/lib/Grounded/see/commitment/NewCommitmentState.dart @@ -443,8 +443,8 @@ class NewCommitmentState extends State 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 margin: const EdgeInsets.only(bottom: 20), decoration: BoxDecoration( color: colorGrey.withValues(alpha: 0.35), - borderRadius: BorderRadius.zero, + borderRadius: BorderRadius.circular(14), ), ), ), diff --git a/frontend/lib/Grounded/see/excuse/ExcuseReportState.dart b/frontend/lib/Grounded/see/excuse/ExcuseReportState.dart index b353f36..a9d9127 100644 --- a/frontend/lib/Grounded/see/excuse/ExcuseReportState.dart +++ b/frontend/lib/Grounded/see/excuse/ExcuseReportState.dart @@ -142,7 +142,7 @@ class ExcuseReportState extends State 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( diff --git a/frontend/lib/Grounded/see/goal/GoalDetailState.dart b/frontend/lib/Grounded/see/goal/GoalDetailState.dart index 3e60a15..845783b 100644 --- a/frontend/lib/Grounded/see/goal/GoalDetailState.dart +++ b/frontend/lib/Grounded/see/goal/GoalDetailState.dart @@ -242,7 +242,7 @@ class GoalDetailState extends State 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 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 color: routine.kind == RoutineKind.MustDo ? colorPrimaryDark : tone, + shape: BoxShape.circle, ), child: Icon(CupertinoIcons.play_fill, size: 15, color: colorWhite), diff --git a/frontend/lib/Grounded/see/goal/GoalEditorState.dart b/frontend/lib/Grounded/see/goal/GoalEditorState.dart index 4b48408..eb61334 100644 --- a/frontend/lib/Grounded/see/goal/GoalEditorState.dart +++ b/frontend/lib/Grounded/see/goal/GoalEditorState.dart @@ -282,7 +282,7 @@ class GoalEditorState extends State 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 implements ConnectGoalEditor { alignment: Alignment.center, decoration: BoxDecoration( color: colour, + shape: BoxShape.circle, border: Border.all( color: selected ? colorWhite : Colors.transparent, width: 3, diff --git a/frontend/lib/Grounded/see/goal/Goals.dart b/frontend/lib/Grounded/see/goal/Goals.dart index 554e408..52dce4a 100644 --- a/frontend/lib/Grounded/see/goal/Goals.dart +++ b/frontend/lib/Grounded/see/goal/Goals.dart @@ -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 createState() => GoalsState(); diff --git a/frontend/lib/Grounded/see/goal/GoalsState.dart b/frontend/lib/Grounded/see/goal/GoalsState.dart index bbdd247..fc40ae7 100644 --- a/frontend/lib/Grounded/see/goal/GoalsState.dart +++ b/frontend/lib/Grounded/see/goal/GoalsState.dart @@ -74,7 +74,8 @@ class GoalsState extends State 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 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 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 implements ConnectGoals { children: [ GlassSurface( onDark: true, - radius: BorderRadius.zero, + radius: BorderRadius.circular(14), padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5), child: text( diff --git a/frontend/lib/Grounded/see/group/GroupChatState.dart b/frontend/lib/Grounded/see/group/GroupChatState.dart index ddcdd98..9e2066e 100644 --- a/frontend/lib/Grounded/see/group/GroupChatState.dart +++ b/frontend/lib/Grounded/see/group/GroupChatState.dart @@ -119,8 +119,8 @@ class GroupChatState extends State 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 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 implements ConnectGroupChat { child: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - color: colorWhite.withValues(alpha: 0.7), - child: text(_dayName(moment), 11, TextType.Medium, - color: colorInkSoft), + decoration: BoxDecoration( + color: colorWhite.withValues(alpha: 0.7), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: + text(_dayName(moment), 11, TextType.Medium, color: colorInkSoft), ), ), ); @@ -241,7 +245,10 @@ class GroupChatState extends State implements ConnectGroupChat { alignment: Alignment.centerRight, child: Container( padding: const EdgeInsets.fromLTRB(14, 11, 14, 10), - color: colorNight, + 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 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 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 implements ConnectGroupChat { Widget _composer() { return Container( - color: colorWhite, + 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 implements ConnectGroupChat { child: Container( padding: const EdgeInsets.symmetric(horizontal: 14), alignment: Alignment.center, - color: colorAmbientMid, + 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 implements ConnectGroupChat { child: Container( width: 50, height: 50, - color: colorNight, + decoration: BoxDecoration( + color: colorNight, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), child: Icon(CupertinoIcons.paperplane_fill, size: 20, color: colorLime), ), diff --git a/frontend/lib/Grounded/see/group/GroupDetailState.dart b/frontend/lib/Grounded/see/group/GroupDetailState.dart index d3f702e..a888cfd 100644 --- a/frontend/lib/Grounded/see/group/GroupDetailState.dart +++ b/frontend/lib/Grounded/see/group/GroupDetailState.dart @@ -137,8 +137,8 @@ class GroupDetailState extends State _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 behavior: HitTestBehavior.opaque, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - color: colorAmbientMid, + 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 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 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), - color: colorLime, - child: text("${_group.unread} new", 10, - TextType.Medium, + decoration: BoxDecoration( + color: colorLime, + 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 Container( width: 46, height: 46, - color: colorWhite.withValues(alpha: 0.12), - child: Icon(CupertinoIcons.arrow_right, - size: 20, color: colorWhite), + decoration: BoxDecoration( + color: colorWhite.withValues(alpha: 0.12), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: + Icon(CupertinoIcons.arrow_right, size: 20, color: colorWhite), ), ], ), @@ -409,7 +418,9 @@ class GroupDetailState extends State ), 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 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 onTap: () => _onLog(goal, objective), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), - color: colorNight, + 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 child: Container( width: 44, height: 44, - color: nudged ? colorAmbientMid : colorNight, + 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 /// 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 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 child: Container( padding: const EdgeInsets.symmetric(vertical: 14), alignment: Alignment.center, - color: colorAmbientMid, - child: text("+${formatAmount(preset)}", 15, - TextType.Medium, + decoration: BoxDecoration( + color: colorAmbientMid, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: text( + "+${formatAmount(preset)}", 15, TextType.Medium, color: colorInk), ), ), @@ -636,8 +657,8 @@ class GroupDetailState extends State 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), ], ), diff --git a/frontend/lib/Grounded/see/group/GroupEditorState.dart b/frontend/lib/Grounded/see/group/GroupEditorState.dart index 25e71ab..7878809 100644 --- a/frontend/lib/Grounded/see/group/GroupEditorState.dart +++ b/frontend/lib/Grounded/see/group/GroupEditorState.dart @@ -99,7 +99,8 @@ class GroupEditorState extends State } 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 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( options: const ["Create", "Join"], @@ -221,6 +221,7 @@ class GroupEditorState extends State 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 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 height: 40, alignment: Alignment.center, decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(12)), color: colour, border: Border.all( color: selected ? colorWhite : Colors.transparent, diff --git a/frontend/lib/Grounded/see/group/GroupGoalEditorState.dart b/frontend/lib/Grounded/see/group/GroupGoalEditorState.dart index a16079f..f593069 100644 --- a/frontend/lib/Grounded/see/group/GroupGoalEditorState.dart +++ b/frontend/lib/Grounded/see/group/GroupGoalEditorState.dart @@ -195,8 +195,7 @@ class GroupGoalEditorState extends State 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 keyboard: const TextInputType.numberWithOptions( decimal: true), formatters: [ - FilteringTextInputFormatter.allow(RegExp(r"[0-9.]")), + FilteringTextInputFormatter.allow( + RegExp(r"[0-9.]")), ], ), ), diff --git a/frontend/lib/Grounded/see/group/Groups.dart b/frontend/lib/Grounded/see/group/Groups.dart index 8cd2fb9..01f8965 100644 --- a/frontend/lib/Grounded/see/group/Groups.dart +++ b/frontend/lib/Grounded/see/group/Groups.dart @@ -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 createState() => GroupsState(); diff --git a/frontend/lib/Grounded/see/group/GroupsState.dart b/frontend/lib/Grounded/see/group/GroupsState.dart index 141da41..5913732 100644 --- a/frontend/lib/Grounded/see/group/GroupsState.dart +++ b/frontend/lib/Grounded/see/group/GroupsState.dart @@ -86,7 +86,8 @@ class GroupsState extends State 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 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 implements ConnectGroups { ), ], ), + clipBehavior: Clip.antiAlias, child: Stack( fit: StackFit.expand, children: [ @@ -175,7 +178,10 @@ class GroupsState extends State implements ConnectGroups { Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6), - color: colorLime, + decoration: BoxDecoration( + color: colorLime, + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), child: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/frontend/lib/Grounded/see/group/ViewGroupChat.dart b/frontend/lib/Grounded/see/group/ViewGroupChat.dart index 3d9a5e6..82b4f04 100644 --- a/frontend/lib/Grounded/see/group/ViewGroupChat.dart +++ b/frontend/lib/Grounded/see/group/ViewGroupChat.dart @@ -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"); } } diff --git a/frontend/lib/Grounded/see/group/ViewGroupDetail.dart b/frontend/lib/Grounded/see/group/ViewGroupDetail.dart index e0797b0..60fe4a0 100644 --- a/frontend/lib/Grounded/see/group/ViewGroupDetail.dart +++ b/frontend/lib/Grounded/see/group/ViewGroupDetail.dart @@ -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"); } } diff --git a/frontend/lib/Grounded/see/home/HomeState.dart b/frontend/lib/Grounded/see/home/HomeState.dart index a894fc2..ea0493f 100644 --- a/frontend/lib/Grounded/see/home/HomeState.dart +++ b/frontend/lib/Grounded/see/home/HomeState.dart @@ -81,6 +81,10 @@ class HomeState extends State implements ConnectHome { bool _weekly = false; + /// The open tab, and the tabs built so far. + int _tab = 0; + + final Set _opened = {0}; Standing _standing = Standing.Good; @@ -183,28 +187,14 @@ class HomeState extends State 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 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 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,9 +306,9 @@ class HomeState extends State implements ConnectHome { return FadeTransition( opacity: curve, child: SlideTransition( - position: Tween( - begin: const Offset(0, 0.12), end: Offset.zero) - .animate(curve), + position: + Tween(begin: const Offset(0, 0.12), end: Offset.zero) + .animate(curve), child: child, ), ); @@ -389,85 +407,112 @@ class HomeState extends State implements ConnectHome { final bool queueIsHome = StandingEngine.showsOverdueQueueAsHome(_standing) && !_distressed; - return AnnotatedRegion( - value: SystemUiOverlayStyle.light, - child: Scaffold( - backgroundColor: colorAmbientMid, - body: AmbientBackground( - child: Stack( - children: [ - SingleChildScrollView( - padding: const EdgeInsets.only(bottom: 130), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - _header(), - Padding( - padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - if (_distressed) - _frosted(_distressBody()) - else if (queueIsHome) - _frosted(_groundedBody()) - else - _planBody(), - ], - ), - ), + // Back on another tab returns to Today rather than leaving the app. + return PopScope( + canPop: _tab == 0, + onPopInvokedWithResult: _onBackToToday, + child: AnnotatedRegion( + value: SystemUiOverlayStyle.light, + child: Scaffold( + backgroundColor: colorAmbientMid, + body: AmbientBackground( + child: Stack( + children: [ + IndexedStack( + index: _tab, + children: [ + _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: 0, - entries: [ - ( - 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), - ), - ], + Positioned( + left: 0, + right: 0, + bottom: 0, + child: FloatingNav( + selected: _tab, + entries: [ + ( + 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, + mainAxisSize: MainAxisSize.min, + children: [ + _header(), + Padding( + padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + if (_distressed) + _frosted(_distressBody()) + else if (queueIsHome) + _frosted(_groundedBody()) + else + _planBody(), + ], + ), + ), + ], + ), + ); + } + // ── Header ──────────────────────────────────────────────────────────────── /// The deep forest block: who you are, the week as a streak, and the day @@ -475,6 +520,7 @@ class HomeState extends State 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 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 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 implements ConnectHome { final DateTime first = DateTime(now.year, now.month, now.day) .subtract(const Duration(days: 6)); const List days = [ - "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 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 implements ConnectHome { /// The week in numbers: the average kept, a bar per day, and the classes. Widget _weeklyNumbers() { - final Map byDay = _week?.completionByWeekday ?? {}; - final double average = byDay.isEmpty - ? 0 - : byDay.values.reduce((a, b) => a + b) / byDay.length; + final Map byDay = + _week?.completionByWeekday ?? {}; + final double average = + byDay.isEmpty ? 0 : byDay.values.reduce((a, b) => a + b) / byDay.length; const List letters = ["M", "T", "W", "T", "F", "S", "S"]; final DateTime now = DateTime.now(); final List order = [ @@ -781,12 +839,17 @@ class HomeState extends State implements ConnectHome { duration: const Duration(milliseconds: 400), curve: Curves.easeOutCubic, height: 90 * (byDay[day] ?? 0).clamp(0.04, 1.0), - color: (byDay[day] ?? 0) >= _keptFloor - ? colorBarElective - : colorHeaderLine, + 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 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 implements ConnectHome { const SizedBox(height: 9), Container( height: 7, - color: colorHeaderLine, + 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 implements ConnectHome { child: Container( width: 54, height: 54, - color: colorWhite, + 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, - color: colorWhite, - padding: EdgeInsets.fromLTRB( - 18, 14, 18, MediaQuery.of(sheetContext).padding.bottom + 22), + decoration: BoxDecoration( + color: colorWhite, + 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 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 implements ConnectHome { onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(vertical: 22), - color: colorAmbientMid, + decoration: BoxDecoration( + color: colorAmbientMid, + borderRadius: BorderRadius.all(Radius.circular(12)), + ), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -1067,7 +1144,10 @@ class HomeState extends State implements ConnectHome { onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(vertical: 22), - color: colorAmbientMid, + decoration: BoxDecoration( + color: colorAmbientMid, + borderRadius: BorderRadius.all(Radius.circular(12)), + ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -1093,7 +1173,9 @@ class HomeState extends State 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 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 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 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 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 implements ConnectHome { ), ], ), - child: ClipRect( + child: ClipOval( child: SceneArt( palette: palette, seed: seed, @@ -1376,7 +1459,7 @@ class HomeState extends State 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 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 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 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 implements ConnectHome { Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), - color: colorLime, - child: text("${group.unread} new", 10, - TextType.Medium, + decoration: BoxDecoration( + color: colorLime, + 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 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 implements ConnectHome { child: Container( width: 48, height: 48, - color: colorNight, + 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 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 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 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 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 implements ConnectHome { height: 58, alignment: Alignment.center, decoration: BoxDecoration( + shape: BoxShape.circle, gradient: const RadialGradient( center: Alignment(-0.3, -0.4), colors: [colorOverdueGlow, colorStandingGrounded], @@ -1943,7 +2039,7 @@ class HomeState extends State 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 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 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 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 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 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), diff --git a/frontend/lib/Grounded/see/live/LiveTaskState.dart b/frontend/lib/Grounded/see/live/LiveTaskState.dart index 6c3033c..a472198 100644 --- a/frontend/lib/Grounded/see/live/LiveTaskState.dart +++ b/frontend/lib/Grounded/see/live/LiveTaskState.dart @@ -184,8 +184,8 @@ class LiveTaskState extends State decoration: BoxDecoration( color: colorSheetBackground, borderRadius: const BorderRadius.only( - topLeft: Radius.zero, - topRight: Radius.zero, + topLeft: Radius.circular(16), + topRight: Radius.circular(16), ), ), child: SingleChildScrollView( diff --git a/frontend/lib/Grounded/see/login/LoginState.dart b/frontend/lib/Grounded/see/login/LoginState.dart index 217f675..ff3920a 100644 --- a/frontend/lib/Grounded/see/login/LoginState.dart +++ b/frontend/lib/Grounded/see/login/LoginState.dart @@ -107,8 +107,8 @@ class LoginState extends State 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, diff --git a/frontend/lib/Grounded/see/overdue/OverdueQueueState.dart b/frontend/lib/Grounded/see/overdue/OverdueQueueState.dart index e89b7fb..bf78bd1 100644 --- a/frontend/lib/Grounded/see/overdue/OverdueQueueState.dart +++ b/frontend/lib/Grounded/see/overdue/OverdueQueueState.dart @@ -147,8 +147,8 @@ class OverdueQueueState extends State 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 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 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 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 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 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 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: [ diff --git a/frontend/lib/Grounded/see/parent/ParentViewModel.dart b/frontend/lib/Grounded/see/parent/ParentViewModel.dart index c229681..18b5af1 100644 --- a/frontend/lib/Grounded/see/parent/ParentViewModel.dart +++ b/frontend/lib/Grounded/see/parent/ParentViewModel.dart @@ -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), diff --git a/frontend/lib/Grounded/see/progress/Progress.dart b/frontend/lib/Grounded/see/progress/Progress.dart index 71d91d3..efa8d1c 100644 --- a/frontend/lib/Grounded/see/progress/Progress.dart +++ b/frontend/lib/Grounded/see/progress/Progress.dart @@ -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 createState() => ProgressState(); diff --git a/frontend/lib/Grounded/see/progress/ProgressState.dart b/frontend/lib/Grounded/see/progress/ProgressState.dart index ae8fbd3..7f2231d 100644 --- a/frontend/lib/Grounded/see/progress/ProgressState.dart +++ b/frontend/lib/Grounded/see/progress/ProgressState.dart @@ -33,7 +33,13 @@ class ProgressState extends State implements ConnectProgress { bool _loaded = false; static const List _weekLetters = [ - "M", "T", "W", "T", "F", "S", "S" + "M", + "T", + "W", + "T", + "F", + "S", + "S" ]; @override @@ -130,7 +136,8 @@ class ProgressState extends State 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 implements ConnectProgress { "Weekly report card", _onOpenReportCard, icon: CupertinoIcons.doc_text, - radius: 0, ), const SizedBox(height: 12), ], @@ -190,7 +196,10 @@ class ProgressState extends State implements ConnectProgress { onTap: _onOpenTargets, child: Container( padding: const EdgeInsets.fromLTRB(18, 16, 14, 16), - color: colorNight, + 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 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 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), - color: tone.withValues(alpha: 0.12), + 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 implements ConnectProgress { const SizedBox(height: 4), Container( height: future ? 4 : 6 + 84 * fraction, - color: isCurrent - ? colorNight - : (future ? colorMuted : colorChartGrid), + 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 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 implements ConnectProgress { return Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.fromLTRB(14, 12, 16, 12), - color: colorCard, + decoration: BoxDecoration( + color: colorCard, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), child: Row( children: [ Container( width: 42, height: 42, alignment: Alignment.center, - color: kindColor(kind).withValues(alpha: 0.14), + 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), diff --git a/frontend/lib/Grounded/see/progress/TargetSetupState.dart b/frontend/lib/Grounded/see/progress/TargetSetupState.dart index 952da20..7aefe04 100644 --- a/frontend/lib/Grounded/see/progress/TargetSetupState.dart +++ b/frontend/lib/Grounded/see/progress/TargetSetupState.dart @@ -166,7 +166,6 @@ class TargetSetupState extends State "Save targets", _onSave, icon: CupertinoIcons.checkmark, - radius: 0, ), const SizedBox(height: 12), ], @@ -225,8 +224,8 @@ class TargetSetupState extends State 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 width: 48, height: 48, alignment: Alignment.center, - color: enabled ? colorNight : colorMuted, + decoration: BoxDecoration( + color: enabled ? colorNight : colorMuted, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), child: Icon(icon, size: 20, color: enabled ? colorLime : colorGrey), ), ); diff --git a/frontend/lib/Grounded/see/progress/ViewProgress.dart b/frontend/lib/Grounded/see/progress/ViewProgress.dart index ec0639a..3c363c2 100644 --- a/frontend/lib/Grounded/see/progress/ViewProgress.dart +++ b/frontend/lib/Grounded/see/progress/ViewProgress.dart @@ -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 records = - await getDataManager().getRunLog(); + final List 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; } diff --git a/frontend/lib/Grounded/see/progress/ViewTargetSetup.dart b/frontend/lib/Grounded/see/progress/ViewTargetSetup.dart index cd87cc7..8dad86d 100644 --- a/frontend/lib/Grounded/see/progress/ViewTargetSetup.dart +++ b/frontend/lib/Grounded/see/progress/ViewTargetSetup.dart @@ -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( diff --git a/frontend/lib/Grounded/see/reportcard/ReportCardScreenState.dart b/frontend/lib/Grounded/see/reportcard/ReportCardScreenState.dart index 6f4b6df..f044595 100644 --- a/frontend/lib/Grounded/see/reportcard/ReportCardScreenState.dart +++ b/frontend/lib/Grounded/see/reportcard/ReportCardScreenState.dart @@ -99,7 +99,7 @@ class ReportCardScreenState extends State 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 alignment: Alignment.center, decoration: BoxDecoration( color: colorWhite, - borderRadius: BorderRadius.zero, + borderRadius: BorderRadius.circular(12), ), child: text(_report.grade, 26, TextType.Light, color: colorPrimaryDark), diff --git a/frontend/lib/Grounded/see/routine/RoutineBuilderState.dart b/frontend/lib/Grounded/see/routine/RoutineBuilderState.dart index 8b8a510..2f0f5b4 100644 --- a/frontend/lib/Grounded/see/routine/RoutineBuilderState.dart +++ b/frontend/lib/Grounded/see/routine/RoutineBuilderState.dart @@ -402,7 +402,7 @@ class RoutineBuilderState extends State 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 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 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 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 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), ), diff --git a/frontend/lib/Grounded/see/routine/RoutineRunnerState.dart b/frontend/lib/Grounded/see/routine/RoutineRunnerState.dart index 6bbe8ab..ad331a9 100644 --- a/frontend/lib/Grounded/see/routine/RoutineRunnerState.dart +++ b/frontend/lib/Grounded/see/routine/RoutineRunnerState.dart @@ -462,7 +462,7 @@ class RoutineRunnerState extends State 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 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 ), 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 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 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 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 ), ], ), - child: ClipRect( + child: ClipOval( child: Stack( fit: StackFit.expand, children: [ @@ -798,7 +799,7 @@ class RoutineRunnerState extends State 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 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 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 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 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 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 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 ), 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 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( diff --git a/frontend/lib/Grounded/see/routine/Routines.dart b/frontend/lib/Grounded/see/routine/Routines.dart index 5f03337..f352c1a 100644 --- a/frontend/lib/Grounded/see/routine/Routines.dart +++ b/frontend/lib/Grounded/see/routine/Routines.dart @@ -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 createState() => RoutinesState(); diff --git a/frontend/lib/Grounded/see/routine/RoutinesState.dart b/frontend/lib/Grounded/see/routine/RoutinesState.dart index 749ac0c..46c2503 100644 --- a/frontend/lib/Grounded/see/routine/RoutinesState.dart +++ b/frontend/lib/Grounded/see/routine/RoutinesState.dart @@ -158,7 +158,7 @@ class RoutinesState extends State 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,7 +265,8 @@ class RoutinesState extends State implements ConnectRoutines { padding: const EdgeInsets.fromLTRB(16, 6, 16, 0), child: Row( children: [ - glassCircle(CupertinoIcons.arrow_left, _onBack), + if (!widget.tab) + glassCircle(CupertinoIcons.arrow_left, _onBack), const Spacer(), glassCircle(CupertinoIcons.add, _onBuild), ], @@ -330,7 +331,7 @@ class RoutinesState extends State 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 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 implements ConnectRoutines { ), ], ), - child: ClipRect( + child: ClipOval( child: kind == null ? Container( color: colorWhite, @@ -471,7 +473,7 @@ class RoutinesState extends State 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 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 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 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 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 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 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 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 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 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 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 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), ), diff --git a/frontend/lib/Grounded/see/routine/StepEditorState.dart b/frontend/lib/Grounded/see/routine/StepEditorState.dart index d301186..437b8e9 100644 --- a/frontend/lib/Grounded/see/routine/StepEditorState.dart +++ b/frontend/lib/Grounded/see/routine/StepEditorState.dart @@ -67,8 +67,8 @@ class StepEditorState extends State { 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 { padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration( color: kindBackground(widget.kind), - borderRadius: BorderRadius.zero, + borderRadius: BorderRadius.circular(10), ), child: Row( children: [ diff --git a/frontend/lib/Grounded/see/settings/SettingsState.dart b/frontend/lib/Grounded/see/settings/SettingsState.dart index fa2d76c..3385c1b 100644 --- a/frontend/lib/Grounded/see/settings/SettingsState.dart +++ b/frontend/lib/Grounded/see/settings/SettingsState.dart @@ -99,8 +99,8 @@ class SettingsState extends State 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 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 implements ConnectSettings { color: _amnestyRemaining > 0 ? colorStandingGoodBg : colorMuted, - borderRadius: BorderRadius.zero, + borderRadius: BorderRadius.circular(12), ), child: Icon( CupertinoIcons.checkmark_shield_fill, diff --git a/frontend/lib/Grounded/see/splash/SplashState.dart b/frontend/lib/Grounded/see/splash/SplashState.dart index 4e45032..46c8b95 100644 --- a/frontend/lib/Grounded/see/splash/SplashState.dart +++ b/frontend/lib/Grounded/see/splash/SplashState.dart @@ -239,7 +239,7 @@ class SplashState extends State 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 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( color: colorPrimary.withValues(alpha: 0.4), @@ -342,7 +342,7 @@ class SplashState extends State 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 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 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 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 implements ConnectSplash { width: 150, decoration: BoxDecoration( color: colorNight, - borderRadius: BorderRadius.zero, + borderRadius: BorderRadius.circular(14), ), ).animate().scaleX( begin: 0.04, diff --git a/frontend/lib/Grounded/see/system/sessionexpired/SessionExpiredState.dart b/frontend/lib/Grounded/see/system/sessionexpired/SessionExpiredState.dart index f97d1af..58a85ea 100644 --- a/frontend/lib/Grounded/see/system/sessionexpired/SessionExpiredState.dart +++ b/frontend/lib/Grounded/see/system/sessionexpired/SessionExpiredState.dart @@ -71,7 +71,7 @@ class SessionExpiredState extends State 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), diff --git a/frontend/lib/Grounded/see/system/updateme/UpdateMeState.dart b/frontend/lib/Grounded/see/system/updateme/UpdateMeState.dart index 4968a18..d1af31d 100644 --- a/frontend/lib/Grounded/see/system/updateme/UpdateMeState.dart +++ b/frontend/lib/Grounded/see/system/updateme/UpdateMeState.dart @@ -68,7 +68,7 @@ class UpdateMeState extends State 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), diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 39537c7..38ddd52 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -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(), diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 3228b71..6493df9 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -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