diff --git a/.github/workflows/deploy-web.yml b/.github/workflows/deploy-web.yml
index 9678ef3..37f17f4 100644
--- a/.github/workflows/deploy-web.yml
+++ b/.github/workflows/deploy-web.yml
@@ -1,41 +1,46 @@
-name: web kakao api added
-
+name: Deploy Flutter Web to GitHub Pages
on:
push:
- branches: [ "kpaasTest" ] # 1. 'develop'에서 'kpaasTest'로 변경
+ branches: [ "kpaasTest" ]
jobs:
build_and_deploy:
runs-on: ubuntu-latest
- # gh-pages 브랜치에 쓰기 권한 부여
permissions:
- contents: write
+ contents: write
steps:
- # 2. 레포지토리 코드 가져오기
- name: 📦 Checkout Repository
uses: actions/checkout@v4
- # 3. Flutter 환경 설정
- name: ☕ Set up Flutter SDK
uses: subosito/flutter-action@v2
with:
channel: 'stable'
-
- # 4. Flutter 웹 설정 및 파일 생성
- - name: 🌐 Configure Flutter for Web
- run: flutter create . --platforms web
-
- # 5. 프로젝트 의존성 설치
+
+ - name: 🌐 Create Web Platform
+ run: |
+ if [ ! -d "web" ]; then
+ flutter create . --platforms web
+ fi
+
+ # ⭐ 빈 .env 파일 생성 (pubspec.yaml asset 오류 방지)
+ - name: 📄 Create Empty .env File
+ run: touch .env
+
- name: 🧩 Get Dependencies
run: flutter pub get
-
- # 6. Build Web App (환경 변수 주입 및 HTML 렌더러 사용)
+
+ - name: 📝 Add Base Href Placeholder
+ run: |
+ if ! grep -q 'FLUTTER_BASE_HREF' web/index.html; then
+ sed -i 's|
|\n |' web/index.html
+ fi
+
- name: 🏗️ Build Web App
run: |
flutter build web --release \
- --web-renderer=html \
--base-href "/FE/" \
--dart-define=API_BASE_URL="${{ secrets.API_BASE_URL }}" \
--dart-define=KAKAO_NATIVE_APP_KEY="${{ secrets.KAKAO_NATIVE_APP_KEY }}" \
@@ -43,8 +48,7 @@ jobs:
--dart-define=NAVER_CLIENT_ID="${{ secrets.NAVER_CLIENT_ID }}" \
--dart-define=NAVER_CLIENT_SECRET="${{ secrets.NAVER_CLIENT_SECRET }}" \
--dart-define=OPENWEATHERMAP_API_KEY="${{ secrets.OPENWEATHERMAP_API_KEY }}"
-
- # 7. 빌드 결과물을 GitHub Pages에 배포
+
- name: 🚀 Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
diff --git a/.github/workflows/mai123123n.yml b/.github/workflows/mai123123n.yml
deleted file mode 100644
index e947245..0000000
--- a/.github/workflows/mai123123n.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-# .github/workflows/deploy-flutter-web.yml
-
-name: Deploy Flutter Web to GitHub Pages
-
-on:
- push:
- branches: [ "feature/test-1" ] # develop 브랜치에 푸시될 때 실행
-
-jobs:
- build_and_deploy:
- runs-on: ubuntu-latest
-
- # gh-pages 브랜치에 쓰기 권한 부여
- permissions:
- contents: write
-
- steps:
- # 1. 레포지토리 코드 가져오기
- - name: 📦 Checkout Repository
- uses: actions/checkout@v4
-
- # 2. Flutter 환경 설정 (⭐ 수정됨)
- # Flutter 버전을 고정하여 '--web-renderer' 옵션을 인식하도록 합니다.
- - name: ☕ Set up Flutter SDK
- uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- flutter-version: '3.22.3' # 3.22.3 또는 3.24.x 등 최신 안정 버전 명시
-
- # 3. .env 파일 생성 (원본 유지)
- # 프로젝트에 필요한 Secret들을 .env 파일로 생성합니다.
- - name: ✍️ Create .env file
- run: |
- echo "API_BASE_URL=${{ secrets.API_BASE_URL }}" >> .env
- echo "KAKAO_NATIVE_APP_KEY=${{ secrets.KAKAO_NATIVE_APP_KEY }}" >> .env
- echo "NAVER_CLIENT_ID=${{ secrets.NAVER_CLIENT_ID }}" >> .env
- echo "NAVER_CLIENT_SECRET=${{ secrets.NAVER_CLIENT_SECRET }}" >> .env
- echo "OPENWEATHERMAP_API_KEY=${{ secrets.OPENWEATHERMAP_API_KEY }}" >> .env
- echo ".env 파일 생성 완료:"
- cat .env # 생성된 파일 내용 확인 (키 값 자체는 마스킹 처리됨)
-
- # 4. Flutter 웹 활성화 (⭐ 수정됨)
- # 'flutter create' 대신 'flutter config'를 사용하여 웹 지원을 활성화합니다.
- - name: 🌐 Enable Web
- run: flutter config --enable-web
-
- # 5. 프로젝트 의존성 설치
- - name: 🧩 Get Dependencies
- run: flutter pub get
-
- # 6. Flutter 웹 앱 빌드 (⭐ 수정됨)
- # '--web-renderer=html' 대신 '--web-renderer html' (공백)을 사용합니다.
- - name: 🏗️ Build Web App
- run: flutter build web --release --web-renderer html --base-href "/FE/"
-
- # 7. 빌드 결과물을 GitHub Pages에 배포 (원본 유지)
- - name: 🚀 Deploy to GitHub Pages
- uses: peaceiris/actions-gh-pages@v3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./build/web
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index f5fed91..0000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-name: Deploy Flutter Web to GitHub Pages
-on:
- push:
- branches: [ "develop" ]
-
-jobs:
- build_and_deploy:
- runs-on: ubuntu-latest
-
- permissions:
- contents: write
-
- steps:
- - name: 📦 Checkout Repository
- uses: actions/checkout@v4
-
- - name: ☕ Set up Flutter SDK
- uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
-
- - name: ✍️ Create .env file
- run: |
- echo "API_BASE_URL=${{ secrets.API_BASE_URL }}" >> .env
- echo "KAKAO_NATIVE_APP_KEY=${{ secrets.KAKAO_NATIVE_APP_KEY }}" >> .env
- echo "NAVER_CLIENT_ID=${{ secrets.NAVER_CLIENT_ID }}" >> .env
- echo "NAVER_CLIENT_SECRET=${{ secrets.NAVER_CLIENT_SECRET }}" >> .env
- echo "OPENWEATHERMAP_API_KEY=${{ secrets.OPENWEATHERMAP_API_KEY }}" >> .env
- echo ".env 파일 생성 완료:"
- cat .env
-
- - name: 🌐 Configure Flutter for Web
- run: flutter create . --platforms web
-
- - name: 🧩 Get Dependencies
- run: flutter pub get
-
- # ⭐ --web-renderer 옵션 제거
- - name: 🏗️ Build Web App
- run: flutter build web --release --base-href "/FE/"
-
- - name: 🚀 Deploy to GitHub Pages
- uses: peaceiris/actions-gh-pages@v3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./build/web
diff --git a/.github/workflows/main123.yml b/.github/workflows/main123.yml
deleted file mode 100644
index e947245..0000000
--- a/.github/workflows/main123.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-# .github/workflows/deploy-flutter-web.yml
-
-name: Deploy Flutter Web to GitHub Pages
-
-on:
- push:
- branches: [ "feature/test-1" ] # develop 브랜치에 푸시될 때 실행
-
-jobs:
- build_and_deploy:
- runs-on: ubuntu-latest
-
- # gh-pages 브랜치에 쓰기 권한 부여
- permissions:
- contents: write
-
- steps:
- # 1. 레포지토리 코드 가져오기
- - name: 📦 Checkout Repository
- uses: actions/checkout@v4
-
- # 2. Flutter 환경 설정 (⭐ 수정됨)
- # Flutter 버전을 고정하여 '--web-renderer' 옵션을 인식하도록 합니다.
- - name: ☕ Set up Flutter SDK
- uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- flutter-version: '3.22.3' # 3.22.3 또는 3.24.x 등 최신 안정 버전 명시
-
- # 3. .env 파일 생성 (원본 유지)
- # 프로젝트에 필요한 Secret들을 .env 파일로 생성합니다.
- - name: ✍️ Create .env file
- run: |
- echo "API_BASE_URL=${{ secrets.API_BASE_URL }}" >> .env
- echo "KAKAO_NATIVE_APP_KEY=${{ secrets.KAKAO_NATIVE_APP_KEY }}" >> .env
- echo "NAVER_CLIENT_ID=${{ secrets.NAVER_CLIENT_ID }}" >> .env
- echo "NAVER_CLIENT_SECRET=${{ secrets.NAVER_CLIENT_SECRET }}" >> .env
- echo "OPENWEATHERMAP_API_KEY=${{ secrets.OPENWEATHERMAP_API_KEY }}" >> .env
- echo ".env 파일 생성 완료:"
- cat .env # 생성된 파일 내용 확인 (키 값 자체는 마스킹 처리됨)
-
- # 4. Flutter 웹 활성화 (⭐ 수정됨)
- # 'flutter create' 대신 'flutter config'를 사용하여 웹 지원을 활성화합니다.
- - name: 🌐 Enable Web
- run: flutter config --enable-web
-
- # 5. 프로젝트 의존성 설치
- - name: 🧩 Get Dependencies
- run: flutter pub get
-
- # 6. Flutter 웹 앱 빌드 (⭐ 수정됨)
- # '--web-renderer=html' 대신 '--web-renderer html' (공백)을 사용합니다.
- - name: 🏗️ Build Web App
- run: flutter build web --release --web-renderer html --base-href "/FE/"
-
- # 7. 빌드 결과물을 GitHub Pages에 배포 (원본 유지)
- - name: 🚀 Deploy to GitHub Pages
- uses: peaceiris/actions-gh-pages@v3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./build/web
diff --git a/.github/workflows/main123123.yml b/.github/workflows/main123123.yml
deleted file mode 100644
index 012cb8f..0000000
--- a/.github/workflows/main123123.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-네, 클로드(Claude)가 제안한 내용이 **아주 정확하고 좋은 해결책**입니다\! 👏
-
-하얀 화면이 뜨는 문제는 클로드의 분석대로, GitHub Actions가 `.env` 파일을 웹 빌드에 포함시키지 못해서 발생하는 **환경 변수 문제**가 맞습니다.
-
-클로드가 제안한 `--dart-define` 방식은 CI/CD에서 환경 변수를 주입하는 **가장 표준적이고 안정적인 방법**입니다.
-
-### **그런데... (중요\! ⚠️)**
-
-클로드가 제안한 `.yaml` 파일은 이전에 발생했던 **Wasm 호환성 오류**(`flutter_secure_storage_web` 관련)를 해결하기 위한 `--web-renderer=html` 옵션을 빠뜨렸습니다.
-
-가장 완벽한 방법은 **두 가지 해결책을 모두 합치는 것**입니다.
-
-1. **환경 변수 문제 해결**: `echo` 대신 `--dart-define` 사용 (클로드 제안)
-2. **Wasm 호환성 문제 해결**: `--web-renderer=html` 사용 (우리 해결책)
-
-제가 두 가지를 모두 반영하여 Canvas의 `deploy-web.yml` 파일을 수정해 드릴게요.
-
-### 1단계: `.yaml` 파일 수정
-
-http://googleusercontent.com/immersive_entry_chip/0
-
-### 2단계: Dart 코드 수정 (필수\!)
-
-`--dart-define`을 사용하려면, 클로드의 설명대로 **Dart 코드에서 환경 변수를 불러오는 방식**을 `dotenv.env`에서 `String.fromEnvironment`로 **반드시** 변경해야 합니다.
-
-`ApiConfig.dart`, `main.dart`, `NaverMapApiService.dart`, `WeatherApiService.dart` 등 `.env`를 사용하는 모든 파일을 수정해야 합니다.
-
-**예시 1: `ApiConfig.dart`**
-
-````dart
-// Before:
-// import 'package:flutter_dotenv/flutter_dotenv.dart';
-// static String get baseUrl {
-// final value = dotenv.env['API_BASE_URL'];
-// ...
-// }
-
-// After:
-class ApiConfig {
- static String get baseUrl {
- // --dart-define으로 주입된 값을 컴파일 타임에 읽어옵니다.
- const value = String.fromEnvironment('API_BASE_URL');
- if (value.isEmpty) {
- throw StateError('Missing API_BASE_URL (was not passed with --dart-define)');
- }
- return value;
- }
- // ...
-}
-
-**예시 2: `main.dart` (카카오 키)**
-```dart
-// Before:
-// await dotenv.load(fileName: '.env');
-// final kakaoNativeAppKey = dotenv.env['KAKAO_NATIVE_APP_KEY'];
-
-// After:
-Future main() async {
- // .env 파일 로드 관련 코드가 더 이상 필요 없습니다.
-
- // --dart-define으로 주입된 값을 읽어옵니다.
- const kakaoNativeAppKey = String.fromEnvironment('KAKAO_NATIVE_APP_KEY');
- if (kakaoNativeAppKey.isEmpty) {
- throw Exception('KAKAO_NATIVE_APP_KEY is not set (was not passed with --dart-define)');
- }
-
- KakaoSdk.init(nativeAppKey: kakaoNativeAppKey);
- runApp(const MyApp());
-}
-
-**예시 3: `NaverMapApiService.dart` (네이버 키)**
-```dart
-// Before:
-// final String? _clientId = dotenv.env['NAVER_CLIENT_ID'];
-
-// After:
-class NaverMapApiService {
- // const String.fromEnvironment로 읽어옵니다.
- final String? _clientId = const String.fromEnvironment('NAVER_CLIENT_ID');
- final String? _clientSecret = const String.fromEnvironment('NAVER_CLIENT_SECRET');
- // ...
-}
-
-이렇게 **1) `.yaml` 파일 수정**과 **2) Dart 코드 수정**을 모두 완료하고 푸시하면, GitHub Secrets가 올바르게 주입되어 하얀 화면 문제가 해결될 것입니다!
-````
diff --git a/.github/workflows/main1313131313.yml b/.github/workflows/main1313131313.yml
deleted file mode 100644
index d5200a8..0000000
--- a/.github/workflows/main1313131313.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-# .github/workflows/deploy-flutter-web.yml
-name: Deploy Flutter Web to GitHub Pages
-
-on:
- push:
- branches: [ "develop" ]
- workflow_dispatch:
-
-jobs:
- build_and_deploy:
- runs-on: ubuntu-latest
- permissions:
- contents: write
-
- steps:
- - name: 📦 Checkout Repository
- uses: actions/checkout@v4
-
- - name: ☕ Set up Flutter SDK
- uses: subosito/flutter-action@v2
- with:
- channel: stable
- flutter-version: '3.22.3'
-
- - name: 🌐 Enable Web
- run: flutter config --enable-web
-
- - name: 🧩 Get Dependencies
- run: flutter pub get
-
- - name: 🏗️ Build Web App
- run: |
- flutter build web \
- --release \
- --web-renderer html \
- --base-href "/FE/" \
- --dart-define API_BASE_URL=${{ secrets.API_BASE_URL }} \
- --dart-define KAKAO_NATIVE_APP_KEY=${{ secrets.KAKAO_NATIVE_APP_KEY }} \
- --dart-define NAVER_CLIENT_ID=${{ secrets.NAVER_CLIENT_ID }} \
- --dart-define NAVER_CLIENT_SECRET=${{ secrets.NAVER_CLIENT_SECRET }} \
- --dart-define OPENWEATHERMAP_API_KEY=${{ secrets.OPENWEATHERMAP_API_KEY }}
-
- - name: 🔐 Inject Kakao JS Key into index.html
- run: |
- if [ -z "${{ secrets.KAKAO_JAVASCRIPT_KEY }}" ]; then
- echo "KAKAO_JAVASCRIPT_KEY is not set"; exit 1;
- fi
- sed -i "s/__KAKAO_JS_KEY__/${{ secrets.KAKAO_JAVASCRIPT_KEY }}/g" build/web/index.html
-
- - name: 🚀 Deploy to GitHub Pages
- uses: peaceiris/actions-gh-pages@v3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./build/web
diff --git a/.github/workflows/main133133.yml b/.github/workflows/main133133.yml
deleted file mode 100644
index f34df76..0000000
--- a/.github/workflows/main133133.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-# .github/workflows/deploy-flutter-web.yml
-
-name: Deploy Flutter Web to GitHub Pages
-
-on:
- push:
- branches: [ "feature/test-1" ] # feature/test-1 브랜치에 푸시될 때만 실행
-
-jobs:
- build_and_deploy:
- runs-on: ubuntu-latest
-
- permissions:
- contents: write
-
- steps:
- # 1. 레포지토리 코드 가져오기
- - name: 📦 Checkout Repository
- uses: actions/checkout@v4
-
- # 2. Flutter 환경 설정 (버전 고정)
- - name: ☕ Set up Flutter SDK
- uses: subosito/flutter-action@v2
- with:
- channel: 'stable'
- flutter-version: '3.22.3' # 또는 최신 안정 버전
-
- # 3. .env 파일 생성 (로컬 개발 대응, 실제 빌드엔 중요도 낮음)
- - name: ✍️ Create .env file (Optional)
- run: |
- echo "API_BASE_URL=${{ secrets.API_BASE_URL }}" >> .env
- echo "KAKAO_NATIVE_APP_KEY=${{ secrets.KAKAO_NATIVE_APP_KEY }}" >> .env
- echo "NAVER_CLIENT_ID=${{ secrets.NAVER_CLIENT_ID }}" >> .env
- echo "NAVER_CLIENT_SECRET=${{ secrets.NAVER_CLIENT_SECRET }}" >> .env
- echo "OPENWEATHERMAP_API_KEY=${{ secrets.OPENWEATHERMAP_API_KEY }}" >> .env
- cat .env
-
- # 4. Flutter 웹 활성화
- - name: 🌐 Enable Web
- run: flutter config --enable-web
-
- # 5. 프로젝트 의존성 설치
- - name: 🧩 Get Dependencies
- run: flutter pub get
-
- # 6. Flutter 웹 앱 빌드 (⭐ dart-define 옵션 필수!)
- - name: 🏗️ Build Web App
- run: |
- flutter build web --release --web-renderer html --base-href "/FE/" \
- --dart-define API_BASE_URL=${{ secrets.API_BASE_URL }} \
- --dart-define KAKAO_NATIVE_APP_KEY=${{ secrets.KAKAO_NATIVE_APP_KEY }} \
- --dart-define NAVER_CLIENT_ID=${{ secrets.NAVER_CLIENT_ID }} \
- --dart-define NAVER_CLIENT_SECRET=${{ secrets.NAVER_CLIENT_SECRET }} \
- --dart-define OPENWEATHERMAP_API_KEY=${{ secrets.OPENWEATHERMAP_API_KEY }}
-
- # 7. 빌드 결과물을 GitHub Pages에 배포
- - name: 🚀 Deploy to GitHub Pages
- uses: peaceiris/actions-gh-pages@v3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: ./build/web
diff --git a/.metadata b/.metadata
index 3cf9e8c..5722f85 100644
--- a/.metadata
+++ b/.metadata
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: "a402d9a4376add5bc2d6b1e33e53edaae58c07f8"
+ revision: "9f455d2486bcb28cad87b062475f42edc959f636"
channel: "stable"
project_type: app
@@ -13,11 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- - platform: windows
- create_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
- base_revision: a402d9a4376add5bc2d6b1e33e53edaae58c07f8
+ create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
+ base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
+ - platform: web
+ create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
+ base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
# User provided section
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 90cdb35..8e4abd5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,7 +9,7 @@
defaultHeaders = {
- 'Content-Type': 'application/json',
- };
-
- // -------- Auth/User --------
+ // Auth
static const String kakaoLogin = '/auth/kakao';
+ static const String userInfo = '/auth/user-info';
+
+ // Tips
+ static const String tips = '/tip';
+ static String tipDetail(int id) => '/tip/$id';
+
+ // User
static const String user = '/user';
- static const String userInfo = '/user'; // alias (기존 사용처 호환)
static const String userProfile = '/user/profile';
static const String userBookmarks = '/user/bookmarks';
static const String userChallenges = '/user/challenges';
- // -------- Tips --------
- static const String tips = '/tip';
- static String tipDetail(int tipId) => '/tip/$tipId';
-
- // -------- Terms --------
+ // Terms
static const String terms = '/term';
- // -------- Bookmark --------
+ // Bookmark
static const String bookmark = '/bookmark';
- // -------- Bills --------
+ // Bills
+ static const String billsOcr = '/api/bills/ocr';
static const String billsSummary = '/api/bills/summary';
static const String billsReportDetail = '/api/bills/report/detail';
-}
+ // Headers
+ static const Map defaultHeaders = {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ };
+}
diff --git a/lib/features/home/data/naver_map_api_service.dart b/lib/features/home/data/naver_map_api_service.dart
index 1ad579f..f1c43ae 100644
--- a/lib/features/home/data/naver_map_api_service.dart
+++ b/lib/features/home/data/naver_map_api_service.dart
@@ -3,20 +3,23 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
-// 주소 검색 결과를 담을 간단한 모델
+// ... (Address 모델은 동일)
class Address {
final String roadAddress;
final String jibunAddress;
final double lat;
final double lon;
- Address({required this.roadAddress, required this.jibunAddress, required this.lat, required this.lon});
+ Address(
+ {required this.roadAddress,
+ required this.jibunAddress,
+ required this.lat,
+ required this.lon});
factory Address.fromJson(Map json) {
return Address(
roadAddress: json['roadAddress'] ?? '',
jibunAddress: json['jibunAddress'] ?? '',
- // 네이버 API는 x가 경도(lon), y가 위도(lat)입니다.
lat: double.tryParse(json['y'] ?? '0.0') ?? 0.0,
lon: double.tryParse(json['x'] ?? '0.0') ?? 0.0,
);
@@ -24,37 +27,44 @@ class Address {
}
class NaverMapApiService {
- String? _id() {
- String v = const String.fromEnvironment('NAVER_CLIENT_ID');
- if (v.isEmpty) { try { v = dotenv.env['NAVER_CLIENT_ID'] ?? ''; } catch (_) {} }
- return v.isEmpty ? null : v;
- }
- String? _secret() {
- String v = const String.fromEnvironment('NAVER_CLIENT_SECRET');
- if (v.isEmpty) { try { v = dotenv.env['NAVER_CLIENT_SECRET'] ?? ''; } catch (_) {} }
- return v.isEmpty ? null : v;
+ final String _clientId;
+ final String _clientSecret;
+
+ NaverMapApiService()
+ : _clientId = _getEnv('NAVER_CLIENT_ID'),
+ _clientSecret = _getEnv('NAVER_CLIENT_SECRET');
+
+ static String _getEnv(String key) {
+ String value = String.fromEnvironment(key);
+ if (value.isEmpty) {
+ value = dotenv.env[key] ?? '';
+ }
+ return value;
}
// Geocoding: 주소 검색
Future> searchAddress(String query) async {
- final id = _id();
- final secret = _secret();
- if (id == null || secret == null) {
- throw Exception("API keys are not configured in .env file");
+ if (_clientId.isEmpty || _clientSecret.isEmpty) {
+ throw Exception(
+ "API keys are not configured in .env or via --dart-define");
}
final response = await http.get(
- Uri.parse('https://maps.apigw.ntruss.com/map-geocode/v2/geocode?query=$query'),
+ Uri.parse(
+ 'https://maps.apigw.ntruss.com/map-geocode/v2/geocode?query=$query'),
headers: {
- 'X-NCP-APIGW-API-KEY-ID': id,
- 'X-NCP-APIGW-API-KEY': secret,
+ // ⭐ 오류 수정: id, secret -> _clientId, _clientSecret
+ 'X-NCP-APIGW-API-KEY-ID': _clientId,
+ 'X-NCP-APIGW-API-KEY': _clientSecret,
},
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['status'] == 'OK' && data['addresses'] != null) {
- return (data['addresses'] as List).map((addr) => Address.fromJson(addr)).toList();
+ return (data['addresses'] as List)
+ .map((addr) => Address.fromJson(addr))
+ .toList();
}
return [];
} else {
@@ -64,43 +74,55 @@ class NaverMapApiService {
// Reverse Geocoding: 좌표 -> 주소 변환
Future coordToAddress(double lat, double lon) async {
- final id = _id();
- final secret = _secret();
- if (id == null || secret == null) {
- throw Exception("API keys are not configured in .env file");
+ if (_clientId.isEmpty || _clientSecret.isEmpty) {
+ throw Exception(
+ "API keys are not configured in .env or via --dart-define");
}
final response = await http.get(
- Uri.parse('https://maps.apigw.ntruss.com/map-reversegeocode/v2/gc?coords=$lon,$lat&output=json'),
+ Uri.parse(
+ 'https://maps.apigw.ntruss.com/map-reversegeocode/v2/gc?coords=$lon,$lat&output=json'),
headers: {
- 'X-NCP-APIGW-API-KEY-ID': id,
- 'X-NCP-APIGW-API-KEY': secret,
+ // ⭐ 오류 수정: id, secret -> _clientId, _clientSecret
+ 'X-NCP-APIGW-API-KEY-ID': _clientId,
+ 'X-NCP-APIGW-API-KEY': _clientSecret,
},
);
+ // 디버깅 로그 (기존과 동일)
+ if (kDebugMode) {
+ print('--- 네이버 Reverse Geocoding 응답 ---');
+ print('Status Code: ${response.statusCode}');
+ print('Response Body: ${utf8.decode(response.bodyBytes)}');
+ print('----------------------------------');
+ }
+
if (response.statusCode == 200) {
- final data = json.decode(response.body);
+ final data = json.decode(utf8.decode(response.bodyBytes));
if (data['status']['code'] == 0 && data['results'].isNotEmpty) {
final region = data['results'][0]['region'];
- return '${region['area1']['name']} ${region['area2']['name']} ${region['area3']['name']}';
+ final area1 = region['area1']?['name'] ?? '';
+ final area2 = region['area2']?['name'] ?? '';
+ final area3 = region['area3']?['name'] ?? '';
+
+ if (area1.isNotEmpty || area2.isNotEmpty || area3.isNotEmpty) {
+ return '$area1 $area2 $area3'.trim();
+ } else {
+ return '상세 주소 정보를 찾을 수 없습니다.';
+ }
}
- return '주소를 찾을 수 없습니다.';
+ return '주소를 찾을 수 없습니다. (응답 코드: ${data['status']?['code']})';
} else {
- // API 호출 실패 시, 서버로부터 받은 실제 응답 내용을 출력합니다.
- // 이렇게 하면 인증 실패인지, 쿼리 문제인지 정확히 알 수 있습니다.
final errorBody = json.decode(response.body);
final errorMessage = errorBody['error']?['message'] ?? response.body;
-
- // 디버그 콘솔에 상세 오류 출력
if (kDebugMode) {
print('--- 네이버 API 오류 ---');
print('Status Code: ${response.statusCode}');
print('Error Body: ${response.body}');
print('---------------------');
}
-
- // 사용자에게 보여줄 예외 메시지
throw Exception('주소 변환 실패: $errorMessage');
}
}
-}
\ No newline at end of file
+}
+
diff --git a/lib/features/home/data/weather_api_service.dart b/lib/features/home/data/weather_api_service.dart
index 6a04813..1f5b0af 100644
--- a/lib/features/home/data/weather_api_service.dart
+++ b/lib/features/home/data/weather_api_service.dart
@@ -1,15 +1,16 @@
import 'dart:convert';
-import 'package:flutter/foundation.dart'; // kDebugMode를 위해 import
+import 'package:flutter/foundation.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
-// API 응답 데이터를 담을 모델 클래스
+// ... (WeatherData 모델은 동일)
class WeatherData {
final double temp;
final String description;
final String icon;
- WeatherData({required this.temp, required this.description, required this.icon});
+ WeatherData(
+ {required this.temp, required this.description, required this.icon});
factory WeatherData.fromJson(Map json) {
return WeatherData(
@@ -21,24 +22,45 @@ class WeatherData {
}
class WeatherApiService {
- String? _resolveKey() {
- String v = const String.fromEnvironment('OPENWEATHERMAP_API_KEY');
- if (v.isEmpty) {
- try { v = dotenv.env['OPENWEATHERMAP_API_KEY'] ?? ''; } catch (_) {}
+ // ⭐ 변경점: final 변수로 선언
+ final String _apiKey;
+ static const String _baseUrl =
+ 'https://api.openweathermap.org/data/2.5/weather';
+
+ // ⭐ 변경점: 생성자에서 하이브리드 로직 구현
+ WeatherApiService()
+ : _apiKey =
+ const String.fromEnvironment('OPENWEATHERMAP_API_KEY').isEmpty
+ ? dotenv.env['OPENWEATHERMAP_API_KEY'] ?? ''
+ : const String.fromEnvironment('OPENWEATHERMAP_API_KEY');
+
+ Future getWeather(double lat, double lon) async {
+ // ⭐ 변경점: 키 유효성 검사
+ if (_apiKey.isEmpty) {
+ throw Exception(
+ "OpenWeatherMap API key is not configured in .env or via --dart-define");
+ }
+
+ final url =
+ '$_baseUrl?lat=$lat&lon=$lon&appid=$_apiKey&units=metric&lang=kr';
+
+ final response = await http.get(Uri.parse(url));
+
+ if (kDebugMode) {
+ print('--- OpenWeatherMap API 응답 ---');
+ print('Request URL: $url');
+ print('Status Code: ${response.statusCode}');
+ print('Response Body: ${utf8.decode(response.bodyBytes)}');
+ print('-----------------------------');
}
- return v.isEmpty ? null : v;
- }
- Future getWeather(double lat, double lon) async {
- final apiKey = _resolveKey();
- if (apiKey == null) return null;
- final url = Uri.parse('https://api.openweathermap.org/data/2.5/weather?lat=$lat&lon=$lon&appid=$apiKey&units=metric&lang=kr');
- final res = await http.get(url);
- if (res.statusCode == 200) {
- final map = jsonDecode(res.body) as Map;
- return WeatherData.fromJson(map);
+ if (response.statusCode == 200) {
+ return WeatherData.fromJson(
+ json.decode(utf8.decode(response.bodyBytes)));
+ } else {
+ throw Exception(
+ 'Failed to load weather data. Status: ${response.statusCode}, Body: ${utf8.decode(response.bodyBytes)}');
}
- return null;
}
}
diff --git a/lib/features/landing/presentation/login_screen.dart b/lib/features/landing/presentation/login_screen.dart
index 14cffff..5cf1fc5 100644
--- a/lib/features/landing/presentation/login_screen.dart
+++ b/lib/features/landing/presentation/login_screen.dart
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
-import 'package:billow/main.dart';
+import 'package:billow/main.dart' hide MainNavigationPage;
import '../../../services/auth_service.dart';
import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart';
diff --git a/lib/main.dart b/lib/main.dart
index 01e2e1c..f4b199d 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,65 +1,157 @@
-import 'package:flutter/material.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter_dotenv/flutter_dotenv.dart';
-import 'package:kakao_flutter_sdk_common/kakao_flutter_sdk_common.dart';
import 'package:billow/features/landing/presentation/splash_screen.dart';
-import 'package:billow/features/landing/presentation/kakao_web_callback_screen.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart'; // 1. dotenv import
+import 'package:flutter/foundation.dart'; // 2. kIsWeb import
+import 'features/home/presentation/home_screen.dart';
+import 'features/challenge/presentation/challenge_screen.dart';
+import 'features/tips/presentation/tips_screen.dart';
+import 'features/profile/presentation/profile_screen.dart';
+import 'features/auth/presentation/auth_test_screen.dart';
+import 'package:kakao_flutter_sdk_user/kakao_flutter_sdk_user.dart';
+import 'services/token_storage.dart';
Future main() async {
+ // 3. runApp 전에 Flutter 엔진 초기화 보장
WidgetsFlutterBinding.ensureInitialized();
- String kakaoKey = const String.fromEnvironment('KAKAO_NATIVE_APP_KEY');
- if (kakaoKey.isEmpty) {
+ // 4. 로컬 디버깅을 위해 .env 파일 로드
+ if (!kIsWeb) {
try {
- await dotenv.load(fileName: '.env');
- kakaoKey = dotenv.env['KAKAO_NATIVE_APP_KEY'] ?? '';
- } catch (_) {}
+ await dotenv.load(fileName: ".env");
+ } catch (e) {
+ print('⚠️ .env 파일을 로드할 수 없습니다: $e');
+ }
}
- if (kakaoKey.isEmpty) {
- throw Exception('KAKAO_NATIVE_APP_KEY is not set (use --dart-define or .env)');
+
+ // 5. 플랫폼(웹/모바일)에 따라 다른 카카오 키로 SDK 초기화
+ if (kIsWeb) {
+ // --- 웹 환경일 경우 ---
+ const jsAppKey = String.fromEnvironment('KAKAO_JAVASCRIPT_APP_KEY');
+ if (jsAppKey.isEmpty) {
+ throw Exception('KAKAO_JAVASCRIPT_APP_KEY is not set via --dart-define');
+ }
+ KakaoSdk.init(javaScriptAppKey: jsAppKey);
+ } else {
+ // --- 모바일 (Android/iOS) 환경일 경우 ---
+ String nativeAppKey =
+ const String.fromEnvironment('KAKAO_NATIVE_APP_KEY');
+ if (nativeAppKey.isEmpty) {
+ nativeAppKey = dotenv.env['KAKAO_NATIVE_APP_KEY'] ?? '';
+ }
+
+ if (nativeAppKey.isEmpty) {
+ throw Exception(
+ 'KAKAO_NATIVE_APP_KEY is not set in .env or via --dart-define');
+ }
+ KakaoSdk.init(nativeAppKey: nativeAppKey);
}
- KakaoSdk.init(nativeAppKey: kakaoKey);
- runApp(const BillowApp());
+ // 6. MyApp 실행
+ runApp(const MyApp());
}
-class BillowApp extends StatelessWidget {
- const BillowApp({super.key});
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
@override
Widget build(BuildContext context) {
- final theme = ThemeData(
- useMaterial3: true,
- colorScheme: const ColorScheme.light(
- primary: Colors.teal,
- secondary: Colors.teal,
- surface: Colors.white,
- background: Colors.white,
- ),
- scaffoldBackgroundColor: Colors.white,
- appBarTheme: const AppBarTheme(
- backgroundColor: Colors.white,
- surfaceTintColor: Colors.transparent,
- elevation: 0.5,
- ),
- bottomNavigationBarTheme: const BottomNavigationBarThemeData(
- backgroundColor: Colors.white,
- selectedItemColor: Colors.teal,
- unselectedItemColor: Colors.grey,
- type: BottomNavigationBarType.fixed,
- ),
- );
-
return MaterialApp(
+ title: 'Billow',
debugShowCheckedModeBanner: false,
- theme: theme,
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
+ scaffoldBackgroundColor: Colors.grey[50],
+ appBarTheme: const AppBarTheme(
+ backgroundColor: Colors.white,
+ surfaceTintColor: Colors.transparent,
+ elevation: 0.5,
+ ),
+ useMaterial3: true,
+ ),
home: const SplashScreen(),
- onGenerateRoute: (settings) {
- if (settings.name == '/auth/callback') {
- return MaterialPageRoute(builder: (_) => const KakaoWebCallbackScreen());
- }
- return null;
- },
);
}
}
+
+// MainNavigationPage는 SplashScreen이나 로그인 로직에서 호출되므로
+// main.dart에 같이 두어도 괜찮습니다.
+class MainNavigationPage extends StatefulWidget {
+ const MainNavigationPage({super.key});
+
+ @override
+ State createState() => _MainNavigationPageState();
+}
+
+class _MainNavigationPageState extends State {
+ int _selectedIndex = 0;
+
+ final List _pages = [
+ const HomeScreen(),
+ const ChallengeScreen(),
+ const TipsScreen(),
+ const ProfileScreen(),
+ ];
+
+ void _onItemTapped(int index) {
+ setState(() {
+ _selectedIndex = index;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ toolbarHeight: 64,
+ titleSpacing: 40,
+ title: Text(
+ 'Billow',
+ style: TextStyle(
+ color: Theme.of(context).primaryColor,
+ fontWeight: FontWeight.bold,
+ fontSize: 22,
+ ),
+ ),
+ actions: [
+ IconButton(
+ icon: const Icon(Icons.vpn_key, color: Colors.black54),
+ tooltip: 'Auth Test',
+ onPressed: () {
+ Navigator.of(context).push(
+ MaterialPageRoute(builder: (_) => const AuthTestScreen()),
+ );
+ },
+ ),
+ // Swagger 토큰 출력용 버튼 (이전 코드에서 복원)
+ IconButton(
+ icon: const Icon(Icons.key, size: 24, color: Colors.orange),
+ tooltip: 'Show Bearer Token (Swagger용)',
+ onPressed: () async {
+ await TokenStorage.printAccessTokenForSwagger();
+ },
+ ),
+ IconButton(
+ icon: const Icon(Icons.notifications_none, color: Colors.black54),
+ tooltip: 'Notifications',
+ onPressed: () {},
+ ),
+ const SizedBox(width: 24),
+ ],
+ ),
+ body: _pages[_selectedIndex],
+ bottomNavigationBar: NavigationBar(
+ selectedIndex: _selectedIndex,
+ onDestinationSelected: _onItemTapped,
+ destinations: const [
+ NavigationDestination(icon: Icon(Icons.home_outlined), label: '홈'),
+ NavigationDestination(icon: Icon(Icons.emoji_events_outlined), label: '챌린지'),
+ NavigationDestination(icon: Icon(Icons.lightbulb_outline), label: '꿀팁'),
+ NavigationDestination(icon: Icon(Icons.person_outline), label: '마이'),
+ ],
+ ),
+ );
+ }
+}
+
+// 7. 머지 충돌로 잘못 들어온 하단 코드는 모두 삭제합니다.
+
diff --git a/pubspec.lock b/pubspec.lock
index f564eea..1164ff0 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.4.0"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.7"
args:
dependency: transitive
description:
@@ -113,6 +121,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.4"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.2"
clock:
dependency: transitive
description:
@@ -350,6 +366,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.5"
+ flutter_launcher_icons:
+ dependency: "direct dev"
+ description:
+ name: flutter_launcher_icons
+ sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.14.4"
flutter_lints:
dependency: "direct dev"
description:
@@ -536,6 +560,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.5.4"
image_picker:
dependency: "direct main"
description:
@@ -864,6 +896,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.2"
+ posix:
+ dependency: transitive
+ description:
+ name: posix
+ sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.3"
pub_semver:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index d9e6e4a..182f6cb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -65,6 +65,13 @@ dev_dependencies:
flutter_lints: ^3.0.0
build_runner: ^2.4.6
json_serializable: ^6.7.1
+ flutter_launcher_icons: "^0.14.4"
+
+flutter_launcher_icons:
+ android: "launcher_icon" # Android 아이콘 활성화 및 이름 설정 (res/mipmap-xxxhdi/launcher_icon.png)
+ ios: true # iOS 아이콘 활성화
+ image_path: "assets/images/billow_logo.png" # 준비한 아이콘 이미지 경로 (예시)
+ min_sdk_android: 21
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
diff --git a/web/favicon.png b/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
Binary files /dev/null and b/web/favicon.png differ
diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png
new file mode 100644
index 0000000..b749bfe
Binary files /dev/null and b/web/icons/Icon-192.png differ
diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png
new file mode 100644
index 0000000..88cfd48
Binary files /dev/null and b/web/icons/Icon-512.png differ
diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..eb9b4d7
Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ
diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..d69c566
Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ
diff --git a/web/manifest.json b/web/manifest.json
new file mode 100644
index 0000000..0c36f6e
--- /dev/null
+++ b/web/manifest.json
@@ -0,0 +1,35 @@
+{
+ "name": "billow",
+ "short_name": "billow",
+ "start_url": ".",
+ "display": "standalone",
+ "background_color": "#0175C2",
+ "theme_color": "#0175C2",
+ "description": "A new Flutter project.",
+ "orientation": "portrait-primary",
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "icons/Icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-maskable-192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "maskable"
+ },
+ {
+ "src": "icons/Icon-maskable-512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
+ }
+ ]
+}