Add groups with shared goals, objectives and chat; drop the water tracker
Groups let people keep each other honest: create one or join with a six-letter invite code. Each group has shared goals split into measurable objectives (km, sessions, words) that every member logs toward, a leaderboard of kept rate and streak with nudges, and a group chat where check-ins and nudges appear as cards beside the talk. The open chat polls for new messages. Reached from a new Groups tab (Training moves into the day rail), a Home "Your groups" rail that opens the chat, and a Group tile in the add sheet. Mock mode seeds two groups, a joinable one and members who reply. The water tracker is removed; it did not fit the app. Bottom sheets are now square app-wide via the theme. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
74
frontend/lib/Grounded/designs/MemberAvatar.dart
Normal file
74
frontend/lib/Grounded/designs/MemberAvatar.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../about/internal/application/TextType.dart';
|
||||
import '../utils/Colors.dart';
|
||||
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
|
||||
/// initial on it, so everyone is recognisable without a photo.
|
||||
Widget memberAvatar(String name, double size, {Color? border}) {
|
||||
const List<List<Color>> palettes = <List<Color>>[
|
||||
sceneWorkout,
|
||||
sceneMeditation,
|
||||
sceneMustDo,
|
||||
];
|
||||
final int seed = sceneSeed(name.isEmpty ? "?" : name);
|
||||
final String initial =
|
||||
name.isEmpty ? "?" : name.substring(0, 1).toUpperCase();
|
||||
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Several portraits overlapping, with "+N" once there are more than fit.
|
||||
Widget avatarStack(List<String> names, double size,
|
||||
{int max = 4, Color? border}) {
|
||||
final List<String> shown = names.take(max).toList();
|
||||
final int extra = names.length - shown.length;
|
||||
final double step = size * 0.68;
|
||||
final int slots = shown.length + (extra > 0 ? 1 : 0);
|
||||
|
||||
return SizedBox(
|
||||
width: slots == 0 ? 0 : size + step * (slots - 1),
|
||||
height: size,
|
||||
child: Stack(
|
||||
children: [
|
||||
for (int index = 0; index < shown.length; index++)
|
||||
Positioned(
|
||||
left: step * index,
|
||||
child: memberAvatar(shown[index], size, border: border),
|
||||
),
|
||||
if (extra > 0)
|
||||
Positioned(
|
||||
left: step * shown.length,
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: colorNight,
|
||||
border: Border.all(color: border ?? colorWhite, width: 2),
|
||||
),
|
||||
child: text("+$extra", size * 0.34, TextType.Medium,
|
||||
color: colorWhite),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user