Skip to content

Commit 2d698b7

Browse files
authored
Geminiの分析情報をDBに入れる (#159) (#165)
* feat(camera): Gemini分析結果のDB保存機能を実装 - ScanDataServiceにsaveAnalysisCommentメソッドを追加 - QRスキャン画面で分析後に自動的にFirestoreへ保存 - 既存の分析結果がある場合は初期表示 - 再分析ボタンで最新の分析結果に更新可能 - users/{userId}/meishies/{friendUserId}コレクションに保存 * fix: コードの整形を改善 * fix(camera): PRレビュー指摘事項を修正 - ユーザーIDのログ出力を削除してプライバシーを保護 - 非同期処理後のsetState()にmountedチェックを追加 - saveAnalysisCommentのエラーを分離して分析結果を保護
1 parent 321044f commit 2d698b7

File tree

2 files changed

+97
-30
lines changed

2 files changed

+97
-30
lines changed

lib/ui/camera/services/scan_data_service.dart

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ class ScanDataService {
4242

4343
/// 取得したTIDに基づいて、アニメ詳細情報を取得する
4444
static Future<List<Map<String, dynamic>>> getAnimeDetails(
45-
Set<String> tids) async {
45+
Set<String> tids,
46+
) async {
4647
List<Map<String, dynamic>> animeList = [];
4748
try {
4849
// "titles" コレクションから全件取得(件数が多い場合は where クエリなどで絞ることを検討)
@@ -103,8 +104,9 @@ class ScanDataService {
103104

104105
// URLフォーマットの場合: https://animeishi-viewer.web.app/user/USER_ID
105106
if (trimmedValue.startsWith('https://animeishi-viewer.web.app/user/')) {
106-
final userId = trimmedValue
107-
.substring('https://animeishi-viewer.web.app/user/'.length);
107+
final userId = trimmedValue.substring(
108+
'https://animeishi-viewer.web.app/user/'.length,
109+
);
108110
return userId.isNotEmpty ? userId : null;
109111
}
110112

@@ -130,7 +132,9 @@ class ScanDataService {
130132

131133
/// analysisCommentを取得
132134
static Future<String?> getAnalysisComment(
133-
String currentUserId, String friendUserId) async {
135+
String currentUserId,
136+
String friendUserId,
137+
) async {
134138
try {
135139
final doc = await _firestore
136140
.collection('users')
@@ -146,6 +150,29 @@ class ScanDataService {
146150
}
147151
return null;
148152
}
153+
154+
/// analysisCommentを保存
155+
static Future<void> saveAnalysisComment(
156+
String currentUserId,
157+
String friendUserId,
158+
String comment,
159+
) async {
160+
try {
161+
await _firestore
162+
.collection('users')
163+
.doc(currentUserId)
164+
.collection('meishies')
165+
.doc(friendUserId)
166+
.set({
167+
'analysisComment': comment,
168+
'updatedAt': FieldValue.serverTimestamp(),
169+
}, SetOptions(merge: true));
170+
print('analysisComment保存成功');
171+
} catch (e) {
172+
print('analysisComment保存エラー: $e');
173+
rethrow;
174+
}
175+
}
149176
}
150177

151178
/// スキャンデータ取得結果を格納するクラス

lib/ui/camera/view/scandata.dart

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import 'package:flutter/material.dart';
22
import 'package:mobile_scanner/mobile_scanner.dart';
3+
import 'package:firebase_auth/firebase_auth.dart';
34
import 'package:animeishi/ui/home/view/home_page.dart';
45
import 'package:animeishi/ui/camera/services/anime_analysis_service.dart';
56
import 'package:animeishi/ui/camera/services/scan_data_service.dart';
67
import '../controllers/scan_result_animation_controller.dart';
7-
import '../services/scan_data_service.dart';
88
import '../components/scan_result_widgets.dart';
99
import '../components/scan_result_painters.dart';
1010

@@ -60,6 +60,23 @@ class _ScanDataWidgetState extends State<ScanDataWidget>
6060
// データ取得
6161
try {
6262
final result = await ScanDataService.fetchUserData(_userId!);
63+
64+
// 既存の分析結果を取得
65+
final currentUserId = FirebaseAuth.instance.currentUser?.uid;
66+
if (currentUserId != null && result.success) {
67+
final savedComment = await ScanDataService.getAnalysisComment(
68+
currentUserId,
69+
_userId!,
70+
);
71+
if (savedComment != null) {
72+
if (!mounted) return;
73+
setState(() {
74+
analysisComment = savedComment;
75+
});
76+
}
77+
}
78+
79+
if (!mounted) return;
6380
setState(() {
6481
_scanResult = result;
6582
_isLoading = false;
@@ -94,11 +111,28 @@ class _ScanDataWidgetState extends State<ScanDataWidget>
94111
animeList,
95112
username: username,
96113
);
114+
115+
// 分析結果をFirestoreに保存
116+
final currentUserId = FirebaseAuth.instance.currentUser?.uid;
117+
if (currentUserId != null) {
118+
try {
119+
await ScanDataService.saveAnalysisComment(
120+
currentUserId,
121+
userId,
122+
comment,
123+
);
124+
} catch (e) {
125+
debugPrint('analysisComment保存失敗: $e');
126+
}
127+
}
128+
129+
if (!mounted) return;
97130
setState(() {
98131
analysisComment = comment;
99132
isAnalyzing = false;
100133
});
101134
} catch (e) {
135+
if (!mounted) return;
102136
setState(() {
103137
analysisComment = 'AI分析に失敗しました: $e';
104138
isAnalyzing = false;
@@ -166,10 +200,7 @@ class _ScanDataWidgetState extends State<ScanDataWidget>
166200
(route) => false,
167201
);
168202
},
169-
icon: const Icon(
170-
Icons.arrow_back,
171-
color: Color(0xFF667EEA),
172-
),
203+
icon: const Icon(Icons.arrow_back, color: Color(0xFF667EEA)),
173204
),
174205
),
175206
),
@@ -225,30 +256,13 @@ class _ScanDataWidgetState extends State<ScanDataWidget>
225256
ScanResultWidgets.buildAnimeListCard(animeList),
226257

227258
const SizedBox(height: 24),
228-
ElevatedButton.icon(
229-
icon: const Icon(Icons.insights),
230-
label: const Text('AIで視聴傾向を分析'),
231-
onPressed: isAnalyzing ? null : () => _runAnalysis(_userId!),
232-
style: ElevatedButton.styleFrom(
233-
backgroundColor: const Color(0xFF667EEA),
234-
foregroundColor: Colors.white,
235-
minimumSize: const Size(double.infinity, 48),
236-
),
237-
),
238-
const SizedBox(height: 16),
239-
if (isAnalyzing)
240-
Row(
241-
children: [
242-
const CircularProgressIndicator(),
243-
const SizedBox(width: 12),
244-
const Text('分析中...'),
245-
],
246-
),
247-
if (analysisComment != null)
259+
260+
// 分析結果表示(既存の場合)
261+
if (analysisComment != null && !isAnalyzing)
248262
Container(
249263
width: double.infinity,
250264
padding: const EdgeInsets.all(16),
251-
margin: const EdgeInsets.only(top: 8),
265+
margin: const EdgeInsets.only(bottom: 16),
252266
decoration: BoxDecoration(
253267
color: Colors.white,
254268
borderRadius: BorderRadius.circular(15),
@@ -282,6 +296,32 @@ class _ScanDataWidgetState extends State<ScanDataWidget>
282296
],
283297
),
284298
),
299+
300+
// 分析ボタン
301+
ElevatedButton.icon(
302+
icon: const Icon(Icons.insights),
303+
label: Text(analysisComment != null ? 'AIで再分析' : 'AIで視聴傾向を分析'),
304+
onPressed: isAnalyzing ? null : () => _runAnalysis(_userId!),
305+
style: ElevatedButton.styleFrom(
306+
backgroundColor: const Color(0xFF667EEA),
307+
foregroundColor: Colors.white,
308+
minimumSize: const Size(double.infinity, 48),
309+
),
310+
),
311+
312+
// 分析中表示
313+
if (isAnalyzing)
314+
const Padding(
315+
padding: EdgeInsets.only(top: 16),
316+
child: Row(
317+
mainAxisAlignment: MainAxisAlignment.center,
318+
children: [
319+
CircularProgressIndicator(),
320+
SizedBox(width: 12),
321+
Text('分析中...'),
322+
],
323+
),
324+
),
285325
],
286326
),
287327
),

0 commit comments

Comments
 (0)