From d90a82b0a4b8778f473f13a6f35ca1b574f93f8e Mon Sep 17 00:00:00 2001 From: Sangwook123 Date: Wed, 3 Apr 2024 05:04:39 +0900 Subject: [PATCH] [feat] #1 core-ui extension --- .../java/org/sopt/ui/base/BindingActivity.kt | 26 ++++++++++ .../base/BindingBottomSheetDialogFragment.kt | 44 ++++++++++++++++ .../org/sopt/ui/base/BindingDialogFragment.kt | 44 ++++++++++++++++ .../java/org/sopt/ui/base/BindingFragment.kt | 33 ++++++++++++ .../java/org/sopt/ui/context/ContextExt.kt | 52 +++++++++++++++++++ .../org/sopt/ui/context/ResourceProvider.kt | 8 +++ .../sopt/ui/context/ResourceProviderImpl.kt | 12 +++++ .../org/sopt/ui/di/ResourceProviderModule.kt | 17 ++++++ .../java/org/sopt/ui/fragment/FragmentExt.kt | 36 +++++++++++++ .../main/java/org/sopt/ui/intent/IntentExt.kt | 10 ++++ .../java/org/sopt/ui/view/ItemDiffCallback.kt | 18 +++++++ 11 files changed, 300 insertions(+) create mode 100644 core/ui/src/main/java/org/sopt/ui/base/BindingActivity.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/base/BindingBottomSheetDialogFragment.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/base/BindingDialogFragment.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/base/BindingFragment.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/context/ContextExt.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/context/ResourceProvider.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/context/ResourceProviderImpl.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/di/ResourceProviderModule.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/fragment/FragmentExt.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/intent/IntentExt.kt create mode 100644 core/ui/src/main/java/org/sopt/ui/view/ItemDiffCallback.kt diff --git a/core/ui/src/main/java/org/sopt/ui/base/BindingActivity.kt b/core/ui/src/main/java/org/sopt/ui/base/BindingActivity.kt new file mode 100644 index 0000000..2be4df5 --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/base/BindingActivity.kt @@ -0,0 +1,26 @@ +package org.sopt.ui.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.viewbinding.ViewBinding +import org.sopt.ui.context.hideKeyboard + +abstract class BindingActivity( + private val inflater: (LayoutInflater) -> T, +) : AppCompatActivity() { + protected lateinit var binding: T + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = inflater(layoutInflater) + setContentView(binding.root) + } + + override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { + hideKeyboard(currentFocus ?: View(this)) + return super.dispatchTouchEvent(ev) + } +} diff --git a/core/ui/src/main/java/org/sopt/ui/base/BindingBottomSheetDialogFragment.kt b/core/ui/src/main/java/org/sopt/ui/base/BindingBottomSheetDialogFragment.kt new file mode 100644 index 0000000..27aecf9 --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/base/BindingBottomSheetDialogFragment.kt @@ -0,0 +1,44 @@ +package org.sopt.ui.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.viewbinding.ViewBinding +import com.google.android.material.bottomsheet.BottomSheetDialogFragment + +abstract class BindingBottomSheetDialogFragment( + private val inflater: (LayoutInflater) -> T, +) : BottomSheetDialogFragment() { + 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.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + ) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = inflater(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() + } +} diff --git a/core/ui/src/main/java/org/sopt/ui/base/BindingDialogFragment.kt b/core/ui/src/main/java/org/sopt/ui/base/BindingDialogFragment.kt new file mode 100644 index 0000000..b718067 --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/base/BindingDialogFragment.kt @@ -0,0 +1,44 @@ +package org.sopt.ui.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.fragment.app.DialogFragment +import androidx.viewbinding.ViewBinding + +abstract class BindingDialogFragment( + private val inflater: (LayoutInflater) -> T, +) : 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, + ) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + _binding = inflater(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() + } +} diff --git a/core/ui/src/main/java/org/sopt/ui/base/BindingFragment.kt b/core/ui/src/main/java/org/sopt/ui/base/BindingFragment.kt new file mode 100644 index 0000000..6f0924d --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/base/BindingFragment.kt @@ -0,0 +1,33 @@ +package org.sopt.ui.base + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding + +abstract class BindingFragment( + private val inflater: (LayoutInflater) -> T, +) : Fragment() { + private var _binding: T? = null + protected val binding + get() = requireNotNull(_binding) { + Log.d("error", "$_binding") + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + _binding = inflater(layoutInflater) + return binding.root + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() + } +} diff --git a/core/ui/src/main/java/org/sopt/ui/context/ContextExt.kt b/core/ui/src/main/java/org/sopt/ui/context/ContextExt.kt new file mode 100644 index 0000000..a0ac46b --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/context/ContextExt.kt @@ -0,0 +1,52 @@ +package org.sopt.ui.context + +import android.app.Activity +import android.content.Context +import android.content.res.Resources +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.View +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import android.widget.Toast +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.DialogFragment +import com.google.android.material.snackbar.Snackbar + +fun Context.toast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() +} + +fun Context.longToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show() +} + +fun Context.snackBar(anchorView: View, message: () -> String) { + Snackbar.make(anchorView, message(), Snackbar.LENGTH_SHORT).show() +} + +fun Context.stringOf(@StringRes resId: Int) = getString(resId) + +fun Context.colorOf(@ColorRes resId: Int) = ContextCompat.getColor(this, resId) + +fun Context.drawableOf(@DrawableRes resId: Int) = ContextCompat.getDrawable(this, resId) + +fun Context.hideKeyboard(view: View) { + val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) +} + +fun Context.dialogFragmentResize(dialogFragment: DialogFragment, horizontalMargin: Float) { + val dpToPixel = Resources.getSystem().displayMetrics.density + val dialogHorizontalMarginInPixels = + (dpToPixel * horizontalMargin + 0.5f).toInt() // 반올림 처리 + val deviceWidth = Resources.getSystem().displayMetrics.widthPixels + dialogFragment.dialog?.window?.setLayout( + deviceWidth - 2 * dialogHorizontalMarginInPixels, + WindowManager.LayoutParams.WRAP_CONTENT, + ) + dialogFragment.dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) +} diff --git a/core/ui/src/main/java/org/sopt/ui/context/ResourceProvider.kt b/core/ui/src/main/java/org/sopt/ui/context/ResourceProvider.kt new file mode 100644 index 0000000..6cda508 --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/context/ResourceProvider.kt @@ -0,0 +1,8 @@ +package org.sopt.ui.context + +import androidx.annotation.StringRes + + +interface ResourceProvider { + fun getString(@StringRes id: Int): String +} \ No newline at end of file diff --git a/core/ui/src/main/java/org/sopt/ui/context/ResourceProviderImpl.kt b/core/ui/src/main/java/org/sopt/ui/context/ResourceProviderImpl.kt new file mode 100644 index 0000000..6267fbf --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/context/ResourceProviderImpl.kt @@ -0,0 +1,12 @@ +package org.sopt.ui.context + +import android.content.Context +import androidx.annotation.StringRes +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class ResourceProviderImpl @Inject constructor( + @ApplicationContext private val context: Context +) : ResourceProvider { + override fun getString(@StringRes id: Int) = context.stringOf(id) +} \ No newline at end of file diff --git a/core/ui/src/main/java/org/sopt/ui/di/ResourceProviderModule.kt b/core/ui/src/main/java/org/sopt/ui/di/ResourceProviderModule.kt new file mode 100644 index 0000000..aa083e7 --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/di/ResourceProviderModule.kt @@ -0,0 +1,17 @@ +package org.sopt.ui.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.sopt.ui.context.ResourceProvider +import org.sopt.ui.context.ResourceProviderImpl +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class ResourceProviderModule { + @Binds + @Singleton + abstract fun bindsResourceProvider(resourceProvider: ResourceProviderImpl): ResourceProvider +} \ No newline at end of file diff --git a/core/ui/src/main/java/org/sopt/ui/fragment/FragmentExt.kt b/core/ui/src/main/java/org/sopt/ui/fragment/FragmentExt.kt new file mode 100644 index 0000000..fa38709 --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/fragment/FragmentExt.kt @@ -0,0 +1,36 @@ +package org.sopt.ui.fragment + +import android.view.View +import android.widget.Toast +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.google.android.material.snackbar.Snackbar + +fun Fragment.toast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() +} + +fun Fragment.longToast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show() +} + +fun Fragment.snackBar(anchorView: View, message: () -> String) { + Snackbar.make(anchorView, message(), Snackbar.LENGTH_SHORT).show() +} + +fun Fragment.stringOf(@StringRes resId: Int) = getString(resId) + +fun Fragment.colorOf(@ColorRes resId: Int) = ContextCompat.getColor(requireContext(), resId) + +fun Fragment.drawableOf(@DrawableRes resId: Int) = + ContextCompat.getDrawable(requireContext(), resId) + +val Fragment.viewLifeCycle + get() = viewLifecycleOwner.lifecycle + +val Fragment.viewLifeCycleScope + get() = viewLifecycleOwner.lifecycleScope diff --git a/core/ui/src/main/java/org/sopt/ui/intent/IntentExt.kt b/core/ui/src/main/java/org/sopt/ui/intent/IntentExt.kt new file mode 100644 index 0000000..62b8b70 --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/intent/IntentExt.kt @@ -0,0 +1,10 @@ +package org.sopt.ui.intent + +import android.content.Intent +import android.os.Build +import android.os.Parcelable + +fun Intent.getParcelable(key: String, c: Class): T? = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelableExtra(key, c) + else -> getParcelableExtra(key) as? T +} diff --git a/core/ui/src/main/java/org/sopt/ui/view/ItemDiffCallback.kt b/core/ui/src/main/java/org/sopt/ui/view/ItemDiffCallback.kt new file mode 100644 index 0000000..80fec40 --- /dev/null +++ b/core/ui/src/main/java/org/sopt/ui/view/ItemDiffCallback.kt @@ -0,0 +1,18 @@ +package org.sopt.ui.view + +import androidx.recyclerview.widget.DiffUtil + +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) +}