branding added
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
/// Application entry point: wires Provider and the Values Sort practice.
|
||||
/// Application entry point: wires Provider and the Values Compass practice.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -23,11 +23,4 @@ class ValueCard {
|
||||
domain: json['domain'] as String? ?? 'Values',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'domain': domain,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Session state for the Values Sort practice: sorting, ranking, reflection.
|
||||
/// Session state for the Values Compass practice: sorting, ranking, reflection.
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
@@ -25,14 +25,13 @@ enum SortStage {
|
||||
sorting,
|
||||
hub,
|
||||
overview,
|
||||
pileReview,
|
||||
ranking,
|
||||
reflection,
|
||||
}
|
||||
|
||||
/// ChangeNotifier that owns the deck, pile assignments, ranking, and journals.
|
||||
class ValuesSortController extends ChangeNotifier {
|
||||
ValuesSortController({required StorageService this._storage});
|
||||
ValuesSortController({required this._storage});
|
||||
|
||||
final StorageService _storage;
|
||||
|
||||
@@ -65,9 +64,6 @@ class ValuesSortController extends ChangeNotifier {
|
||||
/// Reflections keyed by `valueId|promptIndex`.
|
||||
final Map<String, String> _reflections = {};
|
||||
|
||||
ValuePile? _reviewPile;
|
||||
ValuePile? get reviewPile => _reviewPile;
|
||||
|
||||
String? _activeReflectionValueId;
|
||||
String? get activeReflectionValueId => _activeReflectionValueId;
|
||||
|
||||
@@ -112,7 +108,6 @@ class ValuesSortController extends ChangeNotifier {
|
||||
..addAll(_deck);
|
||||
_rankedIds.clear();
|
||||
_reflections.clear();
|
||||
_reviewPile = null;
|
||||
_activeReflectionValueId = null;
|
||||
_activePromptIndex = 0;
|
||||
}
|
||||
@@ -218,12 +213,6 @@ class ValuesSortController extends ChangeNotifier {
|
||||
_commit();
|
||||
}
|
||||
|
||||
void openPileReview(ValuePile pile) {
|
||||
_reviewPile = pile;
|
||||
_stage = SortStage.pileReview;
|
||||
_commit();
|
||||
}
|
||||
|
||||
void goToRanking() {
|
||||
_syncRankingWithMostImportant();
|
||||
if (_rankedIds.isEmpty) {
|
||||
@@ -333,7 +322,6 @@ class ValuesSortController extends ChangeNotifier {
|
||||
'version': 1,
|
||||
'deckFingerprint': _deckFingerprint,
|
||||
'stage': _stage.name,
|
||||
'reviewPile': _reviewPile?.name,
|
||||
'assignments': {
|
||||
for (final e in _assignments.entries) e.key: e.value.name,
|
||||
},
|
||||
@@ -414,7 +402,6 @@ class ValuesSortController extends ChangeNotifier {
|
||||
.where(knownIds.contains),
|
||||
);
|
||||
|
||||
_reviewPile = ValuePileX.fromName(data['reviewPile'] as String?);
|
||||
_activeReflectionValueId = data['activeReflectionValueId'] as String?;
|
||||
if (_activeReflectionValueId != null &&
|
||||
!knownIds.contains(_activeReflectionValueId)) {
|
||||
@@ -423,19 +410,21 @@ class ValuesSortController extends ChangeNotifier {
|
||||
_activePromptIndex = (data['activePromptIndex'] as num?)?.toInt() ?? 0;
|
||||
|
||||
final stageName = data['stage'] as String? ?? SortStage.welcome.name;
|
||||
_stage = SortStage.values.firstWhere(
|
||||
(s) => s.name == stageName,
|
||||
orElse: () => SortStage.welcome,
|
||||
);
|
||||
// Remap removed refine stage onto the hub for older sessions.
|
||||
if (stageName == 'pileReview') {
|
||||
_stage = SortStage.hub;
|
||||
} else {
|
||||
_stage = SortStage.values.firstWhere(
|
||||
(s) => s.name == stageName,
|
||||
orElse: () => SortStage.welcome,
|
||||
);
|
||||
}
|
||||
|
||||
// If restored mid-sort but nothing left unsorted, land on the hub.
|
||||
if (_stage == SortStage.sorting && isSortComplete) {
|
||||
_seedRankingFromMostImportant();
|
||||
_stage = SortStage.hub;
|
||||
}
|
||||
if (_stage == SortStage.pileReview && _reviewPile == null) {
|
||||
_stage = SortStage.hub;
|
||||
}
|
||||
} catch (_) {
|
||||
await _storage.clearSortSession();
|
||||
_resetSortState();
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
/// Refine a single pile — promote / demote cards (Important Cards pattern).
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../models/value_pile.dart';
|
||||
import '../providers/values_sort_provider.dart';
|
||||
import '../widgets/practice_chrome.dart';
|
||||
import '../widgets/value_card_tile.dart';
|
||||
import '../utils/app_theme.dart';
|
||||
|
||||
class PileReviewScreen extends StatelessWidget {
|
||||
const PileReviewScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sort = context.watch<ValuesSortController>();
|
||||
final pile = sort.reviewPile ?? ValuePile.important;
|
||||
final cards = sort.cardsInPile(pile);
|
||||
final text = Theme.of(context).textTheme;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return PracticeScaffold(
|
||||
header: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 12, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: sort.goToOverview,
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Reviewing · ${pile.label}',
|
||||
style: text.labelMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
footer: PrimaryPillButton(
|
||||
label: 'Back to overview',
|
||||
onPressed: sort.goToOverview,
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
Text(pile.label, style: text.displaySmall),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${cards.length} values in this pile. Promote toward your core or demote when something feels less essential.',
|
||||
style: text.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: ValuePile.values.map((p) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ActionChip(
|
||||
avatar: CircleAvatar(
|
||||
backgroundColor: p.accent.withValues(alpha: 0.2),
|
||||
radius: 8,
|
||||
),
|
||||
label: Text('${p.shortLabel} ${sort.countInPile(p)}'),
|
||||
onPressed: () => sort.openPileReview(p),
|
||||
backgroundColor: p == pile
|
||||
? AppTheme.surfaceContainerHigh
|
||||
: AppTheme.surfaceContainerLow,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (cards.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40),
|
||||
child: Text(
|
||||
'No cards in this pile yet.',
|
||||
style: text.bodyLarge?.copyWith(color: colors.onSurfaceVariant),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
else
|
||||
...cards.map((card) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: ValueCardTile(
|
||||
card: card,
|
||||
compact: true,
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
tooltip: 'Promote',
|
||||
onPressed: () => _shift(sort, card.id, pile, up: true),
|
||||
icon: const Icon(Icons.arrow_upward),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Demote',
|
||||
onPressed: () =>
|
||||
_shift(sort, card.id, pile, up: false),
|
||||
icon: const Icon(Icons.arrow_downward),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _shift(
|
||||
ValuesSortController sort,
|
||||
String id,
|
||||
ValuePile current, {
|
||||
required bool up,
|
||||
}) {
|
||||
final values = ValuePile.values;
|
||||
final index = values.indexOf(current);
|
||||
final next = up ? index - 1 : index + 1;
|
||||
if (next < 0 || next >= values.length) return;
|
||||
sort.moveCard(id, values[next]);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../providers/values_sort_provider.dart';
|
||||
import 'card_sorting_screen.dart';
|
||||
import 'pile_review_screen.dart';
|
||||
import 'practice_hub_screen.dart';
|
||||
import 'rank_order_screen.dart';
|
||||
import 'reflection_screen.dart';
|
||||
@@ -56,7 +55,6 @@ class PracticeFlowScreen extends StatelessWidget {
|
||||
SortStage.sorting => const CardSortingScreen(),
|
||||
SortStage.hub => const PracticeHubScreen(),
|
||||
SortStage.overview => const SortedOverviewScreen(),
|
||||
SortStage.pileReview => const PileReviewScreen(),
|
||||
SortStage.ranking => const RankOrderScreen(),
|
||||
SortStage.reflection => const ReflectionScreen(),
|
||||
},
|
||||
|
||||
@@ -135,14 +135,6 @@ class RankOrderScreen extends StatelessWidget {
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Pillar: ${card.domain}',
|
||||
style: text.labelSmall?.copyWith(
|
||||
color: colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Bird's-eye overview of all five piles with demote / refine actions.
|
||||
/// Bird's-eye overview of all five piles with reorder actions.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -25,7 +25,6 @@ class _SortedOverviewScreenState extends State<SortedOverviewScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
final sort = context.watch<ValuesSortController>();
|
||||
final text = Theme.of(context).textTheme;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return PracticeScaffold(
|
||||
header: Padding(
|
||||
@@ -47,8 +46,6 @@ class _SortedOverviewScreenState extends State<SortedOverviewScreen> {
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
Text('All Sorted Values', style: text.displaySmall),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
onChanged: (v) => setState(() => _query = v.trim().toLowerCase()),
|
||||
decoration: const InputDecoration(
|
||||
@@ -93,12 +90,7 @@ class _SortedOverviewScreenState extends State<SortedOverviewScreen> {
|
||||
onDemote: (card) => _showDemoteSheet(context, sort, card),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Tip: refine any pile before ranking your core values.',
|
||||
style: text.bodySmall?.copyWith(color: colors.onSurfaceVariant),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -89,20 +89,24 @@ class _SplashScreenState extends State<SplashScreen>
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClipOval(
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
child: Image.asset(
|
||||
'assets/images/emblem.png',
|
||||
'assets/images/app_icon.png',
|
||||
width: 88,
|
||||
height: 88,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, _, _) => Container(
|
||||
width: 88,
|
||||
height: 88,
|
||||
color: colors.primaryContainer,
|
||||
child: const Icon(
|
||||
Icons.spa,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.explore,
|
||||
size: 44,
|
||||
color: Colors.white,
|
||||
color: colors.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../providers/values_sort_provider.dart';
|
||||
import '../utils/app_theme.dart';
|
||||
import '../widgets/practice_chrome.dart';
|
||||
|
||||
class WelcomeScreen extends StatelessWidget {
|
||||
@@ -136,9 +135,9 @@ class WelcomeScreen extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surfaceContainerLow,
|
||||
color: colors.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppTheme.outlineVariant),
|
||||
border: Border.all(color: colors.outlineVariant),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -214,7 +213,7 @@ class _GuideStep extends StatelessWidget {
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 12,
|
||||
backgroundColor: AppTheme.surfaceContainerHigh,
|
||||
backgroundColor: colors.surfaceContainerHigh,
|
||||
child: Text(
|
||||
'$index',
|
||||
style: text.labelMedium?.copyWith(
|
||||
@@ -266,12 +265,12 @@ class _CardFan extends StatelessWidget {
|
||||
height: hero ? 155 : 135,
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: hero ? Colors.white : AppTheme.surfaceContainer,
|
||||
color: hero ? colors.surface : colors.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: AppTheme.outlineVariant),
|
||||
border: Border.all(color: colors.outlineVariant),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0x142C2825),
|
||||
color: colors.shadow.withValues(alpha: 0.08),
|
||||
blurRadius: hero ? 16 : 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
|
||||
@@ -30,7 +30,7 @@ class StorageService {
|
||||
return _prefs.setString(StorageKeys.themeMode, value);
|
||||
}
|
||||
|
||||
/// Serialized Values Sort session JSON, or null when none saved.
|
||||
/// Serialized Values Compass session JSON, or null when none saved.
|
||||
String? get sortSessionJson => _prefs.getString(StorageKeys.sortSession);
|
||||
|
||||
Future<void> setSortSessionJson(String json) {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
/// Shared string keys, timings, and layout breakpoints used across the app.
|
||||
/// Shared string keys, timings, and layout used across the app.
|
||||
library;
|
||||
|
||||
/// Human-readable name shown in the splash screen and brand chrome.
|
||||
const String kAppName = 'Values Sort';
|
||||
|
||||
/// Package / Android application id.
|
||||
const String kPackageName = 'com.example.myapp';
|
||||
const String kAppName = 'Values Compass';
|
||||
|
||||
/// How long the splash screen stays visible before navigating onward.
|
||||
const Duration kSplashDuration = Duration(milliseconds: 1400);
|
||||
@@ -16,11 +13,5 @@ abstract final class StorageKeys {
|
||||
static const sortSession = 'values_sort_session_v1';
|
||||
}
|
||||
|
||||
/// Width thresholds used to adapt padding and card density.
|
||||
abstract final class Breakpoints {
|
||||
static const compact = 600.0;
|
||||
static const medium = 840.0;
|
||||
}
|
||||
|
||||
/// Maximum content width on tablets so reflective flows stay readable.
|
||||
const double kMaxContentWidth = 600.0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Shared layout chrome for Values Sort screens.
|
||||
/// Shared layout chrome for Values Compass screens.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -44,7 +44,7 @@ class PracticeScaffold extends StatelessWidget {
|
||||
child: ThemeToggleButton(),
|
||||
),
|
||||
),
|
||||
if (header != null) header!,
|
||||
?header,
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: pad,
|
||||
@@ -76,23 +76,27 @@ class BrandMark extends StatelessWidget {
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
ClipOval(
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.asset(
|
||||
'assets/images/emblem.png',
|
||||
'assets/images/app_icon.png',
|
||||
width: 32,
|
||||
height: 32,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, _, _) => Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: colors.primaryContainer,
|
||||
child: Icon(Icons.spa, size: 18, color: colors.onPrimary),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(Icons.explore, size: 18, color: colors.onPrimary),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'VALUES SORT',
|
||||
'VALUES COMPASS',
|
||||
style: labels.labelSmall?.copyWith(
|
||||
letterSpacing: 2.2,
|
||||
color: colors.onSurfaceVariant,
|
||||
|
||||
Reference in New Issue
Block a user