Flutter 앱 골격 · 5탭 네비게이션 · 로그인 화면 · Firestore 보안 규칙·인덱스

This commit is contained in:
Sn-Kinos
2026-08-10 01:02:03 +09:00
parent fbba4405c3
commit 233ef99837
113 changed files with 8549 additions and 2 deletions
+14
View File
@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
+91
View File
@@ -0,0 +1,91 @@
import java.util.Properties
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
// Firebase 설정 파일(google-services.json)이 투입되면(S2) 아래 한 줄을 함께 푼다.
// 파일 없이 플러그인만 적용하면 Gradle 빌드 자체가 실패한다.
// id("com.google.gms.google-services")
}
// 릴리스 서명 키는 `android/key.properties` 에서 읽는다. 파일은 저장소에 넣지 않는다.
// 형식: storeFile= / storePassword= / keyAlias= / keyPassword=
val keystoreProperties = Properties().apply {
val f = rootProject.file("key.properties")
if (f.exists()) f.inputStream().use { load(it) }
}
/// 이번 실행이 릴리스 산출물(assembleRelease · bundleRelease 등)을 요구하는가.
val wantsRelease = gradle.startParameter.taskNames.any { it.contains("Release", ignoreCase = true) }
// 앱 식별자 단일 출처. Q14(앱 식별자 확정) 회신이 오면 Android 쪽은 이 한 줄만 고친다.
// 주의: Kotlin 패키지(kr.co.sweech.fancake)와 소스 디렉터리는 바꿀 필요가 없다 —
// 스토어 식별자는 applicationId 뿐이고 namespace 는 R 클래스 패키지일 뿐이다.
val fancakeApplicationId = "kr.co.sweech.fancake"
android {
namespace = fancakeApplicationId
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// 잠정값. 스토어 등록 명의(2주차_확인요청항목 Q5)가 발주사 명의로 확정되면 교체한다(Q14).
// 스토어에 한 번 올리면 변경 불가 — 첫 제출 전에 확정해야 한다.
applicationId = fancakeApplicationId
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
create("release") {
if (keystoreProperties.isNotEmpty()) {
storeFile = file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
}
}
}
buildTypes {
release {
// 키가 없으면 **릴리스 빌드를 실패시킨다.** 기본 템플릿은 debug 키로 조용히 서명하는데,
// 그렇게 만든 release 산출물은 Play 가 거부하고 배포도 못 하면서 겉보기엔 성공한다.
// 릴리스 서명 키는 스토어 등록 명의(OPEN-QUESTIONS Q5 · 내부 조치 S3)가 정해져야
// 만들 수 있다 — 지금은 없는 게 정상이고, 없다는 사실이 드러나야 한다.
//
// 검사는 릴리스 태스크를 실제로 요청했을 때만 한다. 설정 시점에 무조건 던지면
// `assembleDebug` 까지 같이 깨진다.
if (keystoreProperties.isEmpty()) {
if (wantsRelease) {
throw GradleException(
"릴리스 서명 키가 없습니다. android/key.properties 를 만들고 키스토어를 지정하세요. " +
"(미해결: OPEN-QUESTIONS Q5 스토어 등록 명의 · 내부 조치 S3) " +
"디버그 빌드는 `flutter build apk --debug` 로 그대로 됩니다."
)
}
} else {
signingConfig = signingConfigs.getByName("release")
}
}
}
}
flutter {
source = "../.."
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="Fancake"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -0,0 +1,5 @@
package kr.co.sweech.fancake
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 런치 배경 = color-cosmic-void(#000). Flutter 첫 프레임 전 흰 화면이 번쩍이지 않게 한다. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/cosmic_void" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 런치 배경 = color-cosmic-void(#000). Flutter 첫 프레임 전 흰 화면이 번쩍이지 않게 한다. -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/cosmic_void" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- design/design-tokens.md 의 color-cosmic-void. 네이티브 런치 화면이 Flutter 첫 프레임과
같은 캔버스에서 시작하도록 쓴다. 앱은 다크 전용이라 values-night 를 따로 두지 않는다. -->
<resources>
<color name="cosmic_void">#FF000000</color>
<color name="ic_launcher_background">#120E25</color>
</resources>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off — 이 앱은 다크 전용(결정 D-19)이라 night 와 같은 값을 쓴다 -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
+24
View File
@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
+2
View File
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
+30
View File
@@ -0,0 +1,30 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
// Firebase 설정 파일(google-services.json) 투입 시(S2) 아래 한 줄과
// app/build.gradle.kts 의 id("com.google.gms.google-services") 를 '함께' 푼다.
// 파일 없이 플러그인만 적용하면 Gradle 빌드가 실패한다.
// id("com.google.gms.google-services") version "4.4.4" apply false
}
include(":app")