branding added

This commit is contained in:
davmar
2026-09-18 00:16:49 +02:00
parent 78fd23f69f
commit c1bb355986
32 changed files with 342 additions and 287 deletions
+6 -6
View File
@@ -1,15 +1,15 @@
# Values Sort # Values Compass
Flutter Android app for a guided personal-values card-sort practice. Flutter Android app for a guided personal-values card-sort practice.
Built with Flutter **3.47** (Dart **3.13**), Material 3, Provider, and local persistence. Built with Flutter (Dart **3.13**), Material 3, Provider, and local persistence.
Package name: `com.example.myapp` Package name: `com.valuesort.app`
## Practice flow ## Practice flow
1. Welcome (+ how-it-works guide) 1. Welcome (+ how-it-works guide)
2. Sort each value into five reflective piles 2. Sort each value into five reflective piles
3. Review / refine piles 3. Review piles and reorder cards as needed
4. Rank your most important values 4. Rank your most important values
5. Reflect with journal prompts 5. Reflect with journal prompts
@@ -23,13 +23,13 @@ lib/
├── data/values_deck.dart ├── data/values_deck.dart
├── models/ ├── models/
├── providers/ ├── providers/
├── screens/ # welcome → sort → overview → rank → reflect ├── screens/ # welcome → sort → hub → overview / rank / reflect
├── services/storage_service.dart ├── services/storage_service.dart
├── utils/ ├── utils/
└── widgets/ └── widgets/
assets/ assets/
├── data/values_deck.json ├── data/values_deck.json
└── images/emblem.png └── images/emblem.png / app_icon.png
stitch/ # exported Stitch HTML/PNG design references stitch/ # exported Stitch HTML/PNG design references
``` ```
+2 -3
View File
@@ -5,7 +5,7 @@ plugins {
} }
android { android {
namespace = "com.example.myapp" namespace = "com.valuesort.app"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
@@ -15,8 +15,7 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.valuesort.app"
applicationId = "com.example.myapp"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config. // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion minSdk = flutter.minSdkVersion
+1 -1
View File
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="MyApp" android:label="Values Compass"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
@@ -1,4 +1,4 @@
package com.example.myapp package com.valuesort.app
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 84 KiB

+1 -1
View File
@@ -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; library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
-7
View File
@@ -23,11 +23,4 @@ class ValueCard {
domain: json['domain'] as String? ?? 'Values', domain: json['domain'] as String? ?? 'Values',
); );
} }
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'description': description,
'domain': domain,
};
} }
+11 -22
View File
@@ -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; library;
import 'dart:async'; import 'dart:async';
@@ -25,14 +25,13 @@ enum SortStage {
sorting, sorting,
hub, hub,
overview, overview,
pileReview,
ranking, ranking,
reflection, reflection,
} }
/// ChangeNotifier that owns the deck, pile assignments, ranking, and journals. /// ChangeNotifier that owns the deck, pile assignments, ranking, and journals.
class ValuesSortController extends ChangeNotifier { class ValuesSortController extends ChangeNotifier {
ValuesSortController({required StorageService this._storage}); ValuesSortController({required this._storage});
final StorageService _storage; final StorageService _storage;
@@ -65,9 +64,6 @@ class ValuesSortController extends ChangeNotifier {
/// Reflections keyed by `valueId|promptIndex`. /// Reflections keyed by `valueId|promptIndex`.
final Map<String, String> _reflections = {}; final Map<String, String> _reflections = {};
ValuePile? _reviewPile;
ValuePile? get reviewPile => _reviewPile;
String? _activeReflectionValueId; String? _activeReflectionValueId;
String? get activeReflectionValueId => _activeReflectionValueId; String? get activeReflectionValueId => _activeReflectionValueId;
@@ -112,7 +108,6 @@ class ValuesSortController extends ChangeNotifier {
..addAll(_deck); ..addAll(_deck);
_rankedIds.clear(); _rankedIds.clear();
_reflections.clear(); _reflections.clear();
_reviewPile = null;
_activeReflectionValueId = null; _activeReflectionValueId = null;
_activePromptIndex = 0; _activePromptIndex = 0;
} }
@@ -218,12 +213,6 @@ class ValuesSortController extends ChangeNotifier {
_commit(); _commit();
} }
void openPileReview(ValuePile pile) {
_reviewPile = pile;
_stage = SortStage.pileReview;
_commit();
}
void goToRanking() { void goToRanking() {
_syncRankingWithMostImportant(); _syncRankingWithMostImportant();
if (_rankedIds.isEmpty) { if (_rankedIds.isEmpty) {
@@ -333,7 +322,6 @@ class ValuesSortController extends ChangeNotifier {
'version': 1, 'version': 1,
'deckFingerprint': _deckFingerprint, 'deckFingerprint': _deckFingerprint,
'stage': _stage.name, 'stage': _stage.name,
'reviewPile': _reviewPile?.name,
'assignments': { 'assignments': {
for (final e in _assignments.entries) e.key: e.value.name, for (final e in _assignments.entries) e.key: e.value.name,
}, },
@@ -414,7 +402,6 @@ class ValuesSortController extends ChangeNotifier {
.where(knownIds.contains), .where(knownIds.contains),
); );
_reviewPile = ValuePileX.fromName(data['reviewPile'] as String?);
_activeReflectionValueId = data['activeReflectionValueId'] as String?; _activeReflectionValueId = data['activeReflectionValueId'] as String?;
if (_activeReflectionValueId != null && if (_activeReflectionValueId != null &&
!knownIds.contains(_activeReflectionValueId)) { !knownIds.contains(_activeReflectionValueId)) {
@@ -423,19 +410,21 @@ class ValuesSortController extends ChangeNotifier {
_activePromptIndex = (data['activePromptIndex'] as num?)?.toInt() ?? 0; _activePromptIndex = (data['activePromptIndex'] as num?)?.toInt() ?? 0;
final stageName = data['stage'] as String? ?? SortStage.welcome.name; final stageName = data['stage'] as String? ?? SortStage.welcome.name;
_stage = SortStage.values.firstWhere( // Remap removed refine stage onto the hub for older sessions.
(s) => s.name == stageName, if (stageName == 'pileReview') {
orElse: () => SortStage.welcome, _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 restored mid-sort but nothing left unsorted, land on the hub.
if (_stage == SortStage.sorting && isSortComplete) { if (_stage == SortStage.sorting && isSortComplete) {
_seedRankingFromMostImportant(); _seedRankingFromMostImportant();
_stage = SortStage.hub; _stage = SortStage.hub;
} }
if (_stage == SortStage.pileReview && _reviewPile == null) {
_stage = SortStage.hub;
}
} catch (_) { } catch (_) {
await _storage.clearSortSession(); await _storage.clearSortSession();
_resetSortState(); _resetSortState();
-129
View File
@@ -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]);
}
}
-2
View File
@@ -6,7 +6,6 @@ import 'package:provider/provider.dart';
import '../providers/values_sort_provider.dart'; import '../providers/values_sort_provider.dart';
import 'card_sorting_screen.dart'; import 'card_sorting_screen.dart';
import 'pile_review_screen.dart';
import 'practice_hub_screen.dart'; import 'practice_hub_screen.dart';
import 'rank_order_screen.dart'; import 'rank_order_screen.dart';
import 'reflection_screen.dart'; import 'reflection_screen.dart';
@@ -56,7 +55,6 @@ class PracticeFlowScreen extends StatelessWidget {
SortStage.sorting => const CardSortingScreen(), SortStage.sorting => const CardSortingScreen(),
SortStage.hub => const PracticeHubScreen(), SortStage.hub => const PracticeHubScreen(),
SortStage.overview => const SortedOverviewScreen(), SortStage.overview => const SortedOverviewScreen(),
SortStage.pileReview => const PileReviewScreen(),
SortStage.ranking => const RankOrderScreen(), SortStage.ranking => const RankOrderScreen(),
SortStage.reflection => const ReflectionScreen(), SortStage.reflection => const ReflectionScreen(),
}, },
-8
View File
@@ -135,14 +135,6 @@ class RankOrderScreen extends StatelessWidget {
color: colors.onSurfaceVariant, color: colors.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 4),
Text(
'Pillar: ${card.domain}',
style: text.labelSmall?.copyWith(
color: colors.onSurfaceVariant,
),
),
], ],
), ),
), ),
+2 -10
View File
@@ -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; library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -25,7 +25,6 @@ class _SortedOverviewScreenState extends State<SortedOverviewScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sort = context.watch<ValuesSortController>(); final sort = context.watch<ValuesSortController>();
final text = Theme.of(context).textTheme; final text = Theme.of(context).textTheme;
final colors = Theme.of(context).colorScheme;
return PracticeScaffold( return PracticeScaffold(
header: Padding( header: Padding(
@@ -47,8 +46,6 @@ class _SortedOverviewScreenState extends State<SortedOverviewScreen> {
), ),
child: ListView( child: ListView(
children: [ children: [
Text('All Sorted Values', style: text.displaySmall),
const SizedBox(height: 16),
TextField( TextField(
onChanged: (v) => setState(() => _query = v.trim().toLowerCase()), onChanged: (v) => setState(() => _query = v.trim().toLowerCase()),
decoration: const InputDecoration( decoration: const InputDecoration(
@@ -93,12 +90,7 @@ class _SortedOverviewScreenState extends State<SortedOverviewScreen> {
onDemote: (card) => _showDemoteSheet(context, sort, card), onDemote: (card) => _showDemoteSheet(context, sort, card),
); );
}), }),
const SizedBox(height: 12), const SizedBox(height: 8),
Text(
'Tip: refine any pile before ranking your core values.',
style: text.bodySmall?.copyWith(color: colors.onSurfaceVariant),
textAlign: TextAlign.center,
),
], ],
), ),
); );
+10 -6
View File
@@ -89,20 +89,24 @@ class _SplashScreenState extends State<SplashScreen>
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ClipOval( ClipRRect(
borderRadius: BorderRadius.circular(22),
child: Image.asset( child: Image.asset(
'assets/images/emblem.png', 'assets/images/app_icon.png',
width: 88, width: 88,
height: 88, height: 88,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, _, _) => Container( errorBuilder: (_, _, _) => Container(
width: 88, width: 88,
height: 88, height: 88,
color: colors.primaryContainer, decoration: BoxDecoration(
child: const Icon( color: colors.primaryContainer,
Icons.spa, borderRadius: BorderRadius.circular(22),
),
child: Icon(
Icons.explore,
size: 44, size: 44,
color: Colors.white, color: colors.onPrimary,
), ),
), ),
), ),
+6 -7
View File
@@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../providers/values_sort_provider.dart'; import '../providers/values_sort_provider.dart';
import '../utils/app_theme.dart';
import '../widgets/practice_chrome.dart'; import '../widgets/practice_chrome.dart';
class WelcomeScreen extends StatelessWidget { class WelcomeScreen extends StatelessWidget {
@@ -136,9 +135,9 @@ class WelcomeScreen extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppTheme.surfaceContainerLow, color: colors.surfaceContainerLow,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppTheme.outlineVariant), border: Border.all(color: colors.outlineVariant),
), ),
child: Row( child: Row(
children: [ children: [
@@ -214,7 +213,7 @@ class _GuideStep extends StatelessWidget {
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 12, radius: 12,
backgroundColor: AppTheme.surfaceContainerHigh, backgroundColor: colors.surfaceContainerHigh,
child: Text( child: Text(
'$index', '$index',
style: text.labelMedium?.copyWith( style: text.labelMedium?.copyWith(
@@ -266,12 +265,12 @@ class _CardFan extends StatelessWidget {
height: hero ? 155 : 135, height: hero ? 155 : 135,
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
decoration: BoxDecoration( decoration: BoxDecoration(
color: hero ? Colors.white : AppTheme.surfaceContainer, color: hero ? colors.surface : colors.surfaceContainer,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppTheme.outlineVariant), border: Border.all(color: colors.outlineVariant),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: const Color(0x142C2825), color: colors.shadow.withValues(alpha: 0.08),
blurRadius: hero ? 16 : 8, blurRadius: hero ? 16 : 8,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
+1 -1
View File
@@ -30,7 +30,7 @@ class StorageService {
return _prefs.setString(StorageKeys.themeMode, value); 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); String? get sortSessionJson => _prefs.getString(StorageKeys.sortSession);
Future<void> setSortSessionJson(String json) { Future<void> setSortSessionJson(String json) {
+2 -11
View File
@@ -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; library;
/// Human-readable name shown in the splash screen and brand chrome. /// Human-readable name shown in the splash screen and brand chrome.
const String kAppName = 'Values Sort'; const String kAppName = 'Values Compass';
/// Package / Android application id.
const String kPackageName = 'com.example.myapp';
/// How long the splash screen stays visible before navigating onward. /// How long the splash screen stays visible before navigating onward.
const Duration kSplashDuration = Duration(milliseconds: 1400); const Duration kSplashDuration = Duration(milliseconds: 1400);
@@ -16,11 +13,5 @@ abstract final class StorageKeys {
static const sortSession = 'values_sort_session_v1'; 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. /// Maximum content width on tablets so reflective flows stay readable.
const double kMaxContentWidth = 600.0; const double kMaxContentWidth = 600.0;
+11 -7
View File
@@ -1,4 +1,4 @@
/// Shared layout chrome for Values Sort screens. /// Shared layout chrome for Values Compass screens.
library; library;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -44,7 +44,7 @@ class PracticeScaffold extends StatelessWidget {
child: ThemeToggleButton(), child: ThemeToggleButton(),
), ),
), ),
if (header != null) header!, ?header,
Expanded( Expanded(
child: Padding( child: Padding(
padding: pad, padding: pad,
@@ -76,23 +76,27 @@ class BrandMark extends StatelessWidget {
return Row( return Row(
children: [ children: [
ClipOval( ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.asset( child: Image.asset(
'assets/images/emblem.png', 'assets/images/app_icon.png',
width: 32, width: 32,
height: 32, height: 32,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, _, _) => Container( errorBuilder: (_, _, _) => Container(
width: 32, width: 32,
height: 32, height: 32,
color: colors.primaryContainer, decoration: BoxDecoration(
child: Icon(Icons.spa, size: 18, color: colors.onPrimary), color: colors.primaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.explore, size: 18, color: colors.onPrimary),
), ),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'VALUES SORT', 'VALUES COMPASS',
style: labels.labelSmall?.copyWith( style: labels.labelSmall?.copyWith(
letterSpacing: 2.2, letterSpacing: 2.2,
color: colors.onSurfaceVariant, color: colors.onSurfaceVariant,
-8
View File
@@ -65,14 +65,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.7" version: "3.0.7"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
url: "https://pub.dev"
source: hosted
version: "1.0.9"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
+4 -50
View File
@@ -1,5 +1,5 @@
name: myapp name: valuecards
description: Values Sort — a guided card-sort practice for clarifying personal values. description: Values Compass — a guided card-sort practice for clarifying personal values.
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
@@ -21,19 +21,10 @@ version: 1.0.0+1
environment: environment:
sdk: ^3.13.2 sdk: ^3.13.2
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
provider: ^6.1.5+1 provider: ^6.1.5+1
shared_preferences: ^2.5.5 shared_preferences: ^2.5.5
google_fonts: ^8.2.1 google_fonts: ^8.2.1
@@ -42,50 +33,13 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter: flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true uses-material-design: true
assets: assets:
- assets/data/values_deck.json - assets/data/values_deck.json
- assets/images/emblem.png - assets/images/emblem.png
- assets/images/app_icon.png
# An image asset can refer to one or more resolution-specific "variants", see - assets/images/app_icon_1024.png
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
+12
View File
@@ -106,5 +106,17 @@
"deviceType": "MOBILE", "deviceType": "MOBILE",
"htmlFile": "practice-hub.html", "htmlFile": "practice-hub.html",
"screenshotFile": "practice-hub.png" "screenshotFile": "practice-hub.png"
},
{
"id": "2ff051bf1fe5482699a5f1bb9a5e980d",
"slug": "app-icon",
"title": "Values Compass App Icon (Values Top / Compass Bottom)",
"width": "512",
"height": "512",
"deviceType": "ICON",
"htmlFile": "app-icon.svg",
"screenshotFile": "app-icon.png",
"htmlBytes": 6549,
"screenshotBytes": 85489
} }
] ]
+125
View File
@@ -0,0 +1,125 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
<defs>
<!-- Background subtle gradient: warm linen into soft stone -->
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#FAF6F0"/>
<stop offset="100%" stop-color="#EFE9DE"/>
</linearGradient>
<!-- Card 1 (back) -->
<linearGradient id="cardBackGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#E8E0D2"/>
<stop offset="100%" stop-color="#DDD3C3"/>
</linearGradient>
<!-- Card 2 (middle) -->
<linearGradient id="cardMidGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#F4EFE6"/>
<stop offset="100%" stop-color="#E5DDD0"/>
</linearGradient>
<!-- Card 3 (front/core anchor) -->
<linearGradient id="cardFrontGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF"/>
<stop offset="100%" stop-color="#FAF7F1"/>
</linearGradient>
<!-- Gold / Warm Taupe Compass Accent -->
<linearGradient id="goldAccent" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#C4975E"/>
<stop offset="50%" stop-color="#7D6B58"/>
<stop offset="100%" stop-color="#5E4E3E"/>
</linearGradient>
<!-- Drop Shadows -->
<filter id="shadowBack" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="5" stdDeviation="7" flood-color="#4A3F35" flood-opacity="0.08"/>
</filter>
<filter id="shadowMid" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#4A3F35" flood-opacity="0.09"/>
</filter>
<filter id="shadowFront" x="-25%" y="-20%" width="150%" height="150%">
<feDropShadow dx="0" dy="12" stdDeviation="14" flood-color="#4A3F35" flood-opacity="0.12"/>
</filter>
<filter id="glowCenter" x="-30%" y="-30%" width="160%" height="160%">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#C4975E" flood-opacity="0.25"/>
</filter>
</defs>
<!-- Squircle Base Container for App Icon -->
<rect width="512" height="512" rx="116" fill="url(#bgGrad)"/>
<!-- Subtle tactile border -->
<rect width="508" height="508" x="2" y="2" rx="114" fill="none" stroke="#FFFFFF" stroke-width="2.5" stroke-opacity="0.7"/>
<rect width="510" height="510" x="1" y="1" rx="115" fill="none" stroke="#D8CEBF" stroke-width="1" stroke-opacity="0.35"/>
<!-- TOP TEXT: "Values" in even bigger font -->
<text x="256" y="96" font-family="'Newsreader', 'Georgia', serif" font-size="46" font-weight="700" fill="#2E241C" text-anchor="middle" letter-spacing="1.2">Values</text>
<!-- CENTER DECK: Layered Cards (Vertically centered between y=120 and y=390, centered at y=255) -->
<!-- BACK CARD -->
<g transform="translate(256, 255) rotate(-9) translate(-256, -255)" filter="url(#shadowBack)">
<rect x="168" y="148" width="176" height="214" rx="24" fill="url(#cardBackGrad)" stroke="#D0C5B4" stroke-width="1.5"/>
<line x1="198" y1="180" x2="252" y2="180" stroke="#B8AC9B" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
</g>
<!-- MIDDLE CARD -->
<g transform="translate(256, 255) rotate(7) translate(-256, -255)" filter="url(#shadowMid)">
<rect x="168" y="148" width="176" height="214" rx="24" fill="url(#cardMidGrad)" stroke="#E0D7C9" stroke-width="1.5"/>
<line x1="198" y1="180" x2="270" y2="180" stroke="#A89B8A" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
</g>
<!-- FRONT CARD -->
<g filter="url(#shadowFront)">
<!-- Primary Card Body -->
<rect x="168" y="142" width="176" height="226" rx="24" fill="url(#cardFrontGrad)" stroke="#ECE4D8" stroke-width="1.5"/>
<!-- Inset border line for tactile editorial paper texture -->
<rect x="178" y="152" width="156" height="206" rx="18" fill="none" stroke="#7D6B58" stroke-width="1" stroke-opacity="0.12" stroke-dasharray="3 3"/>
<!-- Pill indicator -->
<rect x="238" y="163" width="36" height="3" rx="1.5" fill="#7D6B58" fill-opacity="0.25"/>
<!-- CENTER EMBLEM: Compass / North Star Icon -->
<g transform="translate(256, 252)">
<!-- Outer delicate concentric aura circles -->
<circle cx="0" cy="0" r="48" fill="none" stroke="#7D6B58" stroke-opacity="0.1" stroke-width="1"/>
<circle cx="0" cy="0" r="37" fill="#FAF6F0" stroke="#7D6B58" stroke-opacity="0.18" stroke-width="1.2"/>
<!-- 4 cardinal tick marks -->
<line x1="0" y1="-43" x2="0" y2="-39" stroke="#7D6B58" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
<line x1="0" y1="39" x2="0" y2="43" stroke="#7D6B58" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
<line x1="-43" y1="0" x2="-39" y2="0" stroke="#7D6B58" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
<line x1="39" y1="0" x2="43" y2="0" stroke="#7D6B58" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
<!-- 8-Pointed Mindfulness Star / Compass Star of Core Values -->
<!-- North Point -->
<path d="M 0,0 L -6,-11 L 0,-33 L 6,-11 Z" fill="url(#goldAccent)" filter="url(#glowCenter)"/>
<!-- South Point -->
<path d="M 0,0 L -6,11 L 0,33 L 6,11 Z" fill="#7D6B58"/>
<!-- East Point -->
<path d="M 0,0 L 11,-6 L 33,0 L 11,6 Z" fill="#8C7965"/>
<!-- West Point -->
<path d="M 0,0 L -11,-6 L -33,0 L -11,6 Z" fill="#6F5E4E"/>
<!-- Diagonal Secondary Petals -->
<path d="M 0,0 L 4,-8 L 16,-16 L 8,-4 Z" fill="#A48C74" opacity="0.85"/>
<path d="M 0,0 L -4,-8 L -16,-16 L -8,-4 Z" fill="#917A63" opacity="0.85"/>
<path d="M 0,0 L 4,8 L 16,16 L 8,4 Z" fill="#786552" opacity="0.85"/>
<path d="M 0,0 L -4,8 L -16,16 L -8,4 Z" fill="#6A5846" opacity="0.85"/>
<!-- Center Stillness Core -->
<circle cx="0" cy="0" r="6" fill="#FAF6F0" stroke="#7D6B58" stroke-width="1.8"/>
<circle cx="0" cy="0" r="2.8" fill="#C4975E"/>
</g>
<!-- Three grounded dots at bottom of card -->
<g transform="translate(256, 332)">
<circle cx="-13" cy="0" r="2.4" fill="#7D6B58" opacity="0.25"/>
<circle cx="0" cy="0" r="2.8" fill="#C4975E" opacity="0.8"/>
<circle cx="13" cy="0" r="2.4" fill="#7D6B58" opacity="0.25"/>
</g>
</g>
<!-- BOTTOM TEXT: "Compass" in even bigger font -->
<text x="256" y="445" font-family="'Newsreader', 'Georgia', serif" font-size="46" font-weight="700" fill="#2E241C" text-anchor="middle" letter-spacing="1.2">Compass</text>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

+15
View File
@@ -0,0 +1,15 @@
{
"name": "projects/9343387512370733994/screens/2ff051bf1fe5482699a5f1bb9a5e980d",
"title": "Values Compass App Icon (Values Top / Compass Bottom)",
"screenshot": {
"name": "projects/9343387512370733994/screens/37733e82516730535cc8c45d50f9c83977990ea75683fd3b9c5aae43deb599ac/fileEntries/screenshot",
"downloadUrl": "https://lh3.googleusercontent.com/aida/AEtjO1V7dhzWlGzADkZJ8s9ld_l4GvEdZrbr95uqltFAiNz4A3amMuVlieFKXgEeSbXi9JXw5EKVu9kizHY2o62tAWpZ81-RddVCw_8KXWcvWjEwOxPzALUFySqbtXkuW2ghbCe9-Ne-L9KTjubvBLv9MSOaPyEpIKNrbfqlh53CoYtCc9g1UunfdGYMPYXefoJ6BHDS2eERw2Y1JZdS5lW-s65D_qWCwZ4zBjMaDa66GOcTexVMKFo7DA-kt4s"
},
"htmlCode": {
"name": "projects/9343387512370733994/screens/2ff051bf1fe5482699a5f1bb9a5e980d/fileEntries/html",
"downloadUrl": "https://contribution.usercontent.google.com/download?c=CgthaWRhX2NvZGVmeBJ7Eh1hcHBfY29tcGFuaW9uX2dlbmVyYXRlZF9maWxlcxpaCiVodG1sXzAwMDY1YmI1MTMzNzJjM2YwNDRmNDUwZGQ3M2RhNGY3EgsSBxD_iY311x0YAZIBIwoKcHJvamVjdF9pZBIVQhM5MzQzMzg3NTEyMzcwNzMzOTk0&filename=&opi=89354086",
"mimeType": "image/svg+xml"
},
"width": "512",
"height": "512"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

+125
View File
@@ -0,0 +1,125 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
<defs>
<!-- Background subtle gradient: warm linen into soft stone -->
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#FAF6F0"/>
<stop offset="100%" stop-color="#EFE9DE"/>
</linearGradient>
<!-- Card 1 (back) -->
<linearGradient id="cardBackGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#E8E0D2"/>
<stop offset="100%" stop-color="#DDD3C3"/>
</linearGradient>
<!-- Card 2 (middle) -->
<linearGradient id="cardMidGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#F4EFE6"/>
<stop offset="100%" stop-color="#E5DDD0"/>
</linearGradient>
<!-- Card 3 (front/core anchor) -->
<linearGradient id="cardFrontGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF"/>
<stop offset="100%" stop-color="#FAF7F1"/>
</linearGradient>
<!-- Gold / Warm Taupe Compass Accent -->
<linearGradient id="goldAccent" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#C4975E"/>
<stop offset="50%" stop-color="#7D6B58"/>
<stop offset="100%" stop-color="#5E4E3E"/>
</linearGradient>
<!-- Drop Shadows -->
<filter id="shadowBack" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="5" stdDeviation="7" flood-color="#4A3F35" flood-opacity="0.08"/>
</filter>
<filter id="shadowMid" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#4A3F35" flood-opacity="0.09"/>
</filter>
<filter id="shadowFront" x="-25%" y="-20%" width="150%" height="150%">
<feDropShadow dx="0" dy="12" stdDeviation="14" flood-color="#4A3F35" flood-opacity="0.12"/>
</filter>
<filter id="glowCenter" x="-30%" y="-30%" width="160%" height="160%">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#C4975E" flood-opacity="0.25"/>
</filter>
</defs>
<!-- Squircle Base Container for App Icon -->
<rect width="512" height="512" rx="116" fill="url(#bgGrad)"/>
<!-- Subtle tactile border -->
<rect width="508" height="508" x="2" y="2" rx="114" fill="none" stroke="#FFFFFF" stroke-width="2.5" stroke-opacity="0.7"/>
<rect width="510" height="510" x="1" y="1" rx="115" fill="none" stroke="#D8CEBF" stroke-width="1" stroke-opacity="0.35"/>
<!-- TOP TEXT: "Values" in even bigger font -->
<text x="256" y="96" font-family="'Newsreader', 'Georgia', serif" font-size="46" font-weight="700" fill="#2E241C" text-anchor="middle" letter-spacing="1.2">Values</text>
<!-- CENTER DECK: Layered Cards (Vertically centered between y=120 and y=390, centered at y=255) -->
<!-- BACK CARD -->
<g transform="translate(256, 255) rotate(-9) translate(-256, -255)" filter="url(#shadowBack)">
<rect x="168" y="148" width="176" height="214" rx="24" fill="url(#cardBackGrad)" stroke="#D0C5B4" stroke-width="1.5"/>
<line x1="198" y1="180" x2="252" y2="180" stroke="#B8AC9B" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
</g>
<!-- MIDDLE CARD -->
<g transform="translate(256, 255) rotate(7) translate(-256, -255)" filter="url(#shadowMid)">
<rect x="168" y="148" width="176" height="214" rx="24" fill="url(#cardMidGrad)" stroke="#E0D7C9" stroke-width="1.5"/>
<line x1="198" y1="180" x2="270" y2="180" stroke="#A89B8A" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
</g>
<!-- FRONT CARD -->
<g filter="url(#shadowFront)">
<!-- Primary Card Body -->
<rect x="168" y="142" width="176" height="226" rx="24" fill="url(#cardFrontGrad)" stroke="#ECE4D8" stroke-width="1.5"/>
<!-- Inset border line for tactile editorial paper texture -->
<rect x="178" y="152" width="156" height="206" rx="18" fill="none" stroke="#7D6B58" stroke-width="1" stroke-opacity="0.12" stroke-dasharray="3 3"/>
<!-- Pill indicator -->
<rect x="238" y="163" width="36" height="3" rx="1.5" fill="#7D6B58" fill-opacity="0.25"/>
<!-- CENTER EMBLEM: Compass / North Star Icon -->
<g transform="translate(256, 252)">
<!-- Outer delicate concentric aura circles -->
<circle cx="0" cy="0" r="48" fill="none" stroke="#7D6B58" stroke-opacity="0.1" stroke-width="1"/>
<circle cx="0" cy="0" r="37" fill="#FAF6F0" stroke="#7D6B58" stroke-opacity="0.18" stroke-width="1.2"/>
<!-- 4 cardinal tick marks -->
<line x1="0" y1="-43" x2="0" y2="-39" stroke="#7D6B58" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
<line x1="0" y1="39" x2="0" y2="43" stroke="#7D6B58" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
<line x1="-43" y1="0" x2="-39" y2="0" stroke="#7D6B58" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
<line x1="39" y1="0" x2="43" y2="0" stroke="#7D6B58" stroke-width="1.5" stroke-linecap="round" opacity="0.5"/>
<!-- 8-Pointed Mindfulness Star / Compass Star of Core Values -->
<!-- North Point -->
<path d="M 0,0 L -6,-11 L 0,-33 L 6,-11 Z" fill="url(#goldAccent)" filter="url(#glowCenter)"/>
<!-- South Point -->
<path d="M 0,0 L -6,11 L 0,33 L 6,11 Z" fill="#7D6B58"/>
<!-- East Point -->
<path d="M 0,0 L 11,-6 L 33,0 L 11,6 Z" fill="#8C7965"/>
<!-- West Point -->
<path d="M 0,0 L -11,-6 L -33,0 L -11,6 Z" fill="#6F5E4E"/>
<!-- Diagonal Secondary Petals -->
<path d="M 0,0 L 4,-8 L 16,-16 L 8,-4 Z" fill="#A48C74" opacity="0.85"/>
<path d="M 0,0 L -4,-8 L -16,-16 L -8,-4 Z" fill="#917A63" opacity="0.85"/>
<path d="M 0,0 L 4,8 L 16,16 L 8,4 Z" fill="#786552" opacity="0.85"/>
<path d="M 0,0 L -4,8 L -16,16 L -8,4 Z" fill="#6A5846" opacity="0.85"/>
<!-- Center Stillness Core -->
<circle cx="0" cy="0" r="6" fill="#FAF6F0" stroke="#7D6B58" stroke-width="1.8"/>
<circle cx="0" cy="0" r="2.8" fill="#C4975E"/>
</g>
<!-- Three grounded dots at bottom of card -->
<g transform="translate(256, 332)">
<circle cx="-13" cy="0" r="2.4" fill="#7D6B58" opacity="0.25"/>
<circle cx="0" cy="0" r="2.8" fill="#C4975E" opacity="0.8"/>
<circle cx="13" cy="0" r="2.4" fill="#7D6B58" opacity="0.25"/>
</g>
</g>
<!-- BOTTOM TEXT: "Compass" in even bigger font -->
<text x="256" y="445" font-family="'Newsreader', 'Georgia', serif" font-size="46" font-weight="700" fill="#2E241C" text-anchor="middle" letter-spacing="1.2">Compass</text>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

+7 -7
View File
@@ -1,18 +1,18 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:myapp/main.dart';
import 'package:myapp/models/value_card.dart';
import 'package:myapp/models/value_pile.dart';
import 'package:myapp/providers/values_sort_provider.dart';
import 'package:myapp/services/storage_service.dart';
import 'package:myapp/utils/constants.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:valuecards/main.dart';
import 'package:valuecards/models/value_card.dart';
import 'package:valuecards/models/value_pile.dart';
import 'package:valuecards/providers/values_sort_provider.dart';
import 'package:valuecards/services/storage_service.dart';
import 'package:valuecards/utils/constants.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
GoogleFonts.config.allowRuntimeFetching = false; GoogleFonts.config.allowRuntimeFetching = false;
testWidgets('splash shows Values Sort then opens welcome', (tester) async { testWidgets('splash shows Values Compass then opens welcome', (tester) async {
SharedPreferences.setMockInitialValues({}); SharedPreferences.setMockInitialValues({});
final storage = StorageService(); final storage = StorageService();
await storage.init(); await storage.init();