Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .idea/navEditor.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ dependencies {

// intro tutorial
implementation 'com.github.AppIntro:AppIntro:5.1.0'

// fav button
implementation 'com.github.ivbaranov:materialfavoritebutton:0.1.5'
}

// ktlint
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/com/esp/localjobs/adapters/JobItem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import com.esp.localjobs.data.repository.userFirebaseRepository
import com.esp.localjobs.databinding.ItemJobBinding
import com.esp.localjobs.fragments.JobDetailsFragment
import com.esp.localjobs.fragments.JobsFragmentDirections
import com.esp.localjobs.utils.IFavoritesManager
import com.esp.localjobs.utils.favoritesManager
import com.xwray.groupie.databinding.BindableItem
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
Expand All @@ -22,6 +24,10 @@ import kotlinx.coroutines.launch
@InternalCoroutinesApi
class JobItem(val job: Job) : BindableItem<ItemJobBinding>() {

companion object {
private val favManager: IFavoritesManager = favoritesManager
}

override fun getId() = job.uid.hashCode().toLong()

@InternalCoroutinesApi
Expand All @@ -35,6 +41,19 @@ class JobItem(val job: Job) : BindableItem<ItemJobBinding>() {
setAuthor(author)
}

GlobalScope.launch(Dispatchers.IO) {
val favorites = favManager.get()
if (favorites.contains([email protected])) {
favToggle.isFavorite = true
}
favToggle.setOnFavoriteChangeListener { _, isChecked ->
if (!isChecked)
favManager.remove([email protected])
else
favManager.add([email protected])
}
}

[email protected]()?.let {
Glide.with(cardView.context).load(it).placeholder(R.drawable.placeholder).into(imageView)
} ?: Glide.with(cardView.context).load("https://picsum.photos/400").placeholder(R.drawable.placeholder).into(
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/com/esp/localjobs/data/models/User.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.esp.localjobs.data.models

import android.os.Parcelable
import com.google.firebase.auth.FirebaseUser
import kotlinx.android.parcel.Parcelize

@Parcelize
data class User(
val uid: String = "",
val displayName: String = "",
val phoneNumber: String = "",
val photoUrl: String = "",
val mail: String = ""
)
) : Parcelable

fun FirebaseUser.toUser() = User(
uid = uid,
Expand Down
85 changes: 82 additions & 3 deletions app/src/main/java/com/esp/localjobs/fragments/JobsFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,51 @@ import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SearchView
import androidx.core.view.forEach
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.RecyclerView
import com.esp.localjobs.R
import com.esp.localjobs.adapters.JobItem
import com.esp.localjobs.data.models.Location
import com.esp.localjobs.data.models.User
import com.esp.localjobs.data.repository.JobsRepository
import com.esp.localjobs.fragments.FiltersFragment.Companion.FILTER_FRAGMENT_TAG
import com.esp.localjobs.fragments.map.LocationPickerFragment
import com.esp.localjobs.utils.favoritesManager
import com.esp.localjobs.viewModels.FilterViewModel
import com.esp.localjobs.viewModels.JobsViewModel
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.ViewHolder
import kotlinx.android.synthetic.main.fragment_filter_status.*
import kotlinx.android.synthetic.main.fragment_jobs.*
import kotlinx.android.synthetic.main.fragment_jobs.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

/**
* Fragment used to display a list of jobs
* Fragment used to display a list of jobs. If arguments include an User then the fragment
* shows the user's jobs/proposals
*/
@InternalCoroutinesApi
class JobsFragment : Fragment(), LocationPickerFragment.OnLocationPickedListener {
class JobsFragment : Fragment(), LocationPickerFragment.OnLocationPickedListener, CoroutineScope {
private lateinit var mJob: kotlinx.coroutines.Job
override val coroutineContext: CoroutineContext
get() = mJob + Dispatchers.Main

private val jobsViewModel: JobsViewModel by activityViewModels()
private val filterViewModel: FilterViewModel by activityViewModels()

private val args: JobsFragmentArgs by navArgs()

val adapter = GroupAdapter<ViewHolder>()

override fun onCreateView(
Expand All @@ -56,7 +71,17 @@ class JobsFragment : Fragment(), LocationPickerFragment.OnLocationPickedListener
setupAdapter()

observeChangesInJobList()
observeFilters()

when {
args.user != null -> setupUserJobsView(args.user as User)
args.showFavorites -> showFavorites()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm e' ok, ma invece di fare cosi', farei una sottoclasse di questa per i preferiti, e aprirei direttamente quella

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

si probabilmente è più pulito

else -> observeFilters()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mJob = kotlinx.coroutines.Job()
}

private fun setupUI(view: View) = with(view) {
Expand Down Expand Up @@ -137,6 +162,11 @@ class JobsFragment : Fragment(), LocationPickerFragment.OnLocationPickedListener
}

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// hide menu actions if we are showing some user's jobs/proposals or favorites
if (args.user != null || args.showFavorites) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Facendo una sottoclasse non dovremmo necessariamente usare questi if (:

menu.forEach { it.isVisible = false }
return
}
inflater.inflate(R.menu.menu_search, menu)
val searchView = menu.findItem(R.id.action_search_item).actionView as SearchView
setupSearchView(searchView)
Expand Down Expand Up @@ -181,6 +211,55 @@ class JobsFragment : Fragment(), LocationPickerFragment.OnLocationPickedListener
}
}

private fun setupUserJobsView(user: User) {
activity?.title = getString(R.string.user_jobs_title, user.displayName)
val fromJobs = filterViewModel.filteringJobs ?: true

val toCheck = if (fromJobs)
R.id.radio_job
else
R.id.radio_proposal

jobs_type_radio_group.check(toCheck)
jobs_type_radio_group.setOnCheckedChangeListener { _, checkedId ->
if (checkedId == R.id.radio_job) {
loadJobs(JobsRepository.JobFilter(
uid = user.uid,
filteringJobs = true
))
} else {
loadJobs(JobsRepository.JobFilter(
uid = user.uid,
filteringJobs = false
))
}
}

fabAdd.visibility = View.GONE
active_filters.visibility = View.GONE
jobs_type_radio_group.visibility = View.VISIBLE

loadJobs(JobsRepository.JobFilter(
uid = user.uid,
filteringJobs = fromJobs
))
}

private fun showFavorites() = launch {
activity?.title = getString(R.string.favorites_title)
fabAdd.visibility = View.GONE
active_filters.visibility = View.GONE

val deferredJobs = async(Dispatchers.IO) { favoritesManager.get() }
adapter.clear()
val jobs = deferredJobs.await()
if (jobs.isEmpty()) {
no_jobs_title.text = getString(R.string.empty_favorites_title)
no_jobs_message.text = ""
} else
adapter.update(jobs.map { JobItem(it) })
}

override fun onLocationPicked(location: Location, distance: Int?) {
Log.d(TAG, "location: $location")
filterViewModel.setLocation(location)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.esp.localjobs.R
import com.esp.localjobs.data.models.User
import com.esp.localjobs.data.repository.userFirebaseRepository
import com.esp.localjobs.databinding.FragmentUserProfileBinding
import com.esp.localjobs.viewModels.LoginViewModel
Expand Down Expand Up @@ -60,22 +61,27 @@ class UserProfileFragment : Fragment(), CoroutineScope {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userId = args.userID

if (userId == null)
setupCurrentUserProfile()
else
setupUserDetails(userId)
args.userID?.let {
setupUserDetails(it)
} ?: setupCurrentUserProfile()
}

private fun setupCurrentUserProfile() {
name.text = getString(R.string.not_logged_in)
logout.visibility = View.GONE
login.visibility = View.VISIBLE
val user = loginViewModel.getCurrentUser()

if (user == null) {
name.text = getString(R.string.not_logged_in)
logout.visibility = View.GONE
login.visibility = View.VISIBLE
} else {
binding.user = user
setupUserJobsButton(user)
setupFavoritesButton()

loginViewModel.getCurrentUser()?.let {
binding.user = it
logout.visibility = View.VISIBLE
favorites_button.visibility = View.VISIBLE

login.visibility = View.GONE
}

Expand All @@ -93,7 +99,30 @@ class UserProfileFragment : Fragment(), CoroutineScope {
if (!isActive)
return@launch

binding.user = user
user?.let {
binding.user = it
setupUserJobsButton(it)
}
}

private fun setupUserJobsButton(user: User) {
user_jobs.visibility = View.VISIBLE
user_jobs.setOnClickListener {
val action =
UserProfileFragmentDirections.actionDestinationUserProfileToDestinationJobs(user)
findNavController().navigate(action)
}
}

private fun setupFavoritesButton() {
favorites_button.setOnClickListener {
val action =
UserProfileFragmentDirections.actionDestinationUserProfileToDestinationJobs(
null,
true
)
findNavController().navigate(action)
}
}

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.esp.localjobs.utils

import com.esp.localjobs.data.models.Job

interface IFavoritesManager {
fun add(job: Job)
fun remove(job: Job)
suspend fun get(): Set<Job>
}
66 changes: 66 additions & 0 deletions app/src/main/java/com/esp/localjobs/utils/favoritesManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.esp.localjobs.utils

import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.core.content.edit
import com.esp.localjobs.LocalJobsApplication
import com.esp.localjobs.data.base.BaseRepository
import com.esp.localjobs.data.models.Job
import com.esp.localjobs.data.repository.JobsRepository

object favoritesManager : IFavoritesManager {
private const val FAV_KEY = "favourites_ids"

private val loader: BaseRepository<Job> by lazy { JobsRepository() }
private val sharedPreferences: SharedPreferences by lazy {
LocalJobsApplication.applicationContext()
.getSharedPreferences("favorites", Context.MODE_PRIVATE)
}

private var favorites: MutableSet<Job>? = null

override fun add(job: Job) {
val favKeys = sharedPreferences.getStringSet(FAV_KEY, mutableSetOf<String>())
?: mutableSetOf<String>()
favKeys.add(job.id)
Log.d("favorites", "adding: ${job.id}")
sharedPreferences.edit(commit = true) {
// stringSet is bugged so i must do this :/
remove(FAV_KEY)
apply()
putStringSet(FAV_KEY, favKeys)
apply()
}
favorites?.add(job)
}

override fun remove(job: Job) {
val favKeys = sharedPreferences.getStringSet(FAV_KEY, mutableSetOf<String>()) ?: return
favKeys.remove(job.id)
Log.d("favorites", "removing: ${job.id}")
sharedPreferences.edit(commit = true) {
// stringSet is bugged so i must do this :/
remove(FAV_KEY)
apply()
putStringSet(FAV_KEY, favKeys)
apply()
}
favorites?.remove(job)
}

override suspend fun get(): Set<Job> {
if (favorites == null) {
favorites = load()
}
return (favorites as MutableSet<Job>).toSet()
}

private suspend fun load(): MutableSet<Job> {
val favKeys = sharedPreferences.getStringSet(FAV_KEY, mutableSetOf<String>())
Log.d("favorites", "loading: $favKeys")
val favList = mutableSetOf<Job>()
favKeys?.forEach { key -> loader.get(key)?.let { favList.add(it) } }
return favList
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class FilterViewModel : ViewModel() {
val query: String?
get() = activeFilters.value?.query

val filteringJobs: Boolean?
get() = activeFilters.value?.filteringJobs

init {
val context = LocalJobsApplication.applicationContext()
val filter = retrieveLastUsedFilter(context)
Expand Down
Loading