Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Floating Action Button #85

Merged
merged 40 commits into from
Sep 11, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
bc6cb5c
test: 스냅샷 테스트
EvergreenTree97 Sep 3, 2022
ba17c33
feat: click 이벤트 없는 icon
EvergreenTree97 Sep 3, 2022
b1d02b7
feat; fab버튼 dialog 수정 필요
EvergreenTree97 Sep 3, 2022
5c9e5c7
Merge branch 'develop' of https://github.com/sungbinland/duckie-quack…
EvergreenTree97 Sep 4, 2022
d9f0696
feat: fab 버튼 구현
EvergreenTree97 Sep 4, 2022
6219c47
feat: icon 추가
EvergreenTree97 Sep 4, 2022
a599467
feat: icon 추가
EvergreenTree97 Sep 4, 2022
b3c7089
test: 스냅샷 테스트
EvergreenTree97 Sep 4, 2022
54927c1
remove: nonClickableImage 삭제
EvergreenTree97 Sep 4, 2022
ee10633
test: 스냅샷 테스트 네이밍 변경
EvergreenTree97 Sep 4, 2022
98e2d57
refactor: quackClickable 사용 및 NonclickImageIcon 삭제
EvergreenTree97 Sep 4, 2022
c885f3b
refactor : 변수와 함수 사이 empty line 추가
EvergreenTree97 Sep 6, 2022
8b4b24b
refactor: kdoc 수정 및 불필요한 람다 생성 제거
EvergreenTree97 Sep 6, 2022
1e5fad9
feat: Playground 추가
EvergreenTree97 Sep 6, 2022
3c28e78
refactor : 불필요한 속성 명시 제거
EvergreenTree97 Sep 6, 2022
1caa739
style: resource 코드 정렬
EvergreenTree97 Sep 6, 2022
bb084f4
refactor: offset 함수 변경
EvergreenTree97 Sep 6, 2022
1854bbb
refactor: 불필요한 null 제거
EvergreenTree97 Sep 6, 2022
04b81ca
refactor: QuackText에 style 주는 대신 대신 QuackSubTitle 사용
EvergreenTree97 Sep 6, 2022
f82179e
refactor: Playground argument 변경
EvergreenTree97 Sep 6, 2022
d7f2a63
refactor: expanded 제어권 제공
EvergreenTree97 Sep 6, 2022
17c816e
test: 스냅샷 테스트 expanded parameter로 제공
EvergreenTree97 Sep 6, 2022
7d751b7
refactor: Spacer 제거 후 padding으로 대체
EvergreenTree97 Sep 6, 2022
0945183
refactor: QuackPopupItem -> QuackDialogItem 네이밍 변경 및 DialogMenu Conte…
EvergreenTree97 Sep 6, 2022
c6b45cf
feat: Item과 Text 사이 간격 추가
EvergreenTree97 Sep 6, 2022
7a7269c
refactor: QuackPopupItem -> QuackDialogItem으로 변경
EvergreenTree97 Sep 6, 2022
f5b85bd
refactor: Column 에서 LazyColumn 으로 변경
EvergreenTree97 Sep 6, 2022
9e72012
refactor: @NonRestartableComposable 불필요한 것들 제거하고 필요한 것 추가
EvergreenTree97 Sep 6, 2022
146fe05
feat: class에 hashcode와 equals 추가
EvergreenTree97 Sep 6, 2022
38099fc
feat: AnimatedVisibility 적용
EvergreenTree97 Sep 6, 2022
ca63030
refactor: Item의 중복 클릭 이벤트 없애고, (QuackDialogMenuItem) -> Unit 람다 추가
EvergreenTree97 Sep 6, 2022
a301b21
test: 파라미터 변경에 따라 람다 추가
EvergreenTree97 Sep 6, 2022
1a6a8ff
refactor: named arguments 추가
EvergreenTree97 Sep 6, 2022
83729e3
docs: 파라미터 변경에 따른 Kdoc 변경
EvergreenTree97 Sep 6, 2022
2cc59d4
Merge branch 'develop' of https://github.com/sungbinland/duckie-quack…
EvergreenTree97 Sep 8, 2022
f8ed180
refactor: fab offset,size 람다로 구현
EvergreenTree97 Sep 8, 2022
3ac360b
refactor: Playground Box로 변경
EvergreenTree97 Sep 8, 2022
fe588ee
fix: NewLine 추가
EvergreenTree97 Sep 8, 2022
27071e0
refactor: lint module 린트 에러
EvergreenTree97 Sep 8, 2022
1833c3b
refactor: offset 구하는거 kdoc에 자세히 명시 및 린트 에러 수정
EvergreenTree97 Sep 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
/*
* Designed and developed by 2022 SungbinLand, Team Duckie
*
* [fab.kt] created by Ji Sungbin on 22. 9. 3. 오후 1:15
*
* Licensed under the MIT.
* Please see full license: https://github.com/sungbinland/quack-quack/blob/main/LICENSE
*/

package team.duckie.quackquack.ui.component

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import team.duckie.quackquack.ui.color.QuackColor
import team.duckie.quackquack.ui.constant.QuackHeight
import team.duckie.quackquack.ui.constant.QuackWidth
import team.duckie.quackquack.ui.icon.QuackIcon
import team.duckie.quackquack.ui.modifier.applyQuackSize
import team.duckie.quackquack.ui.modifier.quackClickable
import team.duckie.quackquack.ui.textstyle.QuackTextStyle

private val QuackFloatingActionButtonSize = 48.dp

private val QuackFloatingActionButtonShape = CircleShape
private val QuackPopUpMenuShape = RoundedCornerShape(12.dp)

private val QuackFabTopPadding = 20.dp
private val QuackFabHorizontalPadding = 16.dp
private val QuackFabItemPadding = 16.dp

private val QuackFabItemSpacing = 8.dp

@Immutable
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
class QuackPopUpMenuItem(
val quackIcon: QuackIcon,
val text: String,
val onClick: () -> Unit,
)

EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
private val dummyPopUpMenuItem = persistentListOf(
QuackPopUpMenuItem(
quackIcon = QuackIcon.Close,
text = "피드",
onClick = {}
),
QuackPopUpMenuItem(
quackIcon = QuackIcon.Close,
text = "덕딜",
onClick = {}
),
)

@Composable
fun FloatingMenuActionButton(
items: PersistentList<QuackPopUpMenuItem>,
) {
var expanded by remember { mutableStateOf(false) }
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
var positionInRoot by remember { mutableStateOf(Offset.Zero) }
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
QuackText(
text = "${positionInRoot.x} , ${positionInRoot.y}}",
style = QuackTextStyle.Body1,
)
}
BasicFloatingActionButton(
modifier = Modifier
.onGloballyPositioned { layoutCoordinates ->
positionInRoot = layoutCoordinates.positionInRoot()
},
onClick = {
expanded = true
},
)
if (expanded) {
Column {
QuackDialog(
buttonOffset = positionInRoot,
onDismissRequest = {
expanded = false
},
) {
Column(
horizontalAlignment = Alignment.End,
) {
QuackDialogMenu(
items = items,
)
Spacer(
modifier = Modifier.height(
height = QuackFabItemSpacing,
),
)
BasicFloatingActionButton(
onClick = {
expanded = true
}
)
}
}
}

}

}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun QuackDialog(
buttonOffset: Offset,
onDismissRequest: () -> Unit,
content: @Composable () -> Unit,
) {
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
dismissOnBackPress = true,
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
usePlatformDefaultWidth = false,
),
) {
Box(
modifier = Modifier
.fillMaxSize()
.clickable {
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
onDismissRequest()
},
contentAlignment = Alignment.TopStart,
) {
Box(
modifier = Modifier.offset(
x = (buttonOffset.x).dp,
y = (buttonOffset.y).dp,
),
) {
content()
}

}
}
}

@Composable
@NonRestartableComposable
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
private fun QuackDialogMenu(
items: PersistentList<QuackPopUpMenuItem>,
) {
QuackSurface(
modifier = Modifier.applyQuackSize(
width = QuackWidth.Wrap,
height = QuackHeight.Wrap,
),
shape = QuackPopUpMenuShape,
backgroundColor = QuackColor.White,
) {
Column(
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
modifier = Modifier
.padding(
horizontal = QuackFabHorizontalPadding,
)
.padding(
top = QuackFabTopPadding
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
items.forEach { item: QuackPopUpMenuItem ->
QuackDialogMenuContent(
itemIcon = item.quackIcon,
itemText = item.text,
onClickItem = item.onClick,
)
Spacer(
modifier = Modifier.height(
height = QuackFabItemPadding,
),
)
}
}
}
}


@Composable
@NonRestartableComposable
internal fun QuackDialogMenuContent(
itemIcon: QuackIcon,
itemText: String,
onClickItem: () -> Unit,
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
) {
Row(
modifier = Modifier.quackClickable {
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
onClickItem()
},
verticalAlignment = Alignment.CenterVertically,
)
{
QuackNonClickableImage(
icon = itemIcon,
)
QuackText(
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
text = itemText,
style = QuackTextStyle.Subtitle,
)
}
}

@Composable
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
fun FloatingActionButton(
onClick: () -> Unit,
) {
BasicFloatingActionButton(
onClick = onClick,
)
}

@Composable
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
private fun BasicFloatingActionButton(
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
QuackSurface(
modifier = modifier
.applyQuackSize(
width = QuackWidth.Custom(QuackFloatingActionButtonSize),
height = QuackHeight.Custom(QuackFloatingActionButtonSize),
),
shape = QuackFloatingActionButtonShape,
backgroundColor = QuackColor.DuckieOrange,
) {
QuackImage(
icon = QuackIcon.Close,
onClick = onClick,
)
}
}

@Preview
@Composable
fun fabPreview() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter,
) {
FloatingMenuActionButton(
items = dummyPopUpMenuItem,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,29 @@ internal fun QuackImage(
),
)
}

/**
* 이미지 하나만 표시하는 컴포넌트의 아이콘 버전
*
* @param icon 표시할 아이콘의 drawable 아이디.
* 만약 null 이 들어온다면 아이콘을 그리지 않습니다.
* @param tint 아이콘에 적용할 틴트 값
* @param onClick 아이콘이 클릭됐을 때 실행할 람다식
*/
@Composable
@NonRestartableComposable
internal fun QuackNonClickableImage(
icon: QuackIcon?,
EvergreenTree97 marked this conversation as resolved.
Show resolved Hide resolved
tint: QuackColor = QuackColor.Black,
) {
if (icon == null) return
Image(
painter = painterResource(
id = icon.drawableId,
),
contentDescription = null,
colorFilter = ColorFilter.tint(
color = tint.value,
),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Designed and developed by 2022 SungbinLand, Team Duckie
*
* [QuackFloatingActionButton.kt] created by Ji Sungbin on 22. 9. 4. 오전 2:23
*
* Licensed under the MIT.
* Please see full license: https://github.com/sungbinland/quack-quack/blob/main/LICENSE
*/

@file:Suppress("TestFunctionName", "SpellCheckingInspection")

package team.duckie.quackquack.ui

import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import team.duckie.quackquack.ui.rule.AnimationTestRule
import team.duckie.quackquack.ui.textstyle.QuackFontScale
import team.duckie.quackquack.ui.util.boxSnapshot
import team.duckie.quackquack.ui.util.buildPaparazzi

@RunWith(TestParameterInjector::class)
class QuackFloatingActionButton {
@get:Rule
val paparazzi = buildPaparazzi()

@get:Rule
val animationTest = AnimationTestRule()

@Test
fun QuackFloatingActionButton(
@TestParameter("0.5", "1.0", "1.5", "2.0") fontScale: Double,
) {
paparazzi.boxSnapshot(
name = "[fontScale:$fontScale]",
) {
QuackFontScale = fontScale
team.duckie.quackquack.ui.component.FloatingActionButton(
onClick = {},
)
}
}
}