WIP busqueda, hastags y pantalla de explorar perfiles

This commit is contained in:
erik-everardo 2024-03-31 22:50:06 -06:00
parent 8730bbe796
commit 49ccebb539
15 changed files with 337 additions and 30 deletions

View File

@ -0,0 +1,11 @@
package com.isolaatti.hashtags.presentation
import androidx.lifecycle.ViewModel
class HashtagsViewModel : ViewModel() {
}
class HashtagPostsViewModel : ViewModel() {
}

View File

@ -0,0 +1,39 @@
package com.isolaatti.hashtags.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.isolaatti.databinding.FragmentPostsHashtagBinding
import androidx.navigation.fragment.navArgs
class HashtagPostsFragment : Fragment() {
private lateinit var binding: FragmentPostsHashtagBinding
private val navArgs: HashtagPostsFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentPostsHashtagBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.toolbar.title = "#${navArgs.hashtag}"
setupListeners()
}
private fun setupListeners() {
binding.toolbar.setNavigationOnClickListener {
findNavController().popBackStack()
}
}
}

View File

@ -1,12 +0,0 @@
package com.isolaatti.hashtags.ui
import android.content.Context
import com.isolaatti.common.IsolaattiBaseActivity
class HashtagsActivity : IsolaattiBaseActivity() {
companion object {
fun startActivity(context: Context, hashtag: String? = null) {
}
}
}

View File

@ -0,0 +1,35 @@
package com.isolaatti.hashtags.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.isolaatti.databinding.FragmentHashtagsBinding
class HashtagsFragment : Fragment() {
private lateinit var binding: FragmentHashtagsBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentHashtagsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupListeners()
}
private fun setupListeners() {
binding.toolbar.setNavigationOnClickListener {
findNavController().popBackStack()
}
}
}

View File

@ -0,0 +1,6 @@
package com.isolaatti.profile.ui
import androidx.fragment.app.Fragment
class BrowseProfilesFragment : Fragment() {
}

View File

@ -5,8 +5,8 @@ import retrofit2.http.GET
import retrofit2.http.Query
interface SearchApi {
@GET("Search/Quick")
fun quickSearch(@Query("q") query: String): Call<SearchDto>
@GET("Search/v2")
fun quickSearch(@Query("query") query: String): Call<SearchDto>
@GET("hashtags/trending")
fun getTrendingHashtags(): Call<HashtagsDto>

View File

@ -1,14 +1,33 @@
package com.isolaatti.search.data
import com.isolaatti.posting.posts.data.remote.FeedDto
enum class SearchResultType {
Profile, Post, Hashtag, Unknown
}
data class ProfileSearchDto(val id: Int, val name: String, val imageId: String?, val following: Boolean)
data class SearchDto(
val profiles: List<ProfileSearchDto>,
val posts: List<FeedDto.PostDto>
// TODO add the other types
)
data class SearchDto(val result: List<SearchResultDto>)
data class SearchResultDto(
val origin: String,
val resourceId: String,
val title: String,
val description: String
) {
companion object {
const val ORIGIN_POSTS = "posts"
const val ORIGIN_HASHTAGS = "hashtags"
const val ORIGIN_USERS = "users"
}
val type: SearchResultType get() {
return when(origin) {
ORIGIN_POSTS -> SearchResultType.Post
ORIGIN_HASHTAGS -> SearchResultType.Hashtag
ORIGIN_USERS -> SearchResultType.Profile
else -> SearchResultType.Unknown
}
}
}
data class HashtagsDto(val result: List<String>)

View File

@ -0,0 +1,59 @@
package com.isolaatti.search.presentation
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import com.isolaatti.R
import com.isolaatti.databinding.SearchResultItemBinding
import com.isolaatti.search.data.SearchResultDto
import com.isolaatti.search.data.SearchResultType
import com.isolaatti.utils.UrlGen
class SearchResultsAdapter(
private val onItemClick: (item: SearchResultDto ) -> Unit = {}
) : ListAdapter<SearchResultDto, SearchResultsAdapter.SearchResultViewHolder>(itemCallback) {
companion object {
val itemCallback = object: DiffUtil.ItemCallback<SearchResultDto>() {
override fun areItemsTheSame(oldItem: SearchResultDto, newItem: SearchResultDto): Boolean {
return oldItem.resourceId == newItem.resourceId && oldItem.type == newItem.type
}
override fun areContentsTheSame(oldItem: SearchResultDto, newItem: SearchResultDto): Boolean {
return oldItem == newItem
}
}
}
inner class SearchResultViewHolder(val searchResultItemBinding: SearchResultItemBinding) : ViewHolder(searchResultItemBinding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchResultViewHolder {
return SearchResultViewHolder(SearchResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: SearchResultViewHolder, position: Int) {
val searchResult = getItem(position)
holder.searchResultItemBinding.apply {
searchResultTitle.text = searchResult.title
searchResultDescription.text = searchResult.description
root.setOnClickListener { onItemClick(searchResult) }
// TODO complete this
val image = when(searchResult.type) {
SearchResultType.Profile -> {
val userId = searchResult.resourceId.toIntOrNull()
if(userId != null) {
UrlGen.userProfileImage(userId)
} else {
R.drawable.baseline_search_24
}
}
else -> R.drawable.baseline_search_24
}
searchResultImage.load(image)
}
}
}

View File

@ -10,18 +10,21 @@ import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.carousel.CarouselLayoutManager
import com.google.android.material.carousel.UncontainedCarouselStrategy
import com.google.android.material.chip.Chip
import com.isolaatti.R
import com.isolaatti.databinding.FragmentSearchBinding
import com.isolaatti.hashtags.ui.HashtagsActivity
import com.isolaatti.profile.ui.ProfileActivity
import com.isolaatti.search.data.HashtagsDto
import com.isolaatti.search.data.NewestUsersDto
import com.isolaatti.search.data.SearchDto
import com.isolaatti.search.data.SearchHistoryEntity
import com.isolaatti.search.data.SearchResultType
import com.isolaatti.search.presentation.SearchResultsAdapter
import com.isolaatti.search.presentation.SearchSuggestionsAdapter
import com.isolaatti.search.presentation.SearchViewModel
import com.isolaatti.search.presentation.UserCarouselAdapter
@ -38,6 +41,7 @@ class SearchFragment : Fragment() {
private val viewModel: SearchViewModel by viewModels()
private var searchSuggestionsAdapter: SearchSuggestionsAdapter? = null
private var newestUsersAdapter: UserCarouselAdapter? = null
private var searchResultsAdapter: SearchResultsAdapter? = null
private val searchSuggestionsObserver: Observer<List<SearchHistoryEntity>> = Observer {
searchSuggestionsAdapter?.submitList(it)
@ -45,6 +49,7 @@ class SearchFragment : Fragment() {
private val searchResultsObserver: Observer<SearchDto> = Observer {
Log.d(LOG_TAG, it.toString())
searchResultsAdapter?.submitList(it.result)
}
private val trendingHashtagsObserver: Observer<HashtagsDto> = Observer {
@ -55,7 +60,7 @@ class SearchFragment : Fragment() {
viewBinding.chipGroup.addView(Chip(requireContext()).apply {
text = "#$hashtag"
setOnClickListener {
requireContext().startActivity(Intent(requireContext(), HashtagsActivity::class.java))
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagPostsFragment(hashtag))
}
})
}
@ -123,7 +128,18 @@ class SearchFragment : Fragment() {
layoutManager = CarouselLayoutManager(UncontainedCarouselStrategy())
}
searchResultsAdapter = SearchResultsAdapter(
onItemClick = {
when(it.type) {
SearchResultType.Profile -> {}
SearchResultType.Post -> {}
SearchResultType.Hashtag -> {}
SearchResultType.Unknown -> {}
}
}
)
viewBinding.recyclerViewSearchResults.adapter = searchResultsAdapter
viewBinding.recyclerViewSearchResults.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
}
private fun setupListeners() {
@ -147,11 +163,19 @@ class SearchFragment : Fragment() {
if(it.itemId == R.id.close_button) {
showResults(false)
true
}
} else {
false
}
viewBinding.searchView.addTransitionListener { searchView, transitionState, transitionState2 -> }
}
viewBinding.openHashtagsButton.setOnClickListener {
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagsFragment())
}
viewBinding.browseProfilesButton.setOnClickListener {
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToBrowseProfilesFragment())
}
}
private fun setupObservers() {

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/baseline_close_24"
app:title="@string/hashtags"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/baseline_arrow_back_24"
app:titleCentered="true"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -52,11 +52,12 @@
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.button.MaterialButton
android:id="@+id/open_hashtags_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@id/chip_group"
android:layout_marginTop="8dp"
android:text="@string/see_all"/>
android:text="@string/go_to_hashtags"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
@ -90,11 +91,12 @@
app:layout_constraintTop_toBottomOf="@id/new_users_card_title"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/browse_profiles_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@id/new_users_recycler_view"
android:layout_marginTop="8dp"
android:text="@string/see_all"/>
android:text="@string/browse_profiles"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="70dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="4dp"
style="?attr/materialCardViewFilledStyle">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/search_result_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/baseline_search_24"/>
<TextView
android:id="@+id/search_result_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/search_result_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/search_result_image"
app:layout_constraintTop_toTopOf="parent"
android:maxLines="1"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
tools:text="Title" />
<TextView
android:id="@+id/search_result_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/search_result_image"
app:layout_constraintTop_toBottomOf="@+id/search_result_title"
android:maxLines="1"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
tools:text="Description" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -30,5 +30,35 @@
<fragment
android:id="@+id/searchFragment"
android:name="com.isolaatti.search.ui.SearchFragment"
android:label="@string/search" />
android:label="@string/search" >
<action
android:id="@+id/action_searchFragment_to_hashtagsFragment"
app:destination="@id/hashtagsFragment" />
<action
android:id="@+id/action_searchFragment_to_hashtagPostsFragment"
app:destination="@id/hashtagPostsFragment2" />
<action
android:id="@+id/action_searchFragment_to_browseProfilesFragment"
app:destination="@id/browseProfilesFragment" />
</fragment>
<fragment
android:id="@+id/hashtagsFragment"
android:name="com.isolaatti.hashtags.ui.HashtagsFragment"
android:label="HashtagsFragment" >
<action
android:id="@+id/action_hashtagsFragment2_to_hashtagPostsFragment2"
app:destination="@id/hashtagPostsFragment2" />
</fragment>
<fragment
android:id="@+id/hashtagPostsFragment2"
android:name="com.isolaatti.hashtags.ui.HashtagPostsFragment"
android:label="HashtagPostsFragment" >
<argument
android:name="hashtag"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/browseProfilesFragment"
android:name="com.isolaatti.profile.ui.BrowseProfilesFragment"
android:label="BrowseProfilesFragment" />
</navigation>

View File

@ -201,4 +201,6 @@
<string name="hashtags">Hashtags</string>
<string name="newest_profiles">Newest profiles</string>
<string name="see_all">See all</string>
<string name="go_to_hashtags">Go to hashtags</string>
<string name="browse_profiles">Browse profiles</string>
</resources>