feat: git push status indicator + multi-select bulk push
Release APK / build (push) Has been cancelled

- DB migration v2: git_pushed_at column on incidents
- markIncidentPushed(id): stamps the push timestamp
- loadGitConfig(): shared helper in lib/git.ts, removes duplication
- Home screen: colored dot per incident (red=never pushed,
  orange=dirty/modified after push, green=up to date);
  dot visible only when git is configured
- Home screen: long-press enters selection mode, tap toggles;
  action bar with Select unpushed / Select all shortcuts +
  bulk push with sequential progress counter
- [id].tsx: marks incident pushed after successful push,
  updates local state without reload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 18:11:23 +02:00
parent 1437e99cbb
commit 947ae5917c
5 changed files with 392 additions and 139 deletions
+12 -52
View File
@@ -10,10 +10,9 @@ import {
import { useLocalSearchParams, router, useNavigation } from "expo-router";
import * as Clipboard from "expo-clipboard";
import * as SecureStore from "expo-secure-store";
import { getIncidentById, updateIncident, deleteIncident } from "@/lib/db";
import { getIncidentById, updateIncident, deleteIncident, markIncidentPushed } from "@/lib/db";
import { renderMarkdown } from "@/lib/markdown";
import { pushIncidentToGit } from "@/lib/git";
import type { GitProvider } from "@/lib/git";
import { pushIncidentToGit, loadGitConfig } from "@/lib/git";
import type { Incident } from "@/types/incident";
import { Colors } from "@/constants/theme";
@@ -81,60 +80,21 @@ export default function IncidentDetailScreen() {
async function handleGitPush() {
if (!incident) return;
const [provider, template, giteaUrl, giteaToken, giteaOwner, giteaRepo, githubToken, githubOwner, githubRepo, gitlabUrl, gitlabToken, gitlabOwner, gitlabRepo] =
await Promise.all([
SecureStore.getItemAsync("git_provider"),
SecureStore.getItemAsync("md_template"),
SecureStore.getItemAsync("gitea_url"),
SecureStore.getItemAsync("gitea_token"),
SecureStore.getItemAsync("gitea_owner"),
SecureStore.getItemAsync("gitea_repo"),
SecureStore.getItemAsync("github_token"),
SecureStore.getItemAsync("github_owner"),
SecureStore.getItemAsync("github_repo"),
SecureStore.getItemAsync("gitlab_url"),
SecureStore.getItemAsync("gitlab_token"),
SecureStore.getItemAsync("gitlab_owner"),
SecureStore.getItemAsync("gitlab_repo"),
]);
const p = (provider ?? "gitea") as GitProvider;
let url: string | undefined;
let token: string | null;
let owner: string | null;
let repo: string | null;
if (p === "gitea") {
url = giteaUrl ?? undefined;
token = giteaToken;
owner = giteaOwner;
repo = giteaRepo;
} else if (p === "github") {
token = githubToken;
owner = githubOwner;
repo = githubRepo;
} else {
url = gitlabUrl ?? undefined;
token = gitlabToken;
owner = gitlabOwner;
repo = gitlabRepo;
}
const needsUrl = p === "gitea" || p === "gitlab";
if (!token || !owner || !repo || (needsUrl && !url)) {
Alert.alert("Git not configured", `Set up ${p.charAt(0).toUpperCase() + p.slice(1)} in Settings first.`);
const [config, template] = await Promise.all([
loadGitConfig(),
SecureStore.getItemAsync("md_template"),
]);
if (!config) {
Alert.alert("Git not configured", "Set up a git provider in Settings first.");
return;
}
setPushing(true);
const result = await pushIncidentToGit(
incident,
{ provider: p, url, token, owner, repo },
template ?? undefined
);
const result = await pushIncidentToGit(incident, config, template ?? undefined);
setPushing(false);
if (result.success) {
const now = new Date().toISOString();
await markIncidentPushed(incident.id);
setIncident((prev) => prev ? { ...prev, gitPushedAt: now } : prev);
Alert.alert("Pushed", result.url ?? "Push successful.");
} else {
Alert.alert("Push failed", result.error ?? "Unknown error");