diff --git a/app/build.gradle b/app/build.gradle index 81103d26..36b14651 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,6 +22,9 @@ android { buildConfigField "String", "DATABASE_URL", localProperties["database.url"] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" resConfigs "ko-rKR", "ko" + vectorDrawables { + useSupportLibrary true + } } buildTypes { release { @@ -46,9 +49,18 @@ android { composeOptions { kotlinCompilerExtensionVersion '1.4.7' } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } } dependencies { + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' + implementation platform('androidx.compose:compose-bom:2022.10.00') + implementation 'androidx.compose.ui:ui-graphics' + androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00') def nav_version = "2.5.3" def hilt_version = "2.44" def room_version = "2.4.3" @@ -109,7 +121,7 @@ dependencies { implementation "androidx.paging:paging-compose:$pagingComposeVersion" // Exoplayer - implementation 'com.google.android.exoplayer:exoplayer:2.18.1' + implementation 'com.google.android.exoplayer:exoplayer:2.18.7' // Coordinator-layout implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' // ffmpeg diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a25bc8ee..0fba92e4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,7 +46,7 @@ android:name=".presentation.uploadfilm.UploadFilmActivity" android:exported="false" /> diff --git a/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginActivity.kt b/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginActivity.kt index 20d5ee3d..41dfa99d 100644 --- a/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginActivity.kt @@ -33,7 +33,7 @@ class LoginActivity : BaseActivity(R.layout.activity_login override fun initView() { setGoogleLogin() - setObserveLoginResult() + //setObserveLoginResult() } override fun onStart() { @@ -52,7 +52,7 @@ class LoginActivity : BaseActivity(R.layout.activity_login } } - private fun setObserveLoginResult() { +/* private fun setObserveLoginResult() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collectLatest { state -> @@ -75,7 +75,7 @@ class LoginActivity : BaseActivity(R.layout.activity_login } } } - } + }*/ private fun showSnackBarMessage(message: String) { Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginComposeActivity.kt b/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginComposeActivity.kt new file mode 100644 index 00000000..92482fc6 --- /dev/null +++ b/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginComposeActivity.kt @@ -0,0 +1,228 @@ +package com.boostcamp.dailyfilm.presentation.login + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.boostcamp.dailyfilm.R +import com.boostcamp.dailyfilm.presentation.calendar.CalendarActivity +import com.boostcamp.dailyfilm.presentation.util.UiState +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInClient +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import com.google.android.gms.common.api.ApiException +import com.google.android.material.snackbar.Snackbar +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class LoginComposeActivity : ComponentActivity() { + private val viewModel by viewModels() + private lateinit var activityResultLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val options = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(getString(R.string.default_web_client_id)) + .requestEmail().build() + val client = GoogleSignIn.getClient(this, options) + + activityResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result: ActivityResult -> + if (result.resultCode == RESULT_OK) { + val task = GoogleSignIn.getSignedInAccountFromIntent(result.data) + try { + val account = task.getResult(ApiException::class.java) + viewModel.requestLogin(account.idToken!!) + } catch (e: ApiException) { + showSnackBarMessage(this, getString(R.string.failed_google_login)) + } + } + } + setContent { + LoginUI(viewModel, client, activityResultLauncher) + } + } +} + +@Composable +fun LoginUI( + viewModel: LoginViewModel, + client: GoogleSignInClient, + activityResultLauncher: ActivityResultLauncher +) { + val activity = LocalContext.current as Activity + + val uiState = viewModel.uiState.collectAsStateWithLifecycle() + var isLoading by rememberSaveable { + mutableStateOf(false) + } + LoginView(client, activityResultLauncher) + ProgressLoading(isLoading = isLoading) + when (val result = uiState.value.getContentIfNotHandled()) { + is UiState.Uninitialized -> { + autoLogin(activity) + } + + is UiState.Loading -> { + isLoading = true + } + + is UiState.Success -> { + isLoading = false + activity.startActivity( + Intent( + LocalContext.current, + CalendarActivity::class.java + ) + ) + activity.finish() + } + + is UiState.Failure -> { + isLoading = false + result.throwable.message?.let { + showSnackBarMessage(activity, it) + } + } + + else -> {} + } +} + +@Composable +private fun LoginView( + client: GoogleSignInClient, + activityResultLauncher: ActivityResultLauncher +) { + val backgroundColor = if (isSystemInDarkTheme()) { + colorResource(id = R.color.light_blue) + } else { + Color.White + } + val contentsColor = if (isSystemInDarkTheme()) { + Color.White + } else { + colorResource(id = R.color.dark_gray) + } + Column { + Logo() + LoginButton(activityResultLauncher, client, backgroundColor, contentsColor) + } +} + +@Composable +fun ProgressLoading(isLoading: Boolean) { + if (isLoading) { + Surface( + color = Color.Black.copy(alpha = 0.2f), + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.2f)) + + ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + CircularProgressIndicator( + Modifier.size(150.dp), + color = colorResource(id = R.color.dark_gray), + strokeWidth = 8.dp + ) + } + } + } +} + +@Composable +fun LoginButton( + activityResultLauncher: ActivityResultLauncher, + client: GoogleSignInClient, + backgroundColor: Color, + contentColor: Color +) { + + Button( + onClick = { + activityResultLauncher.launch(client.signInIntent) + }, + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .padding(start = 48.dp, end = 48.dp), + shape = RoundedCornerShape(6.dp), + colors = ButtonDefaults.buttonColors( + containerColor = backgroundColor, + contentColor = contentColor + ), + border = BorderStroke(2.dp, colorResource(id = R.color.dark_gray)) + ) { + Image( + painter = painterResource(id = R.drawable.btn_google_login), + contentDescription = stringResource(R.string.google_logo) + ) + Text(text = stringResource(R.string.sign_in_with_google), modifier = Modifier.padding(6.dp)) + } +} + +@Composable +fun Logo() { + Image( + painter = painterResource(id = R.mipmap.app_logo), + contentDescription = stringResource(R.string.dailyfilm_logo), + modifier = Modifier + .fillMaxWidth() + .height(350.dp), + alignment = Alignment.Center + ) + +} + +private fun autoLogin(content: Activity) { + GoogleSignIn.getLastSignedInAccount(content)?.let { + content.startActivity(Intent(content, CalendarActivity::class.java)) + content.finish() + } +} + +private fun showSnackBarMessage(context: Activity, message: String) { + Snackbar.make(context.findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show() +} \ No newline at end of file diff --git a/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginViewModel.kt b/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginViewModel.kt index 3265a2ff..a33e9c6b 100644 --- a/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginViewModel.kt +++ b/app/src/main/java/com/boostcamp/dailyfilm/presentation/login/LoginViewModel.kt @@ -4,10 +4,12 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.boostcamp.dailyfilm.data.login.LoginRepository import com.boostcamp.dailyfilm.data.model.Result +import com.boostcamp.dailyfilm.presentation.util.Event import com.boostcamp.dailyfilm.presentation.util.UiState import com.google.firebase.auth.FirebaseUser import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel @@ -15,20 +17,25 @@ class LoginViewModel @Inject constructor( private val loginRepository: LoginRepository ) : ViewModel() { - private val _uiState = MutableStateFlow>(UiState.Uninitialized) + private val _uiState = MutableStateFlow>>(Event(UiState.Uninitialized)) val uiState = _uiState.asStateFlow() fun requestLogin(idToken: String) { - _uiState.value = UiState.Loading + event(UiState.Loading) loginRepository.requestLogin(idToken).onEach { result -> when (result) { is Result.Success -> { - _uiState.value = UiState.Success(result.data) + event(UiState.Success(result.data)) } is Result.Error -> { - _uiState.value = UiState.Failure(result.exception) + event(UiState.Failure(result.exception)) } } }.launchIn(viewModelScope) } + private fun event(event: UiState) { + viewModelScope.launch { + _uiState.emit(Event(event)) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/boostcamp/dailyfilm/presentation/selectvideo/SelectVideoViewModel.kt b/app/src/main/java/com/boostcamp/dailyfilm/presentation/selectvideo/SelectVideoViewModel.kt index eb00283d..f377b27f 100644 --- a/app/src/main/java/com/boostcamp/dailyfilm/presentation/selectvideo/SelectVideoViewModel.kt +++ b/app/src/main/java/com/boostcamp/dailyfilm/presentation/selectvideo/SelectVideoViewModel.kt @@ -3,7 +3,6 @@ package com.boostcamp.dailyfilm.presentation.selectvideo import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.paging.Pager import androidx.paging.PagingData import androidx.paging.cachedIn import com.boostcamp.dailyfilm.data.model.VideoItem diff --git a/app/src/main/java/com/boostcamp/dailyfilm/presentation/settings/compose/SettingComposeActivity.kt b/app/src/main/java/com/boostcamp/dailyfilm/presentation/settings/compose/SettingComposeActivity.kt index 8c04405d..614e0125 100644 --- a/app/src/main/java/com/boostcamp/dailyfilm/presentation/settings/compose/SettingComposeActivity.kt +++ b/app/src/main/java/com/boostcamp/dailyfilm/presentation/settings/compose/SettingComposeActivity.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.boostcamp.dailyfilm.R import com.boostcamp.dailyfilm.presentation.login.LoginActivity +import com.boostcamp.dailyfilm.presentation.login.LoginComposeActivity import com.boostcamp.dailyfilm.presentation.settings.SettingsEvent import com.boostcamp.dailyfilm.presentation.settings.SettingsViewModel import com.boostcamp.dailyfilm.presentation.ui.theme.DailyFilmTheme @@ -212,7 +213,7 @@ fun navigateToLogin(context: Context) { context.startActivity( Intent( context, - LoginActivity::class.java, + LoginComposeActivity::class.java, ).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) }, diff --git a/app/src/main/res/drawable-night/btn_google_login.xml b/app/src/main/res/drawable-night/btn_google_login.xml new file mode 100644 index 00000000..5a47a395 --- /dev/null +++ b/app/src/main/res/drawable-night/btn_google_login.xml @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/btn_google_login.xml b/app/src/main/res/drawable/btn_google_login.xml new file mode 100644 index 00000000..b5d84dd1 --- /dev/null +++ b/app/src/main/res/drawable/btn_google_login.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 98324ed7..1172d847 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -15,7 +15,7 @@ Please select a date Video does not exist Your current device does not have a camera function + DailyFilm Logo + Google logo + Sign in with Google \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 1ac4e325..9b52d544 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -36,4 +36,7 @@ 日付を選択してください ビデオが存在しません 現在お使いのデバイスにはカメラ機能がありません。 + DailyFilm Logo + Google logo + Sign in with Google \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 97b07a36..1eea83cb 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -35,4 +35,7 @@ 请选择日期 视频不存在 您当前的设备没有相机功能 + DailyFilm Logo + Google logo + Sign in with Google \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 99574093..6dc696d2 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -9,6 +9,7 @@ #E1E1E1 #646464 #BBBBBB + #FF4285F4 #202022 #202022 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 552ad814..7f675de7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,10 +43,15 @@ 검색 범위 선택 검색 범위를 설정하세요. 설정 + 데일리필름 로고 + 구글 로고 + 구글계정으로 로그인하기 확인 취소 정말 로그아웃 하시겠습니까? 정말 탈퇴 하시겠습니까? 계정 설정 화면 + TotalComposeActivity + Failed Google Login \ No newline at end of file