示例功能
Todos、个人资料、上传、推荐、权限等可替换的产品界面。
这些界面是可运行的产品示例,不是框架层代码。建议保留它们展示的 app pattern,再把文案、字段和业务逻辑替换成你的应用功能。
提供什么
| Surface | 状态 | 实现 |
|---|---|---|
| Home | Example | feature/home/HomeScreen.kt + HomeViewModel |
| Todos | Example | feature/todos/ + TodosRepository |
| Profile | Example | feature/profile/ + ProfileRepository |
| Refer | Example | feature/refer/ReferScreen.kt |
| Permissions | Example | feature/permissions/PermissionsScreen.kt |
| Upload sample | Example | feature/upload/ + UploadsRepository |
Home
Home tab 是 app 内的产品首页示例。它展示 stack overview、基于 entitlement 的 license card、capability preview,以及进入 live demos 的快捷入口。
HomeViewModel 读取两个来源:
| 来源 | 作用 |
|---|---|
PurchasesController.isConfigured | 判断是否配置了 REVENUECAT_ANDROID_KEY |
EntitlementController.state | 判断当前账号是否拥有 Supabase 里的 pro entitlement |
这两个来源会映射成 HomeLicenseState.Owned、Available 或 Unconfigured。Screen 本身不直接操作导航;HomeRoute 接收 onOpenTodos、onOpenUploads、onGetTemplate、onManageLicense、onRedeemCode 等 callbacks。新增自己的入口页时,优先复制这种 callback-driven pattern。
"What's inside" preview 指向可删除的 Showcase leaf。相关 seam 用 // Showcase promo (removable leaf) 标记,所以删除 Showcase 时不影响 Home 里的核心 demos。
Todos
Todos 是参考 CRUD 功能。它的 canonical entry 是 Home stack 中的 AppRoutes.Todos,不是底部 tab。
| 能力 | 代码路径 |
|---|---|
| 登录门禁 | SessionController.currentUser 为空时,TodosScreen 显示 sign-in card |
| 加载/创建/更新/删除 | TodosViewModel -> TodosRepository -> SupabaseTodosRemoteDataSource |
| 筛选 | TodosFilter.All、Active、Completed 渲染为 FilterChip |
| Inline edit | Row 内编辑态,带 save/cancel buttons |
| 排序 | Up/down buttons,先 optimistic UI,再调用 setSortOrders() |
| 错误状态 | TodosUiState.errorMessage |
TodosRepository.clearCompleted() 已存在,但当前 Compose screen 没有暴露 clear-completed 确认弹窗。如果需要这个动作,请在 TodosViewModel 中接线,并在调用 repository helper 前加 Material AlertDialog。
Profile
Me tab 是账号中心。未登录用户看到居中的登录提示;已登录用户看到 profile、license、account、notification、permissions、legal 和 sign-out rows。
PersonalInfoScreen 展示可编辑 profile fields 和 app preferences:
| 字段或设置 | 来源 |
|---|---|
| Full name | profiles.full_name |
| Bio | profiles.bio |
| Phone | profiles.phone |
| Location | profiles.location |
| Avatar display | profiles.avatar_url,通过 AppAsyncImage 显示 |
| Theme | ThemeController + DataStore |
| Language | LocaleController + DataStore |
AccountSecurityScreen 展示通常会保留的账号操作:
| 操作 | 实现 |
|---|---|
| Change email | 发送 email-change OTP,再验证 OTP |
| Change password | 校验新密码,然后调用 Supabase Auth |
| Biometric app lock | 通过 BiometricPrompt 确认,再持久化 toggle |
| Sign out other sessions | auth.signOut(SignOutScope.OTHERS) |
| Delete account | 确认弹窗,然后调用 AuthRepository.deleteAccount() |
删除账号必须按当前代码诚实理解:它会删除用户的 todos、把 profile 字段置空,并在本机退出登录。它不会删除 Supabase auth.users 行,因为模板还没有附带 delete-account Edge Function。发布到 Google Play 前,请增加服务端删除函数,避免账号删除政策风险。
Refer
ReferScreen 根据 app package name 生成 Play Store URL:
https://play.google.com/store/apps/details?id=<package-name>它用 Intent.ACTION_SEND、Intent.EXTRA_TITLE 和 Intent.EXTRA_TEXT 打开 Android 系统分享面板。分享成功后会调用 RatingController.recordTrigger(activity, "referral_shared"),进入应用内评价的 cooldown 逻辑。
Permissions
Permissions screen 是权限预热页:先解释为什么需要权限,再发起 runtime permission request。
| 行 | Android 权限 | 说明 |
|---|---|---|
| Notifications | Android 13+ 使用 POST_NOTIFICATIONS;API 33 以下视为已授权 | FCM 通知显示前需要 |
| Camera | CAMERA | 展示普通 dangerous permission |
| Tracking | com.google.android.gms.permission.AD_ID | Analytics/attribution SDK 使用的广告 ID 权限 |
README 仍然提到 Photos,但当前代码没有请求照片/媒体 runtime permission。上传示例使用 Android system photo picker,因此不需要宽泛的相册读取权限。
Upload sample
上传示例是一个实用的 Supabase Storage example:
PickMultipleVisualMedia -> UploadViewModel -> UploadsRepository -> sample-uploadsUploadViewModel 会从选中的 URI 解析 display name、MIME type 和 size,拒绝超过 25 MB 的文件,在主线程外读取 bytes,并把 uploadAsFlow() progress 收集成每个 job 的进度行。成功上传的对象路径是:
<user-id>/<timestamp>-<sanitized-file-name>Bucket id 硬编码为 UploadsRepository.BUCKET_ID = "sample-uploads"。把它改成真实媒体功能时,请同步替换 bucket、policy 和 object path。
保留这些 pattern
- Route composable 放在 feature 边界,把 callbacks 向内传。
- Screen composables 尽量保持 stateless。
- ViewModel 负责把 repository
AppResult映射成单一 UI state。 - 所有 Supabase table access 都走 repository 或 remote data source。
- 创建一次性控件前,优先使用 shared component set。
- 在产品里诚实标注示例功能,避免 starter/demo copy 意外进入正式产品。
