announcement
GitHub用于在 Oinkoin 应用中创建和发布用户公告。涵盖编写 Markdown 内容、注册清单及更新国际化键,支持启动弹窗或页面展示,面向最终用户的内容分发任务。
Trigger Scenarios
Install
npx skills add emavgl/oinkoin --skill announcement -g -y
SKILL.md
Frontmatter
{
"name": "announcement",
"description": "Adds a new in-app announcement to Oinkoin using the announcements framework. Use when the user wants to send a user-facing announcement, show a startup dialog, or publish a blog post about a new feature.",
"argument-hint": "<id> [title]"
}
Skill: announcement
Add a new in-app announcement to Oinkoin.
Usage
/announcement 2026-10-new-feature "New Feature X is here"
The <id> is a date-prefixed slug (YYYY-MM-slug). The title is the dialog/page heading.
How announcements work
The framework has three layers:
| Layer | Location | Purpose |
|---|---|---|
| Manifest | assets/docs/announcements/manifest.json |
Lists all announcements (id, date, title key, dialog flag) |
| Body | assets/docs/announcements/<id>.md |
Markdown content shown in the dialog and announcements page |
| Settings | Settings → Announcements | Lists all entries; tapping one opens the markdown viewer |
A manifest entry with "dialog": true also triggers a one-time startup dialog (dismissed permanently via SharedPreferences), shown by maybeShowAnnouncementDialog() from the Shell once authentication passes and the first frame is laid out. Add "dialogAudience" to restrict that dialog to specific builds (see Step 2).
Workflow
Step 1 — Create the markdown body
Create assets/docs/announcements/<id>.md (English only).
Rules:
- First line should be a bold heading:
**Your announcement title** - Keep the tone friendly and concise
- Links are tappable in the dialog (uses
flutter_markdown_plus) - Announcements are intentionally English-only by default — do not create translated copies unless explicitly asked
Step 2 — Register in the manifest
Add an entry to assets/docs/announcements/manifest.json:
{
"communications": [
{
"id": "2026-10-new-feature",
"date": "2026-10-15",
"titleKey": "New Feature X is here",
"dialog": true,
"dialogAudience": ["free", "alpha", "debug"]
}
]
}
Fields:
- id: folder-safe slug, prefixed with date for sorting
- date: ISO-8601 date (YYYY-MM-DD)
- titleKey: exact English string used as the i18n key (must match
en-US.json) - dialog:
trueto show a one-time startup dialog;falsefor page-only entries - dialogAudience (optional): array restricting the startup dialog to
specific builds. Tags are
debug/releaseplus the product flavor (free/alpha/dev/pro; F-Droid reports aspro). The dialog shows when any listed tag matches the running build — e.g.["free", "alpha", "debug"]targets free-flavor users plus alpha/debug test builds. Omit or leave empty to target every build. The announcements page always lists the entry regardless of this field. Resolved byresolveBuildAudience()inlib/comms/announcement-dialog.dart.
Step 3 — Add the i18n key
The titleKey must exist in assets/locales/en-US.json as both key and value:
"New Feature X is here": "New Feature X is here"
Then run:
python3 scripts/update_en_strings.py
This syncs the key into all locale files (value = key = untranslated, which is fine since announcements are English-only).
Step 4 — Document context (optional but recommended)
Add the key to _automated_translation.json with "status": "verified" so future /translate runs don't try to translate it:
"New Feature X is here": {
"key": "New Feature X is here",
"context": "Title of a startup announcement dialog for Feature X",
"file": "lib/comms/announcement-dialog.dart",
"page": "Startup dialog + Settings > Announcements",
"component": "Dialog title / list item title",
"meaning": "Announcement about Feature X availability",
"notes": "English-only announcement by decision.",
"status": "verified"
}
Step 5 — Write the companion blog post
Every announcement also gets a blog post. Reuse the announcement markdown
instead of writing from scratch: copy assets/docs/announcements/<id>.md
under the frontmatter below, then expand where worthwhile (background,
details, FAQ). Keep the announcement body as the condensed version.
Create website/src/content/blog/<slug>.md (slug usually matches the
announcement id without the date prefix):
---
title: 'Your Blog Title'
description: 'Short description for SEO.'
pubDate: 2026-10-15
---
<announcement markdown, then expanded sections>
The blog is at https://oinkoin.com/blog/<slug>.
Link to it from the announcement body markdown: [Read more in our blog](https://oinkoin.com/blog/<slug>).
Step 6 — Verify
flutter analyze
flutter test test/communication_service_test.dart
Then test on-device:
- Clear app data (
adb shell pm clear <package>) - Launch the app
- Verify the dialog appears on first launch (use a build whose flavor is in
dialogAudience, if set — aflutter rundebug build always matchesdebug) - Dismiss and relaunch — dialog should NOT appear again
- Check Settings → Announcements shows the entry (regardless of
dialogAudience)
Removing an announcement
To remove the startup dialog but keep the entry visible on the announcements page:
- Set
"dialog": falsein the manifest
To remove entirely:
- Delete the
.mdfile - Remove the entry from
manifest.json - Remove the i18n key from
en-US.jsonand re-runpython3 scripts/update_en_strings.py
File reference
| File | Role |
|---|---|
assets/docs/announcements/manifest.json |
Announcement registry |
assets/docs/announcements/<id>.md |
Body content (English) |
lib/services/communication-service.dart |
Service: loads manifest, resolves bodies, show-once logic |
lib/comms/announcement-dialog.dart |
Startup dialog UI + maybeShowAnnouncementDialog() + resolveBuildAudience() |
lib/shell.dart |
Calls maybeShowAnnouncementDialog() after auth + first frame |
lib/comms/announcements-page.dart |
Settings list page |
lib/comms/communication-detail-page.dart |
Markdown viewer page |
lib/settings/settings-page.dart:355-369 |
Settings item that navigates to Announcements |
Version History
-
9971f01
Current 2026-09-23 03:02
新增配套博客文章功能,包含 1.13.0 版本的变更日志。
- 586baa8 2026-08-28 23:50


