diff --git a/.github/workflows/ucmcCI.yml b/.github/workflows/ucmcCI.yml index c07316de..3f1c4afe 100644 --- a/.github/workflows/ucmcCI.yml +++ b/.github/workflows/ucmcCI.yml @@ -22,6 +22,10 @@ jobs: run: chmod +x gradlew - name: Create google-service run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./data/google-services.json + - name: Create data dir + run: mkdir ./presentation/src/main/java/com/gta/presentation/secret + - name: Create Secrets + run: echo '${{ secrets.CONSTANTS }}' > ./presentation/src/main/java/com/gta/presentation/secret/Secrets.kt - name: Build with Gradle run: ./gradlew build - name: Build Signed APK diff --git a/.gitignore b/.gitignore index 90c042b3..01ee1747 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,4 @@ lint/tmp/ # Mac *.DS_Store +/presentation/src/main/java/com/gta/presentation/secret/Secrets.kt diff --git a/data/src/androidTest/java/com/gta/data/ExampleInstrumentedTest.kt b/data/src/androidTest/java/com/gta/data/ExampleInstrumentedTest.kt deleted file mode 100644 index cc0b4f0e..00000000 --- a/data/src/androidTest/java/com/gta/data/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.gta.data - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * 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.gta.data.test", appContext.packageName) - } -} diff --git a/presentation/build.gradle b/presentation/build.gradle index 8a541505..ee46fc8a 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -43,6 +43,11 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.7.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'com.shobhitpuri.custombuttons:google-signin:1.1.0' + + implementation platform('com.google.firebase:firebase-bom:31.0.2') + implementation 'com.google.firebase:firebase-auth-ktx' + implementation 'com.google.android.gms:play-services-auth:20.3.0' // Test testImplementation 'junit:junit:4.13.2' diff --git a/presentation/src/main/AndroidManifest.xml b/presentation/src/main/AndroidManifest.xml index c67e4368..731daca2 100644 --- a/presentation/src/main/AndroidManifest.xml +++ b/presentation/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ @@ -22,4 +22,4 @@ - \ No newline at end of file + diff --git a/presentation/src/main/java/com/gta/presentation/di/FirebaseAuthModule.kt b/presentation/src/main/java/com/gta/presentation/di/FirebaseAuthModule.kt new file mode 100644 index 00000000..3906a535 --- /dev/null +++ b/presentation/src/main/java/com/gta/presentation/di/FirebaseAuthModule.kt @@ -0,0 +1,20 @@ +package com.gta.presentation.di + +import com.google.firebase.auth.FirebaseAuth +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import java.util.Locale +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object FirebaseAuthModule { + + @Singleton + @Provides + fun provideFirebaseAuth() = FirebaseAuth.getInstance().apply { + setLanguageCode(Locale.getDefault().language) + } +} diff --git a/presentation/src/main/java/com/gta/presentation/di/FirebaseSigninModule.kt b/presentation/src/main/java/com/gta/presentation/di/FirebaseSigninModule.kt new file mode 100644 index 00000000..46b79ea8 --- /dev/null +++ b/presentation/src/main/java/com/gta/presentation/di/FirebaseSigninModule.kt @@ -0,0 +1,26 @@ +package com.gta.presentation.di + +import android.content.Context +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import com.gta.presentation.secret.FIREBASE_CLIENT_ID +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dagger.hilt.android.qualifiers.ActivityContext + +@Module +@InstallIn(ActivityComponent::class) +class FirebaseSigninModule { + @Provides + fun provideGoogleSignInOptions() = GoogleSignInOptions + .Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(FIREBASE_CLIENT_ID) + .requestEmail() + .build() + + @Provides + fun provideGoogleSignInClient(@ActivityContext context: Context, options: GoogleSignInOptions) = + GoogleSignIn.getClient(context, options) +} diff --git a/presentation/src/main/java/com/gta/presentation/ui/LoginActivity.kt b/presentation/src/main/java/com/gta/presentation/ui/LoginActivity.kt deleted file mode 100644 index 128bd1bd..00000000 --- a/presentation/src/main/java/com/gta/presentation/ui/LoginActivity.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.gta.presentation.ui - -import android.content.Intent -import android.os.Bundle -import com.gta.presentation.databinding.ActivityLoginBinding -import com.gta.presentation.ui.base.BaseActivity - -class LoginActivity : BaseActivity(ActivityLoginBinding::inflate) { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - startMainActivity() - } - - private fun startMainActivity() { - val intent = Intent(this, MainActivity::class.java) - startActivity(intent) - finish() - } -} diff --git a/presentation/src/main/java/com/gta/presentation/ui/login/LoginActivity.kt b/presentation/src/main/java/com/gta/presentation/ui/login/LoginActivity.kt new file mode 100644 index 00000000..ca54f4bd --- /dev/null +++ b/presentation/src/main/java/com/gta/presentation/ui/login/LoginActivity.kt @@ -0,0 +1,76 @@ +package com.gta.presentation.ui.login + +import android.content.Intent +import android.os.Bundle +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.google.android.gms.auth.api.Auth +import com.google.android.gms.auth.api.signin.GoogleSignInClient +import com.gta.presentation.databinding.ActivityLoginBinding +import com.gta.presentation.ui.MainActivity +import com.gta.presentation.ui.base.BaseActivity +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class LoginActivity : BaseActivity(ActivityLoginBinding::inflate) { + + @Inject + lateinit var googleSignInClient: GoogleSignInClient + + private val viewModel: LoginViewModel by viewModels() + + private val requestActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == RESULT_OK) { + val authIntent = it.data ?: return@registerForActivityResult + val account = Auth.GoogleSignInApi.getSignInResultFromIntent(authIntent)?.signInAccount + viewModel.signinWithToken(account?.idToken) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initCollector() + binding.btnLoginGoogle.setOnClickListener { + googleLogin() + } + } + + override fun onResume() { + super.onResume() + viewModel.checkLoginState() + } + + private fun initCollector() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.loginEvent.collectLatest { state -> + /* + 로그인에 성공 했다면 + 1. real time db에 회원정보가 저장되어있는지 확인 + 2. 있으면 바로 화면 전환 + 3. 없으면 db에 회원정보 생성 후 화면 전환 + */ + if (state) { + startMainActivity() + } + } + } + } + } + + private fun startMainActivity() { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + finish() + } + + private fun googleLogin() { + requestActivity.launch(googleSignInClient.signInIntent) + } +} diff --git a/presentation/src/main/java/com/gta/presentation/ui/login/LoginViewModel.kt b/presentation/src/main/java/com/gta/presentation/ui/login/LoginViewModel.kt new file mode 100644 index 00000000..fe1400da --- /dev/null +++ b/presentation/src/main/java/com/gta/presentation/ui/login/LoginViewModel.kt @@ -0,0 +1,43 @@ +package com.gta.presentation.ui.login + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.GoogleAuthProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class LoginViewModel @Inject constructor( + private val auth: FirebaseAuth +) : ViewModel() { + + private val _loginEvent = MutableSharedFlow() + val loginEvent: SharedFlow get() = _loginEvent + + fun checkLoginState() { + emitLoginEvent(auth.currentUser != null) + } + + fun signinWithToken(token: String?) { + token ?: return + val credential = GoogleAuthProvider.getCredential(token, null) + auth.signInWithCredential(credential).addOnCompleteListener { task -> + if (task.isSuccessful) { + emitLoginEvent(true) + } else { + Timber.e(task.exception) + } + } + } + + private fun emitLoginEvent(state: Boolean) { + viewModelScope.launch { + _loginEvent.emit(state) + } + } +} diff --git a/presentation/src/main/res/drawable/ic_logo.xml b/presentation/src/main/res/drawable/ic_logo.xml new file mode 100644 index 00000000..2e96ac45 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_logo.xml @@ -0,0 +1,9 @@ + + + diff --git a/presentation/src/main/res/layout/activity_login.xml b/presentation/src/main/res/layout/activity_login.xml index de4edd1e..2de709a1 100644 --- a/presentation/src/main/res/layout/activity_login.xml +++ b/presentation/src/main/res/layout/activity_login.xml @@ -4,6 +4,42 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.LoginActivity"> + tools:context=".ui.login.LoginActivity"> - \ No newline at end of file + + + + + + + +