綴Photo の勉強ノートに戻る
プロダクト設計2026.04.04

「また今度」ボタンの裏側にある設計。スキップした写真をちゃんと戻す仕組み

FlutterUX設計綴Photoプロダクト設計

綴Photo では、写真を1枚ずつ眺めて「綴る」か「また今度」かを選ぶ画面があります。 Tinder のスワイプを思い浮かべてもらうと近いです。 でも、写真は人を選ぶのと違って、消したくない。 「また今度」と言ったらちゃんと「また」戻ってきてほしい。 その小さな願いをコードに落とすときに、考えたことを残しておく。


なぜ「また今度」というボタンを作ったか

最初は「スキップ」というボタン名だった。 でも実際に自分で使ってみると、なんか冷たい。 「スキップ」って言葉、写真に対して使うと「もう見ない」みたいな響きがある。

大切な写真は、今日の自分には言葉にできなくても、 3ヶ月後の自分なら綴れるかもしれない。 そういう写真をスキップ扱いするのは、アプリの世界観に合わない気がした。

だから「また今度」というラベルにした。 ボタンの名前を変えただけで、「これは捨てるんじゃなくて、後で戻ってくる動作なんだ」 というメンタルモデルが揃った気がする。

UX の話なんだけど、最初に決めた1つの言葉が、後の機能設計を全部引っ張る感覚があった。


詰まったところ:「また」を実現するのが難しい

ラベルは決まった。じゃあ実装はというと、ここからが本番だった。

「また今度」と言うからには、押した瞬間に3か所の扱いが分かれていないといけません。

置き場所は3つ。押した瞬間に扱いが分かれる。綴る候補の並びメモリセッション中だけリストから外れる並びは保存しないpassed_photos端末 SQLite行を書くpassed_at が入る閉じても残る写真ファイル端末ストレージ触らない消さないライブラリの実体「次の写真」に進むのは左。戻せる根拠は真ん中。右は捨てていない。

リストから外さないと、次の写真に進めません。 しばらく経ったら、別の写真と一緒にもう一回出てきます。

最初は「Pass フラグ」を立てるだけにしようとした。 is_passed = true にして、リストから除外する、それだけ。

でもこれだと「再浮上」が実現できません。 全部 Pass した瞬間、ストックがゼロになって、もう何も出てこなくなります。

「また」を成立させるには、いつ Pass したか と、いつ再浮上させていいか を持っておく必要があった。


Pass テーブルに「passed_at」を持たせて、間隔で再浮上させる

結論としては、Pass を「フラグ」じゃなく「イベント」として扱う設計に変えた。

持ち場所は SQLite の2か所。戻す先の並びはメモリで、保存しない。passed_photospassed_at(いつ Pass したか)端末 SQLite。閉じても残るuser_settingsskip_resurface_days(N 日)端末 SQLite。ユーザー設定交差点。N 日経ったか。保存しない綴る候補の並び(メモリ)。また出てくる
dart
// 大まかなイメージ。実際のテーブル定義は別記事に分けたい
class PassRecord {
  final String photoId;
  final DateTime passedAt;
}
 
bool shouldResurface(PassRecord r, Duration interval) {
  return DateTime.now().difference(r.passedAt) >= interval;
}

「N 日」をどれくらいにするかは、最初は固定値にしていた。 でもこれが意外と人によって感覚が違います。

  • 毎日アプリを開く人にとっては「3日後の再浮上」でも早く感じるかもしれない
  • 週に1回しか開かない人にとっては「14日後」だと永遠に出てこない可能性がある

なので、再浮上の間隔はユーザー設定にした。 これは設計判断としてかなり大きかった。


「Pass ≠ 削除」という UX を貫く

実装の話に行く前に、UX として一番こだわったポイントを書いておきたい。

それは「Pass しても罪悪感を残さない」ということだ。

写真を見て、今日の自分にはまだ言葉が出ない。 でもアプリ上で「捨てた」感が出ると、その瞬間に思い出ごと閉じてしまう気がする。 大切な写真ってそういう種類のものだと俺は思っていて、 扱いを間違えるとアプリが「重い」存在になる。

だから、

  • ラベルは「また今度」
  • アニメーションは消えるんじゃなく、横にスッと逃げるだけ
  • フィードバック表示も「保留しました」とか柔らかい表現にしている

この辺は、どれか1つでも雑にすると一気に冷たくなります。 細かいけど、こういうところに「綴Photo らしさ」を集中させています。


詰まったポイント:Pass された写真の「次回出す順番」

Pass の保存はできた。再浮上の判定もできた。 でも次に詰まったのが、「Pass された写真を、いつ・どの順序で戻すか」だった。

たとえば

  • 100枚 Pass されている
  • そのうち 30枚 が再浮上条件を満たしている

このとき、30枚を一度に出すのか、何枚かずつ出すのか、新しい写真と混ぜるのか。

ここを雑に作ると「Pass した写真が一気に押し寄せてきた」みたいな体験になって、 「いや、また今度って言ったじゃん」となります。

なので、

  • 新しい写真と再浮上写真を混ぜて出す
  • 一度に何枚も再浮上させない(混ぜる比率を決める)
  • 同じ写真を短期間で連続再浮上させない

というルールを少しずつ追加していった。 このあたりは今もチューニング中で、ベストなバランスは正直まだ手探りだ。 詳しい比率や打ち切り方の話は、別記事に分けたい。



学んだこと

  • ボタンのラベル一つが、機能設計全体を引っ張る
  • 「Pass」をフラグじゃなくイベントとして扱うと、再浮上が自然に表現できる
  • 再浮上の間隔は、固定値よりユーザー設定にした方が体験の幅が広がる
  • 「罪悪感を残さない」ことは UI のあらゆるレイヤー(ラベル・アニメ・コピー)に効いてくる
  • メタファー(再浮上、水面下、沈める)が決まると、コードの命名が揃う

「また今度」ボタンは、機能としては小さい。 でも、その裏でやっていることは

  • DB 設計
  • 候補の混ぜ方
  • アニメーション
  • 言葉選び

までを横断していて、アプリ全体の世界観を支える部品になっていた。 こういう「小さいけど効く」部分を、丁寧に作るのが個人開発の楽しさだと思う。


追記(2026-05): 再浮上システムは廃止した

この記事で書いた「Pass されてから N 日後に自動で再浮上させる」仕組みは、その後やめた。理由は3つ。

  • 仕組みが複雑だった — passed_at の管理、再浮上間隔、新しい写真と混ぜる比率、連続再浮上の抑制…と、判定とチューニングのパラメータが増え続けた。1人開発で持ち続けるには重い
  • UX がしっくりこなかった — 自分で使ってみると、勝手に戻ってくる写真が思ったより嬉しくない。自分のタイミングで見返したいことの方が多かった
  • 別の導線でカバーできた — 「また今度」した写真だけを集めた一覧を用意して、そこからいつでも見返せるようにした。「時間が経ったら自動で戻す」必要そのものがなくなった

ただし 「また今度(Pass)」ボタン自体は残しています。「いま言葉にしない」を選べること自体は、この記事で書いた通り大事だと思っているから。変えたのは「その後どう戻すか」の部分である。

Pass の行はどちらも端末 SQLite の passed_photos に残る。変えたのは戻し方。当時passed_photospassed_atuser_settingsskip_resurface_days綴る候補の並びメモリへ自動で混ぜるいまpassed_photos行はそのまま残るまた今度にした写真本棚。DB から読む綴るセッションメモリ。自分で開く写真ファイルはどちらも消さない。

「Pass をフラグじゃなくイベントとして持つ」「メタファーで命名を揃える」あたりの考え方は今でも使えるので、当時の設計記録としてこの記事は残しておく。


この記事は2026年4月時点の自分の理解と実装方針で書いています。アプリは継続開発中なので、現在の実装と細部が違っている可能性があります。