綴Photo では、写真を1枚ずつ眺めて「綴る」か「また今度」かを選ぶ画面があります。 Tinder のスワイプを思い浮かべてもらうと近いです。 でも、写真は人を選ぶのと違って、消したくない。 「また今度」と言ったらちゃんと「また」戻ってきてほしい。 その小さな願いをコードに落とすときに、考えたことを残しておく。
なぜ「また今度」というボタンを作ったか
最初は「スキップ」というボタン名だった。 でも実際に自分で使ってみると、なんか冷たい。 「スキップ」って言葉、写真に対して使うと「もう見ない」みたいな響きがある。
大切な写真は、今日の自分には言葉にできなくても、 3ヶ月後の自分なら綴れるかもしれない。 そういう写真をスキップ扱いするのは、アプリの世界観に合わない気がした。
だから「また今度」というラベルにした。 ボタンの名前を変えただけで、「これは捨てるんじゃなくて、後で戻ってくる動作なんだ」 というメンタルモデルが揃った気がする。
UX の話なんだけど、最初に決めた1つの言葉が、後の機能設計を全部引っ張る感覚があった。
詰まったところ:「また」を実現するのが難しい
ラベルは決まった。じゃあ実装はというと、ここからが本番だった。
「また今度」と言うからには、押した瞬間に3か所の扱いが分かれていないといけません。
リストから外さないと、次の写真に進めません。 しばらく経ったら、別の写真と一緒にもう一回出てきます。
最初は「Pass フラグ」を立てるだけにしようとした。
is_passed = true にして、リストから除外する、それだけ。
でもこれだと「再浮上」が実現できません。 全部 Pass した瞬間、ストックがゼロになって、もう何も出てこなくなります。
「また」を成立させるには、いつ Pass したか と、いつ再浮上させていいか を持っておく必要があった。
Pass テーブルに「passed_at」を持たせて、間隔で再浮上させる
結論としては、Pass を「フラグ」じゃなく「イベント」として扱う設計に変えた。
// 大まかなイメージ。実際のテーブル定義は別記事に分けたい
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 をフラグじゃなくイベントとして持つ」「メタファーで命名を揃える」あたりの考え方は今でも使えるので、当時の設計記録としてこの記事は残しておく。
この記事は2026年4月時点の自分の理解と実装方針で書いています。アプリは継続開発中なので、現在の実装と細部が違っている可能性があります。