0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeで「逃げられない」集中タイマーアプリを1日で作ってApp Storeにリリースした

Posted at

Claude Codeで「逃げられない」集中タイマーアプリを1日で作ってApp Storeにリリースした

スクリーンショット 2026-01-13 21.59.20.png

TL;DR

  • YouTube視聴 × ポモドーロタイマーのアプリ「Tube道場」を個人開発
  • Claude Codeを使ってMVP実装からApp Store申請まで実質1日
  • 技術スタック: React Native (Expo) + TypeScript + Jotai + Firebase

📱 App Store: https://apps.apple.com/us/app/tubedojo/id6757209170


作ったもの

Tube道場」は、YouTube動画を見ながらポモドーロタイマーで集中できるアプリです。

最大の特徴はFocus Mode(集中モード)。一度開始したら、タイマーが終わるまで中断できません。一時停止ボタンもリセットボタンも消えます。

機能 説明
YouTube統合 アプリ内でYouTube再生。BGM/環境音を聴きながら作業
Focus Mode 開始したら中断不可。意志力に頼らない強制集中
リアルタイム接続数 「今○人が集中中」で孤独感を軽減

なぜ作ったか

自分自身、集中力が続かない問題を抱えていました。

  • ポモドーロアプリを使っても「ちょっとだけ一時停止」が簡単すぎて逃げてしまう
  • YouTubeでBGMを流すと、関連動画に吸い込まれる
  • 一人で作業していると孤独でサボりがち

システム的に逃げ道を塞ぐアプリが欲しかったのです。


技術スタック

React Native (Expo SDK 52)
TypeScript
Jotai(状態管理)
Firebase Realtime Database(オンライン人数)
Cloudflare Workers(カウント集計)

Claude Codeでの開発フロー

1. CLAUDE.mdで文脈を共有

プロジェクトルートにCLAUDE.mdを作成し、以下を記載:

# Tube道場

## 概要
YouTube視聴 × ポモドーロタイマーの集中支援アプリ

## 技術スタック
- React Native (Expo)
- TypeScript
- Jotai(状態管理)
- Firebase Realtime Database

## ディレクトリ構造
src/
├── components/   # UIコンポーネント
├── stores/       # Jotai atoms
├── hooks/        # カスタムフック
└── screens/      # 画面コンポーネント

## 開発ルール
- 状態管理は必ずJotaiを使用
- コンポーネントは機能単位で分割
- Firebase操作はhooks/usePresence.tsに集約

このファイルがあることで、Claude Codeはプロジェクト全体を理解した上でコードを生成してくれます。

2. 設計書を先に書く

docs/app-design-doc.mdに画面設計を記載:

## メイン画面

┌─────────────────────────┐
│  🔴 25:00              │  ← タイマー表示
│                         │
│  ┌───────────────────┐ │
│  │                   │ │  ← YouTube Player
│  │    (動画再生)      │ │
│  │                   │ │
│  └───────────────────┘ │
│                         │
│  [▶ 開始]  [⚙ 設定]    │  ← Focus Mode中は非表示
│                         │
│  👥 42人が集中中        │  ← リアルタイム表示
└─────────────────────────┘

ASCII artでモックアップを書いておくと、Claude Codeが意図を正確に理解してくれます。

3. 機能単位で指示を出す

指示例:Focus Modeの実装

Focus Modeを実装してください。

要件:
- Focus Mode中は一時停止・リセットボタンを非表示
- 設定画面へのアクセスも無効化
- バックグラウンド移行を検知して警告モーダルを表示
- 状態はJotaiのatomで管理

Claude Codeはこの指示から、必要なコンポーネント・hooks・atomsを一貫して生成してくれました。


実装のポイント

Focus Modeの状態管理(Jotai)

// stores/focusMode.ts
import { atom } from 'jotai';

export const focusModeEnabledAtom = atom(false);
export const focusModeActiveAtom = atom(false);

// Focus Mode中かどうかを派生
export const isFocusRunningAtom = atom(
  (get) => get(focusModeEnabledAtom) && get(focusModeActiveAtom)
);
// components/TimerControls.tsx
import { useAtomValue } from 'jotai';
import { isFocusRunningAtom } from '../stores/focusMode';

export const TimerControls = () => {
  const isFocusRunning = useAtomValue(isFocusRunningAtom);

  // Focus Mode中はボタンを表示しない
  if (isFocusRunning) return null;

  return (
    <View>
      <Button title="一時停止" onPress={handlePause} />
      <Button title="リセット" onPress={handleReset} />
    </View>
  );
};

ボタンを「無効化」ではなく「非表示」にすることで、物理的に押せなくしています。

バックグラウンド検知

// hooks/useAppState.ts
import { useEffect } from 'react';
import { AppState } from 'react-native';
import { useAtomValue, useSetAtom } from 'jotai';

export const useBackgroundWarning = () => {
  const isFocusRunning = useAtomValue(isFocusRunningAtom);
  const setShowWarning = useSetAtom(showWarningModalAtom);

  useEffect(() => {
    const sub = AppState.addEventListener('change', (state) => {
      if (isFocusRunning && state === 'background') {
        // アプリがバックグラウンドに行ったら警告
        setShowWarning(true);
      }
    });
    return () => sub.remove();
  }, [isFocusRunning]);
};

オンライン人数の取得(Firebase)

// hooks/usePresence.ts
import { useEffect, useState } from 'react';
import { ref, onValue, set, serverTimestamp } from 'firebase/database';
import { db } from '../config/firebase';

export const usePresence = () => {
  const [onlineCount, setOnlineCount] = useState(0);
  const visitorId = useRef(generateId()).current;

  useEffect(() => {
    // 自分のpresenceを登録
    const presenceRef = ref(db, `presence/${visitorId}`);
    set(presenceRef, {
      visitorId,
      expiresAt: Date.now() + 60000, // 1分後に期限切れ
    });

    // カウントを購読
    const countRef = ref(db, 'stats/onlineCount');
    const unsub = onValue(countRef, (snapshot) => {
      setOnlineCount(snapshot.val() || 0);
    });

    return () => unsub();
  }, []);

  return { onlineCount };
};

App Store申請でハマったこと

「YouTube」がNGだった

サブタイトルに「集中タイマー × YouTube」と書いたら、Guideline 4.1で却下されました。

第三者のブランド名(YouTube)をメタデータに含めることはできません

修正内容:

  • サブタイトル: 「集中タイマー × YouTube」→「集中タイマー × Tube動画」
  • キーワード: 「YouTube」を削除
  • 説明文: 「YouTube BGM」→「動画BGM」

Claude Codeを使ってみて

良かった点

  • 一貫性: プロジェクト全体で同じパターンを適用してくれる
  • 速度: 手作業で数時間かかる実装が数分で完成
  • 細かい配慮: 画面回転時の状態復元など、指示していない部分も考慮

人間がやること

  • 設計書を書く(何を作りたいかを明確に)
  • 環境構築(Firebase設定、APIキー取得)
  • デザインの最終判断
  • App Store申請作業

まとめ

Claude Codeを使うことで、1日でMVPを完成させてApp Storeにリリースできました。

ポイントは:

  1. CLAUDE.mdで文脈を共有する
  2. 設計書を先に書く(ASCII artでモックアップ)
  3. 機能単位で明確に指示する
  4. 小さく始める(まずMVP、後から機能追加)

「こんなアプリあったらいいな」を形にするハードルが、劇的に下がっています。


リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?