Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
412c8883bc | ||
|
|
c1a24fedfd | ||
|
|
13a0c99f57 | ||
|
|
41c8196a97 | ||
|
|
38f7ada8b5 | ||
|
|
a227f8607c | ||
|
|
5db5d957ab | ||
|
|
0dd1d7232b |
@@ -19,14 +19,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
@@ -51,43 +51,27 @@ jobs:
|
|||||||
|
|
||||||
HEAD_SHA=$(git rev-parse HEAD)
|
HEAD_SHA=$(git rev-parse HEAD)
|
||||||
|
|
||||||
# Find the most recent workflow run where deploy-playstore actually succeeded
|
# Find the most recent successful "Build & Deploy to Play Store" task. Forgejo's API
|
||||||
# (not merely skipped). Bug fix: previous code used commit_sha (always None in
|
# does not expose per-run jobs (/runs/{id}/jobs returns 404), so query /actions/tasks
|
||||||
# Forgejo's API) instead of head_sha, causing LAST_DEPLOYED_SHA to be empty on
|
# (per-job records) directly and filter for the task we care about. Filtering at the
|
||||||
# every run and the fallback diff to only cover HEAD~1..HEAD.
|
# task level also distinguishes runs where the Play Store job actually ran from runs
|
||||||
|
# where it was skipped — at the run level both show status=success.
|
||||||
LAST_DEPLOYED_SHA=$(python3 - << 'PYEOF'
|
LAST_DEPLOYED_SHA=$(python3 - << 'PYEOF'
|
||||||
import json, os, sys, urllib.request
|
import json, os, sys, urllib.request
|
||||||
token = os.environ.get("FORGEJO_TOKEN", "")
|
token = os.environ.get("FORGEJO_TOKEN", "")
|
||||||
server = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
|
server = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
|
||||||
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
||||||
base_api = f"{server}/api/v1/repos/{repo}/actions"
|
url = f"{server}/api/v1/repos/{repo}/actions/tasks?status=success&limit=100"
|
||||||
url = f"{base_api}/runs?workflow_id=deploy.yml&status=success&limit=10"
|
|
||||||
req = urllib.request.Request(url, headers={"Authorization": f"token {token}"})
|
req = urllib.request.Request(url, headers={"Authorization": f"token {token}"})
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req) as r:
|
with urllib.request.urlopen(req, timeout=60) as r:
|
||||||
data = json.loads(r.read())
|
data = json.loads(r.read())
|
||||||
runs = [
|
for t in data.get("workflow_runs", []):
|
||||||
r for r in data.get("workflow_runs", [])
|
if (t.get("workflow_id") == "deploy.yml"
|
||||||
if r.get("status") == "success"
|
and t.get("name") == "Build & Deploy to Play Store"
|
||||||
]
|
and t.get("status") == "success"):
|
||||||
# Walk runs newest-first; pick the first one where deploy-playstore
|
print(t.get("head_sha") or "")
|
||||||
# actually ran (conclusion=success), not just skipped.
|
sys.exit(0)
|
||||||
for run in runs:
|
|
||||||
run_id = run.get("id")
|
|
||||||
jobs_url = f"{base_api}/runs/{run_id}/jobs"
|
|
||||||
jobs_req = urllib.request.Request(jobs_url, headers={"Authorization": f"token {token}"})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(jobs_req) as jr:
|
|
||||||
jobs_data = json.loads(jr.read())
|
|
||||||
for job in jobs_data.get("workflow_jobs", []):
|
|
||||||
if "Deploy to Play Store" in job.get("name", "") and (
|
|
||||||
job.get("conclusion") == "success" or
|
|
||||||
job.get("status") == "success"
|
|
||||||
):
|
|
||||||
print(run.get("head_sha") or "")
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception:
|
|
||||||
pass # skip this run if jobs API fails
|
|
||||||
print("")
|
print("")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"::error::LAST_DEPLOYED_SHA lookup failed ({type(e).__name__}: {e})")
|
print(f"::error::LAST_DEPLOYED_SHA lookup failed ({type(e).__name__}: {e})")
|
||||||
@@ -164,14 +148,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
@@ -215,14 +199,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
@@ -260,14 +244,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
@@ -310,14 +294,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
@@ -73,14 +73,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -38,40 +38,27 @@ jobs:
|
|||||||
|
|
||||||
HEAD_SHA=$(git rev-parse HEAD)
|
HEAD_SHA=$(git rev-parse HEAD)
|
||||||
|
|
||||||
# Find the most recent successful website.yml run where the deploy job
|
# Find the most recent successful "Build & Update Website" task. Forgejo's API
|
||||||
# actually ran (not merely skipped). Uses head_sha (not commit_sha which
|
# does not expose per-run jobs (/runs/{id}/jobs returns 404), so query /actions/tasks
|
||||||
# is always None in Forgejo's API).
|
# (per-job records) directly and filter for the task we care about. Filtering at the
|
||||||
|
# task level also distinguishes runs where the deploy job actually ran from runs
|
||||||
|
# where it was skipped — at the run level both show status=success.
|
||||||
LAST_DEPLOYED_SHA=$(python3 - << 'PYEOF'
|
LAST_DEPLOYED_SHA=$(python3 - << 'PYEOF'
|
||||||
import json, os, sys, urllib.request
|
import json, os, sys, urllib.request
|
||||||
token = os.environ.get("FORGEJO_TOKEN", "")
|
token = os.environ.get("FORGEJO_TOKEN", "")
|
||||||
server = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
|
server = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
|
||||||
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
||||||
base_api = f"{server}/api/v1/repos/{repo}/actions"
|
url = f"{server}/api/v1/repos/{repo}/actions/tasks?status=success&limit=100"
|
||||||
url = f"{base_api}/runs?workflow_id=website.yml&status=success&limit=10"
|
|
||||||
req = urllib.request.Request(url, headers={"Authorization": f"token {token}"})
|
req = urllib.request.Request(url, headers={"Authorization": f"token {token}"})
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req) as r:
|
with urllib.request.urlopen(req, timeout=60) as r:
|
||||||
data = json.loads(r.read())
|
data = json.loads(r.read())
|
||||||
runs = [
|
for t in data.get("workflow_runs", []):
|
||||||
r for r in data.get("workflow_runs", [])
|
if (t.get("workflow_id") == "website.yml"
|
||||||
if r.get("status") == "success"
|
and t.get("name") == "Build & Update Website"
|
||||||
]
|
and t.get("status") == "success"):
|
||||||
for run in runs:
|
print(t.get("head_sha") or "")
|
||||||
run_id = run.get("id")
|
sys.exit(0)
|
||||||
jobs_url = f"{base_api}/runs/{run_id}/jobs"
|
|
||||||
jobs_req = urllib.request.Request(jobs_url, headers={"Authorization": f"token {token}"})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(jobs_req) as jr:
|
|
||||||
jobs_data = json.loads(jr.read())
|
|
||||||
for job in jobs_data.get("workflow_jobs", []):
|
|
||||||
if "Build & Update Website" in job.get("name", "") and (
|
|
||||||
job.get("conclusion") == "success" or
|
|
||||||
job.get("status") == "success"
|
|
||||||
):
|
|
||||||
print(run.get("head_sha") or "")
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception:
|
|
||||||
pass # skip this run if jobs API fails
|
|
||||||
print("")
|
print("")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"::error::LAST_DEPLOYED_SHA lookup failed ({type(e).__name__}: {e})")
|
print(f"::error::LAST_DEPLOYED_SHA lookup failed ({type(e).__name__}: {e})")
|
||||||
@@ -130,14 +117,14 @@ jobs:
|
|||||||
RUN_NUMBER: ${{ github.run_number }}
|
RUN_NUMBER: ${{ github.run_number }}
|
||||||
run: |
|
run: |
|
||||||
runner_start=$(date +%s)
|
runner_start=$(date +%s)
|
||||||
created_at=$(curl -sf \
|
created=$(curl -sf --max-time 30 \
|
||||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/tasks?limit=100" \
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/actions/runs?run_number=$RUN_NUMBER" \
|
||||||
| python3 -c "import sys,json;data=json.load(sys.stdin);rs=[r for r in data.get('workflow_runs',[]) if r.get('run_number')==$RUN_NUMBER];print(rs[0]['created_at'] if rs else '')" 2>/dev/null)
|
| python3 -c "import sys,json;rs=json.load(sys.stdin).get('workflow_runs',[]);print(rs[0]['created'] if rs else '')" 2>/dev/null) || true
|
||||||
if [ -n "$created_at" ]; then
|
if [ -n "$created" ]; then
|
||||||
queued_epoch=$(date -d "$created_at" +%s)
|
queued_epoch=$(date -d "$created" +%s)
|
||||||
wait_seconds=$((runner_start - queued_epoch))
|
wait_seconds=$((runner_start - queued_epoch))
|
||||||
echo "Runner wait time: ${wait_seconds}s (queued at $created_at)"
|
echo "Runner wait time: ${wait_seconds}s (queued at $created)"
|
||||||
else
|
else
|
||||||
echo "Runner wait time: unknown (API lookup failed)"
|
echo "Runner wait time: unknown (API lookup failed)"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ repos:
|
|||||||
- id: dart-check
|
- id: dart-check
|
||||||
name: dart format (autofix) + check-fast (parallel)
|
name: dart format (autofix) + check-fast (parallel)
|
||||||
language: system
|
language: system
|
||||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command dagger call --progress=plain -q -m ci --source=. check-fast'
|
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && scripts/precommit_dart_check.sh'
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
always_run: true
|
always_run: true
|
||||||
- id: ci-no-direct-dagger
|
- id: ci-no-direct-dagger
|
||||||
|
|||||||
@@ -124,9 +124,6 @@
|
|||||||
# nix develop --command does not set IN_NIX_SHELL; set it so _preflight passes in CI
|
# nix develop --command does not set IN_NIX_SHELL; set it so _preflight passes in CI
|
||||||
export IN_NIX_SHELL=1
|
export IN_NIX_SHELL=1
|
||||||
|
|
||||||
# Point Dagger client at the running engine socket
|
|
||||||
export DAGGER_HOST=unix:///run/dagger/engine.sock
|
|
||||||
|
|
||||||
# Disable Flutter telemetry inside dev shell
|
# Disable Flutter telemetry inside dev shell
|
||||||
export FLUTTER_SUPPRESS_ANALYTICS=true
|
export FLUTTER_SUPPRESS_ANALYTICS=true
|
||||||
|
|
||||||
|
|||||||
@@ -74,10 +74,6 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
automaticallyImplyLeading: !isMobile,
|
automaticallyImplyLeading: !isMobile,
|
||||||
title: Text(
|
|
||||||
header?.subject ?? '(loading…)',
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.reply),
|
icon: const Icon(Icons.reply),
|
||||||
@@ -133,12 +129,20 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
if (mounted) setState(() => _isFlagged = next);
|
if (mounted) setState(() => _isFlagged = next);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.report_outlined),
|
||||||
|
tooltip: 'Mark as spam',
|
||||||
|
onPressed: header == null
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
unawaited(_markAsSpam(context, header));
|
||||||
|
},
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
itemBuilder: (ctx) => [
|
itemBuilder: (ctx) => [
|
||||||
const PopupMenuItem(value: 'forward', child: Text('Forward')),
|
const PopupMenuItem(value: 'forward', child: Text('Forward')),
|
||||||
const PopupMenuItem(value: 'move', child: Text('Move to folder')),
|
const PopupMenuItem(value: 'move', child: Text('Move to folder')),
|
||||||
const PopupMenuItem(value: 'snooze', child: Text('Snooze')),
|
const PopupMenuItem(value: 'snooze', child: Text('Snooze')),
|
||||||
const PopupMenuItem(value: 'spam', child: Text('Mark as spam')),
|
|
||||||
const PopupMenuItem(
|
const PopupMenuItem(
|
||||||
value: 'mark_unread',
|
value: 'mark_unread',
|
||||||
child: Text('Mark as unread'),
|
child: Text('Mark as unread'),
|
||||||
@@ -166,8 +170,6 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
unawaited(_moveTo(context, header));
|
unawaited(_moveTo(context, header));
|
||||||
} else if (value == 'snooze' && header != null) {
|
} else if (value == 'snooze' && header != null) {
|
||||||
unawaited(_snooze(context, header));
|
unawaited(_snooze(context, header));
|
||||||
} else if (value == 'spam' && header != null) {
|
|
||||||
unawaited(_markAsSpam(context, header));
|
|
||||||
} else if (value == 'mark_unread') {
|
} else if (value == 'mark_unread') {
|
||||||
final nextEmailId = await _getNextEmailIdIfNeeded(header);
|
final nextEmailId = await _getNextEmailIdIfNeeded(header);
|
||||||
await repo.setFlag(widget.emailId, seen: false);
|
await repo.setFlag(widget.emailId, seen: false);
|
||||||
|
|||||||
+59
-51
@@ -5,18 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "93.0.0"
|
version: "99.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.1"
|
version: "12.1.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -165,10 +165,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_assets
|
name: code_assets
|
||||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.2.1"
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -237,18 +237,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
sha256: a4c1ccfee44c7e75ed80484071a5c142a385345e658fd8bd7c4b5c97e7198f98
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.7"
|
version: "3.1.8"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dbus
|
name: dbus
|
||||||
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
|
sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.12"
|
version: "0.7.14"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -349,10 +349,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "0204695694b687b167fd497da5252e9f4aaa162e8d274d6fa1e757380f2a5f46"
|
sha256: fc83774ce5bd7ce08168333b5e53dbe9090ec04eb21e7aa7cd7bac921032c934
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "12.0.0-beta.4"
|
version: "12.0.0-beta.5"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -391,34 +391,42 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
sha256: "0d9035862236fe38250fe1644d7ed3b8254e34a21b2c837c9f539fbb3bba5ef1"
|
sha256: be38e3854d2baabcda8e16966a5fe8748cebb655bb94701494da0f052c2fc352
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "21.0.0"
|
version: "22.0.0"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications_linux
|
name: flutter_local_notifications_linux
|
||||||
sha256: e0f25e243c6c44c825bbbc6b2b2e76f7d9222362adcfe9fd780bf01923c840bd
|
sha256: "9ca97e63776f29ab1b955725c09999fc2c150523269db150c39274f2a43c5a8b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.0.0"
|
version: "8.0.1"
|
||||||
flutter_local_notifications_platform_interface:
|
flutter_local_notifications_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications_platform_interface
|
name: flutter_local_notifications_platform_interface
|
||||||
sha256: e7db3d5b49c2b7ecc68deba4aaaa67a348f92ee0fef34c8e4b4459dbef0d7307
|
sha256: ff0013eae795e8dc8fad4a8992a209e64d3ba2fbd8bf5e43c36bf448f95bd814
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.0"
|
version: "12.0.0"
|
||||||
|
flutter_local_notifications_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_web
|
||||||
|
sha256: "516afaf97a2d1e67a036c6617321b00d205d72f7a67b6eccf936cd565f985878"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
flutter_local_notifications_windows:
|
flutter_local_notifications_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_local_notifications_windows
|
name: flutter_local_notifications_windows
|
||||||
sha256: "3a2654ba104fbb52c618ebed9def24ef270228470718c43b3a6afcd5c81bef0c"
|
sha256: "5aeed973a0c1480706784fad05c5c3a911335ebb561b2274b47fe80b375201e1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.1.0"
|
||||||
flutter_markdown_plus:
|
flutter_markdown_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -431,10 +439,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
|
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.34"
|
version: "2.0.35"
|
||||||
flutter_riverpod:
|
flutter_riverpod:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -447,10 +455,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_secure_storage
|
name: flutter_secure_storage
|
||||||
sha256: d2a6ac2df7353f5ca47eb159a5407c1dba7ec48ca0e02dc38c9ff4d29447b261
|
sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.0"
|
version: "10.3.1"
|
||||||
flutter_secure_storage_darwin:
|
flutter_secure_storage_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -526,10 +534,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "92d8cee7c57dff0a6c409c05597b460002434eccf7424a712283225b3962d03f"
|
sha256: "5922b2861e2235a3504896f0d6fa07d84141b480cf52eecd2f42cd25585a9e8a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "17.2.3"
|
version: "17.3.0"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -542,10 +550,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: hooks
|
name: hooks
|
||||||
sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e"
|
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "2.0.2"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -675,10 +683,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.18.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -707,10 +715,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: native_toolchain_c
|
name: native_toolchain_c
|
||||||
sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
|
sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.17.6"
|
version: "0.19.1"
|
||||||
node_preamble:
|
node_preamble:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -723,10 +731,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: objective_c
|
name: objective_c
|
||||||
sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52"
|
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.3.0"
|
version: "9.4.1"
|
||||||
open_filex:
|
open_filex:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1013,13 +1021,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.2"
|
version: "1.10.2"
|
||||||
sqlite3:
|
sqlite3:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: "56da3e13ed7d28a66f930aa2b2b29db6736a233f08283326e96321dd812030f5"
|
sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.1"
|
version: "3.3.2"
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1088,10 +1096,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
|
sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0+1"
|
version: "3.4.1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1104,26 +1112,26 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
|
sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.30.0"
|
version: "1.31.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.10"
|
version: "0.7.11"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
|
sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.16"
|
version: "0.6.17"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1288,10 +1296,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_android
|
name: webview_flutter_android
|
||||||
sha256: ad5182eff9a550925330cb9f0cb038eddfdd5712aba8b77aa0f0400e50f6e688
|
sha256: a97db7a44f8e71af2f3971c45550a08cce1fb60059c1b8e534251e6cfb753490
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.12.0"
|
version: "4.13.0"
|
||||||
webview_flutter_platform_interface:
|
webview_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1304,10 +1312,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_wkwebview
|
name: webview_flutter_wkwebview
|
||||||
sha256: "82648217f537573e1ca9ae9952d3eacedca6ab5aee69dc84445fc763766dcea2"
|
sha256: c879dd64b87c452aa84381b244d5469da57ba7e8cca6884c7b1e0d406372c12d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.25.1"
|
version: "3.26.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1381,5 +1389,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.11.0 <4.0.0"
|
dart: ">=3.12.0 <4.0.0"
|
||||||
flutter: ">=3.38.4"
|
flutter: ">=3.44.0"
|
||||||
|
|||||||
+3
-3
@@ -28,7 +28,7 @@ dependencies:
|
|||||||
flutter_riverpod: ^3.0.0
|
flutter_riverpod: ^3.0.0
|
||||||
|
|
||||||
# Navigation
|
# Navigation
|
||||||
go_router: ^17.2.3
|
go_router: ^17.3.0
|
||||||
|
|
||||||
# Secure credential storage (passwords)
|
# Secure credential storage (passwords)
|
||||||
flutter_secure_storage: ^10.0.0
|
flutter_secure_storage: ^10.0.0
|
||||||
@@ -37,7 +37,7 @@ dependencies:
|
|||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
|
|
||||||
# File picking (compose attachments) and opening downloaded attachments
|
# File picking (compose attachments) and opening downloaded attachments
|
||||||
file_picker: ^12.0.0-beta.4
|
file_picker: ^12.0.0-beta.5
|
||||||
open_filex: ^4.6.0
|
open_filex: ^4.6.0
|
||||||
mime: ^2.0.0
|
mime: ^2.0.0
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ dependencies:
|
|||||||
flutter_markdown_plus: ^1.0.7
|
flutter_markdown_plus: ^1.0.7
|
||||||
|
|
||||||
# Background sync and local notifications
|
# Background sync and local notifications
|
||||||
flutter_local_notifications: ^21.0.0
|
flutter_local_notifications: ^22.0.0
|
||||||
workmanager: ^0.9.0
|
workmanager: ^0.9.0
|
||||||
|
|
||||||
# Stack trace chain-to-VM conversion for FlutterError.demangleStackTrace
|
# Stack trace chain-to-VM conversion for FlutterError.demangleStackTrace
|
||||||
|
|||||||
Executable
+42
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Pre-commit wrapper for the `dart-check` hook.
|
||||||
|
#
|
||||||
|
# `dagger call ... check-fast` needs a Dagger engine. On a dev machine or in
|
||||||
|
# CI that engine is provisioned from a local container runtime (docker/podman)
|
||||||
|
# or reached through _EXPERIMENTAL_DAGGER_RUNNER_HOST. In engine-less sandboxes
|
||||||
|
# (e.g. the agentloop agent pods that commit on our behalf) none of those
|
||||||
|
# exist, so dagger falls back to its default engine image reference and aborts
|
||||||
|
# with:
|
||||||
|
# start engine: driver for scheme "image" was not available
|
||||||
|
# which blocked every commit the agent tried to make.
|
||||||
|
#
|
||||||
|
# Codeberg CI still runs check-fast on every push, so skipping here is safe:
|
||||||
|
# warn loudly and let the commit through when no engine can be reached.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
# True when dagger has some way to reach/provision an engine.
|
||||||
|
engine_available() {
|
||||||
|
# A shared engine reached over the wire wins outright.
|
||||||
|
[ -n "${_EXPERIMENTAL_DAGGER_RUNNER_HOST:-}" ] && return 0
|
||||||
|
# Otherwise dagger provisions the engine from a local container runtime.
|
||||||
|
# `info` (not `version`) confirms the daemon is actually reachable; cap it
|
||||||
|
# with a timeout so a stale docker context cannot hang the commit.
|
||||||
|
if command -v docker >/dev/null 2>&1 && timeout 10 docker info >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if command -v podman >/dev/null 2>&1 && timeout 10 podman info >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! engine_available; then
|
||||||
|
echo "WARNING: no Dagger engine available (no container runtime, and" \
|
||||||
|
"_EXPERIMENTAL_DAGGER_RUNNER_HOST is unset); skipping dart-check." \
|
||||||
|
"Codeberg CI still runs check-fast on push." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec nix develop --command dagger call --progress=plain -q -m ci --source=. check-fast
|
||||||
@@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:http/testing.dart';
|
import 'package:http/testing.dart';
|
||||||
|
|
||||||
|
import 'package:sharedinbox/core/filter/filter_expression.dart';
|
||||||
import 'package:sharedinbox/core/models/account.dart';
|
import 'package:sharedinbox/core/models/account.dart';
|
||||||
import 'package:sharedinbox/core/models/email.dart';
|
import 'package:sharedinbox/core/models/email.dart';
|
||||||
import 'package:sharedinbox/data/db/database.dart' hide Account;
|
import 'package:sharedinbox/data/db/database.dart' hide Account;
|
||||||
@@ -682,6 +683,91 @@ void main() {
|
|||||||
expect(results[1].subject, 'Older meeting');
|
expect(results[1].subject, 'Older meeting');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'searchEmailsStructured returns results sorted by receivedAt descending',
|
||||||
|
() async {
|
||||||
|
final r = _makeRepos();
|
||||||
|
await r.accounts.addAccount(_account, 'pw');
|
||||||
|
|
||||||
|
await r.db.into(r.db.emails).insert(
|
||||||
|
EmailsCompanion.insert(
|
||||||
|
id: 'acc-1:1',
|
||||||
|
accountId: 'acc-1',
|
||||||
|
mailboxPath: 'INBOX',
|
||||||
|
uid: 1,
|
||||||
|
subject: const Value('Older invoice'),
|
||||||
|
receivedAt: DateTime(2024),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await r.db.into(r.db.emails).insert(
|
||||||
|
EmailsCompanion.insert(
|
||||||
|
id: 'acc-1:2',
|
||||||
|
accountId: 'acc-1',
|
||||||
|
mailboxPath: 'INBOX',
|
||||||
|
uid: 2,
|
||||||
|
subject: const Value('Newer invoice'),
|
||||||
|
receivedAt: DateTime(2024, 6),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final filter = FilterGroup(
|
||||||
|
operator: FilterOperator.and_,
|
||||||
|
children: [
|
||||||
|
FilterLeaf(
|
||||||
|
field: FilterField.subject,
|
||||||
|
comparison: FilterComparison.contains,
|
||||||
|
value: 'invoice',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final results = await r.emails.searchEmailsStructured(null, filter);
|
||||||
|
expect(results, hasLength(2));
|
||||||
|
expect(results[0].subject, 'Newer invoice');
|
||||||
|
expect(results[1].subject, 'Older invoice');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'getEmailsByAddress returns results sorted by receivedAt descending',
|
||||||
|
() async {
|
||||||
|
final r = _makeRepos();
|
||||||
|
await r.accounts.addAccount(_account, 'pw');
|
||||||
|
|
||||||
|
await r.db.into(r.db.emails).insert(
|
||||||
|
EmailsCompanion.insert(
|
||||||
|
id: 'acc-1:1',
|
||||||
|
accountId: 'acc-1',
|
||||||
|
mailboxPath: 'INBOX',
|
||||||
|
uid: 1,
|
||||||
|
subject: const Value('Older hello'),
|
||||||
|
receivedAt: DateTime(2024),
|
||||||
|
fromJson: const Value(
|
||||||
|
'[{"name":"Bob","email":"bob@example.com"}]',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await r.db.into(r.db.emails).insert(
|
||||||
|
EmailsCompanion.insert(
|
||||||
|
id: 'acc-1:2',
|
||||||
|
accountId: 'acc-1',
|
||||||
|
mailboxPath: 'INBOX',
|
||||||
|
uid: 2,
|
||||||
|
subject: const Value('Newer hello'),
|
||||||
|
receivedAt: DateTime(2024, 6),
|
||||||
|
fromJson: const Value(
|
||||||
|
'[{"name":"Bob","email":"bob@example.com"}]',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final results =
|
||||||
|
await r.emails.getEmailsByAddress(null, 'bob@example.com');
|
||||||
|
expect(results, hasLength(2));
|
||||||
|
expect(results[0].subject, 'Newer hello');
|
||||||
|
expect(results[1].subject, 'Older hello');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'searchAddresses returns results sorted by most recently used',
|
'searchAddresses returns results sorted by most recently used',
|
||||||
() async {
|
() async {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ void main() {
|
|||||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('shows subject in app bar after data loads', (tester) async {
|
testWidgets('shows subject in email header section', (tester) async {
|
||||||
final email = testEmail(subject: 'Project update');
|
final email = testEmail(subject: 'Project update');
|
||||||
const body = EmailBody(
|
const body = EmailBody(
|
||||||
emailId: 'acc-1:42',
|
emailId: 'acc-1:42',
|
||||||
@@ -106,8 +106,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Subject appears in both the app bar and the email header section.
|
// Subject appears only in the email header section, not in the app bar.
|
||||||
expect(find.text('Project update'), findsAtLeastNWidgets(1));
|
expect(find.text('Project update'), findsOneWidget);
|
||||||
expect(find.text('See attached slides.'), findsOneWidget);
|
expect(find.text('See attached slides.'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ void main() {
|
|||||||
expect(find.textContaining('carol@example.com'), findsAtLeastNWidgets(1));
|
expect(find.textContaining('carol@example.com'), findsAtLeastNWidgets(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Mark as spam is in popup menu, not a standalone button', (
|
testWidgets('Mark as spam is a standalone button, not in popup menu', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@@ -279,19 +279,19 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// No standalone icon button for mark as spam.
|
// Standalone icon button for mark as spam is in the app bar.
|
||||||
expect(
|
expect(
|
||||||
find.byWidgetPredicate(
|
find.byWidgetPredicate(
|
||||||
(w) => w is Tooltip && w.message == 'Mark as spam',
|
(w) => w is Tooltip && w.message == 'Mark as spam',
|
||||||
),
|
),
|
||||||
findsNothing,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
|
||||||
// It appears in the popup menu.
|
// It does NOT appear in the popup menu.
|
||||||
await tester.tap(find.byType(PopupMenuButton<String>));
|
await tester.tap(find.byType(PopupMenuButton<String>));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('Mark as spam'), findsOneWidget);
|
expect(find.text('Mark as spam'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Mark as spam shows dialog when no junk folder', (
|
testWidgets('Mark as spam shows dialog when no junk folder', (
|
||||||
@@ -309,11 +309,11 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Open the popup menu first, then tap Mark as spam.
|
await tester.tap(
|
||||||
await tester.tap(find.byType(PopupMenuButton<String>));
|
find.byWidgetPredicate(
|
||||||
await tester.pumpAndSettle();
|
(w) => w is Tooltip && w.message == 'Mark as spam',
|
||||||
|
),
|
||||||
await tester.tap(find.text('Mark as spam'));
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('No spam folder found'), findsOneWidget);
|
expect(find.text('No spam folder found'), findsOneWidget);
|
||||||
|
|||||||
@@ -446,10 +446,10 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(EmailDetailScreen), findsOneWidget);
|
expect(find.byType(EmailDetailScreen), findsOneWidget);
|
||||||
// The detail AppBar title shows the first email's subject.
|
// The detail body header shows the first email's subject.
|
||||||
expect(
|
expect(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byType(AppBar),
|
of: find.byType(EmailDetailScreen),
|
||||||
matching: find.text('Alpha Match'),
|
matching: find.text('Alpha Match'),
|
||||||
),
|
),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
|
|||||||
Reference in New Issue
Block a user