diff --git a/.github/workflows/AOS-distribute.yml b/.github/workflows/AOS-distribute.yml new file mode 100644 index 00000000..ed1eed57 --- /dev/null +++ b/.github/workflows/AOS-distribute.yml @@ -0,0 +1,77 @@ +name: AOS-distribute +on: + push: + branches: + - distribute + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'corretto' + java-version: 17 + + - name: Create Properties File + env: + RELEASE_KEYSTORE: ${{ secrets.KEY_STORE_BASE_64 }} + KEYSTORE_PROPERTIES: ${{ secrets.KEYSTORE_PROPERTIES }} + LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} + run: | + echo "$RELEASE_KEYSTORE" | base64 -d > AOS/app/release.keystore + echo "$KEYSTORE_PROPERTIES" > AOS/keystore.properties + echo "$LOCAL_PROPERTIES" > AOS/local.properties + + - name: Create google-services.json + run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > AOS/app/google-services.json + + - name: Build Release AAB + run: ./gradlew bundleRelease + working-directory: ./AOS + + - name: Upload AAB + uses: actions/upload-artifact@v2 + with: + name: app-release.aab + path: AOS/app/build/outputs/bundle/release/app-release.aab + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Download AAB + uses: actions/download-artifact@v2 + with: + name: app-release.aab + path: AOS/app/build/outputs/bundle/release/ + + - name: Create service_account.json + run: echo '${{ secrets.GOOGLE_PLAY_STORE_SERVICE_ACCOUT }}' > service_account.json + + - name: Extract Release Version + run: echo "##[set-output name=version;]$(echo '${{ github.event.head_commit.message }}' | egrep -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')" + id: extract_version_name + + - name: Create GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.extract_version_name.outputs.version }} + release_name: ${{ steps.extract_version_name.outputs.version }} + files: | + AOS/app/build/outputs/bundle/release/app-release.aab + + - name: Deploy to Google Play Store + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJson: service_account.json + packageName: boostcamp.and07.mindsync + releaseName: ${{ steps.extract_version_name.outputs.version }} + releaseFiles: AOS/app/build/outputs/bundle/release/app-release.aab + track: production + whatsNewDirectory: AOS/whatsNewDirectory diff --git a/AOS/app/build.gradle.kts b/AOS/app/build.gradle.kts index 08ffd4c6..a37e2013 100644 --- a/AOS/app/build.gradle.kts +++ b/AOS/app/build.gradle.kts @@ -1,5 +1,3 @@ -import java.util.Properties - plugins { id("com.android.application") id("org.jetbrains.kotlin.android") @@ -7,14 +5,13 @@ plugins { kotlin("kapt") id("com.google.dagger.hilt.android") id("kotlinx-serialization") + id("com.google.gms.google-services") } android { namespace = "boostcamp.and07.mindsync" compileSdk = 34 - val properties = Properties() - properties.load(project.rootProject.file("local.properties").inputStream()) val url = properties["BASE_URL"] ?: "" val googleServerClientId = properties["GOOGLE_SERVER_CLIENT_ID"] ?: "" val kakaoClientId = properties["KAKAO_CLIENT_ID"] ?: "" @@ -25,8 +22,8 @@ android { applicationId = "boostcamp.and07.mindsync" minSdk = 26 targetSdk = 34 - versionCode = 1 - versionName = "1.0" + versionCode = 8 + versionName = "1.0.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "BASE_URL", "$url") @@ -34,15 +31,7 @@ android { buildConfigField("String", "KAKAO_CLIENT_ID", "$kakaoClientId") manifestPlaceholders["KAKAO_CLIENT_ID"] = removeQuotationKakaoClientId } - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro", - ) - } - } + buildFeatures { viewBinding = true dataBinding = true diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/Node.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/Node.kt index 3076c7d1..9976500b 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/Node.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/Node.kt @@ -10,7 +10,12 @@ sealed class Node( open val path: NodePath, open val description: String, open val children: List, -) : java.io.Serializable +) : java.io.Serializable { + abstract fun adjustPosition( + horizontalSpacing: Dp, + totalHeight: Dp, + ): Node +} data class CircleNode( override val id: String, @@ -18,7 +23,14 @@ data class CircleNode( override val path: CirclePath = CirclePath(Dp(0f), Dp(0f), Dp(0f)), override val description: String, override val children: List, -) : Node(id, parentId, path, description, children) +) : Node(id, parentId, path, description, children) { + override fun adjustPosition( + horizontalSpacing: Dp, + totalHeight: Dp, + ): Node { + return this.copy(path = path.adjustPath(horizontalSpacing, totalHeight)) + } +} data class RectangleNode( override val id: String, @@ -26,4 +38,11 @@ data class RectangleNode( override val path: RectanglePath = RectanglePath(Dp(0f), Dp(0f), Dp(0f), Dp(0f)), override val description: String, override val children: List, -) : Node(id, parentId, path, description, children) +) : Node(id, parentId, path, description, children) { + override fun adjustPosition( + horizontalSpacing: Dp, + totalHeight: Dp, + ): Node { + return this.copy(path = path.adjustPath(horizontalSpacing, totalHeight)) + } +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/NodePath.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/NodePath.kt index e6199f7e..0e654c5d 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/NodePath.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/data/model/NodePath.kt @@ -4,7 +4,19 @@ import boostcamp.and07.mindsync.ui.util.Dp import kotlinx.serialization.Serializable @Serializable -sealed class NodePath(open val centerX: Dp, open val centerY: Dp) +sealed class NodePath(open val centerX: Dp, open val centerY: Dp) { + abstract fun adjustPath( + horizontalSpacing: Dp, + totalHeight: Dp, + ): NodePath + + protected fun calculateNewCenterY( + horizontalSpacing: Dp, + totalHeight: Dp, + ): Dp { + return centerY + totalHeight / 2 + horizontalSpacing + } +} data class RectanglePath( override val centerX: Dp, @@ -19,10 +31,24 @@ data class RectanglePath( fun rightX() = centerX + (width / (Dp(2f))) fun bottomY() = centerY + (height / (Dp(2f))) + + override fun adjustPath( + horizontalSpacing: Dp, + totalHeight: Dp, + ): RectanglePath { + return this.copy(centerY = calculateNewCenterY(horizontalSpacing, totalHeight)) + } } data class CirclePath( override val centerX: Dp, override val centerY: Dp, val radius: Dp, -) : NodePath(centerX, centerY) +) : NodePath(centerX, centerY) { + override fun adjustPath( + horizontalSpacing: Dp, + totalHeight: Dp, + ): CirclePath { + return this.copy(centerY = calculateNewCenterY(horizontalSpacing, totalHeight)) + } +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/base/BaseComposeActivity.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/base/BaseComposeActivity.kt index 469ec561..575a08f7 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/base/BaseComposeActivity.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/base/BaseComposeActivity.kt @@ -6,7 +6,6 @@ import androidx.activity.compose.setContent import androidx.compose.runtime.Composable abstract class BaseComposeActivity : ComponentActivity() { - @Composable abstract fun Content() diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSButton.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSButton.kt new file mode 100644 index 00000000..2527ff3e --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSButton.kt @@ -0,0 +1,54 @@ +package boostcamp.and07.mindsync.ui.components + +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import boostcamp.and07.mindsync.ui.theme.Gray3 + +@Composable +fun MSButton( + modifier: Modifier = Modifier, + text: String = "", + onClick: () -> Unit, + textStyle: TextStyle = TextStyle.Default, + backgroundColor: Color = MaterialTheme.colorScheme.primary, + fontColor: Color = Color.Unspecified, + shape: Shape = CircleShape, + disableColor: Color = Gray3, + isEnabled: Boolean = false, +) { + Button( + modifier = modifier, + onClick = onClick, + shape = shape, + colors = + ButtonDefaults.buttonColors( + containerColor = backgroundColor, + disabledContainerColor = disableColor, + ), + enabled = isEnabled, + ) { + Text( + text = text, + style = textStyle, + color = fontColor, + ) + } +} + +@Preview +@Composable +fun ButtonPreview() = + MSPreview { + MSButton( + onClick = { }, + ) + } diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSFloatingButton.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSFloatingButton.kt new file mode 100644 index 00000000..88169f19 --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSFloatingButton.kt @@ -0,0 +1,45 @@ +package boostcamp.and07.mindsync.ui.components + +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import boostcamp.and07.mindsync.R + +@Composable +fun RecycleBinRestoreFloatingButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + backgroundColor: Color = Color(0xFFD4BFF9), +) { + FloatingActionButton( + modifier = modifier, + onClick = onClick, + containerColor = backgroundColor, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_restore_board), + contentDescription = null, + ) + } +} + +@Composable +fun RefreshFloatingButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + backgroundColor: Color = Color(0xFFD4BFF9), +) { + FloatingActionButton( + modifier = modifier, + onClick = onClick, + containerColor = backgroundColor, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_restore_board), + contentDescription = null, + ) + } +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSIconButton.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSIconButton.kt new file mode 100644 index 00000000..d3b8031b --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSIconButton.kt @@ -0,0 +1,171 @@ +package boostcamp.and07.mindsync.ui.components + +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import boostcamp.and07.mindsync.R + +@Composable +fun BackIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_back), + contentDescription = null, + tint = iconColor, + ) + } +} + +@Composable +fun MenuIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_menu), + contentDescription = null, + tint = iconColor, + ) + } +} + +@Composable +fun CircleAddIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_add), + contentDescription = null, + tint = iconColor, + ) + } +} + +@Composable +fun CopyIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_copy), + contentDescription = null, + tint = iconColor, + ) + } +} + +@Composable +fun RemoveIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_remove), + contentDescription = null, + tint = iconColor, + ) + } +} + +@Composable +fun RefreshIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_refresh_board), + contentDescription = null, + tint = iconColor, + ) + } +} + +@Composable +fun EditIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_outlined_drawing), + contentDescription = null, + tint = iconColor, + ) + } +} + +@Composable +fun FoldIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_fold), + contentDescription = null, + tint = iconColor, + ) + } +} + +@Composable +fun RestoreIconButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + iconColor: Color = Color.Black, +) { + IconButton( + modifier = modifier, + onClick = onClick, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_restore_board), + contentDescription = null, + tint = iconColor, + ) + } +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSPreview.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSPreview.kt new file mode 100644 index 00000000..e45eede7 --- /dev/null +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/components/MSPreview.kt @@ -0,0 +1,12 @@ +package boostcamp.and07.mindsync.ui.components + +import androidx.compose.runtime.Composable +import boostcamp.and07.mindsync.ui.theme.MindSyncTheme + +@Composable +fun MSPreview( + isDarkTheme: Boolean = false, + content: @Composable () -> Unit, +) { + MindSyncTheme(darkTheme = isDarkTheme, content = content) +} diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/dialog/NickNameDialog.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/dialog/NickNameDialog.kt index e129ebf4..c02c60aa 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/dialog/NickNameDialog.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/dialog/NickNameDialog.kt @@ -46,17 +46,19 @@ fun NickNameDialog( ) { val screenWidth = LocalConfiguration.current.screenWidthDp.dp val dialogWidth = screenWidth * 0.8f - var textFieldValue = remember { - mutableStateOf( - TextFieldValue( - text = uiState.nickname, - selection = TextRange(uiState.nickname.length), - ), - ) - } - val focusRequester = remember { - FocusRequester() - } + var textFieldValue = + remember { + mutableStateOf( + TextFieldValue( + text = uiState.nickname, + selection = TextRange(uiState.nickname.length), + ), + ) + } + val focusRequester = + remember { + FocusRequester() + } LaunchedEffect(Unit) { focusRequester.requestFocus() @@ -67,10 +69,11 @@ fun NickNameDialog( }, ) { Column( - modifier = Modifier - .background(Color.White, RoundedCornerShape(20.dp)) - .width(dialogWidth) - .padding(start = 10.dp, top = 20.dp, end = 10.dp), + modifier = + Modifier + .background(Color.White, RoundedCornerShape(20.dp)) + .width(dialogWidth) + .padding(start = 10.dp, top = 20.dp, end = 10.dp), ) { Text( text = stringResource(id = R.string.profile_nickname_modify), @@ -80,10 +83,14 @@ fun NickNameDialog( OutlinedTextField( value = textFieldValue.value, - onValueChange = { textFieldValue.value = it; editNickname(it.text) }, - modifier = Modifier - .padding(5.dp) - .focusRequester(focusRequester), + onValueChange = { + textFieldValue.value = it + editNickname(it.text) + }, + modifier = + Modifier + .padding(5.dp) + .focusRequester(focusRequester), placeholder = { Text(text = stringResource(id = R.string.profile_name_limit)) }, @@ -109,12 +116,16 @@ fun NickNameDialog( } TextButton( - onClick = { updateNickname(uiState.editingNickname); closeDialog() }, - modifier = Modifier, - enabled = when (uiState.editingNickname.length) { - in 1..20 -> true - else -> false + onClick = { + updateNickname(uiState.editingNickname) + closeDialog() }, + modifier = Modifier, + enabled = + when (uiState.editingNickname.length) { + in 1..20 -> true + else -> false + }, ) { Text( text = stringResource(id = R.string.profile_modify), diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/profile/ProfileFragment.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/profile/ProfileFragment.kt index 6a2f82bc..e104409d 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/profile/ProfileFragment.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/profile/ProfileFragment.kt @@ -6,7 +6,6 @@ import android.os.Bundle import android.view.View import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.navGraphViewModels import boostcamp.and07.mindsync.R import boostcamp.and07.mindsync.data.repository.login.LogoutEventRepository @@ -33,11 +32,15 @@ class ProfileFragment : BaseComposeFragment() { private lateinit var imagePickerHandler: ImagePickerHandler - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) - imagePickerHandler = ImagePickerHandler(requireActivity()) { uri -> - createImage(uri) - } + imagePickerHandler = + ImagePickerHandler(requireActivity()) { uri -> + createImage(uri) + } } @Composable diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/profile/ProfileScreen.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/profile/ProfileScreen.kt index f8df91a2..e9300492 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/profile/ProfileScreen.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/profile/ProfileScreen.kt @@ -16,23 +16,19 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState 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.draw.clip @@ -43,19 +39,20 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import boostcamp.and07.mindsync.R +import boostcamp.and07.mindsync.ui.components.BackIconButton +import boostcamp.and07.mindsync.ui.components.EditIconButton +import boostcamp.and07.mindsync.ui.components.MSButton import boostcamp.and07.mindsync.ui.dialog.NickNameDialog import boostcamp.and07.mindsync.ui.theme.Blue1 -import boostcamp.and07.mindsync.ui.theme.Gray3 import boostcamp.and07.mindsync.ui.theme.Gray4 import boostcamp.and07.mindsync.ui.theme.MindSyncTheme import boostcamp.and07.mindsync.ui.theme.Red2 import coil.compose.AsyncImage import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch @Composable fun ProfileScreen( @@ -68,69 +65,51 @@ fun ProfileScreen( showImagePicker: () -> Unit, ) { val uiState by profileViewModel.uiState.collectAsStateWithLifecycle() - val nicknameColor = remember { mutableStateOf(Gray4) } + var nicknameColor by remember { mutableStateOf(Gray4) } val snackBarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() HandleProfileEvents( profileViewModel = profileViewModel, onBack = onBack, - nicknameColor = nicknameColor, - snackBarHostState = snackBarHostState, - ) - Scaffold( - snackbarHost = { - SnackbarHost(hostState = snackBarHostState) + updateNicknameColor = { changedColor -> + nicknameColor = changedColor }, - ) { innerPadding -> - BoxWithConstraints( - modifier = Modifier - .padding(innerPadding) - .fillMaxWidth(), - ) { - val guidelineTop = maxHeight * 0.15f - val guidelineStart = maxWidth * 0.1f - val guidelineEnd = maxWidth * 0.1f - ProfileContent( - profileViewModel = profileViewModel, - guidelineTop = guidelineTop, - uiState = uiState, - showImagePicker = showImagePicker, - nicknameColor = nicknameColor, - showDialog = showDialog, - guidelineStart = guidelineStart, - guidelineEnd = guidelineEnd, - updateProfile = updateProfile, - ) - - if (uiState.isShownNicknameDialog) { - NickNameDialog( - uiState = uiState, - editNickname = editNickname, - closeDialog = { showDialog(false) }, - updateNickname = { updateNickname(it) }, + showSnackBar = { errorMessage -> + coroutineScope.launch { + snackBarHostState.showSnackbar( + message = errorMessage, + duration = SnackbarDuration.Short, ) } - } - } + }, + ) + + ProfileContent( + uiState = uiState, + showImagePicker = showImagePicker, + nicknameColor = nicknameColor, + showDialog = showDialog, + updateProfile = updateProfile, + updateNickname = updateNickname, + editNickname = editNickname, + ) } @Composable private fun HandleProfileEvents( profileViewModel: ProfileViewModel, onBack: () -> Unit, - nicknameColor: MutableState, - snackBarHostState: SnackbarHostState, + updateNicknameColor: (Color) -> Unit, + showSnackBar: (String) -> Unit, ) { LaunchedEffect(profileViewModel.event) { profileViewModel.event.collectLatest { event -> when (event) { is ProfileUiEvent.NavigateToBack -> onBack() - is ProfileUiEvent.ShowMessage -> snackBarHostState.showSnackbar( - message = event.message, - duration = SnackbarDuration.Short, - ) + is ProfileUiEvent.ShowMessage -> showSnackBar(event.message) - is ProfileUiEvent.UpdateProfileNickName -> nicknameColor.value = Blue1 + is ProfileUiEvent.UpdateProfileNickName -> updateNicknameColor(Blue1) } } } @@ -138,82 +117,105 @@ private fun HandleProfileEvents( @Composable private fun ProfileContent( - profileViewModel: ProfileViewModel, - guidelineTop: Dp, - uiState: ProfileUiState, - showImagePicker: () -> Unit, - nicknameColor: MutableState, - showDialog: (Boolean) -> Unit, - guidelineStart: Dp, - guidelineEnd: Dp, - updateProfile: (String) -> Unit, + onBack: () -> Unit = { }, + uiState: ProfileUiState = ProfileUiState(), + showImagePicker: () -> Unit = { }, + nicknameColor: Color = Gray4, + showDialog: (Boolean) -> Unit = { }, + updateProfile: (String) -> Unit = { }, + updateNickname: (CharSequence) -> Unit = { }, + editNickname: (CharSequence) -> Unit = { }, + snackBarHostState: SnackbarHostState = SnackbarHostState(), ) { - ProfileTopAppBar { profileViewModel.onClickBack() } - Column( - modifier = Modifier - .fillMaxSize() - .padding(top = guidelineTop), - ) { - ProfileImage( - modifier = Modifier - .align(Alignment.CenterHorizontally), - imageUri = uiState.imageUri, - showImagePicker = showImagePicker, - ) - - Row( - modifier = Modifier - .padding(top = 16.dp), - verticalAlignment = Alignment.CenterVertically, + Scaffold( + topBar = { ProfileTopAppBar(onBack) }, + snackbarHost = { + SnackbarHost(hostState = snackBarHostState) + }, + ) { innerPadding -> + BoxWithConstraints( + modifier = + Modifier + .padding(innerPadding) + .fillMaxWidth(), ) { - Spacer(modifier = Modifier.weight(1f)) - Nickname( - modifier = Modifier.weight(1f), - nickname = uiState.nickname, - nicknameColor = nicknameColor.value, - ) - NicknameEditButton( - modifier = Modifier.weight(0.2f), - showDialog = showDialog, - ) - Spacer(modifier = Modifier.weight(0.8f)) - } + val guidelineTop = maxHeight * 0.15f + val guidelineStart = maxWidth * 0.1f + val guidelineEnd = maxWidth * 0.1f + Column( + modifier = + Modifier + .fillMaxSize() + .padding(top = guidelineTop), + ) { + ProfileImage( + modifier = + Modifier + .align(Alignment.CenterHorizontally), + imageUri = uiState.imageUri, + showImagePicker = showImagePicker, + ) + + Row( + modifier = + Modifier + .padding(top = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Spacer(modifier = Modifier.weight(1f)) + Nickname( + modifier = Modifier.weight(1f), + nickname = uiState.nickname, + nicknameColor = nicknameColor, + ) + NicknameEditButton( + modifier = Modifier.weight(0.2f), + showDialog = showDialog, + ) + Spacer(modifier = Modifier.weight(0.8f)) + } - ModifyButton( - modifier = Modifier - .padding( - top = 30.dp, - start = guidelineStart, - end = guidelineEnd, + ModifyButton( + modifier = + Modifier + .padding( + top = 30.dp, + start = guidelineStart, + end = guidelineEnd, + ) + .fillMaxWidth(), + profileImageName = stringResource(id = R.string.profile_image_name), + updateProfile = updateProfile, + isModify = uiState.isModify, ) - .fillMaxWidth(), - profileImageName = stringResource(id = R.string.profile_image_name), - updateProfile = updateProfile, - isModify = uiState.isModify, + } + } + } + + if (uiState.isShownNicknameDialog) { + NickNameDialog( + uiState = uiState, + editNickname = editNickname, + closeDialog = { showDialog(false) }, + updateNickname = { updateNickname(it) }, ) } } @Composable -private fun ProfileTopAppBar( - onBack: () -> Unit, -) { +private fun ProfileTopAppBar(onBack: () -> Unit) { Row( verticalAlignment = Alignment.CenterVertically, ) { - IconButton( + BackIconButton( onClick = { onBack() }, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_back), - contentDescription = null, - ) - } + ) Text( text = stringResource(id = R.string.profile_my_page), style = MaterialTheme.typography.displayMedium, - modifier = Modifier - .padding(start = 14.dp), + modifier = + Modifier + .padding(start = 14.dp), ) } } @@ -225,28 +227,31 @@ private fun ProfileImage( showImagePicker: () -> Unit, ) { Box( - modifier = modifier - .size(120.dp), + modifier = + modifier + .size(120.dp), ) { AsyncImage( model = imageUri, contentDescription = null, - modifier = Modifier - .clip(CircleShape) - .clickable { - showImagePicker() - }, + modifier = + Modifier + .clip(CircleShape) + .clickable { + showImagePicker() + }, contentScale = ContentScale.Crop, ) Box( contentAlignment = Alignment.Center, - modifier = Modifier - .size(30.dp) - .offset(y = 10.dp) - .clip(shape = RoundedCornerShape(5.dp)) - .background(color = Blue1) - .align(Alignment.TopEnd), + modifier = + Modifier + .size(30.dp) + .offset(y = 10.dp) + .clip(shape = RoundedCornerShape(5.dp)) + .background(color = Blue1) + .align(Alignment.TopEnd), ) { Image( painter = painterResource(id = R.drawable.ic_add_board), @@ -279,15 +284,10 @@ private fun NicknameEditButton( modifier: Modifier = Modifier, showDialog: (Boolean) -> Unit, ) { - IconButton( + EditIconButton( modifier = modifier, onClick = { showDialog(true) }, - ) { - Icon( - painter = painterResource(id = R.drawable.ic_outlined_drawing), - contentDescription = null, - ) - } + ) } @Composable @@ -297,37 +297,21 @@ private fun ModifyButton( updateProfile: (String) -> Unit, isModify: Boolean, ) { - Button( - onClick = { updateProfile(profileImageName) }, + MSButton( modifier = modifier, - colors = ButtonDefaults.buttonColors( - containerColor = Red2, - disabledContainerColor = Gray3, - ), - enabled = isModify, - ) { - Text( - text = stringResource(id = R.string.profile_modify), - style = MaterialTheme.typography.displaySmall, - color = Color.White, - ) - } + onClick = { updateProfile(profileImageName) }, + backgroundColor = Red2, + isEnabled = isModify, + text = stringResource(id = R.string.profile_modify), + textStyle = MaterialTheme.typography.displaySmall, + fontColor = Color.White, + ) } @Preview @Composable private fun ProfileScreenPreview() { MindSyncTheme { - Surface(modifier = Modifier.fillMaxSize()) { - ProfileScreen( - viewModel(), - onBack = { /*TODO*/ }, - updateNickname = { }, - updateProfile = { }, - editNickname = { }, - showDialog = { }, - showImagePicker = { }, - ) - } + ProfileContent() } } diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/theme/Theme.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/theme/Theme.kt index 251292dc..550aa638 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/theme/Theme.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/theme/Theme.kt @@ -18,11 +18,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat -private val DarkColorScheme = darkColorScheme( - primary = Gray6, - secondary = Gray4, - tertiary = Gray5, -) +private val DarkColorScheme = + darkColorScheme( + primary = Gray6, + secondary = Gray4, + tertiary = Gray5, + ) private val LightColorScheme = lightColorScheme() @@ -32,15 +33,16 @@ fun MindSyncTheme( dynamicColor: Boolean = true, content: @Composable () -> Unit, ) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) - } + val colorScheme = + when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } - darkTheme -> DarkColorScheme - else -> LightColorScheme - } + darkTheme -> DarkColorScheme + else -> LightColorScheme + } MaterialTheme( colorScheme = colorScheme, diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/theme/Type.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/theme/Type.kt index 4548eea7..b9ec3847 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/theme/Type.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/theme/Type.kt @@ -13,71 +13,89 @@ val PretendardMedium = FontFamily(Font(R.font.pretendard_medium)) val PretendardRegular = FontFamily(Font(R.font.pretendard_regular)) val PretendardLight = FontFamily(Font(R.font.pretendard_light)) -val defaultTextStyle = TextStyle( - platformStyle = PlatformTextStyle( - includeFontPadding = false, - ), -) +val defaultTextStyle = + TextStyle( + platformStyle = + PlatformTextStyle( + includeFontPadding = false, + ), + ) -val Typography = Typography( - displayLarge = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 24.sp, - ), - displayMedium = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 20.sp, - ), - displaySmall = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 18.sp, - ), - headlineLarge = defaultTextStyle.copy( - fontFamily = PretendardMedium, - fontSize = 16.sp, - ), - headlineMedium = defaultTextStyle.copy( - fontFamily = PretendardRegular, - fontSize = 16.sp, - ), - headlineSmall = defaultTextStyle.copy( - fontFamily = PretendardMedium, - fontSize = 14.sp, - ), - titleLarge = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 18.sp, - ), - titleMedium = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 16.sp, - ), - titleSmall = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 14.sp, - ), - bodyLarge = defaultTextStyle.copy( - fontFamily = PretendardMedium, - fontSize = 12.sp, - ), - bodyMedium = defaultTextStyle.copy( - fontFamily = PretendardRegular, - fontSize = 12.sp, - ), - bodySmall = defaultTextStyle.copy( - fontFamily = PretendardLight, - fontSize = 12.sp, - ), - labelLarge = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 18.sp, - ), - labelMedium = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 16.sp, - ), - labelSmall = defaultTextStyle.copy( - fontFamily = PretendardSemiBold, - fontSize = 14.sp, - ), -) +val Typography = + Typography( + displayLarge = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 24.sp, + ), + displayMedium = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 20.sp, + ), + displaySmall = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 18.sp, + ), + headlineLarge = + defaultTextStyle.copy( + fontFamily = PretendardMedium, + fontSize = 16.sp, + ), + headlineMedium = + defaultTextStyle.copy( + fontFamily = PretendardRegular, + fontSize = 16.sp, + ), + headlineSmall = + defaultTextStyle.copy( + fontFamily = PretendardMedium, + fontSize = 14.sp, + ), + titleLarge = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 18.sp, + ), + titleMedium = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 16.sp, + ), + titleSmall = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 14.sp, + ), + bodyLarge = + defaultTextStyle.copy( + fontFamily = PretendardMedium, + fontSize = 12.sp, + ), + bodyMedium = + defaultTextStyle.copy( + fontFamily = PretendardRegular, + fontSize = 12.sp, + ), + bodySmall = + defaultTextStyle.copy( + fontFamily = PretendardLight, + fontSize = 12.sp, + ), + labelLarge = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 18.sp, + ), + labelMedium = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 16.sp, + ), + labelSmall = + defaultTextStyle.copy( + fontFamily = PretendardSemiBold, + fontSize = 14.sp, + ), + ) diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/LineView.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/LineView.kt index ef571024..507d2a32 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/LineView.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/LineView.kt @@ -15,13 +15,13 @@ import boostcamp.and07.mindsync.ui.util.toPx import boostcamp.and07.mindsync.ui.view.model.DrawInfo class LineView constructor( + private val mindMapContainer: MindMapContainer, context: Context, attrs: AttributeSet? = null, ) : View(context, attrs) { private val drawInfo = DrawInfo(context) private val path = Path() private lateinit var tree: Tree - private val paint = Paint() override fun onDraw(canvas: Canvas) { super.onDraw(canvas) @@ -52,30 +52,44 @@ class LineView constructor( toNode: Node, canvas: Canvas, ) { - val linePaint = - when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { - Configuration.UI_MODE_NIGHT_YES -> { - drawInfo.darkModeLinePaint - } + val linePaint = getLinePaintForMode() - Configuration.UI_MODE_NIGHT_NO -> { - drawInfo.linePaint - } + val path = createPath(fromNode, toNode) + drawPathConditionally(toNode, canvas, path, linePaint) + } - else -> drawInfo.linePaint - } + private fun createPath( + fromNode: Node, + toNode: Node, + ): Path { val startX = getNodeEdgeX(fromNode, true) val startY = fromNode.path.centerY.toPx(context) val endX = getNodeEdgeX(toNode, false) val endY = toNode.path.centerY.toPx(context) val midX = (startX + endX) / 2 - val path = - path.apply { - reset() - moveTo(startX, startY) - cubicTo(midX, startY, midX, endY, endX, endY) - } - canvas.drawPath(path, linePaint) + return path.apply { + reset() + moveTo(startX, startY) + cubicTo(midX, startY, midX, endY, endX, endY) + } + } + + private fun getLinePaintForMode(): Paint { + return when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> drawInfo.darkModeLinePaint + else -> drawInfo.linePaint + } + } + + private fun drawPathConditionally( + toNode: Node, + canvas: Canvas, + path: Path, + linePaint: Paint, + ) { + if (!mindMapContainer.isMoving || mindMapContainer.selectNode?.id != toNode.id) { + canvas.drawPath(path, linePaint) + } } private fun getNodeEdgeX( diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/NodeView.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/NodeView.kt index fa9b7d8a..5b0ffbb2 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/NodeView.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/NodeView.kt @@ -11,7 +11,6 @@ import boostcamp.and07.mindsync.R import boostcamp.and07.mindsync.data.model.CircleNode import boostcamp.and07.mindsync.data.model.Node import boostcamp.and07.mindsync.data.model.RectangleNode -import boostcamp.and07.mindsync.data.model.RectanglePath import boostcamp.and07.mindsync.data.model.Tree import boostcamp.and07.mindsync.ui.util.Dp import boostcamp.and07.mindsync.ui.util.Px @@ -21,7 +20,8 @@ import boostcamp.and07.mindsync.ui.view.layout.MindMapRightLayoutManager import boostcamp.and07.mindsync.ui.view.model.DrawInfo class NodeView( - val mindMapContainer: MindMapContainer, + private val lineView: LineView, + private val mindMapContainer: MindMapContainer, context: Context, attrs: AttributeSet?, ) : View(context, attrs) { @@ -67,22 +67,35 @@ class NodeView( MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { if (mindMapContainer.isMoving) { - mindMapContainer.isMoving = false - mindMapContainer.selectNode?.let { selectedNode -> - findIncludedNode(event.x, event.y) - attachNode(selectedNode) - attachedNode?.let { attachedNode -> - mindMapContainer.update(tree, selectedNode, attachedNode) - } - } + stopNodeMovement() + updateTreeIfNodeAttached(event) } - attachedNode = null - invalidate() + resetStateAndRefreshTree() } } return false } + private fun stopNodeMovement() { + mindMapContainer.isMoving = false + } + + private fun updateTreeIfNodeAttached(event: MotionEvent) { + mindMapContainer.selectNode?.let { selectedNode -> + findIncludedNode(event.x, event.y) + attachNode(selectedNode) + attachedNode?.let { attachedNode -> + mindMapContainer.update(tree, selectedNode, attachedNode) + } + } + } + + private fun resetStateAndRefreshTree() { + attachedNode = null + lineView.updateTree(tree) + invalidate() + } + private fun attachNode(selectedNode: Node) { attachedNode?.let { attachedNode -> tree.doPreorderTraversal { node -> @@ -127,8 +140,11 @@ class NodeView( mindMapContainer.selectNode?.let { selectedNode -> if (selectedNode is CircleNode) return traverseMovedNode(tree.getRootNode(), selectedNode, dx, dy) + mindMapContainer.update(tree) + rightLayoutManager.arrangeNode(tree, selectedNode as RectangleNode) } + lineView.updateTree(tree) invalidate() } @@ -142,43 +158,12 @@ class NodeView( val centerX = Dp(Px(dx).toDp(context)) val centerY = Dp(Px(dy).toDp(context)) tree.updateNode(target.id, target.description, target.children, centerX, centerY) - - target.children.forEach { nodeId -> - val childNode = tree.getNode(nodeId) - traverseChildNode( - target, - childNode, - target.path.centerX + (target.path as RectanglePath).width / 2 + DEFAULT_SPACING_VALUE, - ) - } } - node.children.forEach { nodeId -> traverseMovedNode(tree.getNode(nodeId), target, dx, dy) } } - private fun traverseChildNode( - target: Node, - node: Node, - childNodeSpacing: Dp, - ) { - tree.updateNode( - node.id, - node.description, - node.children, - childNodeSpacing, - target.path.centerY, - ) - node.children.forEach { nodeId -> - traverseChildNode( - node, - tree.getNode(nodeId), - childNodeSpacing + CHILD_NODE_SPACING_VALUE, - ) - } - } - fun updateTree(tree: Tree) { this.tree = tree invalidate() @@ -390,8 +375,6 @@ class NodeView( } companion object { - private const val DEFAULT_SPACING_VALUE = 50f - private const val CHILD_NODE_SPACING_VALUE = 7f private const val ATTACH_CIRCLE_NODE_RANGE_VALUE = 15f private const val ROUNDED_CORNER_RADIUS = 8f } diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/ZoomLayout.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/ZoomLayout.kt index f8a8e84c..08b28d88 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/ZoomLayout.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/ZoomLayout.kt @@ -38,8 +38,8 @@ class ZoomLayout(context: Context, attrs: AttributeSet? = null) : FrameLayout(co lateinit var mindMapContainer: MindMapContainer fun initializeZoomLayout() { - nodeView = NodeView(mindMapContainer, context, attrs = null) - lineView = LineView(context, attrs = null) + lineView = LineView(mindMapContainer, context, attrs = null) + nodeView = NodeView(lineView, mindMapContainer, context, attrs = null) addView(lineView) addView(nodeView) } diff --git a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/layout/MindMapRightLayoutManager.kt b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/layout/MindMapRightLayoutManager.kt index cc965775..ccbefd6a 100644 --- a/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/layout/MindMapRightLayoutManager.kt +++ b/AOS/app/src/main/java/boostcamp/and07/mindsync/ui/view/layout/MindMapRightLayoutManager.kt @@ -10,20 +10,21 @@ class MindMapRightLayoutManager { private val horizontalSpacing = Dp(50f) private val verticalSpacing = Dp(50f) - fun arrangeNode(tree: Tree) { - val root = tree.getRootNode() + fun arrangeNode( + tree: Tree, + rootNode: Node? = null, + ) { + val root = rootNode ?: tree.getRootNode() val totalHeight = measureChildHeight(root, tree) val newHead = if (root.path.centerX.dpVal <= (totalHeight / 2).dpVal) { - val newPath = - root.path.copy( - centerY = totalHeight / 2 + horizontalSpacing, - ) - root.copy(path = newPath) + root.adjustPosition(horizontalSpacing, totalHeight) } else { root } - tree.setRootNode(newHead) + if (rootNode == null) { + tree.setRootNode(newHead as CircleNode) + } recurArrangeNode(newHead, tree) } diff --git a/AOS/app/src/main/res/layout/fragment_mind_map.xml b/AOS/app/src/main/res/layout/fragment_mind_map.xml index 95721581..c1141545 100644 --- a/AOS/app/src/main/res/layout/fragment_mind_map.xml +++ b/AOS/app/src/main/res/layout/fragment_mind_map.xml @@ -28,7 +28,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:visibility="@{vm.selectedNode != null ? View.VISIBLE : View.GONE}" - app:constraint_referenced_ids="imgbtn_mind_map_add,imgbtn_mind_map_back,imgbtn_mind_map_remove,imgbtn_mind_map_edit,view_mind_map_side_bar" + app:constraint_referenced_ids="imgbtn_mind_map_add,imgbtn_mind_map_remove,imgbtn_mind_map_edit,view_mind_map_side_bar" app:layout_constraintBottom_toBottomOf="@id/view_mind_map_side_bar" app:layout_constraintEnd_toEndOf="@id/view_mind_map_side_bar" app:layout_constraintStart_toStartOf="@id/view_mind_map_side_bar" @@ -74,23 +74,10 @@ android:background="@android:color/transparent" android:src="@drawable/ic_outlined_drawing" app:layout_constraintBottom_toBottomOf="@id/view_mind_map_side_bar" - app:layout_constraintEnd_toStartOf="@id/imgbtn_mind_map_back" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintStart_toEndOf="@id/imgbtn_mind_map_remove" app:layout_constraintTop_toTopOf="@id/view_mind_map_side_bar" /> - - - - + - + \ No newline at end of file diff --git a/AOS/build.gradle.kts b/AOS/build.gradle.kts index e0fa51ec..d26e3558 100644 --- a/AOS/build.gradle.kts +++ b/AOS/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("com.google.dagger.hilt.android") version "2.48" apply false id("org.jlleitschuh.gradle.ktlint") version "11.6.1" apply false kotlin("plugin.serialization") version "1.9.0" + id("com.google.gms.google-services") version "4.4.0" apply false } buildscript { repositories { diff --git a/AOS/whatsNewDirectory/whatsnew-ko-KR b/AOS/whatsNewDirectory/whatsnew-ko-KR new file mode 100644 index 00000000..92415fa2 --- /dev/null +++ b/AOS/whatsNewDirectory/whatsnew-ko-KR @@ -0,0 +1,9 @@ +2023.12.15 +version: 1.0.1 + +πŸ’‘ About MindSync +λ§ˆμΈλ“œλ§΅μ„ μ‚¬μš©ν•˜μ—¬ 아이디어λ₯Ό μ‹œκ°μ μœΌλ‘œ ν‘œν˜„ν•΄λ³΄μ„Έμš”. +ν˜‘μ—…μ„ μ›ν•˜λŠ” μΉœκ΅¬λ‚˜ λ™λ£Œλ₯Ό μ΄ˆλŒ€ν•˜λŠ” 것도 κ°€λŠ₯ν•©λ‹ˆλ‹€. +κ°„νŽΈν•˜κ²Œ λ§ˆμΈλ“œλ§΅μ„ μƒμ„±ν•˜κ³ , λ‹€λ₯Έ μ‚¬μš©μžλ“€κ³Ό μ‹€μ‹œκ°„μœΌλ‘œ 곡동 νŽΈμ§‘μ„ 진행할 수 μžˆμ–΄μš”! +🐞 BugFix +- λ§ˆμΈλ“œλ§΅μ΄ λ‹€λ₯Έ λ³΄λ“œμ— λ³΅μ‚¬λ˜λŠ” ν˜„μƒ ν•΄κ²° \ No newline at end of file