M0: Floret skeleton — Material 3 Expressive scaffold over OpenTasks (planned)
All checks were successful
CI / ci (push) Successful in 6m39s

Sibling to Calendula. Pure front-end posture; TaskContract data layer,
task screens and reminder engine to follow (see docs/PLAN.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Luc Makiola
2026-06-17 19:40:53 +02:00
commit 01d1f558b8
40 changed files with 1929 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!--
M0 skeleton: no permissions yet. Tasks-provider permissions
(org.dmfs.permission.* / org.tasks.permission.*), POST_NOTIFICATIONS,
RECEIVE_BOOT_COMPLETED, USE_EXACT_ALARM and the <queries> package
visibility block arrive with the data layer and reminder engine
(see docs/PLAN.md, §57).
-->
<application
android:name=".FloretApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Floret"
tools:targetApi="35">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,11 @@
package de.jeanlucmakiola.floret
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
/**
* Application entry point. Registered as android:name=".FloretApp"
* in AndroidManifest.xml. Hilt initializes its component graph here.
*/
@HiltAndroidApp
class FloretApp : Application()

View File

@@ -0,0 +1,30 @@
package de.jeanlucmakiola.floret
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import dagger.hilt.android.AndroidEntryPoint
import de.jeanlucmakiola.floret.ui.RootScreen
import de.jeanlucmakiola.floret.ui.theme.FloretTheme
/**
* Single activity. M0 hosts only a themed placeholder scaffold; the task
* screens, theme-mode wiring (SettingsViewModel) and notification/widget
* intent routing land in later milestones — mirroring Calendula's MainActivity.
*/
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
FloretTheme {
RootScreen(modifier = Modifier.fillMaxSize())
}
}
}
}

View File

@@ -0,0 +1,48 @@
package de.jeanlucmakiola.floret.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import de.jeanlucmakiola.floret.R
/**
* M0 placeholder root. Proves the theme, Hilt entry point and edge-to-edge
* scaffold render. Replaced in M1 by the lists overview + nav host
* (FloretHost), the analog of Calendula's CalendarHost.
*/
@Composable
fun RootScreen(modifier: Modifier = Modifier) {
Scaffold(modifier = modifier) { inner ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(inner)
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(
text = stringResource(R.string.app_name),
style = MaterialTheme.typography.displaySmall,
color = MaterialTheme.colorScheme.primary,
)
Text(
text = stringResource(R.string.app_tagline),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier.padding(top = 8.dp),
)
}
}
}

View File

@@ -0,0 +1,46 @@
package de.jeanlucmakiola.floret.ui.theme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
/**
* Seed color anchoring the entire palette. A warm mauve — sibling to
* Calendula's cool slate ([0xFF5C6B7A]) and deliberately distinct from
* HouseHoldKeaper's sage. Evokes the marigold florets a Calendula head is
* made of, kept muted to match the family's restrained aesthetic.
*
* Placeholder until branding lands; refine alongside the launcher icon.
*/
val FloretSeed: Color = Color(0xFF7A5C6B)
/**
* Fallback light scheme for devices without dynamic color (API < 31) or when
* the user disables it. Dynamic color (API 31+) is the real path; these are a
* hand-picked approximation of the tonal palette generated from [FloretSeed].
*/
val FloretLightFallback = lightColorScheme(
primary = Color(0xFF6A4E5B),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFF6D9E5),
onPrimaryContainer = Color(0xFF2A101C),
secondary = Color(0xFF705560),
onSecondary = Color(0xFFFFFFFF),
background = Color(0xFFFEFBFC),
onBackground = Color(0xFF1F1A1C),
surface = Color(0xFFFEFBFC),
onSurface = Color(0xFF1F1A1C),
)
val FloretDarkFallback = darkColorScheme(
primary = Color(0xFFE2BAC9),
onPrimary = Color(0xFF402533),
primaryContainer = Color(0xFF583B49),
onPrimaryContainer = Color(0xFFF6D9E5),
secondary = Color(0xFFD8BCC6),
onSecondary = Color(0xFF3A2831),
background = Color(0xFF161113),
onBackground = Color(0xFFE9E0E3),
surface = Color(0xFF161113),
onSurface = Color(0xFFE9E0E3),
)

View File

@@ -0,0 +1,48 @@
package de.jeanlucmakiola.floret.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MotionScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
/**
* App theme. Honors:
* - System light/dark.
* - Dynamic Color on API 31+, else falls back to the hand-tuned scheme
* derived from [FloretSeed].
*
* Mirrors Calendula's theme: MaterialExpressiveTheme routes all component +
* custom motion through MaterialTheme.motionScheme. STANDARD over expressive()
* is the same deliberate choice — spring choreography without the overshoot.
*
* A Settings screen can later override dynamicColor and theme mode; the M0
* foundation just follows the system.
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun FloretTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit,
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val ctx = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(ctx) else dynamicLightColorScheme(ctx)
}
darkTheme -> FloretDarkFallback
else -> FloretLightFallback
}
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = FloretTypography,
motionScheme = MotionScheme.standard(),
content = content,
)
}

View File

@@ -0,0 +1,10 @@
package de.jeanlucmakiola.floret.ui.theme
import androidx.compose.material3.Typography
/**
* Default Material 3 Expressive typography. A custom font + tuned scale will
* land in a later UI-design iteration; the defaults keep the M0 foundation lean
* (same posture as Calendula's scaffolding).
*/
val FloretTypography = Typography()

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/ic_launcher_background" />
</shape>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Floret launcher icon foreground — PLACEHOLDER.
A simple rounded check mark inside the 108dp adaptive-icon canvas
(72dp safe zone). Deliberately not Calendula's calendar mark, so the two
apps never look alike. Replace with real branding (a stylized floret)
when the design lands — see docs/PLAN.md §9.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeWidth="9"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:pathData="M36,55 l13,13 l25,-27" />
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Seed color: warm mauve. Material 3 derives Light & Dark schemes from this. -->
<color name="seed">#FF7A5C6B</color>
<!-- Adaptive icon background -->
<color name="ic_launcher_background">#FF7A5C6B</color>
</resources>

View File

@@ -0,0 +1,4 @@
<resources>
<string name="app_name">Floret</string>
<string name="app_tagline">A modern Material 3 Expressive task app.\nComing into bloom.</string>
</resources>

View File

@@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.Floret" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
</style>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<!-- No file-based backups; settings live in DataStore which is backed up by default. -->
</full-backup-content>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<!-- Allow DataStore backup, exclude nothing extra. -->
</cloud-backup>
<device-transfer>
</device-transfer>
</data-extraction-rules>