diff --git a/.gitignore b/.gitignore index 63ceb23..27581ee 100644 --- a/.gitignore +++ b/.gitignore @@ -192,4 +192,5 @@ fabric.properties !/gradle/wrapper/gradle-wrapper.jar -# End of https://www.toptal.com/developers/gitignore/api/androidstudio,macos,windows \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/androidstudio,macos,windows +.idea/deploymentTargetDropDown.xml diff --git a/app/build.gradle b/app/build.gradle index e4704cb..0405c12 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,17 +44,30 @@ android { dependencies { implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'com.google.code.gson:gson:2.10.1' - testImplementation 'junit:junit:4.13.2' + implementation 'androidx.activity:activity-ktx:1.2.2' + implementation 'androidx.fragment:fragment-ktx:1.5.4' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + testImplementation 'junit:junit:4.13.2' + implementation "com.google.dagger:hilt-android:2.44" kapt "com.google.dagger:hilt-compiler:2.44" - implementation 'androidx.activity:activity-ktx:1.2.2' - implementation 'androidx.fragment:fragment-ktx:1.3.3' + + implementation 'com.google.android.material:material:1.5.0' + implementation 'com.google.code.gson:gson:2.10.1' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1' + + //room + implementation 'androidx.room:room-runtime:2.4.3' + annotationProcessor 'androidx.room:room-compiler:2.4.3' + kapt 'androidx.room:room-compiler:2.4.3' + implementation 'androidx.room:room-ktx:2.4.3' + + //coil + implementation 'io.coil-kt:coil:2.4.0' } kapt { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0d9b3d2..3bf89e4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + + @Insert + suspend fun addFriend(friendInfoEntity: FriendInfoEntity) + + @Insert + suspend fun addFriends(friendList: List) + + @Delete + suspend fun deleteFriend(friendInfoEntity: FriendInfoEntity) + + @Query("DELETE FROM table_friend_info WHERE id = :id") + suspend fun deleteFriendById(id: Int) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/data/datasource/FriendLocalDataSourceImpl.kt b/app/src/main/java/org/sopt/dosopttemplate/data/datasource/FriendLocalDataSourceImpl.kt new file mode 100644 index 0000000..4293f44 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/data/datasource/FriendLocalDataSourceImpl.kt @@ -0,0 +1,32 @@ +package org.sopt.dosopttemplate.data.datasource + +import org.sopt.dosopttemplate.data.database.FriendDataBase +import org.sopt.dosopttemplate.data.local.FriendLocalDataSource +import org.sopt.dosopttemplate.data.model.local.FriendInfoEntity +import javax.inject.Inject + +class FriendLocalDataSourceImpl @Inject constructor( + private val friendDataBase: FriendDataBase +) : FriendLocalDataSource { + override suspend fun getAll(): List { + return friendDataBase.friendInfoDao().getAll() + } + + override suspend fun addFriend(friendInfoEntity: FriendInfoEntity) { + friendDataBase.friendInfoDao().addFriend(friendInfoEntity) + } + + override suspend fun addFriends(friendList: List) { + friendDataBase.friendInfoDao().addFriends(friendList) + } + + override suspend fun deleteFriend(friendInfoEntity: FriendInfoEntity) { + friendDataBase.friendInfoDao().deleteFriend(friendInfoEntity) + } + + override suspend fun deleteFriendById(id: Int) { + friendDataBase.friendInfoDao().deleteFriendById(id) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/data/datasource/SharedPrefDataSourceImpl.kt b/app/src/main/java/org/sopt/dosopttemplate/data/datasource/SharedPrefDataSourceImpl.kt index a853b49..bdeb9e9 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/data/datasource/SharedPrefDataSourceImpl.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/data/datasource/SharedPrefDataSourceImpl.kt @@ -3,8 +3,8 @@ package org.sopt.dosopttemplate.data.datasource import android.content.SharedPreferences import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import org.sopt.dosopttemplate.data.model.UserDto import org.sopt.dosopttemplate.data.local.SharedPrefDataSource +import org.sopt.dosopttemplate.data.model.local.UserDto import javax.inject.Inject class SharedPrefDataSourceImpl @Inject constructor( diff --git a/app/src/main/java/org/sopt/dosopttemplate/data/local/FriendLocalDataSource.kt b/app/src/main/java/org/sopt/dosopttemplate/data/local/FriendLocalDataSource.kt new file mode 100644 index 0000000..81da9cc --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/data/local/FriendLocalDataSource.kt @@ -0,0 +1,11 @@ +package org.sopt.dosopttemplate.data.local + +import org.sopt.dosopttemplate.data.model.local.FriendInfoEntity + +interface FriendLocalDataSource { + suspend fun getAll(): List + suspend fun addFriend(friendInfoEntity: FriendInfoEntity) + suspend fun addFriends(friendList: List) + suspend fun deleteFriend(friendInfoEntity: FriendInfoEntity) + suspend fun deleteFriendById(id: Int) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/data/local/SharedPrefDataSource.kt b/app/src/main/java/org/sopt/dosopttemplate/data/local/SharedPrefDataSource.kt index a2085d4..03385cf 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/data/local/SharedPrefDataSource.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/data/local/SharedPrefDataSource.kt @@ -1,6 +1,6 @@ package org.sopt.dosopttemplate.data.local -import org.sopt.dosopttemplate.data.model.UserDto +import org.sopt.dosopttemplate.data.model.local.UserDto interface SharedPrefDataSource { fun saveUserInfo(userDto: UserDto?) diff --git a/app/src/main/java/org/sopt/dosopttemplate/data/model/local/FriendInfoEntity.kt b/app/src/main/java/org/sopt/dosopttemplate/data/model/local/FriendInfoEntity.kt new file mode 100644 index 0000000..eff13fe --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/data/model/local/FriendInfoEntity.kt @@ -0,0 +1,40 @@ +package org.sopt.dosopttemplate.data.model.local + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.sopt.dosopttemplate.domain.entity.Friend +import java.time.LocalDate + +@Entity(tableName = "table_friend_info") +data class FriendInfoEntity( + @PrimaryKey(autoGenerate = true) + val id: Int?, + val name: String, + val birthday: String, + val music: String?, + @ColumnInfo(name = "image_uri") + val imageUri: String?, +) { + companion object { + fun toFriend(friendInfoList: List) = friendInfoList.map { data -> + Friend( + id = data.id, + name = data.name, + birthday = LocalDate.parse(data.birthday), + music = data.music, + imageUri = data.imageUri + ) + } + + fun toFriendInfoEntity(friendList: List) = friendList.map { data -> + FriendInfoEntity( + id = data.id, + name = data.name, + birthday = data.birthday.toString(), + music = data.music, + imageUri = data.imageUri + ) + } + } +} diff --git a/app/src/main/java/org/sopt/dosopttemplate/data/model/UserDto.kt b/app/src/main/java/org/sopt/dosopttemplate/data/model/local/UserDto.kt similarity index 71% rename from app/src/main/java/org/sopt/dosopttemplate/data/model/UserDto.kt rename to app/src/main/java/org/sopt/dosopttemplate/data/model/local/UserDto.kt index b14d388..17c5c34 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/data/model/UserDto.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/data/model/local/UserDto.kt @@ -1,4 +1,4 @@ -package org.sopt.dosopttemplate.data.model +package org.sopt.dosopttemplate.data.model.local import kotlinx.serialization.Serializable import org.sopt.dosopttemplate.domain.entity.User @@ -8,13 +8,13 @@ data class UserDto( val id: String? = "", val pw: String? = "", val nickname: String? = "", - val hobby: String? = "" + val discription: String? = "" ) { fun toUser(): User? { - return User(id, pw, nickname, hobby) + return User(id, pw, nickname, discription) } fun toUserDto(user: User?): UserDto { - return UserDto(user?.id, user?.pw, user?.nickname, user?.hobby) + return UserDto(user?.id, user?.pw, user?.nickname, user?.discription) } } diff --git a/app/src/main/java/org/sopt/dosopttemplate/data/repository/FriendLocalRepositoryImpl.kt b/app/src/main/java/org/sopt/dosopttemplate/data/repository/FriendLocalRepositoryImpl.kt new file mode 100644 index 0000000..cb8783e --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/data/repository/FriendLocalRepositoryImpl.kt @@ -0,0 +1,33 @@ +package org.sopt.dosopttemplate.data.repository + +import org.sopt.dosopttemplate.data.local.FriendLocalDataSource +import org.sopt.dosopttemplate.data.model.local.FriendInfoEntity.Companion.toFriend +import org.sopt.dosopttemplate.data.model.local.FriendInfoEntity.Companion.toFriendInfoEntity +import org.sopt.dosopttemplate.domain.entity.Friend +import org.sopt.dosopttemplate.domain.repository.FriendLocalRepository +import javax.inject.Inject + +class FriendLocalRepositoryImpl @Inject constructor( + private val friendLocalDataSource: FriendLocalDataSource +) : FriendLocalRepository { + override suspend fun getAll(): Result> = + runCatching { + toFriend(friendLocalDataSource.getAll()) + } + + override suspend fun addFriend(friend: Friend) { + friendLocalDataSource.addFriend(toFriendInfoEntity(listOf(friend)).first()) + } + + override suspend fun addFriends(friendList: List) { + friendLocalDataSource.addFriends(toFriendInfoEntity(friendList)) + } + + override suspend fun deleteFriend(friend: Friend) { + friendLocalDataSource.deleteFriend(toFriendInfoEntity(listOf(friend)).first()) + } + + override suspend fun deleteFriendById(id: Int) { + friendLocalDataSource.deleteFriendById(id) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/data/repository/SharedPrefRepositoryImpl.kt b/app/src/main/java/org/sopt/dosopttemplate/data/repository/SharedPrefRepositoryImpl.kt index 1ac3bbd..a15bb8a 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/data/repository/SharedPrefRepositoryImpl.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/data/repository/SharedPrefRepositoryImpl.kt @@ -1,7 +1,7 @@ package org.sopt.dosopttemplate.data.repository -import org.sopt.dosopttemplate.data.model.UserDto import org.sopt.dosopttemplate.data.local.SharedPrefDataSource +import org.sopt.dosopttemplate.data.model.local.UserDto import org.sopt.dosopttemplate.domain.entity.User import org.sopt.dosopttemplate.domain.repository.SharedPrefRepository import javax.inject.Inject diff --git a/app/src/main/java/org/sopt/dosopttemplate/di/DataBaseModule.kt b/app/src/main/java/org/sopt/dosopttemplate/di/DataBaseModule.kt new file mode 100644 index 0000000..27abe04 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/di/DataBaseModule.kt @@ -0,0 +1,27 @@ +package org.sopt.dosopttemplate.di + +import android.content.Context +import androidx.room.Room +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.sopt.dosopttemplate.data.database.FriendDataBase +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DataBaseModule { + @Singleton + @Provides + fun provideDataBase( + @ApplicationContext context: Context + ): FriendDataBase = + Room.databaseBuilder(context, FriendDataBase::class.java, "sopt_friend.db") + .createFromAsset("databases/sopt_friend.db").build() + + @Singleton + @Provides + fun provideFriendDao(friendDataBase: FriendDataBase) = friendDataBase.friendInfoDao() +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/di/DataSourceModule.kt b/app/src/main/java/org/sopt/dosopttemplate/di/DataSourceModule.kt index 9c6c21d..0d2bbbf 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/di/DataSourceModule.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/di/DataSourceModule.kt @@ -4,7 +4,9 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import org.sopt.dosopttemplate.data.datasource.FriendLocalDataSourceImpl import org.sopt.dosopttemplate.data.datasource.SharedPrefDataSourceImpl +import org.sopt.dosopttemplate.data.local.FriendLocalDataSource import org.sopt.dosopttemplate.data.local.SharedPrefDataSource import javax.inject.Singleton @@ -14,4 +16,8 @@ abstract class DataSourceModule { @Singleton @Binds abstract fun bindsSharedPrefDataSource(sharedPrefDataSource: SharedPrefDataSourceImpl): SharedPrefDataSource + + @Singleton + @Binds + abstract fun bindsFriendLocalDataSource(friendLocalDataSource: FriendLocalDataSourceImpl): FriendLocalDataSource } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/di/RepositoryModule.kt b/app/src/main/java/org/sopt/dosopttemplate/di/RepositoryModule.kt index fe726f4..8866b42 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/di/RepositoryModule.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/di/RepositoryModule.kt @@ -4,7 +4,9 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import org.sopt.dosopttemplate.data.repository.FriendLocalRepositoryImpl import org.sopt.dosopttemplate.data.repository.SharedPrefRepositoryImpl +import org.sopt.dosopttemplate.domain.repository.FriendLocalRepository import org.sopt.dosopttemplate.domain.repository.SharedPrefRepository import javax.inject.Singleton @@ -16,4 +18,10 @@ abstract class RepositoryModule { abstract fun bindsSharedPrefRepository( sharedPrefRepository: SharedPrefRepositoryImpl ): SharedPrefRepository + + @Singleton + @Binds + abstract fun bindsFriendLocalRepository( + friendLocalRepository: FriendLocalRepositoryImpl + ): FriendLocalRepository } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/domain/entity/Friend.kt b/app/src/main/java/org/sopt/dosopttemplate/domain/entity/Friend.kt new file mode 100644 index 0000000..9dd7ec3 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/domain/entity/Friend.kt @@ -0,0 +1,11 @@ +package org.sopt.dosopttemplate.domain.entity + +import java.time.LocalDate + +data class Friend( + val id: Int?, + val name: String, + val birthday: LocalDate?, + val music: String?, + val imageUri: String?, +) diff --git a/app/src/main/java/org/sopt/dosopttemplate/domain/entity/User.kt b/app/src/main/java/org/sopt/dosopttemplate/domain/entity/User.kt index 6e95e5f..177b81a 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/domain/entity/User.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/domain/entity/User.kt @@ -4,5 +4,5 @@ data class User( val id: String? = "", val pw: String? = "", val nickname: String? = "", - val hobby: String? = "" + val discription: String? = "" ) diff --git a/app/src/main/java/org/sopt/dosopttemplate/domain/repository/FriendLocalRepository.kt b/app/src/main/java/org/sopt/dosopttemplate/domain/repository/FriendLocalRepository.kt new file mode 100644 index 0000000..75bb5ed --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/domain/repository/FriendLocalRepository.kt @@ -0,0 +1,11 @@ +package org.sopt.dosopttemplate.domain.repository + +import org.sopt.dosopttemplate.domain.entity.Friend + +interface FriendLocalRepository { + suspend fun getAll(): Result> + suspend fun addFriend(friend: Friend) + suspend fun addFriends(friendList: List) + suspend fun deleteFriend(friend: Friend) + suspend fun deleteFriendById(id: Int) +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/MainActivity.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/MainActivity.kt index 67d1390..dc285e6 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/MainActivity.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/MainActivity.kt @@ -4,16 +4,18 @@ import android.content.Intent import android.os.Bundle import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import androidx.fragment.app.replace import dagger.hilt.android.AndroidEntryPoint import org.sopt.dosopttemplate.R import org.sopt.dosopttemplate.databinding.ActivityMainBinding import org.sopt.dosopttemplate.presentation.login.LoginActivity -import org.sopt.dosopttemplate.presentation.main.MainViewModel.Companion.CODE_LOGOUT -import org.sopt.dosopttemplate.presentation.main.MainViewModel.Companion.CODE_WITHDRAW -import org.sopt.dosopttemplate.presentation.model.UserModel +import org.sopt.dosopttemplate.presentation.main.doandroid.DoAndroidFragment +import org.sopt.dosopttemplate.presentation.main.home.HomeFragment +import org.sopt.dosopttemplate.presentation.main.mypage.MyPageFragment import org.sopt.dosopttemplate.util.binding.BindingActivity import org.sopt.dosopttemplate.util.context.toast -import org.sopt.dosopttemplate.util.intent.getParcelable import org.sopt.dosopttemplate.util.view.UiState import org.sopt.dosopttemplate.util.view.snackBar @@ -22,12 +24,11 @@ class MainActivity : BindingActivity(R.layout.activity_main val viewModel by viewModels() var backPressedTime = 0L val callback = object : OnBackPressedCallback(true) { - //뒤로가기 버튼 콜백 override fun handleOnBackPressed() { - if (System.currentTimeMillis() - backPressedTime >= MIN_TOUCH_DURATION) {//버튼 클릭 딜레이 1500 이상일때 + if (System.currentTimeMillis() - backPressedTime >= MIN_TOUCH_DURATION) { backPressedTime = System.currentTimeMillis() binding.root.snackBar { getString(R.string.main_onbackpressed) } - } else {//미만일때 + } else { finish() } } @@ -36,34 +37,19 @@ class MainActivity : BindingActivity(R.layout.activity_main override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - getUserInfo() - initLogoutButtonClickListener() - initWithdrawButtonClickListener() - initLogoutStateObserver() initAddCallback() + initFragment() + initBnvItemSelectedListener() + initLogoutStateObserver() } private fun initAddCallback() { - this.onBackPressedDispatcher.addCallback(this, callback)//콜백 등록 - } - - private fun getUserInfo() { - val intent = intent - val userModel = intent.getParcelable(USER_KEY, UserModel::class.java) - - binding.data = userModel - } - - private fun initLogoutButtonClickListener() { - binding.btnMainLogout.setOnClickListener { - viewModel.logout() - } + this.onBackPressedDispatcher.addCallback(this, callback) } - private fun initWithdrawButtonClickListener() { - binding.btnMainWithdraw.setOnClickListener { - viewModel.withdraw() - } + private fun initFragment() { + navigateTo() + binding.bnvMain.selectedItemId = R.id.menu_home } private fun initLogoutStateObserver() { @@ -71,8 +57,8 @@ class MainActivity : BindingActivity(R.layout.activity_main when (state) { is UiState.Success -> { when (state.data) { - CODE_LOGOUT -> this.toast(getString(R.string.main_success_logout)) - CODE_WITHDRAW -> this.toast(getString(R.string.main_success_withdraw)) + MainViewModel.CODE_LOGOUT -> this.toast(getString(R.string.main_success_logout)) + MainViewModel.CODE_WITHDRAW -> this.toast(getString(R.string.main_success_withdraw)) } navigateToLoginScreen() } @@ -81,15 +67,29 @@ class MainActivity : BindingActivity(R.layout.activity_main } } } - private fun navigateToLoginScreen() { val intent = Intent(this, LoginActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK startActivity(intent) } + private fun initBnvItemSelectedListener() { + binding.bnvMain.setOnItemSelectedListener { item -> + when(item.itemId) { + R.id.menu_do_android -> navigateTo() + R.id.menu_home -> navigateTo() + R.id.menu_my_page -> navigateTo() + } + true + } + } + + private inline fun navigateTo() { + supportFragmentManager.commit { + replace(R.id.fcv_main, T::class.simpleName) + } + } companion object { private const val MIN_TOUCH_DURATION = 1500 - private const val USER_KEY = "user" } } \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/MainViewModel.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/MainViewModel.kt index a608e42..519e4c2 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/MainViewModel.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/MainViewModel.kt @@ -16,13 +16,23 @@ class MainViewModel @Inject constructor( val logoutState: LiveData> get() = _logoutState fun logout() { - sharedPrefRepository.clearAutoLogin() - _logoutState.value = UiState.Success(CODE_LOGOUT) + runCatching { + sharedPrefRepository.clearAutoLogin() + }.onSuccess { + _logoutState.value = UiState.Success(CODE_LOGOUT) + }.onFailure { t -> + _logoutState.value = UiState.Failure("${t.message}") + } } fun withdraw() { - sharedPrefRepository.clearSharedPref() - _logoutState.value = UiState.Success(CODE_WITHDRAW) + runCatching { + sharedPrefRepository.clearSharedPref() + }.onSuccess { + _logoutState.value = UiState.Success(CODE_WITHDRAW) + }.onFailure { t -> + _logoutState.value = UiState.Failure("${t.message}") + } } companion object { diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/doandroid/DoAndroidFragment.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/doandroid/DoAndroidFragment.kt new file mode 100644 index 0000000..72c8902 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/doandroid/DoAndroidFragment.kt @@ -0,0 +1,10 @@ +package org.sopt.dosopttemplate.presentation.main.doandroid + +import dagger.hilt.android.AndroidEntryPoint +import org.sopt.dosopttemplate.R +import org.sopt.dosopttemplate.databinding.FragmentDoandroidBinding +import org.sopt.dosopttemplate.util.binding.BindingFragment + +@AndroidEntryPoint +class DoAndroidFragment : BindingFragment(R.layout.fragment_doandroid) { +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeAdapter.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeAdapter.kt new file mode 100644 index 0000000..da32b49 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeAdapter.kt @@ -0,0 +1,90 @@ +package org.sopt.dosopttemplate.presentation.main.home + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import org.sopt.dosopttemplate.databinding.ItemHomeBirthdayBinding +import org.sopt.dosopttemplate.databinding.ItemHomeHeaderBinding +import org.sopt.dosopttemplate.databinding.ItemHomePostBinding +import org.sopt.dosopttemplate.presentation.model.HomeModel +import org.sopt.dosopttemplate.presentation.model.HomeModel.FriendInfoModel.Companion.BIRTHDAY_FRIEND_VIEW_TYPE +import org.sopt.dosopttemplate.presentation.model.HomeModel.FriendInfoModel.Companion.NORMAL_FRIEND_VIEW_TYPE +import org.sopt.dosopttemplate.presentation.model.HomeModel.MyInfoModel.Companion.MY_INFO_VIEW_TYPE +import org.sopt.dosopttemplate.util.view.ItemDiffCallback +import java.time.LocalDate + +class HomeAdapter : ListAdapter(DiffUtil) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeViewHolder { + return when (viewType) { + MY_INFO_VIEW_TYPE -> { + HomeViewHolder.MyInfoViewHolder( + ItemHomeHeaderBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + NORMAL_FRIEND_VIEW_TYPE -> { + HomeViewHolder.FriendViewHolder( + ItemHomePostBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + BIRTHDAY_FRIEND_VIEW_TYPE -> { + HomeViewHolder.BirthdayViewHolder( + ItemHomeBirthdayBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + else -> throw IllegalArgumentException(UNKNOWN_TYPE_EXCEPTION) + } + } + + override fun onBindViewHolder(holder: HomeViewHolder, position: Int) { + when (holder) { + is HomeViewHolder.FriendViewHolder -> holder.onBind(currentList[position] as HomeModel.FriendInfoModel) + is HomeViewHolder.BirthdayViewHolder -> holder.onBind(currentList[position] as HomeModel.FriendInfoModel) + is HomeViewHolder.MyInfoViewHolder -> holder.onBind(currentList[position] as HomeModel.MyInfoModel) + } + } + + override fun getItemViewType(position: Int): Int { + return when (currentList[position]) { + is HomeModel.MyInfoModel -> { + MY_INFO_VIEW_TYPE + } + + is HomeModel.FriendInfoModel -> { + val friendInfoModel = currentList[position] as HomeModel.FriendInfoModel + if (isBirthday(friendInfoModel.birthday)) { + BIRTHDAY_FRIEND_VIEW_TYPE + } else { + NORMAL_FRIEND_VIEW_TYPE + } + } + } + } + + private fun isBirthday(birthday: LocalDate?) = + (birthday?.monthValue == LocalDate.now().monthValue && birthday.dayOfMonth == LocalDate.now().dayOfMonth) + + + companion object { + private val DiffUtil = ItemDiffCallback( + onItemsTheSame = { old, new -> old.id == new.id }, + onContentsTheSame = { old, new -> old == new } + ) + private const val UNKNOWN_TYPE_EXCEPTION = "UNKNOWN_TYPE" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeFragment.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeFragment.kt new file mode 100644 index 0000000..5a9be19 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeFragment.kt @@ -0,0 +1,72 @@ +package org.sopt.dosopttemplate.presentation.main.home + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.viewModels +import dagger.hilt.android.AndroidEntryPoint +import org.sopt.dosopttemplate.R +import org.sopt.dosopttemplate.databinding.FragmentHomeBinding +import org.sopt.dosopttemplate.presentation.model.HomeModel +import org.sopt.dosopttemplate.presentation.model.UserModel +import org.sopt.dosopttemplate.util.binding.BindingFragment +import org.sopt.dosopttemplate.util.intent.getParcelable +import org.sopt.dosopttemplate.util.view.UiState +import org.sopt.dosopttemplate.util.view.snackBar + +@AndroidEntryPoint +class HomeFragment : BindingFragment(R.layout.fragment_home) { + private val viewModel by viewModels() + lateinit var myInfo: HomeModel.MyInfoModel + lateinit var homeAdapter: HomeAdapter + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initHomeAdapter() + initFriendListStateObserver() + getMyInfo() + getFriendList() + } + + private fun initHomeAdapter() { + runCatching { homeAdapter = HomeAdapter() + binding.rvHome.adapter = homeAdapter }.onFailure { t-> + if(t is IllegalArgumentException){ + t.printStackTrace() + } + } + } + + private fun initFriendListStateObserver() { + viewModel.friendListState.observe(viewLifecycleOwner) { state -> + when (state) { + is UiState.Success -> { + submitHomeList(state) + } + + is UiState.Failure -> { + binding.root.snackBar { state.msg } + } + else -> {} + } + } + } + + private fun submitHomeList(state: UiState.Success>) { + val list = listOf(myInfo) + state.data.sortedBy { it.name } + homeAdapter.submitList(list) + } + + private fun getMyInfo() { + val userModel = requireActivity().intent.getParcelable(USER_KEY, UserModel::class.java) + val myInformation = HomeModel.MyInfoModel(0, userModel?.nickname, userModel?.discription) + myInfo = myInformation + } + + private fun getFriendList() { + viewModel.getFriendList() + } + + companion object { + private const val USER_KEY = "user" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeViewHolder.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeViewHolder.kt new file mode 100644 index 0000000..c614abe --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeViewHolder.kt @@ -0,0 +1,34 @@ +package org.sopt.dosopttemplate.presentation.main.home + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import org.sopt.dosopttemplate.databinding.ItemHomeBirthdayBinding +import org.sopt.dosopttemplate.databinding.ItemHomeHeaderBinding +import org.sopt.dosopttemplate.databinding.ItemHomePostBinding +import org.sopt.dosopttemplate.presentation.model.HomeModel + +sealed class HomeViewHolder(view: View) : RecyclerView.ViewHolder(view) { + class MyInfoViewHolder( + private val binding: ItemHomeHeaderBinding + ) : HomeViewHolder(binding.root) { + fun onBind(data: HomeModel.MyInfoModel) { + binding.data = data + } + } + + class FriendViewHolder( + private val binding: ItemHomePostBinding + ) : HomeViewHolder(binding.root) { + fun onBind(data: HomeModel.FriendInfoModel) { + binding.data = data + } + } + + class BirthdayViewHolder( + private val binding: ItemHomeBirthdayBinding + ) : HomeViewHolder(binding.root) { + fun onBind(data: HomeModel.FriendInfoModel) { + binding.data = data + } + } +} diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeViewModel.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeViewModel.kt new file mode 100644 index 0000000..0589689 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/home/HomeViewModel.kt @@ -0,0 +1,34 @@ +package org.sopt.dosopttemplate.presentation.main.home + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import org.sopt.dosopttemplate.domain.repository.FriendLocalRepository +import org.sopt.dosopttemplate.presentation.model.HomeModel +import org.sopt.dosopttemplate.presentation.model.HomeModel.FriendInfoModel.Companion.toFriendInfoModel +import org.sopt.dosopttemplate.util.view.UiState +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val friendLocalRepository: FriendLocalRepository +) : ViewModel() { + private val _friendListState = + MutableLiveData>>(UiState.Empty) + val friendListState: LiveData>> get() = _friendListState + + fun getFriendList() { + viewModelScope.launch { + friendLocalRepository.getAll() + .onSuccess { response -> + _friendListState.value = UiState.Success(toFriendInfoModel(response)) + } + .onFailure { t -> + _friendListState.value = UiState.Failure("${t.message}") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/main/mypage/MyPageFragment.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/mypage/MyPageFragment.kt new file mode 100644 index 0000000..a37d820 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/main/mypage/MyPageFragment.kt @@ -0,0 +1,48 @@ +package org.sopt.dosopttemplate.presentation.main.mypage + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.activityViewModels +import dagger.hilt.android.AndroidEntryPoint +import org.sopt.dosopttemplate.R +import org.sopt.dosopttemplate.databinding.FragmentMypageBinding +import org.sopt.dosopttemplate.presentation.main.MainViewModel +import org.sopt.dosopttemplate.presentation.model.UserModel +import org.sopt.dosopttemplate.util.binding.BindingFragment +import org.sopt.dosopttemplate.util.intent.getParcelable + +@AndroidEntryPoint +class MyPageFragment : BindingFragment(R.layout.fragment_mypage) { + private val viewModel by activityViewModels() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + getUserInfo() + initLogoutButtonClickListener() + initWithdrawButtonClickListener() + } + + private fun getUserInfo() { + val intent = requireActivity().intent + val userModel = intent.getParcelable(USER_KEY, UserModel::class.java) + + binding.data = userModel + } + + private fun initLogoutButtonClickListener() { + binding.btnMainLogout.setOnClickListener { + viewModel.logout() + } + } + + private fun initWithdrawButtonClickListener() { + binding.btnMainWithdraw.setOnClickListener { + viewModel.withdraw() + } + } + + companion object { + private const val USER_KEY = "user" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/model/HomeModel.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/model/HomeModel.kt new file mode 100644 index 0000000..57793b0 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/model/HomeModel.kt @@ -0,0 +1,51 @@ +package org.sopt.dosopttemplate.presentation.model + +import org.sopt.dosopttemplate.domain.entity.Friend +import java.time.LocalDate + +sealed class HomeModel { + abstract val id: Int? + + data class MyInfoModel( + override val id: Int?, + val name: String?, + val discription: String?, + ) : HomeModel() { + companion object { + const val MY_INFO_VIEW_TYPE = 0 + } + } + + data class FriendInfoModel( + override val id: Int?, + val name: String, + val birthday: LocalDate?, + val music: String?, + val imageUri: String?, + ) : HomeModel() { + companion object { + const val NORMAL_FRIEND_VIEW_TYPE = 1 + const val BIRTHDAY_FRIEND_VIEW_TYPE = 2 + + fun toFriendInfoModel(friendList: List) = friendList.map { data -> + FriendInfoModel( + id = data.id, + name = data.name, + birthday = data.birthday, + music = data.music, + imageUri = data.imageUri + ) + } + + fun toFriend(friendInfoList: List) = friendInfoList.map { data -> + Friend( + id = data.id, + name = data.name, + birthday = data.birthday, + music = data.music, + imageUri = data.imageUri + ) + } + } + } +} diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/model/UserModel.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/model/UserModel.kt index a886355..31442c7 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/presentation/model/UserModel.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/model/UserModel.kt @@ -9,13 +9,13 @@ data class UserModel( val id: String? = "", val pw: String? = "", val nickname: String? = "", - val hobby: String? = "" + val discription: String? = "" ) : Parcelable { fun toUserModel(user: User?): UserModel { - return UserModel(user?.id, user?.pw, user?.nickname, user?.hobby) + return UserModel(user?.id, user?.pw, user?.nickname, user?.discription) } fun toUser(): User { - return User(id, pw, nickname, hobby) + return User(id, pw, nickname, discription) } } diff --git a/app/src/main/java/org/sopt/dosopttemplate/presentation/signup/SignupActivity.kt b/app/src/main/java/org/sopt/dosopttemplate/presentation/signup/SignupActivity.kt index 8ddb193..5601130 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/presentation/signup/SignupActivity.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/presentation/signup/SignupActivity.kt @@ -65,7 +65,7 @@ class SignupActivity : BindingActivity(R.layout.activity_ } CODE_INVALID_HOBBY -> { - binding.root.snackBar { getString(R.string.signup_fail_hobby) } + binding.root.snackBar { getString(R.string.signup_fail_discription) } } } } diff --git a/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingAdapter.kt b/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingAdapter.kt new file mode 100644 index 0000000..34cbcf2 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingAdapter.kt @@ -0,0 +1,41 @@ +package org.sopt.dosopttemplate.util.binding + +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.databinding.BindingAdapter +import coil.load +import coil.transform.RoundedCornersTransformation +import org.sopt.dosopttemplate.R + +@BindingAdapter("Music") +fun TextView.Music(music: String?) { + if (music == null) { + visibility = View.GONE + } else { + text = music + visibility = View.VISIBLE + } +} + +@BindingAdapter("Music") +fun LinearLayout.Music(music: String?) { + if (music == null) { + visibility = View.GONE + } else { + visibility = View.VISIBLE + } +} + +@BindingAdapter("setImageUrl") +fun ImageView.setImageUrl(imageUrl: String?) { + if (imageUrl == null) { + load(R.drawable.img_profile) + } else { + load(imageUrl) { + placeholder(R.drawable.img_profile) + transformations(RoundedCornersTransformation(10F)) + } + } +} diff --git a/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingDialogFragment.kt b/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingDialogFragment.kt new file mode 100644 index 0000000..98f9fe4 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingDialogFragment.kt @@ -0,0 +1,48 @@ +package org.sopt.dosopttemplate.util.binding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.annotation.LayoutRes +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.DialogFragment + +abstract class BindingDialogFragment( + @LayoutRes val layoutRes: Int +) : DialogFragment() { + private var _binding: T? = null + protected val binding get() = requireNotNull(_binding) { "binding object is not initialized" } + + override fun onStart() { + super.onStart() + dialog?.window?.apply { + setLayout( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT + ) + setBackgroundDrawableResource(android.R.color.transparent) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = DataBindingUtil.inflate(inflater, layoutRes, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.lifecycleOwner = viewLifecycleOwner + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingFragment.kt b/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingFragment.kt new file mode 100644 index 0000000..0ac0c88 --- /dev/null +++ b/app/src/main/java/org/sopt/dosopttemplate/util/binding/BindingFragment.kt @@ -0,0 +1,33 @@ +package org.sopt.dosopttemplate.util.binding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment + +abstract class BindingFragment( + @LayoutRes private val layoutResId: Int +) : Fragment() { + private var _binding: T? = null + protected val binding: T + get() = requireNotNull(_binding) { "binding object is not initialized" } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = DataBindingUtil.inflate(inflater, layoutResId, container, false) + binding.lifecycleOwner = viewLifecycleOwner + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/app/src/main/java/org/sopt/dosopttemplate/util/view/viewExt.kt b/app/src/main/java/org/sopt/dosopttemplate/util/view/viewExt.kt index 9cee84a..e18022c 100644 --- a/app/src/main/java/org/sopt/dosopttemplate/util/view/viewExt.kt +++ b/app/src/main/java/org/sopt/dosopttemplate/util/view/viewExt.kt @@ -1,8 +1,24 @@ package org.sopt.dosopttemplate.util.view import android.view.View +import androidx.recyclerview.widget.DiffUtil import com.google.android.material.snackbar.Snackbar fun View.snackBar(message: () -> String) { Snackbar.make(this, message(), Snackbar.LENGTH_SHORT).show() +} + +class ItemDiffCallback( + val onItemsTheSame: (T, T) -> Boolean, + val onContentsTheSame: (T, T) -> Boolean +) : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: T, + newItem: T + ): Boolean = onItemsTheSame(oldItem, newItem) + + override fun areContentsTheSame( + oldItem: T, + newItem: T + ): Boolean = onContentsTheSame(oldItem, newItem) } \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_alternate_email_24.xml b/app/src/main/res/drawable/baseline_alternate_email_24.xml new file mode 100644 index 0000000..e6182db --- /dev/null +++ b/app/src/main/res/drawable/baseline_alternate_email_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_cake_15.xml b/app/src/main/res/drawable/baseline_cake_15.xml new file mode 100644 index 0000000..c4e0b80 --- /dev/null +++ b/app/src/main/res/drawable/baseline_cake_15.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_home_24.xml b/app/src/main/res/drawable/baseline_home_24.xml new file mode 100644 index 0000000..4c5e854 --- /dev/null +++ b/app/src/main/res/drawable/baseline_home_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_person_24.xml b/app/src/main/res/drawable/baseline_person_24.xml new file mode 100644 index 0000000..bd8dc80 --- /dev/null +++ b/app/src/main/res/drawable/baseline_person_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_play_arrow_15.xml b/app/src/main/res/drawable/baseline_play_arrow_15.xml new file mode 100644 index 0000000..0a06e0e --- /dev/null +++ b/app/src/main/res/drawable/baseline_play_arrow_15.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/img_profile.png b/app/src/main/res/drawable/img_profile.png new file mode 100644 index 0000000..9c79f50 Binary files /dev/null and b/app/src/main/res/drawable/img_profile.png differ diff --git a/app/src/main/res/drawable/shape_gray_line_10_rect.xml b/app/src/main/res/drawable/shape_gray_line_10_rect.xml new file mode 100644 index 0000000..98d6696 --- /dev/null +++ b/app/src/main/res/drawable/shape_gray_line_10_rect.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_purple_line_10_rect.xml b/app/src/main/res/drawable/shape_purple_line_10_rect.xml new file mode 100644 index 0000000..5c6fb16 --- /dev/null +++ b/app/src/main/res/drawable/shape_purple_line_10_rect.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_red_line_10_rect.xml b/app/src/main/res/drawable/shape_red_line_10_rect.xml new file mode 100644 index 0000000..71284ef --- /dev/null +++ b/app/src/main/res/drawable/shape_red_line_10_rect.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2c2fac6..89a982e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,9 +5,6 @@ - - - - - - - - - - - - - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> - -