Skip to content

Commit

Permalink
Merge pull request #87 from snuhcs-course/feat/guide-screen
Browse files Browse the repository at this point in the history
Feat/guide-screen
  • Loading branch information
89645321 authored Dec 2, 2023
2 parents f46bea9 + 0bc694c commit 21fd6b5
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
Expand All @@ -24,6 +27,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
Expand All @@ -41,6 +45,7 @@ import com.example.speechbuddy.compose.settings.SettingsScreen
import com.example.speechbuddy.compose.symbolcreation.SymbolCreationScreen
import com.example.speechbuddy.compose.symbolselection.SymbolSelectionScreen
import com.example.speechbuddy.compose.texttospeech.TextToSpeechScreen
import com.example.speechbuddy.compose.utils.GuideScreen
import com.example.speechbuddy.compose.utils.NoRippleInteractionSource

data class BottomNavItem(
Expand All @@ -56,6 +61,7 @@ fun SpeechBuddyHome(
initialPage: Boolean
) {
val navController = rememberNavController()

val navItems = listOf(
BottomNavItem(
"symbol_selection",
Expand All @@ -82,12 +88,15 @@ fun SpeechBuddyHome(
val topAppBarState = rememberSaveable { mutableStateOf(true) }
val bottomNavBarState = rememberSaveable { mutableStateOf(true) }

val showGuide = remember { mutableStateOf(false) }

Scaffold(
topBar = {
TopAppBar(
topAppBarState = topAppBarState,
items = navItems,
navController = navController
navController = navController,
showGuide = showGuide
)
},
bottomBar = {
Expand All @@ -109,14 +118,21 @@ fun SpeechBuddyHome(
bottomNavBarState = bottomNavBarState
)
}

if (showGuide.value) {
GuideScreen(
onDismissRequest = { showGuide.value = false }
)
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopAppBar(
topAppBarState: MutableState<Boolean>,
items: List<BottomNavItem>,
navController: NavController
navController: NavController,
showGuide: MutableState<Boolean>
) {
val backStackEntry = navController.currentBackStackEntryAsState()
var titleResId: Int? = null
Expand Down Expand Up @@ -148,6 +164,14 @@ fun TopAppBar(
contentScale = ContentScale.Fit
)
},
actions = {
IconButton(onClick = { showGuide.value = true }) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = "guide"
)
}
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
titleContentColor = MaterialTheme.colorScheme.onSecondaryContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp
import com.example.speechbuddy.R
import com.example.speechbuddy.compose.utils.TitleUi


@Composable
fun Copyright(
modifier: Modifier = Modifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ private fun SettingsScreenNavHost(
paddingValues = paddingValues
)
}

composable("copyright") {
Copyright(
paddingValues = paddingValues
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fun SymbolSelectionScreen(
* Without the elvis operator, null pointer exception arises.
* Do NOT erase the elvis operator although it seems useless!
*/
items(entries ?: emptyList()) { entry ->
items(entries) { entry ->
when (entry) {
is Symbol -> SymbolUi(
symbol = entry,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package com.example.speechbuddy.compose.utils

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.example.speechbuddy.R
import kotlinx.coroutines.launch

val pages = listOf<@Composable () -> Unit>(
{
GuideScreenPage(
imageId = R.drawable.bottom_navigation_bar_description,
contentDescriptionId = R.string.guide_screen_bottom_navigation_bar
)
},
{
GuideScreenPage(
imageId = R.drawable.symbol_selection_screen_description,
contentDescriptionId = R.string.guide_screen_symbol_selection_screen
)
},
{
GuideScreenPage(
imageId = R.drawable.text_to_speech_screen_description,
contentDescriptionId = R.string.guide_screen_text_to_speech_screen
)
},
{
GuideScreenPage(
imageId = R.drawable.symbol_creation_screen_description,
contentDescriptionId = R.string.guide_screen_symbol_creation_screen
)
}
)
val pageSize: Int = pages.size

@Composable
fun GuideScreenPage(
imageId: Int,
contentDescriptionId: Int
) {
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = imageId),
contentDescription = stringResource(id = contentDescriptionId),
modifier = Modifier
.fillMaxWidth()
.aspectRatio(10f / 19f) // image width-height ratio
)
}
}

@Composable
fun GuideScreen(
onDismissRequest: () -> Unit
) {
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
usePlatformDefaultWidth = false // Allow custom width
)
) {
GuideScreenPageList()
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun GuideScreenPageList() {
Column(
modifier = Modifier
.fillMaxWidth(0.85f),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val pagerState = rememberPagerState(
initialPage = 0,
initialPageOffsetFraction = 0f
) {
pageSize
}
val coroutineScope = rememberCoroutineScope()

HorizontalPager(
state = pagerState,
verticalAlignment = Alignment.CenterVertically
) { page ->
Box(
modifier = Modifier
.fillMaxHeight(0.85f)
.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
pages[page]()
}
}

Spacer(modifier = Modifier.height(25.dp))

PagerIndicator(pagerState = pagerState) {
coroutineScope.launch {
pagerState.scrollToPage(it)
}
}
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PagerIndicator(
modifier: Modifier = Modifier,
pagerState: PagerState,
indicatorCount: Int = pageSize,
indicatorSize: Dp = 16.dp,
indicatorShape: Shape = CircleShape,
space: Dp = 8.dp,
activeColor: Color = Color(0xffEC407A),
inActiveColor: Color = Color.LightGray,
onClick: ((Int) -> Unit)? = null
) {

val listState = rememberLazyListState()

val totalWidth: Dp = indicatorSize * indicatorCount + space * (indicatorCount - 1)
val widthInPx = LocalDensity.current.run { indicatorSize.toPx() }

val currentItem by remember {
derivedStateOf {
pagerState.currentPage
}
}

val itemCount = pagerState.pageCount

LaunchedEffect(key1 = currentItem) {
val viewportSize = listState.layoutInfo.viewportSize
listState.animateScrollToItem(
currentItem,
(widthInPx / 2 - viewportSize.width / 2).toInt()
)
}

LazyRow(
modifier = modifier.width(totalWidth),
state = listState,
contentPadding = PaddingValues(vertical = space),
horizontalArrangement = Arrangement.spacedBy(space),
userScrollEnabled = false
) {
indicatorItems(
itemCount,
currentItem,
indicatorShape,
activeColor,
inActiveColor,
indicatorSize,
onClick
)
}
}

private fun LazyListScope.indicatorItems(
itemCount: Int,
currentItem: Int,
indicatorShape: Shape,
activeColor: Color,
inActiveColor: Color,
indicatorSize: Dp,
onClick: ((Int) -> Unit)?
) {
items(itemCount) { index ->

val isSelected = (index == currentItem)

Box(
modifier = Modifier
.graphicsLayer {
val scale = 1f
scaleX = scale
scaleY = scale
}
.clip(indicatorShape)
.size(indicatorSize)
.background(
if (isSelected) activeColor else inActiveColor,
indicatorShape
)
.then(
if (onClick != null) {
Modifier
.clickable {
onClick.invoke(index)
}
} else Modifier
)
)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions frontend/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,10 @@
<!-- Copyright Info -->
<string name="copyright_info">저작권 정보</string>
<string name="copyright">본 서비스의 그림상징은 \'한국형 보완대체의사소통용 기본상징 체계집\'의 일부로 박은혜, 김영태(이화여자대학교), 홍기형(성신여자대학교)에게 저작권이 있습니다.\n자료의 무단 사용 및 2차 가공, 배포, 상업적 용도로 사용하는 것을 금합니다.</string>

<!-- Guide Screen -->
<string name="guide_screen_bottom_navigation_bar">하단 아이콘을 눌러 원하는 기능을 이용하세요\n① 상징으로 말하기\n② 음성으로 말하기\n③ 새 상징 만들기\n④ 설정</string>
<string name="guide_screen_symbol_creation_screen">① 사진을 선택하세요\n② 대분류를 선택하세요\n③ 상징 이름을 입력해주세요\n④ 새로운 상징을 만들어보아요</string>
<string name="guide_screen_symbol_selection_screen">① 자신이 원하는 상징을 볼 수 있어요\n② 하트를 눌러 상징을 즐겨찾기에 추가할 수 있어요\n③ 내가 선택한 상징들을 크게 보거나 한번에 지울 수 있어요\n④ 다시 대분류를 눌러 대분류를 볼 수 있어요</string>
<string name="guide_screen_text_to_speech_screen">① 글을 입력해주세요\n② 글을 재생하거나, 재생을 정지할 수 있어요\n③ 글을 한번에 지울 수 있어요</string>
</resources>

0 comments on commit 21fd6b5

Please sign in to comment.