WIP busqueda, hastags y pantalla de explorar perfiles
This commit is contained in:
parent
8730bbe796
commit
49ccebb539
@ -0,0 +1,11 @@
|
||||
package com.isolaatti.hashtags.presentation
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class HashtagsViewModel : ViewModel() {
|
||||
|
||||
}
|
||||
|
||||
class HashtagPostsViewModel : ViewModel() {
|
||||
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package com.isolaatti.profile.ui
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
class BrowseProfilesFragment : Fragment() {
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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>)
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
false
|
||||
|
||||
}
|
||||
|
||||
viewBinding.openHashtagsButton.setOnClickListener {
|
||||
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagsFragment())
|
||||
}
|
||||
|
||||
viewBinding.browseProfilesButton.setOnClickListener {
|
||||
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToBrowseProfilesFragment())
|
||||
}
|
||||
|
||||
viewBinding.searchView.addTransitionListener { searchView, transitionState, transitionState2 -> }
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
|
||||
17
app/src/main/res/layout/fragment_hashtags.xml
Normal file
17
app/src/main/res/layout/fragment_hashtags.xml
Normal 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>
|
||||
18
app/src/main/res/layout/fragment_posts_hashtag.xml
Normal file
18
app/src/main/res/layout/fragment_posts_hashtag.xml
Normal 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>
|
||||
@ -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>
|
||||
|
||||
57
app/src/main/res/layout/search_result_item.xml
Normal file
57
app/src/main/res/layout/search_result_item.xml
Normal 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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
Loading…
x
Reference in New Issue
Block a user