geeknative 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/bin/geeknative.js +12 -0
- package/package.json +31 -4
- package/src/cli.js +61 -0
- package/src/codegen/app.js +97 -0
- package/src/codegen/icons.js +87 -0
- package/src/codegen/index.js +44 -0
- package/src/codegen/strings.js +308 -0
- package/src/codegen/tokens.js +449 -0
- package/src/codegen/util.js +122 -0
- package/src/commands/check.js +143 -0
- package/src/commands/create.js +78 -0
- package/src/commands/doctor.js +108 -0
- package/src/commands/gen.js +44 -0
- package/src/commands/parity.js +134 -0
- package/src/commands/run.js +91 -0
- package/src/lib/exec.js +77 -0
- package/src/lib/fsutil.js +92 -0
- package/src/lib/log.js +37 -0
- package/src/lib/managed.js +80 -0
- package/src/lib/project.js +52 -0
- package/src/lib/toolchains.js +126 -0
- package/template/.claude/agents/android-engineer.md +15 -0
- package/template/.claude/agents/ios-engineer.md +15 -0
- package/template/.claude/agents/web-engineer.md +15 -0
- package/template/.claude/settings.json +65 -0
- package/template/.geeknative/framework.mk +41 -0
- package/template/.geeknative/hooks/after-edit.mjs +28 -0
- package/template/.geeknative/hooks/parity-guard.mjs +49 -0
- package/template/.geeknative/hooks/subagent-context.mjs +24 -0
- package/template/.geeknative/playbooks/bug.md +7 -0
- package/template/.geeknative/playbooks/design.md +10 -0
- package/template/.geeknative/playbooks/engineering.md +9 -0
- package/template/.geeknative/playbooks/feature.md +11 -0
- package/template/.geeknative/playbooks/platform-specific.md +8 -0
- package/template/.geeknative/playbooks/product.md +12 -0
- package/template/.geeknative/templates/feature.md +49 -0
- package/template/.gemini/settings.json +5 -0
- package/template/.github/dependabot.yml +18 -0
- package/template/AGENTS.md +97 -0
- package/template/CLAUDE.md +9 -0
- package/template/DESIGN.md +65 -0
- package/template/Makefile +6 -0
- package/template/PRODUCT.md +40 -0
- package/template/README.md +28 -0
- package/template/_gitignore +33 -0
- package/template/apps/android/AGENTS.md +66 -0
- package/template/apps/android/CLAUDE.md +1 -0
- package/template/apps/android/app/build.gradle.kts +68 -0
- package/template/apps/android/app/identity.properties +4 -0
- package/template/apps/android/app/src/main/AndroidManifest.xml +24 -0
- package/template/apps/android/app/src/main/kotlin/app/App.kt +15 -0
- package/template/apps/android/app/src/main/kotlin/app/MainActivity.kt +49 -0
- package/template/apps/android/app/src/main/kotlin/app/core/AppContainer.kt +27 -0
- package/template/apps/android/app/src/main/kotlin/app/core/designsystem/AppCard.kt +30 -0
- package/template/apps/android/app/src/main/kotlin/app/core/designsystem/AppTheme.kt +35 -0
- package/template/apps/android/app/src/main/kotlin/app/core/navigation/AppNavigation.kt +40 -0
- package/template/apps/android/app/src/main/kotlin/app/core/navigation/Route.kt +13 -0
- package/template/apps/android/app/src/main/kotlin/app/features/home/HomeScreen.kt +67 -0
- package/template/apps/android/app/src/main/kotlin/app/features/settings/Appearance.kt +22 -0
- package/template/apps/android/app/src/main/kotlin/app/features/settings/SettingsRepository.kt +40 -0
- package/template/apps/android/app/src/main/kotlin/app/features/settings/SettingsScreen.kt +113 -0
- package/template/apps/android/app/src/main/kotlin/app/features/settings/SettingsViewModel.kt +28 -0
- package/template/apps/android/app/src/main/kotlin/app/generated/AppIdentity.kt +10 -0
- package/template/apps/android/app/src/main/kotlin/app/generated/Icons.kt +15 -0
- package/template/apps/android/app/src/main/kotlin/app/generated/Tokens.kt +144 -0
- package/template/apps/android/app/src/main/res/drawable/ic_launcher_foreground.xml +17 -0
- package/template/apps/android/app/src/main/res/mipmap/ic_launcher.xml +10 -0
- package/template/apps/android/app/src/main/res/mipmap/ic_launcher_round.xml +10 -0
- package/template/apps/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
- package/template/apps/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
- package/template/apps/android/app/src/main/res/values/generated_colors.xml +15 -0
- package/template/apps/android/app/src/main/res/values/generated_strings.xml +16 -0
- package/template/apps/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
- package/template/apps/android/app/src/main/res/values/themes.xml +7 -0
- package/template/apps/android/app/src/main/res/values-night/generated_colors.xml +15 -0
- package/template/apps/android/app/src/main/res/values-night/themes.xml +6 -0
- package/template/apps/android/app/src/test/kotlin/app/features/settings/SettingsTest.kt +90 -0
- package/template/apps/android/build.gradle.kts +5 -0
- package/template/apps/android/gradle/gradle-daemon-jvm.properties +12 -0
- package/template/apps/android/gradle/libs.versions.toml +37 -0
- package/template/apps/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/template/apps/android/gradle/wrapper/gradle-wrapper.properties +9 -0
- package/template/apps/android/gradle.properties +7 -0
- package/template/apps/android/gradlew +248 -0
- package/template/apps/android/gradlew.bat +82 -0
- package/template/apps/android/settings.gradle.kts +29 -0
- package/template/apps/ios/AGENTS.md +70 -0
- package/template/apps/ios/App/AppMain.swift +13 -0
- package/template/apps/ios/App/Core/AppContainer.swift +22 -0
- package/template/apps/ios/App/Core/DesignSystem/AppCard.swift +15 -0
- package/template/apps/ios/App/Core/Navigation/Route.swift +4 -0
- package/template/apps/ios/App/Core/RootView.swift +32 -0
- package/template/apps/ios/App/Features/Home/HomeScreen.swift +35 -0
- package/template/apps/ios/App/Features/Settings/Appearance.swift +27 -0
- package/template/apps/ios/App/Features/Settings/SettingsRepository.swift +40 -0
- package/template/apps/ios/App/Features/Settings/SettingsScreen.swift +40 -0
- package/template/apps/ios/App/Features/Settings/SettingsViewModel.swift +23 -0
- package/template/apps/ios/App/Generated/AppIdentity.swift +9 -0
- package/template/apps/ios/App/Generated/Colors.xcassets/AccentColor.colorset/Contents.json +38 -0
- package/template/apps/ios/App/Generated/Colors.xcassets/Contents.json +6 -0
- package/template/apps/ios/App/Generated/Icons.swift +8 -0
- package/template/apps/ios/App/Generated/Localizable.xcstrings +127 -0
- package/template/apps/ios/App/Generated/Strings.swift +40 -0
- package/template/apps/ios/App/Generated/Tokens.swift +65 -0
- package/template/apps/ios/App/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png +0 -0
- package/template/apps/ios/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/template/apps/ios/App/Resources/Assets.xcassets/Contents.json +6 -0
- package/template/apps/ios/App.xcodeproj/project.pbxproj +298 -0
- package/template/apps/ios/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/template/apps/ios/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme +89 -0
- package/template/apps/ios/AppTests/SettingsTests.swift +54 -0
- package/template/apps/ios/CLAUDE.md +1 -0
- package/template/apps/ios/Config/App.xcconfig +23 -0
- package/template/apps/ios/Config/AppTests.xcconfig +10 -0
- package/template/apps/ios/Config/Base.xcconfig +25 -0
- package/template/apps/ios/Config/Debug.xcconfig +9 -0
- package/template/apps/ios/Config/Identity.xcconfig +5 -0
- package/template/apps/ios/Config/Release.xcconfig +6 -0
- package/template/apps/web/AGENTS.md +63 -0
- package/template/apps/web/CLAUDE.md +1 -0
- package/template/apps/web/package.json +38 -0
- package/template/apps/web/public/favicon.svg +6 -0
- package/template/apps/web/src/core/AppContainer.ts +18 -0
- package/template/apps/web/src/core/AppearanceEffect.tsx +31 -0
- package/template/apps/web/src/core/designsystem/AppCard.tsx +6 -0
- package/template/apps/web/src/features/home/HomeScreen.tsx +15 -0
- package/template/apps/web/src/features/home/home.test.tsx +22 -0
- package/template/apps/web/src/features/settings/Appearance.ts +29 -0
- package/template/apps/web/src/features/settings/LocalStorageSettingsRepository.ts +21 -0
- package/template/apps/web/src/features/settings/SettingsRepository.ts +7 -0
- package/template/apps/web/src/features/settings/SettingsScreen.tsx +44 -0
- package/template/apps/web/src/features/settings/settings.test.ts +45 -0
- package/template/apps/web/src/features/settings/useSettingsViewModel.ts +42 -0
- package/template/apps/web/src/generated/app.ts +9 -0
- package/template/apps/web/src/generated/icons.ts +9 -0
- package/template/apps/web/src/generated/strings.ts +88 -0
- package/template/apps/web/src/generated/tokens.css +86 -0
- package/template/apps/web/src/routeTree.gen.ts +86 -0
- package/template/apps/web/src/router.tsx +23 -0
- package/template/apps/web/src/routes/__root.tsx +72 -0
- package/template/apps/web/src/routes/index.tsx +6 -0
- package/template/apps/web/src/routes/settings.tsx +9 -0
- package/template/apps/web/src/styles.css +9 -0
- package/template/apps/web/tsconfig.json +26 -0
- package/template/apps/web/tsr.config.json +3 -0
- package/template/apps/web/vite.config.ts +9 -0
- package/template/apps/web/vitest.config.ts +9 -0
- package/template/contracts/app.json +7 -0
- package/template/contracts/icons.json +5 -0
- package/template/contracts/strings/en.json +13 -0
- package/template/contracts/tokens.json +40 -0
- package/template/package.json +17 -0
- package/template/spec/decisions.md +10 -0
- package/template/spec/features/home.md +39 -0
- package/template/spec/features/settings.md +50 -0
- package/template/spec/navigation.md +16 -0
- package/template/spec/parity.md +29 -0
- package/README.md +0 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// GENERATED by geeknative from contracts/tokens.json. Do not edit: change the contract, then run `make gen`.
|
|
2
|
+
package app.generated
|
|
3
|
+
|
|
4
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
5
|
+
import androidx.compose.material3.ColorScheme
|
|
6
|
+
import androidx.compose.material3.Shapes
|
|
7
|
+
import androidx.compose.material3.Typography
|
|
8
|
+
import androidx.compose.material3.darkColorScheme
|
|
9
|
+
import androidx.compose.material3.lightColorScheme
|
|
10
|
+
import androidx.compose.runtime.Immutable
|
|
11
|
+
import androidx.compose.ui.graphics.Color
|
|
12
|
+
import androidx.compose.ui.text.TextStyle
|
|
13
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
14
|
+
import androidx.compose.ui.unit.dp
|
|
15
|
+
import androidx.compose.ui.unit.sp
|
|
16
|
+
|
|
17
|
+
/** Colors for one appearance (light or dark). In composables, read the current set with `AppTheme.colors`. */
|
|
18
|
+
@Immutable
|
|
19
|
+
data class AppColorTokens(
|
|
20
|
+
val background: Color,
|
|
21
|
+
val onBackground: Color,
|
|
22
|
+
val surface: Color,
|
|
23
|
+
val onSurface: Color,
|
|
24
|
+
val surfaceVariant: Color,
|
|
25
|
+
val onSurfaceVariant: Color,
|
|
26
|
+
val primary: Color,
|
|
27
|
+
val onPrimary: Color,
|
|
28
|
+
val outline: Color,
|
|
29
|
+
val error: Color,
|
|
30
|
+
val onError: Color,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
val LightAppColors = AppColorTokens(
|
|
34
|
+
background = Color(0xFFFFFFFF),
|
|
35
|
+
onBackground = Color(0xFF0F172A),
|
|
36
|
+
surface = Color(0xFFF4F5F8),
|
|
37
|
+
onSurface = Color(0xFF0F172A),
|
|
38
|
+
surfaceVariant = Color(0xFFE9EBF1),
|
|
39
|
+
onSurfaceVariant = Color(0xFF566074),
|
|
40
|
+
primary = Color(0xFF4F46E5),
|
|
41
|
+
onPrimary = Color(0xFFFFFFFF),
|
|
42
|
+
outline = Color(0xFFD8DCE5),
|
|
43
|
+
error = Color(0xFFDC2626),
|
|
44
|
+
onError = Color(0xFFFFFFFF),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
val DarkAppColors = AppColorTokens(
|
|
48
|
+
background = Color(0xFF0B0D12),
|
|
49
|
+
onBackground = Color(0xFFE6E8EE),
|
|
50
|
+
surface = Color(0xFF161A23),
|
|
51
|
+
onSurface = Color(0xFFE6E8EE),
|
|
52
|
+
surfaceVariant = Color(0xFF222735),
|
|
53
|
+
onSurfaceVariant = Color(0xFFA3ABBD),
|
|
54
|
+
primary = Color(0xFFA5B4FC),
|
|
55
|
+
onPrimary = Color(0xFF1E1B4B),
|
|
56
|
+
outline = Color(0xFF2D3343),
|
|
57
|
+
error = Color(0xFFF87171),
|
|
58
|
+
onError = Color(0xFF450A0A),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/** Maps the tokens onto Material 3 color roles, so stock components match the design. */
|
|
62
|
+
fun AppColorTokens.toColorScheme(dark: Boolean): ColorScheme =
|
|
63
|
+
(if (dark) darkColorScheme() else lightColorScheme()).copy(
|
|
64
|
+
primary = primary,
|
|
65
|
+
onPrimary = onPrimary,
|
|
66
|
+
primaryContainer = surfaceVariant,
|
|
67
|
+
onPrimaryContainer = onSurface,
|
|
68
|
+
inversePrimary = primary,
|
|
69
|
+
secondary = primary,
|
|
70
|
+
onSecondary = onPrimary,
|
|
71
|
+
secondaryContainer = surfaceVariant,
|
|
72
|
+
onSecondaryContainer = onSurface,
|
|
73
|
+
tertiary = primary,
|
|
74
|
+
onTertiary = onPrimary,
|
|
75
|
+
tertiaryContainer = surfaceVariant,
|
|
76
|
+
onTertiaryContainer = onSurface,
|
|
77
|
+
background = background,
|
|
78
|
+
onBackground = onBackground,
|
|
79
|
+
surface = background,
|
|
80
|
+
onSurface = onBackground,
|
|
81
|
+
surfaceVariant = surfaceVariant,
|
|
82
|
+
onSurfaceVariant = onSurfaceVariant,
|
|
83
|
+
surfaceTint = primary,
|
|
84
|
+
inverseSurface = onBackground,
|
|
85
|
+
inverseOnSurface = background,
|
|
86
|
+
error = error,
|
|
87
|
+
onError = onError,
|
|
88
|
+
outline = outline,
|
|
89
|
+
outlineVariant = outline,
|
|
90
|
+
surfaceBright = background,
|
|
91
|
+
surfaceDim = surfaceVariant,
|
|
92
|
+
surfaceContainerLowest = background,
|
|
93
|
+
surfaceContainerLow = surface,
|
|
94
|
+
surfaceContainer = surface,
|
|
95
|
+
surfaceContainerHigh = surface,
|
|
96
|
+
surfaceContainerHighest = surface,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
/** Spacing. */
|
|
100
|
+
object AppSpacing {
|
|
101
|
+
val xs = 4.dp
|
|
102
|
+
val sm = 8.dp
|
|
103
|
+
val md = 12.dp
|
|
104
|
+
val lg = 16.dp
|
|
105
|
+
val xl = 24.dp
|
|
106
|
+
val xxl = 32.dp
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Corner radii. */
|
|
110
|
+
object AppRadius {
|
|
111
|
+
val sm = 6.dp
|
|
112
|
+
val md = 10.dp
|
|
113
|
+
val lg = 16.dp
|
|
114
|
+
val full = 9999.dp
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Text styles, in sp so they follow the user's font size setting. */
|
|
118
|
+
object AppTypography {
|
|
119
|
+
val display = TextStyle(fontWeight = FontWeight.Bold, fontSize = 34.sp, lineHeight = 41.sp)
|
|
120
|
+
val headline = TextStyle(fontWeight = FontWeight.Bold, fontSize = 28.sp, lineHeight = 34.sp)
|
|
121
|
+
val title = TextStyle(fontWeight = FontWeight.SemiBold, fontSize = 20.sp, lineHeight = 26.sp)
|
|
122
|
+
val body = TextStyle(fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp)
|
|
123
|
+
val label = TextStyle(fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp)
|
|
124
|
+
val caption = TextStyle(fontWeight = FontWeight.Normal, fontSize = 12.sp, lineHeight = 16.sp)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The text styles mapped onto Material 3 type roles, so stock components use them. */
|
|
128
|
+
val AppMaterialTypography = Typography().copy(
|
|
129
|
+
displaySmall = AppTypography.display,
|
|
130
|
+
headlineMedium = AppTypography.headline,
|
|
131
|
+
titleLarge = AppTypography.title,
|
|
132
|
+
bodyLarge = AppTypography.body,
|
|
133
|
+
labelLarge = AppTypography.label,
|
|
134
|
+
bodySmall = AppTypography.caption,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
/** The radii mapped onto Material 3 shape roles. */
|
|
138
|
+
val AppShapes = Shapes(
|
|
139
|
+
extraSmall = RoundedCornerShape(AppRadius.sm),
|
|
140
|
+
small = RoundedCornerShape(AppRadius.sm),
|
|
141
|
+
medium = RoundedCornerShape(AppRadius.md),
|
|
142
|
+
large = RoundedCornerShape(AppRadius.lg),
|
|
143
|
+
extraLarge = RoundedCornerShape(AppRadius.lg),
|
|
144
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Placeholder launcher icon: three dots, one per platform. Replace with your own. -->
|
|
3
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
android:width="108dp"
|
|
5
|
+
android:height="108dp"
|
|
6
|
+
android:viewportWidth="108"
|
|
7
|
+
android:viewportHeight="108">
|
|
8
|
+
<path
|
|
9
|
+
android:fillColor="#FFFFFF"
|
|
10
|
+
android:pathData="M31,54a5.5,5.5 0,1 0,11 0a5.5,5.5 0,1 0,-11 0" />
|
|
11
|
+
<path
|
|
12
|
+
android:fillColor="#FFFFFF"
|
|
13
|
+
android:pathData="M48.5,54a5.5,5.5 0,1 0,11 0a5.5,5.5 0,1 0,-11 0" />
|
|
14
|
+
<path
|
|
15
|
+
android:fillColor="#FFFFFF"
|
|
16
|
+
android:pathData="M66,54a5.5,5.5 0,1 0,11 0a5.5,5.5 0,1 0,-11 0" />
|
|
17
|
+
</vector>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Launcher icon for Android 7.x, which has no adaptive icons. -->
|
|
3
|
+
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
4
|
+
<item>
|
|
5
|
+
<shape android:shape="oval">
|
|
6
|
+
<solid android:color="@color/ic_launcher_background" />
|
|
7
|
+
</shape>
|
|
8
|
+
</item>
|
|
9
|
+
<item android:drawable="@drawable/ic_launcher_foreground" />
|
|
10
|
+
</layer-list>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Launcher icon for Android 7.x, which has no adaptive icons. -->
|
|
3
|
+
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
4
|
+
<item>
|
|
5
|
+
<shape android:shape="oval">
|
|
6
|
+
<solid android:color="@color/ic_launcher_background" />
|
|
7
|
+
</shape>
|
|
8
|
+
</item>
|
|
9
|
+
<item android:drawable="@drawable/ic_launcher_foreground" />
|
|
10
|
+
</layer-list>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<background android:drawable="@color/ic_launcher_background" />
|
|
4
|
+
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
5
|
+
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
6
|
+
</adaptive-icon>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<background android:drawable="@color/ic_launcher_background" />
|
|
4
|
+
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
5
|
+
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
6
|
+
</adaptive-icon>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- GENERATED by geeknative from contracts/tokens.json. Do not edit: change the contract, then run `make gen`. -->
|
|
3
|
+
<resources>
|
|
4
|
+
<color name="token_background">#FFFFFFFF</color>
|
|
5
|
+
<color name="token_on_background">#FF0F172A</color>
|
|
6
|
+
<color name="token_surface">#FFF4F5F8</color>
|
|
7
|
+
<color name="token_on_surface">#FF0F172A</color>
|
|
8
|
+
<color name="token_surface_variant">#FFE9EBF1</color>
|
|
9
|
+
<color name="token_on_surface_variant">#FF566074</color>
|
|
10
|
+
<color name="token_primary">#FF4F46E5</color>
|
|
11
|
+
<color name="token_on_primary">#FFFFFFFF</color>
|
|
12
|
+
<color name="token_outline">#FFD8DCE5</color>
|
|
13
|
+
<color name="token_error">#FFDC2626</color>
|
|
14
|
+
<color name="token_on_error">#FFFFFFFF</color>
|
|
15
|
+
</resources>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- GENERATED by geeknative from contracts/strings. Do not edit: change the contract, then run `make gen`. -->
|
|
3
|
+
<resources>
|
|
4
|
+
<string name="app_name" translatable="false">GeekNative</string>
|
|
5
|
+
<string name="common_back">Back</string>
|
|
6
|
+
<string name="home_open_settings">Settings</string>
|
|
7
|
+
<string name="home_welcome_body">This app is built natively for iOS, Android and the web from one shared spec. Describe what you want to build to your coding agent, and it will build it for all three.</string>
|
|
8
|
+
<string name="home_welcome_title">You\'re all set</string>
|
|
9
|
+
<string name="settings_about_title">About</string>
|
|
10
|
+
<string name="settings_about_version">Version %1$s (%2$d)</string>
|
|
11
|
+
<string name="settings_appearance_dark">Dark</string>
|
|
12
|
+
<string name="settings_appearance_light">Light</string>
|
|
13
|
+
<string name="settings_appearance_system">System</string>
|
|
14
|
+
<string name="settings_appearance_title">Appearance</string>
|
|
15
|
+
<string name="settings_title">Settings</string>
|
|
16
|
+
</resources>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<!-- The window behind Compose. Colors come from contracts/tokens.json (generated_colors.xml). -->
|
|
4
|
+
<style name="Theme.App" parent="android:Theme.Material.Light.NoActionBar">
|
|
5
|
+
<item name="android:windowBackground">@color/token_background</item>
|
|
6
|
+
</style>
|
|
7
|
+
</resources>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- GENERATED by geeknative from contracts/tokens.json. Do not edit: change the contract, then run `make gen`. -->
|
|
3
|
+
<resources>
|
|
4
|
+
<color name="token_background">#FF0B0D12</color>
|
|
5
|
+
<color name="token_on_background">#FFE6E8EE</color>
|
|
6
|
+
<color name="token_surface">#FF161A23</color>
|
|
7
|
+
<color name="token_on_surface">#FFE6E8EE</color>
|
|
8
|
+
<color name="token_surface_variant">#FF222735</color>
|
|
9
|
+
<color name="token_on_surface_variant">#FFA3ABBD</color>
|
|
10
|
+
<color name="token_primary">#FFA5B4FC</color>
|
|
11
|
+
<color name="token_on_primary">#FF1E1B4B</color>
|
|
12
|
+
<color name="token_outline">#FF2D3343</color>
|
|
13
|
+
<color name="token_error">#FFF87171</color>
|
|
14
|
+
<color name="token_on_error">#FF450A0A</color>
|
|
15
|
+
</resources>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
package app.features.settings
|
|
2
|
+
|
|
3
|
+
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
|
4
|
+
import app.generated.AppIdentity
|
|
5
|
+
import java.io.File
|
|
6
|
+
import kotlinx.coroutines.CoroutineScope
|
|
7
|
+
import kotlinx.coroutines.Dispatchers
|
|
8
|
+
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
9
|
+
import kotlinx.coroutines.Job
|
|
10
|
+
import kotlinx.coroutines.cancelAndJoin
|
|
11
|
+
import kotlinx.coroutines.flow.first
|
|
12
|
+
import kotlinx.coroutines.launch
|
|
13
|
+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
14
|
+
import kotlinx.coroutines.test.resetMain
|
|
15
|
+
import kotlinx.coroutines.test.runTest
|
|
16
|
+
import kotlinx.coroutines.test.setMain
|
|
17
|
+
import org.junit.After
|
|
18
|
+
import org.junit.Assert.assertEquals
|
|
19
|
+
import org.junit.Assert.assertFalse
|
|
20
|
+
import org.junit.Assert.assertTrue
|
|
21
|
+
import org.junit.Before
|
|
22
|
+
import org.junit.Rule
|
|
23
|
+
import org.junit.Test
|
|
24
|
+
import org.junit.rules.TemporaryFolder
|
|
25
|
+
|
|
26
|
+
@OptIn(ExperimentalCoroutinesApi::class)
|
|
27
|
+
class SettingsTest {
|
|
28
|
+
@get:Rule
|
|
29
|
+
val folder = TemporaryFolder()
|
|
30
|
+
|
|
31
|
+
@Before
|
|
32
|
+
fun setUp() {
|
|
33
|
+
Dispatchers.setMain(UnconfinedTestDispatcher())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@After
|
|
37
|
+
fun tearDown() {
|
|
38
|
+
Dispatchers.resetMain()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A DataStore-backed repository over [file], living in its own scope (one "app launch"). */
|
|
42
|
+
private fun launchRepository(file: File, scope: CoroutineScope) =
|
|
43
|
+
DataStoreSettingsRepository(PreferenceDataStoreFactory.create(scope = scope, produceFile = { file }))
|
|
44
|
+
|
|
45
|
+
@Test
|
|
46
|
+
fun `AC-settings-1 appearance defaults to System`() = runTest {
|
|
47
|
+
val scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler) + Job())
|
|
48
|
+
val repository = launchRepository(File(folder.root, "settings.preferences_pb"), scope)
|
|
49
|
+
|
|
50
|
+
assertEquals(Appearance.System, repository.appearance.first())
|
|
51
|
+
assertEquals(Appearance.System, Appearance.fromId("sepia"))
|
|
52
|
+
scope.coroutineContext[Job]?.cancelAndJoin()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Test
|
|
56
|
+
fun `AC-settings-2 choosing an appearance applies it to the whole app immediately`() = runTest {
|
|
57
|
+
val repository = InMemorySettingsRepository()
|
|
58
|
+
val viewModel = SettingsViewModel(repository)
|
|
59
|
+
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { viewModel.uiState.collect {} }
|
|
60
|
+
|
|
61
|
+
viewModel.selectAppearance(Appearance.Dark)
|
|
62
|
+
|
|
63
|
+
// MainActivity themes the whole app from the repository's appearance.
|
|
64
|
+
assertEquals(Appearance.Dark, repository.appearance.first())
|
|
65
|
+
assertEquals(Appearance.Dark, viewModel.uiState.value.appearance)
|
|
66
|
+
assertTrue(Appearance.Dark.isDark(systemIsDark = false))
|
|
67
|
+
assertTrue(Appearance.System.isDark(systemIsDark = true))
|
|
68
|
+
assertFalse(Appearance.Light.isDark(systemIsDark = true))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@Test
|
|
72
|
+
fun `AC-settings-3 the chosen appearance survives a restart`() = runTest {
|
|
73
|
+
val file = File(folder.root, "settings.preferences_pb")
|
|
74
|
+
|
|
75
|
+
val firstLaunch = CoroutineScope(UnconfinedTestDispatcher(testScheduler) + Job())
|
|
76
|
+
launchRepository(file, firstLaunch).setAppearance(Appearance.Light)
|
|
77
|
+
firstLaunch.coroutineContext[Job]?.cancelAndJoin()
|
|
78
|
+
|
|
79
|
+
val secondLaunch = CoroutineScope(UnconfinedTestDispatcher(testScheduler) + Job())
|
|
80
|
+
assertEquals(Appearance.Light, launchRepository(file, secondLaunch).appearance.first())
|
|
81
|
+
secondLaunch.coroutineContext[Job]?.cancelAndJoin()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Test
|
|
85
|
+
fun `AC-settings-4 shows the app version and build number`() {
|
|
86
|
+
val state = SettingsUiState()
|
|
87
|
+
assertEquals(AppIdentity.VERSION, state.version)
|
|
88
|
+
assertEquals(AppIdentity.BUILD, state.build)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#This file is generated by :updateDaemonJvm
|
|
2
|
+
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cb205e54cea236622eee3d62deb2c768/redirect
|
|
3
|
+
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/452acd2202c1005abda962c6dae7fc5f/redirect
|
|
4
|
+
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cb205e54cea236622eee3d62deb2c768/redirect
|
|
5
|
+
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/452acd2202c1005abda962c6dae7fc5f/redirect
|
|
6
|
+
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/faa59212208d027d066d1bbe337c5da5/redirect
|
|
7
|
+
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/adf5b89d64773eff73f900e61dd2d632/redirect
|
|
8
|
+
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cb205e54cea236622eee3d62deb2c768/redirect
|
|
9
|
+
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/452acd2202c1005abda962c6dae7fc5f/redirect
|
|
10
|
+
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e0e37f85a97c5f2df685b37b14a25072/redirect
|
|
11
|
+
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/ccc3884e65cd85bb71a09d2223c77229/redirect
|
|
12
|
+
toolchainVersion=21
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[versions]
|
|
2
|
+
agp = "9.4.1"
|
|
3
|
+
kotlin = "2.4.21"
|
|
4
|
+
composeBom = "2026.09.00"
|
|
5
|
+
materialIcons = "1.7.8"
|
|
6
|
+
activityCompose = "1.13.0"
|
|
7
|
+
coreKtx = "1.19.1"
|
|
8
|
+
lifecycle = "2.11.0"
|
|
9
|
+
navigation3 = "1.2.0"
|
|
10
|
+
datastore = "1.2.1"
|
|
11
|
+
kotlinxSerialization = "1.11.0"
|
|
12
|
+
coroutines = "1.11.0"
|
|
13
|
+
junit = "4.13.2"
|
|
14
|
+
|
|
15
|
+
[libraries]
|
|
16
|
+
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
|
17
|
+
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
|
18
|
+
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
|
19
|
+
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
|
20
|
+
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
|
21
|
+
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
|
22
|
+
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
|
23
|
+
androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core", version.ref = "materialIcons" }
|
|
24
|
+
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
|
|
25
|
+
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
|
26
|
+
androidx-lifecycle-viewmodel-navigation3 = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-navigation3", version.ref = "lifecycle" }
|
|
27
|
+
androidx-navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3" }
|
|
28
|
+
androidx-navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "navigation3" }
|
|
29
|
+
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
|
|
30
|
+
kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinxSerialization" }
|
|
31
|
+
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
|
|
32
|
+
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
|
33
|
+
|
|
34
|
+
[plugins]
|
|
35
|
+
android-application = { id = "com.android.application", version.ref = "agp" }
|
|
36
|
+
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
37
|
+
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
distributionBase=GRADLE_USER_HOME
|
|
2
|
+
distributionPath=wrapper/dists
|
|
3
|
+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
|
|
4
|
+
networkTimeout=10000
|
|
5
|
+
retries=0
|
|
6
|
+
retryBackOffMs=500
|
|
7
|
+
validateDistributionUrl=true
|
|
8
|
+
zipStoreBase=GRADLE_USER_HOME
|
|
9
|
+
zipStorePath=wrapper/dists
|