diff --git a/common/resource/src/main/res/drawable/img_friend_accept.xml b/common/resource/src/main/res/drawable/img_friend_accept.xml
new file mode 100644
index 0000000..eb015b3
--- /dev/null
+++ b/common/resource/src/main/res/drawable/img_friend_accept.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/common/resource/src/main/res/drawable/img_friend_empty_plus.xml b/common/resource/src/main/res/drawable/img_friend_empty_plus.xml
new file mode 100644
index 0000000..86f5a4e
--- /dev/null
+++ b/common/resource/src/main/res/drawable/img_friend_empty_plus.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/common/resource/src/main/res/drawable/img_friend_reject.xml b/common/resource/src/main/res/drawable/img_friend_reject.xml
new file mode 100644
index 0000000..dfc2b8d
--- /dev/null
+++ b/common/resource/src/main/res/drawable/img_friend_reject.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/feature/friend/.gitignore b/feature/friend/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/friend/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/friend/build.gradle.kts b/feature/friend/build.gradle.kts
new file mode 100644
index 0000000..3e4de1e
--- /dev/null
+++ b/feature/friend/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ id("convention.android.feature")
+}
+
+android {
+ namespace = "com.idiotfrogs.friend"
+}
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.ext)
+ androidTestImplementation(libs.androidx.test.espresso)
+}
\ No newline at end of file
diff --git a/feature/friend/consumer-rules.pro b/feature/friend/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/friend/proguard-rules.pro b/feature/friend/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/friend/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/friend/src/androidTest/java/com/idiotfrogs/friend/ExampleInstrumentedTest.kt b/feature/friend/src/androidTest/java/com/idiotfrogs/friend/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..5370bc9
--- /dev/null
+++ b/feature/friend/src/androidTest/java/com/idiotfrogs/friend/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.idiotfrogs.friend
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.idiotfrogs.friend.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/feature/friend/src/main/AndroidManifest.xml b/feature/friend/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/feature/friend/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/friend/src/main/java/com/idiotfrogs/friend/FriendScreen.kt b/feature/friend/src/main/java/com/idiotfrogs/friend/FriendScreen.kt
new file mode 100644
index 0000000..e8c290e
--- /dev/null
+++ b/feature/friend/src/main/java/com/idiotfrogs/friend/FriendScreen.kt
@@ -0,0 +1,184 @@
+package com.idiotfrogs.friend
+
+import android.content.Intent
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.material3.Icon
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import androidx.core.content.FileProvider
+import com.idiotfrogs.designsystem.component.MSDetailHeader
+import com.idiotfrogs.designsystem.component.MSMenuFab
+import com.idiotfrogs.designsystem.component.MSText
+import com.idiotfrogs.designsystem.model.MSMenuFabModel
+import com.idiotfrogs.designsystem.theme.MSTheme
+import com.idiotfrogs.designsystem.util.noRippleClickable
+import com.idiotfrogs.friend.component.FriendListItem
+import com.idiotfrogs.friend.component.FriendTopNotification
+import com.idiotfrogs.resource.R
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+@Composable
+fun FriendScreen(
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+
+ var isEmpty by remember { mutableStateOf(false) }
+ var action by remember { mutableStateOf(FriendScreenActionState.IDLE) }
+ var expanded by remember { mutableStateOf(false) }
+ val menuList by remember {
+ mutableStateOf(
+ listOf(
+ MSMenuFabModel("참여 링크 공유") {
+ expanded = false
+
+// val imageUri = FileProvider.getUriForFile(
+// context,
+// "${context.packageName}.provider", // provider authority
+// imageFile // File 객체
+// )
+
+ // 공유 인텐트 생성
+ val shareIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "image/*"
+// putExtra(Intent.EXTRA_STREAM, imageUri)
+ putExtra(
+ Intent.EXTRA_TEXT,
+ "내가 만든 타임 티켓에 함께해줘! 아래 링크로 참여 요청을 보내면 “타임 캡슐 이름”에 합류할 수 있어요. [초대 링크]"
+ )
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ // 시스템 공유 바텀 시트 표시
+ context.startActivity(
+ Intent.createChooser(shareIntent, "공유하기")
+ )
+ },
+ MSMenuFabModel("참여 코드 복사") {
+ expanded = false
+ action = FriendScreenActionState.COPY
+ },
+ )
+ )
+ }
+
+ LaunchedEffect(action) {
+ delay(1000L)
+ action = FriendScreenActionState.IDLE
+ }
+
+ Box {
+ MSMenuFab(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .navigationBarsPadding()
+ .padding(end = 20.dp),
+ expanded = expanded,
+ hasFab = false,
+ offset = DpOffset(x = 0.dp, y = 40.dp),
+ menuList = menuList,
+ onClick = { expanded = !expanded },
+ onDismiss = { expanded = false },
+ )
+
+ if (action != FriendScreenActionState.IDLE) {
+ FriendTopNotification(
+ modifier = Modifier
+ .padding(horizontal = 20.dp)
+ .align(Alignment.TopCenter)
+ .navigationBarsPadding()
+ .zIndex(1f),
+ action = action,
+ )
+ }
+
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .background(MSTheme.color.white)
+ .systemBarsPadding()
+ .padding(horizontal = 20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ MSDetailHeader(
+ title = "맴버 추가",
+ navigateToBack = {},
+ paddingValues = PaddingValues(horizontal = 0.dp, vertical = 16.dp)
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_plus),
+ contentDescription = "Back",
+ modifier = Modifier.noRippleClickable { expanded = true }
+ )
+ }
+ if (isEmpty) {
+ Spacer(Modifier.height(25.dp))
+ Icon(
+ painter = painterResource(R.drawable.img_friend_empty_plus),
+ contentDescription = "emptyIcon",
+ tint = MSTheme.color.greyG1,
+ modifier = Modifier
+ .align(Alignment.End)
+ .padding(end = 40.dp)
+ )
+ Spacer(Modifier.height(32.dp))
+ MSText(
+ text = "대기중인 맴버가 없습니다.\n" + "코드 또는 링크로 맴버를 초대해보세요.",
+ color = MSTheme.color.greyG4,
+ fontWeight = FontWeight.Normal,
+ textAlign = TextAlign.Center
+ )
+ } else {
+ repeat(10) { // TODO 테스트용 코드 -> 추 후 실제 list 변경 필요
+ Spacer(Modifier.height(8.dp))
+ FriendListItem(
+ nickName = when (it) {
+ 0 -> "파란 바나나"
+ 1 -> "검정 복숭아"
+ 2 -> "별 모양 파인애플"
+ 3 -> "초코 체리"
+ 4 -> "자두 수박"
+ else -> "민트 네모 수박"
+ },
+ onAccept = { action = FriendScreenActionState.ACCEPT },
+ onReject = { action = FriendScreenActionState.REJECT }
+ )
+ Spacer(Modifier.height(8.dp))
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun FriendScreenPreview() {
+ FriendScreen()
+}
\ No newline at end of file
diff --git a/feature/friend/src/main/java/com/idiotfrogs/friend/FriendScreenActionState.kt b/feature/friend/src/main/java/com/idiotfrogs/friend/FriendScreenActionState.kt
new file mode 100644
index 0000000..def6476
--- /dev/null
+++ b/feature/friend/src/main/java/com/idiotfrogs/friend/FriendScreenActionState.kt
@@ -0,0 +1,5 @@
+package com.idiotfrogs.friend
+
+enum class FriendScreenActionState {
+ IDLE, ACCEPT, REJECT, COPY
+}
\ No newline at end of file
diff --git a/feature/friend/src/main/java/com/idiotfrogs/friend/component/FriendListItem.kt b/feature/friend/src/main/java/com/idiotfrogs/friend/component/FriendListItem.kt
new file mode 100644
index 0000000..993ce66
--- /dev/null
+++ b/feature/friend/src/main/java/com/idiotfrogs/friend/component/FriendListItem.kt
@@ -0,0 +1,85 @@
+package com.idiotfrogs.friend.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.idiotfrogs.designsystem.component.MSText
+import com.idiotfrogs.designsystem.theme.MSTheme
+import com.idiotfrogs.designsystem.util.noRippleClickable
+import com.idiotfrogs.resource.R
+
+@Composable
+fun FriendListItem(
+ nickName: String,
+ onAccept: () -> Unit,
+ onReject: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image( // TODO 추 후 AsyncImage 변경 필요
+ painter = painterResource(R.drawable.img_profile),
+ contentDescription = "프로필",
+ modifier = Modifier.size(48.dp)
+ )
+ Spacer(Modifier.width(8.dp))
+ MSText(
+ text = nickName,
+ color = MSTheme.color.greyG5,
+ fontSize = 16.dp,
+ fontWeight = FontWeight.Normal,
+ )
+
+ Spacer(Modifier.weight(1f))
+ MSText(
+ modifier = Modifier
+ .background(
+ color = MSTheme.color.greyG1,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(horizontal = 12.dp, vertical = 7.5.dp)
+ .noRippleClickable { onReject() },
+ text = "거절",
+ color = MSTheme.color.greyG4,
+ fontSize = 14.dp,
+ )
+ Spacer(Modifier.width(8.dp))
+ MSText(
+ modifier = Modifier
+ .background(
+ color = MSTheme.color.primaryLight,
+ shape = RoundedCornerShape(8.dp)
+ )
+ .padding(horizontal = 12.dp, vertical = 7.5.dp)
+ .noRippleClickable { onAccept() },
+ text = "수락",
+ color = MSTheme.color.primaryDark,
+ fontSize = 14.dp,
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun FriendListItemPreview() {
+ FriendListItem(
+ "nickName",
+ onAccept = {},
+ onReject = {},
+ )
+}
\ No newline at end of file
diff --git a/feature/friend/src/main/java/com/idiotfrogs/friend/component/FriendTopNotification.kt b/feature/friend/src/main/java/com/idiotfrogs/friend/component/FriendTopNotification.kt
new file mode 100644
index 0000000..a800ab9
--- /dev/null
+++ b/feature/friend/src/main/java/com/idiotfrogs/friend/component/FriendTopNotification.kt
@@ -0,0 +1,69 @@
+package com.idiotfrogs.friend.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.idiotfrogs.designsystem.component.MSText
+import com.idiotfrogs.designsystem.theme.MSTheme
+import com.idiotfrogs.friend.FriendScreenActionState
+import com.idiotfrogs.resource.R
+
+@Composable
+fun FriendTopNotification(
+ action: FriendScreenActionState,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .shadow(
+ elevation = 8.dp,
+ shape = CircleShape,
+ ambientColor = Color(0x50505029),
+ spotColor = Color(0x50505029)
+ )
+ .background(
+ color = MSTheme.color.white,
+ shape = CircleShape
+ )
+ .padding(12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(
+ if (action == FriendScreenActionState.ACCEPT || action == FriendScreenActionState.COPY) R.drawable.img_friend_accept
+ else R.drawable.img_friend_reject
+ ),
+ contentDescription = "알림",
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(Modifier.width(8.dp))
+ MSText(
+ text = when (action) {
+ FriendScreenActionState.ACCEPT -> "참여 요청이 수락되었습니다."
+ FriendScreenActionState.COPY -> "참여 코드 복사되었습니다."
+ else -> "참여 요청이 거절되었습니다."
+ }
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun FriendTopNotificationPreview() {
+ FriendTopNotification(FriendScreenActionState.IDLE)
+}
\ No newline at end of file
diff --git a/feature/friend/src/test/java/com/idiotfrogs/friend/ExampleUnitTest.kt b/feature/friend/src/test/java/com/idiotfrogs/friend/ExampleUnitTest.kt
new file mode 100644
index 0000000..d68eba0
--- /dev/null
+++ b/feature/friend/src/test/java/com/idiotfrogs/friend/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.idiotfrogs.friend
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4d22471..1d85600 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -34,6 +34,7 @@ profileinstaller = "1.4.1"
# 구글 로그인
androidxCredentials = "1.3.0"
identityGoogleId = "1.1.1"
+material = "1.10.0"
[libraries]
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
@@ -82,6 +83,7 @@ identity-google-id = { group = "com.google.android.libraries.identity.googleid",
# 애플 로그인
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
firebase-auth = { group = "com.google.firebase", name = "firebase-auth" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 04cb7a2..923700e 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -43,3 +43,4 @@ include(":feature:create")
include(":feature:setting")
include(":widget")
include(":baselineprofile")
+include(":feature:friend")