Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ app.*.map.json
# ====================================================================
/.claude/settings.local.json
/.report
/.issue
CLAUDE.md
.env
# Play Store CI/CD - ๋ฏผ๊ฐํ•œ ํŒŒ์ผ (์ž๋™ ์ƒ์„ฑ๋จ)
Expand Down
28 changes: 25 additions & 3 deletions CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
{
"metadata": {
"lastUpdated": "2026-02-23T02:13:37Z",
"currentVersion": "1.0.41",
"lastUpdated": "2026-02-23T04:16:06Z",
"currentVersion": "1.0.42",
"projectType": "flutter",
"totalReleases": 12
"totalReleases": 13
},
"releases": [
{
"version": "1.0.42",
"project_type": "flutter",
"date": "2026-02-23",
"pr_number": 40,
"raw_summary": "## Summary by CodeRabbit\n\n## ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ\n\n* **์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ**\n * AI ์žฅ์†Œ ์ถ”์ถœ ๊ธฐ๋Šฅ ์ถ”๊ฐ€: SNS ๋งํฌ์—์„œ ์œ„์น˜ ์ •๋ณด๋ฅผ ์ž๋™์œผ๋กœ ์ถ”์ถœํ•˜๊ณ , ์ถ”์ถœ๋œ ์žฅ์†Œ๋ฅผ ์„ ํƒํ•˜์—ฌ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.\n\n* **๋ฒ„์ „ ์—…๋ฐ์ดํŠธ**\n * ๋ฒ„์ „ 1.0.42๋กœ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
"parsed_changes": {
"์ƒˆ๋กœ์šด_๊ธฐ๋Šฅ": {
"title": "์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ",
"items": [
"AI ์žฅ์†Œ ์ถ”์ถœ ๊ธฐ๋Šฅ ์ถ”๊ฐ€: SNS ๋งํฌ์—์„œ ์œ„์น˜ ์ •๋ณด๋ฅผ ์ž๋™์œผ๋กœ ์ถ”์ถœํ•˜๊ณ , ์ถ”์ถœ๋œ ์žฅ์†Œ๋ฅผ ์„ ํƒํ•˜์—ฌ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
]
},
"๋ฒ„์ „_์—…๋ฐ์ดํŠธ": {
"title": "๋ฒ„์ „ ์—…๋ฐ์ดํŠธ",
"items": [
"๋ฒ„์ „ 1.0.42๋กœ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
]
}
},
"parse_method": "markdown"
},
{
"version": "1.0.41",
"project_type": "flutter",
Expand Down
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
# Changelog

**ํ˜„์žฌ ๋ฒ„์ „:** 1.0.41
**๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ:** 2026-02-23T02:13:37Z
**ํ˜„์žฌ ๋ฒ„์ „:** 1.0.42
**๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ:** 2026-02-23T04:16:06Z

---

## [1.0.42] - 2026-02-23

**PR:** #40

**์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ**
- AI ์žฅ์†Œ ์ถ”์ถœ ๊ธฐ๋Šฅ ์ถ”๊ฐ€: SNS ๋งํฌ์—์„œ ์œ„์น˜ ์ •๋ณด๋ฅผ ์ž๋™์œผ๋กœ ์ถ”์ถœํ•˜๊ณ , ์ถ”์ถœ๋œ ์žฅ์†Œ๋ฅผ ์„ ํƒํ•˜์—ฌ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

**๋ฒ„์ „ ์—…๋ฐ์ดํŠธ**
- ๋ฒ„์ „ 1.0.42๋กœ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

---

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ samples, guidance on mobile development, and a full API reference.
---

<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
## ์ตœ์‹  ๋ฒ„์ „ : v1.0.40 (2026-02-23)
## ์ตœ์‹  ๋ฒ„์ „ : v1.0.41 (2026-02-23)

[์ „์ฒด ๋ฒ„์ „ ๊ธฐ๋ก ๋ณด๊ธฐ](CHANGELOG.md)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'place_model.freezed.dart';
part 'place_model.g.dart';

/// ์žฅ์†Œ ๋ชจ๋ธ
/// ์žฅ์†Œ ๋ชจ๋ธ (๊ณตํ†ต)
@freezed
class PlaceModel with _$PlaceModel {
const factory PlaceModel({
Expand Down
8 changes: 2 additions & 6 deletions lib/common/widgets/main_scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart';

import '../constants/home_colors.dart';
import '../../routing/route_paths.dart';

/// ๋ฉ”์ธ ๋„ค๋น„๊ฒŒ์ด์…˜ ์…ธ (ํ•˜๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” + FAB)
///
Expand All @@ -25,12 +26,7 @@ class MainScaffold extends StatelessWidget {
height: 56.w,
child: FloatingActionButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('AI ์žฅ์†Œ ์ถ”์ถœ ๊ธฐ๋Šฅ ์ค€๋น„ ์ค‘'),
duration: Duration(seconds: 2),
),
);
context.push(RoutePaths.aiExtraction);
},
elevation: 2,
backgroundColor: HomeColors.textPrimary,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../../../common/constants/api_endpoints.dart';
import '../../../common/services/api_client.dart';
import 'models/analyze_request.dart';
import 'models/analyze_response.dart';
import 'models/content_detail_response.dart';

part 'ai_extraction_remote_datasource.g.dart';

@riverpod
AiExtractionRemoteDataSource aiExtractionRemoteDataSource(Ref ref) {
final dio = ref.watch(dioProvider);
return AiExtractionRemoteDataSource(dio);
}

/// AI ์žฅ์†Œ ์ถ”์ถœ Remote DataSource
class AiExtractionRemoteDataSource {
final Dio _dio;

AiExtractionRemoteDataSource(this._dio);

/// AI ๋ถ„์„ ์š”์ฒญ
/// POST /api/content/analyze
Future<AnalyzeResponse> analyze(AnalyzeRequest request) async {
debugPrint('๐Ÿ“ค AiExtraction: Analyzing URL: ${request.sourceUrl}');

final response = await _dio.post(
ApiEndpoints.contentAnalyze,
data: request.toJson(),
);

final result = AnalyzeResponse.fromJson(
response.data as Map<String, dynamic>,
);
debugPrint('โœ… Analyze requested: contentId=${result.contentId}');
return result;
}

/// ์ฝ˜ํ…์ธ  ์ƒ์„ธ ์กฐํšŒ (ํด๋ง์šฉ)
/// GET /api/content/{contentId}
Future<ContentDetailResponse> getContentDetail(int contentId) async {
debugPrint('๐Ÿ“ค AiExtraction: Polling contentId=$contentId');

final response = await _dio.get(
ApiEndpoints.contentDetail(contentId.toString()),
);

final result = ContentDetailResponse.fromJson(
response.data as Map<String, dynamic>,
);
debugPrint('โœ… Content status: ${result.status}');
return result;
}

/// ์žฅ์†Œ ์ €์žฅ
/// POST /api/place/{placeId}/save
Future<void> savePlace(int placeId) async {
debugPrint('๐Ÿ“ค AiExtraction: Saving placeId=$placeId');

await _dio.post(
ApiEndpoints.savePlace(placeId.toString()),
);

debugPrint('โœ… Place saved: placeId=$placeId');
}
}
15 changes: 15 additions & 0 deletions lib/features/ai_extraction/data/ai_extraction_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'models/analyze_request.dart';
import 'models/analyze_response.dart';
import 'models/content_detail_response.dart';

/// AI ์žฅ์†Œ ์ถ”์ถœ Repository ์ธํ„ฐํŽ˜์ด์Šค
abstract class AiExtractionRepository {
/// AI ๋ถ„์„ ์š”์ฒญ
Future<AnalyzeResponse> analyze(AnalyzeRequest request);

/// ์ฝ˜ํ…์ธ  ์ƒ์„ธ ์กฐํšŒ (ํด๋ง์šฉ)
Future<ContentDetailResponse> getContentDetail(int contentId);

/// ์žฅ์†Œ ์ €์žฅ
Future<void> savePlace(int placeId);
}
42 changes: 42 additions & 0 deletions lib/features/ai_extraction/data/ai_extraction_repository_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import 'ai_extraction_repository.dart';
import 'ai_extraction_remote_datasource.dart';
import 'models/analyze_request.dart';
import 'models/analyze_response.dart';
import 'models/content_detail_response.dart';

part 'ai_extraction_repository_impl.g.dart';

@riverpod
AiExtractionRepository aiExtractionRepository(Ref ref) {
final remoteDataSource = ref.watch(aiExtractionRemoteDataSourceProvider);
return AiExtractionRepositoryImpl(remoteDataSource);
}

/// AI ์žฅ์†Œ ์ถ”์ถœ Repository ๊ตฌํ˜„์ฒด
class AiExtractionRepositoryImpl implements AiExtractionRepository {
final AiExtractionRemoteDataSource _remoteDataSource;

AiExtractionRepositoryImpl(this._remoteDataSource);

@override
Future<AnalyzeResponse> analyze(AnalyzeRequest request) async {
debugPrint('๐Ÿ“ AiExtractionRepo: Analyzing...');
return await _remoteDataSource.analyze(request);
}

@override
Future<ContentDetailResponse> getContentDetail(int contentId) async {
debugPrint('๐Ÿ“ AiExtractionRepo: Getting content detail...');
return await _remoteDataSource.getContentDetail(contentId);
}

@override
Future<void> savePlace(int placeId) async {
debugPrint('๐Ÿ“ AiExtractionRepo: Saving place...');
return await _remoteDataSource.savePlace(placeId);
}
}
16 changes: 16 additions & 0 deletions lib/features/ai_extraction/data/models/analyze_request.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';

part 'analyze_request.freezed.dart';
part 'analyze_request.g.dart';

/// AI ๋ถ„์„ ์š”์ฒญ DTO
@freezed
class AnalyzeRequest with _$AnalyzeRequest {
const factory AnalyzeRequest({
/// SNS URL (Instagram, YouTube)
required String sourceUrl,
}) = _AnalyzeRequest;

factory AnalyzeRequest.fromJson(Map<String, dynamic> json) =>
_$AnalyzeRequestFromJson(json);
}
16 changes: 16 additions & 0 deletions lib/features/ai_extraction/data/models/analyze_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';

part 'analyze_response.freezed.dart';
part 'analyze_response.g.dart';

/// AI ๋ถ„์„ ์‘๋‹ต DTO
@freezed
class AnalyzeResponse with _$AnalyzeResponse {
const factory AnalyzeResponse({
/// ํด๋ง์šฉ ์ฝ˜ํ…์ธ  ID
required int contentId,
}) = _AnalyzeResponse;

factory AnalyzeResponse.fromJson(Map<String, dynamic> json) =>
_$AnalyzeResponseFromJson(json);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:freezed_annotation/freezed_annotation.dart';

import '../../../../common/models/place_model.dart';

part 'content_detail_response.freezed.dart';
part 'content_detail_response.g.dart';

/// ์ฝ˜ํ…์ธ  ์ƒ์„ธ ์‘๋‹ต DTO (ํด๋ง์šฉ)
@freezed
class ContentDetailResponse with _$ContentDetailResponse {
const factory ContentDetailResponse({
required int contentId,
/// PENDING, PROCESSING, COMPLETED, FAILED
required String status,
String? sourceUrl,
@Default([]) List<PlaceModel> places,
}) = _ContentDetailResponse;

factory ContentDetailResponse.fromJson(Map<String, dynamic> json) =>
_$ContentDetailResponseFromJson(json);
}
Loading