1つの選択肢を選んで投票するモード(RankingMode.single)
選択肢の中から1つを選んで投票する画面。再投票時はポイント消費(revote1: 3pt, revote2: 7pt)。
| 要素 | 種類 | 説明 |
|---|---|---|
| RankingHeaderView(Xスタイル) | VStack |
画面上部の共通ヘッダ。以下7段構成。 1段目: 作成者アバター + 表示名 + フォローボタン(Xスタイル) + 右上に大きなランクバッジ(44px円、Sは虹グラデ) 2段目: 投票タイプバッジ(シングル/TOP3) + クイズ通過バッジ(hasQuiz=true のみ) 3段目: 大タグ 4段目: 小タグ 5段目: 投票期間 + 残り時間 6段目: タイトル(選択肢直前に再表示) 7段目: 説明文(存在する場合のみ) |
| 選択肢グリッド | LazyVGrid | 各 Choice をカード表示(label + image) |
| 選択ハイライト | Border | 選択中の Choice を視覚化 |
| 投票ボタン | Button | 選択完了でアクティブ化 |
| 消費ポイント表示 | Text | 「投票には Npt を消費します」 |
| バナー広告 | 共通配置 | MainTabView の safeAreaInset(.bottom) でタブバー直上に共通配置(プレミアム時は非表示) |
navigationTitle は維持| 状態 | 表示内容 |
|---|---|
| 未選択 | 投票ボタン無効 |
| 選択済 | 投票ボタン有効 |
| 投票中 | ProgressView、ボタン無効 |
| ポイント不足 | 「ポイントが足りません」アラート |
| 既投票検出 | 「すでに投票済みです」 → ResultView へ |
| 操作 | 遷移先 |
|---|---|
| 選択 + 投票ボタン押下 | VoteService.castVote() → ResultView |
| キャンセル | RankingDetailView へ戻る |
Vote {
let rankingId: UUID
let userId: UUID
let choiceId: UUID
let points: Int // 0 (初投票) / 3 (revote1) / 7 (revote2)
let voteNumber: Int // 1, 2, 3
let ipHash: String?
let deviceHash: String?
}
| バージョン | 日付 | 変更内容 |
|---|---|---|
| 1.0 | 2026-05-09 | 初版作成 |
| 1.1 | 2026-05-10 | RankingHeaderView を Xスタイル化(7段構成)/VoteView の重複タイトル表示を削除/バナー広告を MainTabView 共通配置へ統一 |