Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
13 changes: 13 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'kotlin-parcelize'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'dagger.hilt.android.plugin'
Expand All @@ -22,6 +23,7 @@ android {
def localProperties = new Properties()
localProperties.load(rootProject.file('./local.properties').newDataInputStream())
buildConfigField("String", "BASE_URL", localProperties['baseUrl'])
buildConfigField("String", "WEB_CLIENT_ID", localProperties["webClientId"])
}

signingConfigs {
Expand Down Expand Up @@ -65,6 +67,7 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures {
viewBinding true
}
Expand All @@ -78,17 +81,27 @@ dependencies {

implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.activity:activity-ktx:1.4.0'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

// Lifecycle
def lifecycle_version = '2.4.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

// Firebase
implementation platform('com.google.firebase:firebase-bom:29.2.0')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-crashlytics-ktx'

// Google Login
implementation 'com.google.android.gms:play-services-auth:20.1.0'

// Hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
Expand Down
13 changes: 10 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.moyerun.moyeorun_android">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".MoyeoRunApplication"
Expand All @@ -11,16 +11,23 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MoyeoRunandroid">
android:theme="@style/Theme.MoyeoRunandroid"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:name=".profile.ui.ProfileEditActivity"
android:exported="false" />
<activity
android:name=".login.ui.LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="false" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.moyerun.moyeorun_android

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.moyerun.moyeorun_android.common

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer

typealias EventLiveData<T> = LiveData<Event<T>>

class MutableEventLiveData<T> : MutableLiveData<Event<T>>() {

var event: T?
@Deprecated("getter is NOT supported!", level = DeprecationLevel.ERROR)
get() = throw UnsupportedOperationException()
set(value) {
if (value != null) {
setValue(Event(value))
}
}

fun postEvent(value: T?) {
if (value != null) {
postValue(Event(value))
}
}
}

/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {

var hasBeenHandled = false
private set // Allow external read but not write

/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}

/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}

/**
* An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
* already been handled.
*
* [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
*/
class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
override fun onChanged(event: Event<T>?) {
event?.getContentIfNotHandled()?.let { value ->
onEventUnhandledContent(value)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.moyerun.moyeorun_android.common.di

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import javax.inject.Qualifier

@Qualifier
annotation class IODispatcher

@Qualifier
annotation class DefaultDispatcher

@InstallIn(SingletonComponent::class)
@Module
object CoroutineDispatcherModule {

@IODispatcher
@Provides
fun provideIODispatcher(): CoroutineDispatcher = Dispatchers.IO

@DefaultDispatcher
@Provides
fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import android.view.Window
import androidx.fragment.app.DialogFragment
import com.moyerun.moyeorun_android.common.extension.isActivityDestroyed

open class RoundDialogFragment : DialogFragment() {
abstract class RoundDialogFragment : DialogFragment() {

private var dismissOnPause = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package com.moyerun.moyeorun_android.common.exceptions

import com.moyerun.moyeorun_android.network.api.Error

class ApiException(val url: String, val error: Error) : RuntimeException() {
open class ApiException(val url: String, val error: Error) : RuntimeException() {
val case: String
get() = error.case

override val message: String?
get() = error.message

override fun toString(): String {
return "${super.toString()} url: $url, error: $error"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import android.app.Activity
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.activity.ComponentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.moyerun.moyeorun_android.R
import com.moyerun.moyeorun_android.common.EventLiveData
import com.moyerun.moyeorun_android.common.EventObserver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

fun Activity.toast(msg: String, isShort: Boolean = true) {
Toast.makeText(this, msg, if (isShort) Toast.LENGTH_SHORT else Toast.LENGTH_LONG).show()
Expand All @@ -14,4 +23,19 @@ inline fun FragmentActivity.showAllowingStateLoss(
dialogFragmentFactory: () -> DialogFragment
) {
supportFragmentManager.showAllowingStateLoss(tag, dialogFragmentFactory)
}

fun Activity.showNetworkErrorToast() {
toast(getString(R.string.toast_network_error))
}

fun ComponentActivity.repeatOnStart(block: suspend CoroutineScope.() -> Unit) {
lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED, block) }
}

fun <T> ComponentActivity.observeEvent(
event: EventLiveData<T>,
onEventUnhandledContent: (T) -> Unit
) {
event.observe(this, EventObserver(onEventUnhandledContent))
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fun FirebaseCrashlytics.recordException(logLevel: LogLevel, message: String, thr
class FirebaseLogException : Exception {
constructor(logLevel: LogLevel, message: String) : super("$logLevel : $message")

constructor(logLevel: LogLevel, cause: Throwable) : super(logLevel.toString(), cause)
constructor(logLevel: LogLevel, cause: Throwable) : super("$logLevel : $cause", cause)

constructor(
logLevel: LogLevel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.moyerun.moyeorun_android.R
import com.moyerun.moyeorun_android.common.EventLiveData
import com.moyerun.moyeorun_android.common.EventObserver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

fun Fragment.toast(msg: String, isShort: Boolean = false) {
Toast.makeText(context, msg, if (isShort) Toast.LENGTH_SHORT else Toast.LENGTH_LONG).show()
Expand All @@ -19,4 +27,18 @@ inline fun FragmentManager?.showAllowingStateLoss(
val transaction = beginTransaction()
transaction.add(dialogFragmentFactory(), tag)
transaction.commitAllowingStateLoss()
}

fun Fragment.showNetworkErrorToast() {
toast(getString(R.string.toast_network_error))
}

fun Fragment.repeatOnStart(block: suspend CoroutineScope.() -> Unit) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED, block)
}
}

fun <T> Fragment.observeEvent(event: EventLiveData<T>, onEventUnhandledContent: (T) -> Unit) {
event.observe(viewLifecycleOwner, EventObserver(onEventUnhandledContent))
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.moyerun.moyeorun_android.common.extension

import android.view.View
import android.widget.RadioButton
import android.widget.TextView

fun View.setOnDebounceClickListener(interval: Long = 1000L, action: (View?) -> Unit) {
val debounceClickListener = object : View.OnClickListener {
Expand All @@ -16,4 +18,17 @@ fun View.setOnDebounceClickListener(interval: Long = 1000L, action: (View?) -> U
}
}
setOnClickListener(debounceClickListener)
}

fun TextView.setTextIfNew(text: CharSequence?) {
if (this.text.contentEquals(text).not()) {
setText(text)
}
}

fun RadioButton.setCheckIfNew(check: Boolean) {
val oldValue = isChecked
if (oldValue != check) {
isChecked = check
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.moyerun.moyeorun_android.login

import com.moyerun.moyeorun_android.common.exceptions.ApiException
import com.moyerun.moyeorun_android.network.api.Error

class AuthException(url: String, error: Error): ApiException(url, error)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.moyerun.moyeorun_android.login

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
enum class ProviderType : Parcelable {
GOOGLE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.moyerun.moyeorun_android.login

import android.os.Parcelable
import com.moyerun.moyeorun_android.login.ProviderType
import kotlinx.parcelize.Parcelize

/**
* 회원가입을 위해 프로필 설정 화면으로 진입 시
* 회원가입 API 호출에 필요한 메타 데이터를 전달하기 위한
* DataHolder
*/
@Parcelize
data class SignUpMetaData(
val idToken: String,
val providerType: ProviderType
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.moyerun.moyeorun_android.login.data

import com.moyerun.moyeorun_android.login.data.impl.AuthRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent

@Module
@InstallIn(ViewModelComponent::class)
abstract class AuthModule {

@Binds
abstract fun bindsAuthRepository(repository: AuthRepositoryImpl): AuthRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.moyerun.moyeorun_android.login.data

import com.moyerun.moyeorun_android.login.ProviderType
import com.moyerun.moyeorun_android.login.data.model.SignInResponse
import com.moyerun.moyeorun_android.login.data.model.SignUpRequest
import com.moyerun.moyeorun_android.network.api.Success
import com.moyerun.moyeorun_android.network.calladapter.ApiResult

interface AuthRepository {
suspend fun signIn(
idToken: String,
providerType: ProviderType
): ApiResult<SignInResponse>

suspend fun signUp(signUpRequest: SignUpRequest): ApiResult<SignInResponse>
}
Loading