From 4b4d49fdaa7f4652e5625d549b5a28943a4104eb Mon Sep 17 00:00:00 2001 From: Alexandr Alexeenko Date: Fri, 10 Nov 2023 00:59:53 +0300 Subject: [PATCH] Add recent contribution month to home fragment #25 --- .../ContributionsGridFragment.kt | 81 +++++--------- .../view/contributions_grid/DaysAdapter.kt | 9 +- .../view/contributions_grid/MonthsAdapter.kt | 15 ++- .../gitstat/view/profile/ProfileFragment.kt | 101 +++++++++++------- .../gitstat/view/profile/ProfileViewModel.kt | 12 ++- app/src/main/res/layout/fragment_profile.xml | 21 +++- app/src/main/res/navigation/nav_graph.xml | 3 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/themes.xml | 2 +- 9 files changed, 136 insertions(+), 109 deletions(-) diff --git a/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/ContributionsGridFragment.kt b/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/ContributionsGridFragment.kt index f1a83807..b393b7d5 100644 --- a/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/ContributionsGridFragment.kt +++ b/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/ContributionsGridFragment.kt @@ -1,73 +1,67 @@ package by.alexandr7035.gitstat.view.contributions_grid import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import by.alexandr7035.gitstat.R -import by.alexandr7035.gitstat.data.local.model.ContributionDayEntity +import by.alexandr7035.gitstat.core.extensions.navigateSafe +import by.alexandr7035.gitstat.core.extensions.observeNullSafe import by.alexandr7035.gitstat.data.local.model.ContributionYearWithMonths import by.alexandr7035.gitstat.data.local.model.ContributionsMonthWithDays import by.alexandr7035.gitstat.databinding.FragmentContributionsGridBinding -import by.alexandr7035.gitstat.core.extensions.debug -import by.alexandr7035.gitstat.core.extensions.navigateSafe -import by.alexandr7035.gitstat.core.extensions.observeNullSafe +import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.tabs.TabLayout import dagger.hilt.android.AndroidEntryPoint -import timber.log.Timber import java.text.SimpleDateFormat -import java.util.* +import java.util.Locale @AndroidEntryPoint -class ContributionsGridFragment : Fragment(), DayClickListener { - - private var binding: FragmentContributionsGridBinding? = null +class ContributionsGridFragment : Fragment(R.layout.fragment_contributions_grid) { + private val binding by viewBinding(FragmentContributionsGridBinding::bind) private val viewModel by viewModels() private val safeArgs by navArgs() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - // Inflate the layout for this fragment - binding = FragmentContributionsGridBinding.inflate(inflater, container, false) - return binding?.root - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding?.toolbar?.title = getString(R.string.year_toolbar_title, safeArgs.contributionYear) - binding?.toolbar?.setNavigationOnClickListener { + binding.toolbar.title = getString(R.string.year_toolbar_title, safeArgs.contributionYear) + binding.toolbar.setNavigationOnClickListener { findNavController().navigateUp() } - val adapter = MonthsAdapter(this) + val adapter = MonthsAdapter(onDayClick = { contributionDay -> + findNavController().navigateSafe( + ContributionsGridFragmentDirections.actionContributionsGridFragmentToContributionDayDialogFragment( + contributionDay.count, + contributionDay.date, + contributionDay.color + ) + ) + }) - binding?.monthRecycler?.adapter = adapter - binding?.monthRecycler?.layoutManager = LinearLayoutManager(requireContext()) + binding.monthRecycler.adapter = adapter + binding.monthRecycler.layoutManager = LinearLayoutManager(requireContext()) - viewModel.getContributionYearsWithMonthsLiveData().observeNullSafe(viewLifecycleOwner, { years -> + viewModel.getContributionYearsWithMonthsLiveData().observeNullSafe(viewLifecycleOwner) { years -> if (years.isNotEmpty()) { // Add year tabs depending on years list (reversed) for (year in years.reversed()) { - val tab = binding?.tabLayout?.newTab() + val tab = binding.tabLayout.newTab() // Set year as tab text - tab?.text = year.year.id.toString() - - if (tab != null) { - binding?.tabLayout?.addTab(tab) - } + tab.text = year.year.id.toString() + binding.tabLayout.addTab(tab) } - binding?.tabLayout?.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { val year = years.reversed()[tab.position] adapter.setItems(getMonthsToShow(years, tab.position)) - binding?.toolbar?.title = getString(R.string.year_toolbar_title, year.year.id) + binding.toolbar.title = getString(R.string.year_toolbar_title, year.year.id) } override fun onTabUnselected(tab: TabLayout.Tab) { @@ -77,15 +71,15 @@ class ContributionsGridFragment : Fragment(), DayClickListener { override fun onTabReselected(tab: TabLayout.Tab) { val year = years.reversed()[tab.position] adapter.setItems(getMonthsToShow(years, tab.position)) - binding?.toolbar?.title = getString(R.string.year_toolbar_title, year.year.id) + binding.toolbar.title = getString(R.string.year_toolbar_title, year.year.id) } }) // Set initial tab position - val initialTab = binding?.tabLayout?.getTabAt(0) + val initialTab = binding.tabLayout.getTabAt(0) initialTab?.select() } - }) + } } @@ -124,23 +118,4 @@ class ContributionsGridFragment : Fragment(), DayClickListener { return monthWithDays.reversed() } - - override fun onDestroyView() { - super.onDestroyView() - binding = null - } - - - // Handle contribution cells clicks here - override fun onDayItemClick(contributionDay: ContributionDayEntity) { - Timber.debug("click in FRAGMENT $contributionDay") - - findNavController().navigateSafe( - ContributionsGridFragmentDirections.actionContributionsGridFragmentToContributionDayDialogFragment( - contributionDay.count, - contributionDay.date, - contributionDay.color - ) - ) - } } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/DaysAdapter.kt b/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/DaysAdapter.kt index fea7049d..8b028487 100644 --- a/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/DaysAdapter.kt +++ b/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/DaysAdapter.kt @@ -12,10 +12,10 @@ import by.alexandr7035.gitstat.R import by.alexandr7035.gitstat.core.helpers.TimeHelper import by.alexandr7035.gitstat.data.local.model.ContributionDayEntity import by.alexandr7035.gitstat.databinding.ViewContributionsGridCellBinding -import by.alexandr7035.gitstat.core.extensions.debug -import timber.log.Timber -class DaysAdapter(private val dayClickListener: DayClickListener): RecyclerView.Adapter() { +class DaysAdapter( + private val onDayClick: (ContributionDayEntity) -> Unit +): RecyclerView.Adapter() { private var items: List = emptyList() @@ -57,8 +57,7 @@ class DaysAdapter(private val dayClickListener: DayClickListener): RecyclerView. override fun onClick(view: View) { val clickedDay = items[adapterPosition] - Timber.debug("clicked $clickedDay") - dayClickListener.onDayItemClick(clickedDay) + onDayClick(clickedDay) } } diff --git a/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/MonthsAdapter.kt b/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/MonthsAdapter.kt index 57f89f0c..adcf4e75 100644 --- a/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/MonthsAdapter.kt +++ b/app/src/main/java/by/alexandr7035/gitstat/view/contributions_grid/MonthsAdapter.kt @@ -13,7 +13,9 @@ import timber.log.Timber import java.text.SimpleDateFormat import java.util.* -class MonthsAdapter(private val fragmentDayClickListener: DayClickListener): RecyclerView.Adapter(), DayClickListener { +class MonthsAdapter( + private val onDayClick: (ContributionDayEntity) -> Unit +) : RecyclerView.Adapter() { // FIXME private var items: List = emptyList() @@ -54,19 +56,14 @@ class MonthsAdapter(private val fragmentDayClickListener: DayClickListener): Rec .sumOf { it.count } .toString() - val adapter = DaysAdapter(this) + val adapter = DaysAdapter(onDayClick = onDayClick) + holder.binding.cellsRecycler.adapter = adapter -// holder.binding.cellsRecycler.layoutManager = FlexboxLayoutManager(holder.binding.root.context) holder.binding.cellsRecycler.layoutManager = GridLayoutManager(holder.binding.root.context, 7) // Set days of the month to cells adapter.setItems(items[position].contributionDays) } - class ViewHolder(val binding: ViewMonthContributionsGridBinding): RecyclerView.ViewHolder(binding.root) - - - override fun onDayItemClick(contributionDay: ContributionDayEntity) { - fragmentDayClickListener.onDayItemClick(contributionDay) - } + class ViewHolder(val binding: ViewMonthContributionsGridBinding) : RecyclerView.ViewHolder(binding.root) } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/gitstat/view/profile/ProfileFragment.kt b/app/src/main/java/by/alexandr7035/gitstat/view/profile/ProfileFragment.kt index 75c4a9c6..5dcdd83d 100644 --- a/app/src/main/java/by/alexandr7035/gitstat/view/profile/ProfileFragment.kt +++ b/app/src/main/java/by/alexandr7035/gitstat/view/profile/ProfileFragment.kt @@ -2,93 +2,122 @@ package by.alexandr7035.gitstat.view.profile import android.os.Bundle import android.text.format.DateFormat -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.GridLayoutManager import by.alexandr7035.gitstat.R import by.alexandr7035.gitstat.core.extensions.navigateSafe import by.alexandr7035.gitstat.core.extensions.observeNullSafe import by.alexandr7035.gitstat.databinding.FragmentProfileBinding import by.alexandr7035.gitstat.view.MainActivity +import by.alexandr7035.gitstat.view.contributions_grid.DaysAdapter +import by.kirich1409.viewbindingdelegate.viewBinding import com.squareup.picasso.Picasso import dagger.hilt.android.AndroidEntryPoint +import java.text.SimpleDateFormat +import java.util.Locale @AndroidEntryPoint -class ProfileFragment : Fragment() { - - private var binding: FragmentProfileBinding? = null +class ProfileFragment : Fragment(R.layout.fragment_profile) { + private val binding by viewBinding(FragmentProfileBinding::bind) private val viewModel by viewModels() - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - // Inflate the layout for this fragment - binding = FragmentProfileBinding.inflate(inflater, container, false) - return binding!!.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // Update profile data - viewModel.getUserLiveData().observeNullSafe(viewLifecycleOwner, { + viewModel.getUserLiveData().observeNullSafe(viewLifecycleOwner) { - Picasso.get().load(it.avatar_url).into(binding!!.profileImageView) + Picasso.get().load(it.avatar_url).into(binding.profileImageView) // This field can be empty if (it.name.isEmpty()) { - binding?.nameView?.visibility = View.GONE + binding.nameView.visibility = View.GONE } else { - binding?.nameView?.visibility = View.VISIBLE - binding?.nameView?.text = it.name + binding.nameView.visibility = View.VISIBLE + binding.nameView.text = it.name } - binding!!.loginView.text = getString(R.string.login_string, it.login) + binding.loginView.text = getString(R.string.login_string, it.login) - binding!!.idView.text = it.id.toString() + binding.idView.text = it.id.toString() val dateFormat = getString(R.string.profile_date_format) - binding!!.createdView.text = DateFormat.format(dateFormat, it.created_at) - binding!!.updatedView.text = DateFormat.format(dateFormat, it.updated_at) + binding.createdView.text = DateFormat.format(dateFormat, it.created_at) + binding.updatedView.text = DateFormat.format(dateFormat, it.updated_at) - binding!!.followersView.text = it.followers.toString() + binding.followersView.text = it.followers.toString() // This field can be empty if (it.location.isEmpty()) { setLocationVisibility(false) } else { setLocationVisibility(true) - binding!!.locationView.text = it.location + binding.locationView.text = it.location } - binding!!.totalReposView.text = it.total_repos_count.toString() - binding!!.privateReposView.text = it.private_repos_count.toString() - binding!!.publicReposView.text = it.public_repos_count.toString() - }) + binding.totalReposView.text = it.total_repos_count.toString() + binding.privateReposView.text = it.private_repos_count.toString() + binding.publicReposView.text = it.public_repos_count.toString() + } - binding!!.reposStatDetailedBtn.setOnClickListener { + binding.reposStatDetailedBtn.setOnClickListener { findNavController().navigateSafe(ProfileFragmentDirections.actionProfileFragmentToReposOverviewFragment()) } - binding?.drawerBtn?.setOnClickListener { + binding.drawerBtn.setOnClickListener { (requireActivity() as MainActivity).openDrawerMenu() } + setupContributionsGrid() } private fun setLocationVisibility(isVisible: Boolean) { - binding?.locationIcon?.isGone = ! isVisible - binding?.locationLabel?.isGone = ! isVisible - binding?.locationView?.isGone = ! isVisible + binding.locationIcon.isGone = !isVisible + binding.locationLabel.isGone = !isVisible + binding.locationView.isGone = !isVisible } - override fun onDestroyView() { - super.onDestroyView() - binding = null - } + private fun setupContributionsGrid() { + val adapter = DaysAdapter(onDayClick = { contributionDay -> + findNavController().navigateSafe( + ProfileFragmentDirections.actionProfileFragmentToContributionDayDialogFragment( + contributionDay.count, + contributionDay.date, + contributionDay.color + ) + ) + }) + + viewModel.loadRecentYearContributions().observeNullSafe(viewLifecycleOwner) { it -> + val recentYear = it.last().contributionMonths + // Get current month number + val monthFormat = SimpleDateFormat("MM", Locale.US) + val currentMonth = monthFormat.format(System.currentTimeMillis()).toInt() + + val recentMonth = recentYear.getOrNull(currentMonth-1) + + if (recentMonth != null) { + binding.recentMonthContributions.root.isVisible = true + + binding.recentMonthContributions.cellsRecycler.adapter = adapter + binding.recentMonthContributions.cellsRecycler.layoutManager = GridLayoutManager(requireContext(), 7) + binding.recentMonthContributions.monthCardTitle.text = getString(R.string.recent_month) + binding.recentMonthContributions.monthCardTotalContributions.text = recentMonth.contributionDays.sumOf { day -> + day.count + }.toString() + + adapter.setItems(recentMonth.contributionDays) + } + else { + binding.recentMonthContributions.root.isVisible = false + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/gitstat/view/profile/ProfileViewModel.kt b/app/src/main/java/by/alexandr7035/gitstat/view/profile/ProfileViewModel.kt index b0e5b6d2..176bcec4 100644 --- a/app/src/main/java/by/alexandr7035/gitstat/view/profile/ProfileViewModel.kt +++ b/app/src/main/java/by/alexandr7035/gitstat/view/profile/ProfileViewModel.kt @@ -2,16 +2,26 @@ package by.alexandr7035.gitstat.view.profile import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import by.alexandr7035.gitstat.data.ContributionsRepository import by.alexandr7035.gitstat.data.UserRepository +import by.alexandr7035.gitstat.data.local.model.ContributionYearWithMonths import by.alexandr7035.gitstat.data.local.model.UserEntity import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @HiltViewModel -class ProfileViewModel @Inject constructor(private val userRepository: UserRepository): ViewModel() { +class ProfileViewModel @Inject constructor( + private val userRepository: UserRepository, + private val contributionsRepository: ContributionsRepository +) : ViewModel() { fun getUserLiveData(): LiveData { return userRepository.getUserLiveDataFromCache() } + fun loadRecentYearContributions(): LiveData> { + return contributionsRepository + .getContributionYearsWithMonthsLiveData() + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index bb5117df..224dfee6 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -8,6 +8,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" + android:clipChildren="false" android:paddingBottom="20dp" tools:context="by.alexandr7035.gitstat.view.profile.ProfileFragment"> @@ -74,12 +75,12 @@ + app:layout_constraintTop_toTopOf="@+id/publicReposLabel" + app:srcCompat="@drawable/ic_cloud_filled" /> @@ -374,4 +375,16 @@ app:layout_constraintStart_toEndOf="@id/drawerBtn" app:layout_constraintTop_toTopOf="parent" /> + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 670bddca..95269251 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -28,6 +28,9 @@ + Forked from: %1$s]]> Private repos access Please, provide Notifications permission to show your activities and data sync status + Recent month \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 8793a977..1cdd8717 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -129,7 +129,7 @@