diff --git a/app/src/main/java/com/isolaatti/hashtags/presentation/HashtagsViewModels.kt b/app/src/main/java/com/isolaatti/hashtags/presentation/HashtagsViewModels.kt new file mode 100644 index 0000000..33ad357 --- /dev/null +++ b/app/src/main/java/com/isolaatti/hashtags/presentation/HashtagsViewModels.kt @@ -0,0 +1,11 @@ +package com.isolaatti.hashtags.presentation + +import androidx.lifecycle.ViewModel + +class HashtagsViewModel : ViewModel() { + +} + +class HashtagPostsViewModel : ViewModel() { + +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt new file mode 100644 index 0000000..0763734 --- /dev/null +++ b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsActivity.kt b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsActivity.kt deleted file mode 100644 index 970447c..0000000 --- a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsActivity.kt +++ /dev/null @@ -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) { - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsFragment.kt b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsFragment.kt new file mode 100644 index 0000000..346a1ca --- /dev/null +++ b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsFragment.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/ui/BrowseProfilesFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/BrowseProfilesFragment.kt new file mode 100644 index 0000000..44524e3 --- /dev/null +++ b/app/src/main/java/com/isolaatti/profile/ui/BrowseProfilesFragment.kt @@ -0,0 +1,6 @@ +package com.isolaatti.profile.ui + +import androidx.fragment.app.Fragment + +class BrowseProfilesFragment : Fragment() { +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/data/SearchApi.kt b/app/src/main/java/com/isolaatti/search/data/SearchApi.kt index 86e237c..273da9d 100644 --- a/app/src/main/java/com/isolaatti/search/data/SearchApi.kt +++ b/app/src/main/java/com/isolaatti/search/data/SearchApi.kt @@ -5,8 +5,8 @@ import retrofit2.http.GET import retrofit2.http.Query interface SearchApi { - @GET("Search/Quick") - fun quickSearch(@Query("q") query: String): Call + @GET("Search/v2") + fun quickSearch(@Query("query") query: String): Call @GET("hashtags/trending") fun getTrendingHashtags(): Call diff --git a/app/src/main/java/com/isolaatti/search/data/SearchDto.kt b/app/src/main/java/com/isolaatti/search/data/SearchDto.kt index fbf44c6..d68f296 100644 --- a/app/src/main/java/com/isolaatti/search/data/SearchDto.kt +++ b/app/src/main/java/com/isolaatti/search/data/SearchDto.kt @@ -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, - val posts: List - // TODO add the other types -) +data class SearchDto(val result: List) + +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) diff --git a/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt b/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt new file mode 100644 index 0000000..669e458 --- /dev/null +++ b/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt @@ -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(itemCallback) { + companion object { + val itemCallback = object: DiffUtil.ItemCallback() { + 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt b/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt index d67e85d..37e4101 100644 --- a/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt @@ -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> = Observer { searchSuggestionsAdapter?.submitList(it) @@ -45,6 +49,7 @@ class SearchFragment : Fragment() { private val searchResultsObserver: Observer = Observer { Log.d(LOG_TAG, it.toString()) + searchResultsAdapter?.submitList(it.result) } private val trendingHashtagsObserver: Observer = 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() { diff --git a/app/src/main/res/layout/fragment_hashtags.xml b/app/src/main/res/layout/fragment_hashtags.xml new file mode 100644 index 0000000..cb65f9e --- /dev/null +++ b/app/src/main/res/layout/fragment_hashtags.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_posts_hashtag.xml b/app/src/main/res/layout/fragment_posts_hashtag.xml new file mode 100644 index 0000000..8748d07 --- /dev/null +++ b/app/src/main/res/layout/fragment_posts_hashtag.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index c253351..22024ba 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -52,11 +52,12 @@ + android:text="@string/go_to_hashtags"/> + android:text="@string/browse_profiles"/> diff --git a/app/src/main/res/layout/search_result_item.xml b/app/src/main/res/layout/search_result_item.xml new file mode 100644 index 0000000..014b567 --- /dev/null +++ b/app/src/main/res/layout/search_result_item.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/home_navigation.xml b/app/src/main/res/navigation/home_navigation.xml index a233317..b6349d4 100644 --- a/app/src/main/res/navigation/home_navigation.xml +++ b/app/src/main/res/navigation/home_navigation.xml @@ -30,5 +30,35 @@ + android:label="@string/search" > + + + + + + + + + + + \ 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 031087b..3fe0e28 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -201,4 +201,6 @@ Hashtags Newest profiles See all + Go to hashtags + Browse profiles \ No newline at end of file