diff --git a/flutter/.env.example b/flutter/.env.example index e825d8e..5e26559 100644 --- a/flutter/.env.example +++ b/flutter/.env.example @@ -7,8 +7,9 @@ # 빌드 타임 분리가 필요하면 --dart-define 또는 .env.{flavor} 도입 고려. # ========================================== -# 백엔드 API 베이스 URL (백엔드 글로벌 prefix /api 포함) -# 안드로이드 에뮬레이터에서 호스트 접근 시: http://10.0.2.2:3000/api +# 백엔드 API 베이스 URL (백엔드 글로벌 prefix /api 포함) — 개발용 +# 안드로이드 에뮬레이터에서 호스트 접근 시: http://10.0.2.2:3000/api (또는 adb reverse tcp:3000 tcp:3000 후 localhost) +# 출시 빌드는 이 값 대신 --dart-define=API_URL=https://<도메인>/api 로 넘긴다 (https·공개 주소가 아니면 앱이 시작하지 않는다) API_URL=http://localhost:3000/api # 기록을 어디에 둘지 — api: 서버(기본, 로그인한 회원의 기록) / local: 이 기기 (서버 없이 화면만 볼 때) diff --git a/flutter/android/app/build.gradle.kts b/flutter/android/app/build.gradle.kts index 6b66a87..c7f56eb 100644 --- a/flutter/android/app/build.gradle.kts +++ b/flutter/android/app/build.gradle.kts @@ -1,3 +1,6 @@ +import java.io.FileInputStream +import java.util.Properties + plugins { id("com.android.application") id("kotlin-android") @@ -5,8 +8,16 @@ plugins { id("dev.flutter.flutter-gradle-plugin") } +// 출시 서명 키 — android/key.properties (커밋하지 않는다, 예시는 key.properties.example). +// 파일이 없으면 디버그 키로 서명한다 — 스토어에는 올릴 수 없고, 기기에서 릴리스 빌드를 확인할 때만 쓴다. +val keystorePropertiesFile = rootProject.file("key.properties") +val keystoreProperties = Properties().apply { + if (keystorePropertiesFile.exists()) FileInputStream(keystorePropertiesFile).use { load(it) } +} +val hasReleaseKey = keystorePropertiesFile.exists() + android { - namespace = "com.example.tma_app" + namespace = "com.ttipo.rup" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion @@ -20,21 +31,34 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.tma_app" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. + // 스토어에 올린 뒤에는 바꿀 수 없다 (iOS 번들 ID 도 같은 값) + applicationId = "com.ttipo.rup" + // 버전은 pubspec.yaml 의 version(이름+빌드 번호)을 따른다 minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName } + signingConfigs { + if (hasReleaseKey) { + create("release") { + keyAlias = keystoreProperties.getProperty("keyAlias") + keyPassword = keystoreProperties.getProperty("keyPassword") + storeFile = file(keystoreProperties.getProperty("storeFile")) + storePassword = keystoreProperties.getProperty("storePassword") + } + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") + signingConfig = if (hasReleaseKey) { + signingConfigs.getByName("release") + } else { + logger.warn("android/key.properties 가 없어 릴리스 빌드를 디버그 키로 서명합니다 — 스토어에 올릴 수 없습니다.") + signingConfigs.getByName("debug") + } } } } diff --git a/flutter/android/app/src/main/AndroidManifest.xml b/flutter/android/app/src/main/AndroidManifest.xml index 779e3af..7ea99dd 100644 --- a/flutter/android/app/src/main/AndroidManifest.xml +++ b/flutter/android/app/src/main/AndroidManifest.xml @@ -2,9 +2,9 @@ - + - - - - + diff --git a/flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e80f8c7 Binary files /dev/null and b/flutter/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e94069e Binary files /dev/null and b/flutter/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8a58e98 Binary files /dev/null and b/flutter/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/flutter/android/app/src/main/res/drawable/launch_background.xml b/flutter/android/app/src/main/res/drawable/launch_background.xml index 304732f..ec0caf5 100644 --- a/flutter/android/app/src/main/res/drawable/launch_background.xml +++ b/flutter/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,5 @@ - + - - - - + diff --git a/flutter/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/flutter/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml new file mode 100644 index 0000000..c79c58a --- /dev/null +++ b/flutter/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4..0000000 Binary files a/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 0000000..8fb1742 Binary files /dev/null and b/flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b7..0000000 Binary files a/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 0000000..efc91d3 Binary files /dev/null and b/flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d4391..0000000 Binary files a/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 0000000..5692743 Binary files /dev/null and b/flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d..0000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 0000000..9e00070 Binary files /dev/null and b/flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372e..0000000 Binary files a/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 0000000..64c3b4a Binary files /dev/null and b/flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/flutter/android/app/src/main/res/values-night-v31/styles.xml b/flutter/android/app/src/main/res/values-night-v31/styles.xml new file mode 100644 index 0000000..7b72f4f --- /dev/null +++ b/flutter/android/app/src/main/res/values-night-v31/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/flutter/android/app/src/main/res/values-v31/styles.xml b/flutter/android/app/src/main/res/values-v31/styles.xml new file mode 100644 index 0000000..7b72f4f --- /dev/null +++ b/flutter/android/app/src/main/res/values-v31/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/flutter/android/app/src/main/res/values/colors.xml b/flutter/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..76c67bf --- /dev/null +++ b/flutter/android/app/src/main/res/values/colors.xml @@ -0,0 +1,7 @@ + + + + #FF7500 + + #FFDE84 + diff --git a/flutter/android/key.properties.example b/flutter/android/key.properties.example new file mode 100644 index 0000000..1900427 --- /dev/null +++ b/flutter/android/key.properties.example @@ -0,0 +1,10 @@ +# 출시 서명 키 설정 — 이 파일을 key.properties 로 복사해 값을 채운다 (key.properties 와 키 파일은 커밋하지 않는다). +# +# 키 만들기 (한 번만 · 잃어버리면 같은 앱으로 업데이트를 올릴 수 없으니 안전한 곳에 백업): +# keytool -genkey -v -keystore ~/rup-upload.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload +# +# storeFile 은 android/app 기준 경로 또는 절대 경로 +storeFile=/path/to/rup-upload.jks +storePassword=change-me-store-password +keyAlias=upload +keyPassword=change-me-key-password diff --git a/flutter/branding/app_icon.png b/flutter/branding/app_icon.png new file mode 100644 index 0000000..f841ff7 Binary files /dev/null and b/flutter/branding/app_icon.png differ diff --git a/flutter/branding/app_icon_foreground.png b/flutter/branding/app_icon_foreground.png new file mode 100644 index 0000000..910ae2f Binary files /dev/null and b/flutter/branding/app_icon_foreground.png differ diff --git a/flutter/ios/Runner.xcodeproj/project.pbxproj b/flutter/ios/Runner.xcodeproj/project.pbxproj index 8fe3ebe..4c0355f 100644 --- a/flutter/ios/Runner.xcodeproj/project.pbxproj +++ b/flutter/ios/Runner.xcodeproj/project.pbxproj @@ -372,7 +372,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.tmaApp; + PRODUCT_BUNDLE_IDENTIFIER = com.ttipo.rup; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -388,7 +388,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.tmaApp.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ttipo.rup.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -405,7 +405,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.tmaApp.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ttipo.rup.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -420,7 +420,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.tmaApp.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.ttipo.rup.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -431,7 +431,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -488,7 +488,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -551,7 +551,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.tmaApp; + PRODUCT_BUNDLE_IDENTIFIER = com.ttipo.rup; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -573,7 +573,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.tmaApp; + PRODUCT_BUNDLE_IDENTIFIER = com.ttipo.rup; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4..c85c428 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 7353c41..4c8c9df 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452..0fa575d 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d93..f8ece49 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cd7b00..965749f 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe73094..56ba0d7 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773c..be88802 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452..0fa575d 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463..fec207a 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec3034..8345665 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000..028b6dc Binary files /dev/null and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 0000000..6425467 Binary files /dev/null and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..c40e977 Binary files /dev/null and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..6774b33 Binary files /dev/null and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec3034..8345665 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea..1dc12e6 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..708cb75 Binary files /dev/null and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..3771afd Binary files /dev/null and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32a..a379741 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba..6771dab 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf1..0ed1ebe 100644 Binary files a/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard b/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard index f2e259c..0637349 100644 --- a/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -19,7 +19,7 @@ - + diff --git a/flutter/ios/Runner/Info.plist b/flutter/ios/Runner/Info.plist index 9d62cc8..879ed5e 100644 --- a/flutter/ios/Runner/Info.plist +++ b/flutter/ios/Runner/Info.plist @@ -7,7 +7,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Tma App + rup CFBundleURLTypes @@ -34,7 +34,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - tma_app + rup CFBundlePackageType APPL CFBundleShortVersionString diff --git a/flutter/lib/core/config/app_config.dart b/flutter/lib/core/config/app_config.dart index 451c81c..a7e716a 100644 --- a/flutter/lib/core/config/app_config.dart +++ b/flutter/lib/core/config/app_config.dart @@ -15,8 +15,28 @@ DataMode readDataMode() { return dotenv.maybeGet('DATA_MODE') == DataMode.local.name ? DataMode.local : DataMode.api; } -/// `.env` 의 API_URL — 안드로이드 에뮬레이터에서 호스트에 붙을 때는 http://10.0.2.2:3000/api -String readApiBaseUrl() => (dotenv.isInitialized ? dotenv.maybeGet('API_URL') : null) ?? kDefaultApiUrl; +/// 빌드할 때 넘긴 API 주소 — `flutter build … --dart-define=API_URL=https://…/api` (출시 빌드는 이것을 쓴다) +const String _kDefinedApiUrl = String.fromEnvironment('API_URL'); + +/// 서버 주소 — 빌드 때 넘긴 값 → `.env` 의 API_URL → 개발 서버 순. +/// 안드로이드 에뮬레이터에서 호스트에 붙을 때는 http://10.0.2.2:3000/api (또는 adb reverse 로 localhost) +String readApiBaseUrl() { + if (_kDefinedApiUrl.isNotEmpty) return _kDefinedApiUrl; + return (dotenv.isInitialized ? dotenv.maybeGet('API_URL') : null) ?? kDefaultApiUrl; +} + +/// 내 기기에서만 닿는 주소 — 출시 빌드가 여기를 가리키면 사용자 기기에서 서버에 닿지 않는다 +const Set _kLocalHosts = {'localhost', '127.0.0.1', '10.0.2.2', '0.0.0.0'}; + +/// 출시 빌드의 서버 주소에 문제가 있으면 그 이유, 괜찮으면 null. +/// 출시 빌드는 https 만 쓸 수 있고(Android·iOS 모두 평문 http 를 막는다) 개발용 주소를 가리키면 안 된다 +String? releaseApiUrlProblem(String url) { + final uri = Uri.tryParse(url); + if (uri == null || uri.host.isEmpty) return '서버 주소(API_URL)를 읽을 수 없습니다: $url'; + if (uri.scheme != 'https') return '출시 빌드의 서버 주소는 https 여야 합니다: $url'; + if (_kLocalHosts.contains(uri.host)) return '출시 빌드가 개발용 서버 주소를 가리킵니다: $url'; + return null; +} final dataModeProvider = Provider((ref) => readDataMode()); diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index e487258..f57464b 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -20,6 +21,12 @@ Future main() async { // .env 파일이 없는 경우 무시 (기본값 사용) } + // 출시 빌드가 개발 서버·평문 http 를 가리키면 바로 멈춘다 — 사용자 기기에서 조용히 모든 요청이 실패하는 것보다 낫다 + if (kReleaseMode && readDataMode() == DataMode.api) { + final problem = releaseApiUrlProblem(readApiBaseUrl()); + if (problem != null) throw StateError(problem); + } + // 시안은 세로 화면 전용이다 await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 2a33d1f..acceca8 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.0.1" + archive: + dependency: transitive + description: + name: archive + sha256: "58c8d17d8d589c4259f773522815115de873c397b977a29993588487d3f9acb4" + url: "https://pub.dev" + source: hosted + version: "4.5.0" args: dependency: transitive description: @@ -49,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" cli_config: dependency: transitive description: @@ -57,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -222,6 +246,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.1" + 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: @@ -376,6 +408,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "89ed75b2f8231fe676808c5564f9590d728024ded7ac782a7ed8474f1c95b72a" + url: "https://pub.dev" + source: hosted + version: "4.12.0" image_picker: dependency: "direct main" description: @@ -464,6 +504,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" leak_tracker: dependency: transitive description: @@ -672,6 +720,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" pub_semver: dependency: transitive description: diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index a5d1ba7..0023675 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -58,6 +58,7 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^6.0.0 + flutter_launcher_icons: ^0.14.4 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -117,3 +118,15 @@ flutter: - family: UhBeeKeongKeong fonts: - asset: assets/fonts/UhBeeKeongKeong.ttf + +# 앱 아이콘 — 원본은 branding/ (브랜드 주황 + 흰 워드마크, 1024px). +# 원본을 바꾸면 `dart run flutter_launcher_icons` 로 Android·iOS 크기별 아이콘을 다시 만든다. +flutter_launcher_icons: + android: launcher_icon + ios: true + image_path: branding/app_icon.png + remove_alpha_ios: true + min_sdk_android: 23 + # Android 8+ 적응형 아이콘 — 바탕은 브랜드 주황, 앞은 안전 영역 안의 워드마크 + adaptive_icon_background: "#FF7500" + adaptive_icon_foreground: branding/app_icon_foreground.png diff --git a/flutter/test/app_config_test.dart b/flutter/test/app_config_test.dart new file mode 100644 index 0000000..3b7bb4c --- /dev/null +++ b/flutter/test/app_config_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tma_app/core/config/app_config.dart'; + +void main() { + group('releaseApiUrlProblem', () { + test('공개 https 주소는 출시 빌드에 쓸 수 있다', () { + expect(releaseApiUrlProblem('https://api.rup.app/api'), isNull); + }); + + test('평문 http 는 출시 빌드에 쓸 수 없다 (기기가 막는다)', () { + expect(releaseApiUrlProblem('http://api.rup.app/api'), contains('https')); + }); + + test('개발용 주소는 출시 빌드에 쓸 수 없다', () { + expect(releaseApiUrlProblem('https://localhost:3000/api'), contains('개발용')); + expect(releaseApiUrlProblem('https://10.0.2.2/api'), contains('개발용')); + }); + + test('읽을 수 없는 주소', () { + expect(releaseApiUrlProblem('api'), isNotNull); + }); + }); + + test('빌드 때 넘긴 값이 없고 .env 도 읽지 않았으면 개발 서버 주소', () { + expect(readApiBaseUrl(), kDefaultApiUrl); + }); +} diff --git a/flutter/web/index.html b/flutter/web/index.html index 536a1d2..8ddd70c 100644 --- a/flutter/web/index.html +++ b/flutter/web/index.html @@ -23,13 +23,13 @@ - + - tma_app + rup diff --git a/flutter/web/manifest.json b/flutter/web/manifest.json index 5650593..845817c 100644 --- a/flutter/web/manifest.json +++ b/flutter/web/manifest.json @@ -1,6 +1,6 @@ { - "name": "tma_app", - "short_name": "tma_app", + "name": "rup", + "short_name": "rup", "start_url": ".", "display": "standalone", "background_color": "#0175C2",