Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class MainActivity : ComponentActivity() {

/** Projet à ouvrir (notification touchée). */
private var deepLinkProject by mutableStateOf<String?>(null)
private var deepLinkCorrections by mutableStateOf<String?>(null)

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(
Expand Down Expand Up @@ -94,6 +95,8 @@ class MainActivity : ComponentActivity() {
selected = selected,
onSelect = { selected = it },
onSignOut = { graph.signOut() },
openCorrectionsFor = deepLinkCorrections,
onCorrectionsOpened = { deepLinkCorrections = null },
)
}
}
Expand Down Expand Up @@ -129,8 +132,9 @@ class MainActivity : ComponentActivity() {
return
}
intent.getStringExtra(Notifier.EXTRA_PROJECT)?.let {
deepLinkProject = it
if (intent.getStringExtra(Notifier.EXTRA_OPEN) == Notifier.OPEN_CORRECTIONS) deepLinkCorrections = it else deepLinkProject = it
intent.removeExtra(Notifier.EXTRA_PROJECT)
intent.removeExtra(Notifier.EXTRA_OPEN)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,20 @@ class ApiClient(

suspend fun drafts(): List<DraftSummary> = get("/drafts", DraftList.serializer()).drafts

/* ---- Corrections ---- */
suspend fun corrections(uuid: String): CorrectionsState =
get("/projects/${enc(uuid)}/corrections", CorrectionsState.serializer())

/** « Appliquer » : rattrape GitHub, envoie et met en ligne. Seulement après confirmation. */
suspend fun correctionApply(uuid: String): CorrectionApplied =
send("POST", "/projects/${enc(uuid)}/corrections/apply", buildJsonObject { put("confirm", true) }, CorrectionApplied.serializer())

suspend fun correctionReject(uuid: String): OkOnly =
send("POST", "/projects/${enc(uuid)}/corrections/reject", buildJsonObject { put("confirm", true) }, OkOnly.serializer())

suspend fun correctionRedo(uuid: String): OkOnly =
send("POST", "/projects/${enc(uuid)}/corrections/redo", buildJsonObject { put("confirm", true) }, OkOnly.serializer())

/* Mémoire des agents (écran natif Réglages → Mémoire). */
suspend fun memoryList(tier: String): MemoryList = get("/memory?tier=${enc(tier)}", MemoryList.serializer())

Expand Down
44 changes: 44 additions & 0 deletions apps/android/app/src/main/java/app/jeser/devforge/data/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,50 @@ data class DraftDiscarded(@SerialName("backup_id") val backupId: String = "", va
@Serializable
data class DraftRestored(val files: Int = 0)

/* ---- Corrections (vue simple, sans jargon) ---- */
@Serializable
data class CorrectionStep(val key: String = "", val label: String = "", val state: String = "todo")

@Serializable
data class CorrectionReport(
val broke: String = "",
val changed: String = "",
val files: List<String> = emptyList(),
@SerialName("test_ok") val testOk: Boolean? = null,
val test: String = "",
val todo: String = "",
)

@Serializable
data class Correction(
val id: String = "",
val stage: String = "",
@SerialName("status_label") val statusLabel: String = "",
val persona: String = "",
val summary: String = "",
val files: List<String> = emptyList(),
val message: String? = null,
val note: String? = null,
val steps: List<CorrectionStep> = emptyList(),
val report: CorrectionReport = CorrectionReport(),
@SerialName("backup_id") val backupId: String? = null,
val restorable: Boolean = false,
)

@Serializable
data class CorrectionsState(val current: Correction? = null, val history: List<Correction> = emptyList())

@Serializable
data class CorrectionApplied(
val ok: Boolean = false,
val deployed: Boolean = false,
val conflict: Boolean = false,
val message: String? = null,
)

@Serializable
data class OkOnly(val ok: Boolean = false)

@Serializable
data class DraftReverted(@SerialName("backup_id") val backupId: String = "", val path: String = "")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enum class NotifKind(val key: String, val title: String, val description: String
DeployFailed("deploy_failed", "Mise en ligne échouée", "Rustine 🩹 te prévient et peut réparer."),
AppDown("app_down", "App qui ne répond plus", "Phare 🗼 a repéré une app en panne."),
SpecWaiting("spec_waiting", "Braise attend ton OK", "Une spec est prête à relire."),
Corrections("corrections", "Corrections", "Correction prête, en ligne ou en échec."),
}

interface NotifSettings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ object Notifier {
const val CHANNEL_ALERTS = "alerts"
const val CHANNEL_WAITING = "waiting"
const val EXTRA_PROJECT = "project_uuid"
const val EXTRA_OPEN = "open"
const val OPEN_CORRECTIONS = "corrections"

/** Réglage qui couvre un type d'événement (les 3 étapes de correction partagent un réglage). */
fun settingKey(kind: String): String = if (kind.startsWith("correction_") || kind == "fix_ready") NotifKind.Corrections.key else kind

fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
Expand All @@ -40,34 +45,35 @@ object Notifier {
return NotificationManagerCompat.from(context).areNotificationsEnabled()
}

fun openProjectIntent(context: Context, projectUuid: String): PendingIntent {
fun openProjectIntent(context: Context, projectUuid: String, open: String? = null): PendingIntent {
val intent = Intent(context, MainActivity::class.java).apply {
action = "app.jeser.devforge.OPEN_PROJECT.$projectUuid"
action = "app.jeser.devforge.OPEN_PROJECT.$projectUuid${open?.let { ".$it" } ?: ""}"
putExtra(EXTRA_PROJECT, projectUuid)
open?.let { putExtra(EXTRA_OPEN, it) }
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
return PendingIntent.getActivity(
context,
projectUuid.hashCode(),
(projectUuid + (open ?: "")).hashCode(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
}

fun show(context: Context, events: List<InboxEvent>) {
val settings = PrefsNotifSettings(context)
val wanted = events.filter { settings.enabled(it.kind) }
val wanted = events.filter { settings.enabled(settingKey(it.kind)) }
if (wanted.isEmpty() || !canNotify(context)) return
val nm = NotificationManagerCompat.from(context)
for (e in wanted.take(6)) {
val channel = if (e.kind == "spec_waiting") CHANNEL_WAITING else CHANNEL_ALERTS
val channel = if (e.kind == "spec_waiting" || e.kind == "fix_ready" || e.kind == "correction_live") CHANNEL_WAITING else CHANNEL_ALERTS
val n = NotificationCompat.Builder(context, channel)
.setSmallIcon(R.drawable.ic_stat_devforge)
.setColor(0xFFA78BFA.toInt())
.setContentTitle(e.title)
.setContentText(e.body)
.setStyle(NotificationCompat.BigTextStyle().bigText(e.body))
.setContentIntent(openProjectIntent(context, e.projectUuid))
.setContentIntent(openProjectIntent(context, e.projectUuid, if (e.kind.startsWith("correction_") || e.kind == "fix_ready") OPEN_CORRECTIONS else null))
.setAutoCancel(true)
.setCategory(if (channel == CHANNEL_ALERTS) NotificationCompat.CATEGORY_ERROR else NotificationCompat.CATEGORY_REMINDER)
.setGroup("devforge")
Expand Down
13 changes: 12 additions & 1 deletion apps/android/app/src/main/java/app/jeser/devforge/ui/AppRoot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ fun HomeRoot(
selected: String?,
onSelect: (String?) -> Unit,
onSignOut: () -> Unit,
openCorrectionsFor: String? = null,
onCorrectionsOpened: () -> Unit = {},
) {
val context = LocalContext.current
val uri = LocalUriHandler.current
Expand All @@ -88,6 +90,10 @@ fun HomeRoot(
}
}
var draftFor by rememberSaveable { mutableStateOf<String?>(null) }
// Notification « correction » : ouvre directement la feuille Corrections de l'app.
LaunchedEffect(openCorrectionsFor) {
openCorrectionsFor?.let { draftFor = it; onSelect(it); onCorrectionsOpened() }
}
var memoryOpen by rememberSaveable { mutableStateOf(false) }
// Écran plein (app, mémoire) : la barre Apps / Braise / Réglages reste visible dessous.
val screen: (@Composable (Modifier) -> Unit)? = when {
Expand Down Expand Up @@ -320,6 +326,11 @@ private fun ProjectRoute(graph: AppGraph, uuid: String, onBack: (() -> Unit)?, o
onOpenPreview = vm::openPreview,
onUrlOpened = vm::urlOpened,
onOpenDraft = vm::openDraft,
onOpenCorrections = vm::openCorrections,
onApplyCorrection = vm::applyCorrection,
onRejectCorrection = vm::rejectCorrection,
onRedoCorrection = vm::redoCorrection,
onRestoreCorrection = vm::restoreCorrection,
onDraftValidate = vm::validateDraft,
onDraftDiscard = vm::discardDraft,
onDraftRevert = vm::revertDraftFile,
Expand All @@ -328,7 +339,7 @@ private fun ProjectRoute(graph: AppGraph, uuid: String, onBack: (() -> Unit)?, o
onDraftValidatedSeen = vm::draftValidatedSeen,
onUndoShown = vm::undoShown,
),
initialSheet = if (openDraft) ProjectSheet.Draft else ProjectSheet.None,
initialSheet = if (openDraft) ProjectSheet.Corrections else ProjectSheet.None,
)
}

Expand Down
Loading
Loading