diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8d20c28..cee1912 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -82,4 +82,7 @@ dependencies { implementation(Dependency.BottomNav.NAV_FRAGMENT) implementation(Dependency.BottomNav.NAV_UI) + + implementation("com.github.bumptech.glide:glide:4.13.0") + annotationProcessor("com.github.bumptech.glide:compiler:4.13.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c2c39ee..b78de2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="com.gsps.gsp_android"> @@ -14,21 +14,20 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.GSP_Android" > + android:theme="@style/Theme.GSP_Android"> - + android:exported="false" /> + android:exported="false" /> - + @@ -48,7 +47,7 @@ android:theme="@style/Theme.MaterialComponents.DayNight.DarkActionBar" /> + android:exported="true"> @@ -57,7 +56,6 @@ + android:exported="false" /> - \ No newline at end of file diff --git a/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarAdapter.kt b/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarAdapter.kt new file mode 100644 index 0000000..ee8be81 --- /dev/null +++ b/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarAdapter.kt @@ -0,0 +1,120 @@ +package com.gsps.gsp_android.ui.main + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.gsps.gsp_android.R +import com.gsps.gsp_android.databinding.ItemDayBinding + +class CalendarAdapter(val context: Context) : + ListAdapter(CalendarDiffCallback()) { + var lastCheckedPosition = -1 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return CalendarViewHolder( + ItemDayBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is CalendarViewHolder) { + holder.bind(getItem(position)) + } + } + + fun initCheck() { + lastCheckedPosition = -1 + scheduleAdapter.submitList(null) + } + + inner class CalendarViewHolder(private val binding: ItemDayBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: CalendarDayModel) { + binding.rbDay.text = item.dayOfMonth.toString() + + setVisibility(binding, item) + + binding.rbDay.setOnClickListener { + if (lastCheckedPosition == adapterPosition && item.isChecked) { + lastCheckedPosition = -1 + item.isChecked = false + } else { + item.isChecked = true + if (lastCheckedPosition != -1) { + getItem(lastCheckedPosition).isChecked = false + setVisibility(binding, getItem(lastCheckedPosition)) + notifyItemChanged(lastCheckedPosition) + } + lastCheckedPosition = adapterPosition + } + setVisibility(binding, item) + + if (item.dateType == DateType.SELECTED) { + scheduleAdapter.submitList(item.plan) + } else { + scheduleAdapter.submitList(null) + } + } + } + + private fun setDateType(item: CalendarDayModel) { + if (item.plan.isEmpty()) item.dateType = DateType.NORMAL + else item.dateType = DateType.PLANNED + + if (item.isChecked) item.dateType = DateType.SELECTED + + if (item.month == 0) item.dateType = DateType.EMPTY + } + + private fun setVisibility(binding: ItemDayBinding, item: CalendarDayModel) { + setDateType(item) + + when (item.dateType) { + DateType.EMPTY -> { + binding.rbDay.visibility = View.INVISIBLE + binding.ivPlan.visibility = View.INVISIBLE + } + DateType.NORMAL -> { + binding.rbDay.isChecked = false + binding.rbDay.visibility = View.VISIBLE + binding.ivPlan.visibility = View.INVISIBLE + } + DateType.PLANNED -> { + binding.rbDay.isChecked = false + binding.rbDay.visibility = View.VISIBLE + binding.ivPlan.visibility = View.VISIBLE + binding.ivPlan.setColorFilter( + ContextCompat.getColor( + context, + R.color.main_lighten + ) + ) + } + DateType.SELECTED -> { + binding.rbDay.isChecked = true + binding.rbDay.visibility = View.VISIBLE + binding.ivPlan.visibility = View.INVISIBLE + } + } + } + } +} + +class CalendarDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: CalendarDayModel, newItem: CalendarDayModel): Boolean { + return oldItem === newItem + } + + override fun areContentsTheSame(oldItem: CalendarDayModel, newItem: CalendarDayModel): Boolean { + return oldItem == newItem + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarDayModel.kt b/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarDayModel.kt new file mode 100644 index 0000000..085209c --- /dev/null +++ b/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarDayModel.kt @@ -0,0 +1,14 @@ +package com.gsps.gsp_android.ui.main + +data class CalendarDayModel( + var dateType: DateType = DateType.EMPTY, + var month: Int = 0, + var dayOfMonth: Int = 0, + var plan: MutableList = mutableListOf(), + var isChecked: Boolean = false, + var dayOfWeek: Int = 0 +) + +enum class DateType { + EMPTY, NORMAL, PLANNED, SELECTED +} diff --git a/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarFragment.kt b/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarFragment.kt new file mode 100644 index 0000000..49cf13c --- /dev/null +++ b/app/src/main/java/com/gsps/gsp_android/ui/main/CalendarFragment.kt @@ -0,0 +1,134 @@ +package com.gsps.gsp_android.ui.main + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.gsps.gsp_android.R +import com.gsps.gsp_android.databinding.FragmentCalendarBinding +import com.gsps.gsp_android.ui.base.BaseFragment +import com.gsps.gsp_android.utils.DimensionUtils +import java.util.* + +lateinit var scheduleAdapter: ScheduleAdapter + +class CalendarFragment : BaseFragment(R.layout.fragment_calendar) { + private val calendarAdapter: CalendarAdapter by lazy { CalendarAdapter(requireContext()) } + + override fun initView() { + scheduleAdapter = ScheduleAdapter(requireContext()) + + binding.dayContainer.apply { + itemAnimator = null + layoutManager = GridLayoutManager(context, 7, GridLayoutManager.VERTICAL, false) + adapter = calendarAdapter + } + + binding.scheduleContainer.apply { + itemAnimator = null + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + adapter = scheduleAdapter + } + + var calendar = Calendar.getInstance() + + calendar.timeInMillis = System.currentTimeMillis() + calendar.set(Calendar.DAY_OF_MONTH, 1) + + val tmpCal = calendar.timeInMillis + calendar.timeInMillis = tmpCal + + setCalendar(calendar) + + binding.btnLastMonth.setOnClickListener { + calendar = changeCalendar(calendar, -1) + setCalendar(calendar) + } + + binding.btnNextMonth.setOnClickListener { + calendar = changeCalendar(calendar, +1) + setCalendar(calendar) + } + + binding.tvMonth.setOnClickListener { + calendar.timeInMillis = System.currentTimeMillis() + calendar.set(Calendar.DAY_OF_MONTH, 1) + + val tmpCal = calendar.timeInMillis + calendar.timeInMillis = tmpCal + + setCalendar(calendar) + } + + binding.scheduleContainer.addItemDecoration(object : RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets(outRect, view, parent, state) + + val dimension = DimensionUtils() + + when (parent.getChildAdapterPosition(view)) { + 0 -> { + outRect.top = dimension.dpToPx(context!!, 16f) + outRect.bottom = dimension.dpToPx(context!!, 8f) + } + scheduleAdapter.itemCount - 1 -> { + outRect.top = dimension.dpToPx(context!!, 8f) + outRect.bottom = dimension.dpToPx(context!!, 16f) + } + else -> { + outRect.top = dimension.dpToPx(context!!, 8f) + outRect.bottom = dimension.dpToPx(context!!, 8f) + } + } + + if (scheduleAdapter.itemCount == 1) { + outRect.top = dimension.dpToPx(context!!, 16f) + outRect.bottom = dimension.dpToPx(context!!, 16f) + } + } + }) + } + + private fun changeCalendar(calendar: Calendar, factor: Int): Calendar { + val year = calendar.get(Calendar.YEAR) + val month = calendar.get(Calendar.MONTH) + 1 + + when (factor) { + -1 -> { + if (month == 1) + calendar.set(year - 1, 11, 1) + else + calendar.set(year, month - 2, 1) + } + +1 -> { + if (month == 12) + calendar.set(year + 1, 0, 1) + else + calendar.set(year, month, 1) + } + } + return calendar + } + + private fun setCalendar(calendar: Calendar) { + val maxDate = calendar.getActualMaximum(Calendar.DATE) + val week = calendar.get(Calendar.DAY_OF_WEEK) - 1 + val month = calendar.get(Calendar.MONTH) + 1 + val list = MutableList(week, init = { CalendarDayModel() }) + + for (i in 1..maxDate) { + list.add(CalendarDayModel(DateType.NORMAL, month, i)) + } + + binding.tvMonth.text = "${calendar.get(Calendar.YEAR)}년 ${month}월" + + calendarAdapter.initCheck() + calendarAdapter.submitList(list) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gsps/gsp_android/ui/main/InterestsFullListAdapter.kt b/app/src/main/java/com/gsps/gsp_android/ui/main/InterestsFullListAdapter.kt index 906e135..e24ec0d 100644 --- a/app/src/main/java/com/gsps/gsp_android/ui/main/InterestsFullListAdapter.kt +++ b/app/src/main/java/com/gsps/gsp_android/ui/main/InterestsFullListAdapter.kt @@ -6,14 +6,12 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.gsps.gsp_android.databinding.InterestsFullListItemBinding -class InterestsFullListAdapter( - context: Context, - private val itemList: MutableList -) : RecyclerView.Adapter() { +class InterestsFullListAdapter(context: Context, private val itemList: MutableList) : + RecyclerView.Adapter() { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int - ): InterestsFullListAdapter.InterestsFullListHolder { + ): InterestsFullListHolder { val binding = InterestsFullListItemBinding.inflate( LayoutInflater.from(parent.context), @@ -24,7 +22,7 @@ class InterestsFullListAdapter( } override fun onBindViewHolder( - holder: InterestsFullListAdapter.InterestsFullListHolder, + holder: InterestsFullListHolder, position: Int ) { holder.bind(itemList[position]) diff --git a/app/src/main/java/com/gsps/gsp_android/ui/main/MainActivity.kt b/app/src/main/java/com/gsps/gsp_android/ui/main/MainActivity.kt index 36f4310..bef0587 100644 --- a/app/src/main/java/com/gsps/gsp_android/ui/main/MainActivity.kt +++ b/app/src/main/java/com/gsps/gsp_android/ui/main/MainActivity.kt @@ -1,7 +1,7 @@ package com.gsps.gsp_android.ui.main -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity import com.gsps.gsp_android.R class MainActivity : AppCompatActivity() { diff --git a/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleAdapter.kt b/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleAdapter.kt new file mode 100644 index 0000000..98a451c --- /dev/null +++ b/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleAdapter.kt @@ -0,0 +1,80 @@ +package com.gsps.gsp_android.ui.main + +import android.content.Context +import android.graphics.drawable.GradientDrawable +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.gsps.gsp_android.R +import com.gsps.gsp_android.databinding.ItemScheduleBinding +import java.time.format.DateTimeFormatter + +class ScheduleAdapter(private val context: Context) : + ListAdapter(ScheduleDiffCallback()) { + lateinit var holder: ScheduleViewHolder + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScheduleViewHolder { + val binding = + ItemScheduleBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ScheduleViewHolder(context, binding) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is ScheduleViewHolder) { + holder.setScheduleView(position) + holder.bind(getItem(position)) + this.holder = holder + } + } +} + +class ScheduleViewHolder(private val context: Context, private val binding: ItemScheduleBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(item: ScheduleModel) { + with(binding) { + tvScheduleTitle.text = item.title + tvLocation.text = item.place + tvUserName.text = item.companyName + tvScheduleTime.text = item.start.format(DateTimeFormatter.ofPattern("a H:mm")) + + clItem.setOnClickListener { + val dialog = ScheduleDialog(context) + dialog.start(context, item) + } + } + } + + fun setScheduleView(position: Int) { + val drawable: GradientDrawable = + ContextCompat.getDrawable(context, R.drawable.circle) as GradientDrawable + + when (position % 3) { + 0 -> { + drawable.setColor(context.getColor(R.color.main_lighten)) + binding.icCircle.setImageDrawable(drawable) + } + 1 -> { + drawable.setColor(context.getColor(R.color.main)) + binding.icCircle.setImageDrawable(drawable) + } + 2 -> { + drawable.setColor(context.getColor(R.color.main_darken)) + binding.icCircle.setImageDrawable(drawable) + } + } + } +} + +class ScheduleDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ScheduleModel, newItem: ScheduleModel): Boolean { + return oldItem === newItem + } + + override fun areContentsTheSame(oldItem: ScheduleModel, newItem: ScheduleModel): Boolean { + return oldItem == newItem + } +} + diff --git a/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleDialog.kt b/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleDialog.kt new file mode 100644 index 0000000..5600c30 --- /dev/null +++ b/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleDialog.kt @@ -0,0 +1,52 @@ +package com.gsps.gsp_android.ui.main + +import android.app.Dialog +import android.content.Context +import android.view.LayoutInflater +import android.view.Window +import android.view.WindowManager +import androidx.databinding.DataBindingUtil +import com.bumptech.glide.Glide +import com.gsps.gsp_android.R +import com.gsps.gsp_android.databinding.DialogScheduleBinding +import com.gsps.gsp_android.utils.DimensionUtils +import java.time.format.DateTimeFormatter + +class ScheduleDialog(context: Context) : Dialog(context) { + private val dialog = Dialog(context) + private val binding: DialogScheduleBinding = + DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.dialog_schedule, null, false) + private val defaultImage = R.drawable.rounded_corner_4dp + + fun start(context: Context, item: ScheduleModel) { + val dimension = DimensionUtils() + + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) + dialog.setContentView(binding.root) + dialog.setCancelable(true) + + with(binding) { + startTime.text = item.start.format(DateTimeFormatter.ofPattern("M월 d일 a H:mm")) + finishTime.text = item.end.format(DateTimeFormatter.ofPattern("M월 d일 a H:mm")) + tvScheduleTitle.text = item.title + place.text = "${item.place} ${item.placeDetail}" + userName.text = item.companyName + + Glide.with(context) + .load(item.logoUrl) + .placeholder(defaultImage) + .error(defaultImage) + .fallback(defaultImage) + .into(ivLogo) + + btnClose.setOnClickListener { + dialog.dismiss() + } + } + dialog.show() + dialog.window?.setLayout( + dimension.dpToPx(context, 320f), + WindowManager.LayoutParams.WRAP_CONTENT + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleModel.kt b/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleModel.kt new file mode 100644 index 0000000..d0d07d3 --- /dev/null +++ b/app/src/main/java/com/gsps/gsp_android/ui/main/ScheduleModel.kt @@ -0,0 +1,17 @@ +package com.gsps.gsp_android.ui.main + +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId + +data class ScheduleModel( + var start: LocalDateTime = Instant.ofEpochMilli(System.currentTimeMillis()) + .atZone(ZoneId.systemDefault()).toLocalDateTime(), + var end: LocalDateTime = Instant.ofEpochMilli(System.currentTimeMillis()) + .atZone(ZoneId.systemDefault()).toLocalDateTime(), + var title: String = "", + var place: String = "", + var placeDetail: String = "", + var companyName: String = "", + var logoUrl: String = "" +) diff --git a/app/src/main/java/com/gsps/gsp_android/utils/DimensionUtils.kt b/app/src/main/java/com/gsps/gsp_android/utils/DimensionUtils.kt new file mode 100644 index 0000000..a87ace9 --- /dev/null +++ b/app/src/main/java/com/gsps/gsp_android/utils/DimensionUtils.kt @@ -0,0 +1,14 @@ +package com.gsps.gsp_android.utils + +import android.content.Context +import android.util.TypedValue + +class DimensionUtils { + fun dpToPx(context: Context, dp: Float): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + context.resources.displayMetrics + ).toInt() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/circle.xml b/app/src/main/res/drawable/circle.xml new file mode 100644 index 0000000..9915a0b --- /dev/null +++ b/app/src/main/res/drawable/circle.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/item_calendar_day_background.xml b/app/src/main/res/drawable/item_calendar_day_background.xml new file mode 100644 index 0000000..72f95a6 --- /dev/null +++ b/app/src/main/res/drawable/item_calendar_day_background.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/item_calendar_day_textcolor.xml b/app/src/main/res/drawable/item_calendar_day_textcolor.xml new file mode 100644 index 0000000..ed232f3 --- /dev/null +++ b/app/src/main/res/drawable/item_calendar_day_textcolor.xml @@ -0,0 +1,5 @@ + + + + + \ 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 f0c5698..34223ea 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_calendar.xml b/app/src/main/res/layout/fragment_calendar.xml new file mode 100644 index 0000000..2ff4097 --- /dev/null +++ b/app/src/main/res/layout/fragment_calendar.xml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_day.xml b/app/src/main/res/layout/item_day.xml new file mode 100644 index 0000000..71813b8 --- /dev/null +++ b/app/src/main/res/layout/item_day.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_schedule.xml b/app/src/main/res/layout/item_schedule.xml new file mode 100644 index 0000000..c6c3d00 --- /dev/null +++ b/app/src/main/res/layout/item_schedule.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 598c0b5..2a5e272 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,4 +44,6 @@ 최근 검색어 협업 신청하러가기 신규가입회사 + 일정 + 상세보기 \ No newline at end of file