From 8730bbe7966c7fee6d6ebecea0e564aef03c2584 Mon Sep 17 00:00:00 2001 From: erik-everardo Date: Fri, 29 Mar 2024 00:10:55 -0600 Subject: [PATCH] WIP 2 search --- app/src/main/AndroidManifest.xml | 1 + .../com/isolaatti/database/AppDatabase.kt | 2 +- .../isolaatti/hashtags/ui/HashtagsActivity.kt | 12 ++ .../main/java/com/isolaatti/search/Module.kt | 14 ++ .../com/isolaatti/search/data/SearchApi.kt | 9 +- .../com/isolaatti/search/data/SearchDao.kt | 25 +++- .../com/isolaatti/search/data/SearchDto.kt | 15 +++ .../search/data/SearchHistoryEntity.kt | 9 +- .../search/data/SearchRepositoryImpl.kt | 88 +++++++++++++ .../search/domain/SearchRepository.kt | 11 ++ .../presentation/SearchSuggestionsAdapter.kt | 46 +++++++ .../search/presentation/SearchViewModel.kt | 101 ++++++++++++++- .../presentation/UserCarouselAdapter.kt | 58 +++++++++ .../com/isolaatti/search/ui/SearchFragment.kt | 121 +++++++++++++++++- app/src/main/res/drawable/avatar.xml | 19 +++ .../res/drawable/clock_rotate_left_solid.xml | 9 ++ app/src/main/res/drawable/markdown.xml | 9 ++ app/src/main/res/layout/fragment_search.xml | 106 ++++++++++++++- .../res/layout/search_suggestion_item.xml | 40 ++++++ .../main/res/layout/users_carousel_item.xml | 42 ++++++ app/src/main/res/menu/search_bar_menu.xml | 9 ++ app/src/main/res/values/strings.xml | 3 + 22 files changed, 730 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/isolaatti/hashtags/ui/HashtagsActivity.kt create mode 100644 app/src/main/java/com/isolaatti/search/data/SearchDto.kt create mode 100644 app/src/main/java/com/isolaatti/search/data/SearchRepositoryImpl.kt create mode 100644 app/src/main/java/com/isolaatti/search/presentation/SearchSuggestionsAdapter.kt create mode 100644 app/src/main/java/com/isolaatti/search/presentation/UserCarouselAdapter.kt create mode 100644 app/src/main/res/drawable/avatar.xml create mode 100644 app/src/main/res/drawable/clock_rotate_left_solid.xml create mode 100644 app/src/main/res/drawable/markdown.xml create mode 100644 app/src/main/res/layout/search_suggestion_item.xml create mode 100644 app/src/main/res/layout/users_carousel_item.xml create mode 100644 app/src/main/res/menu/search_bar_menu.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7b7d472..e2c6abd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,6 +44,7 @@ + + + @GET("hashtags/trending") + fun getTrendingHashtags(): Call + + @GET("Search/newestUsers") + fun getNewestUsers(@Query("limit") limit: Int, @Query("after") after: Int?): Call } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/data/SearchDao.kt b/app/src/main/java/com/isolaatti/search/data/SearchDao.kt index 938a7b5..6e291f3 100644 --- a/app/src/main/java/com/isolaatti/search/data/SearchDao.kt +++ b/app/src/main/java/com/isolaatti/search/data/SearchDao.kt @@ -7,15 +7,32 @@ import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Transaction @Dao interface SearchDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertTerm(searchTerm: SearchHistoryEntity) - @Delete - fun deleteTerm(searchTerm: SearchHistoryEntity) + @Query("SELECT COUNT(*) FROM search_history WHERE `query` = :query") + fun itemCount(query: String): Int - @Query("SELECT * FROM search_history ORDER BY time DESC") - fun getTerms(): LiveData> + @Transaction + fun insertTermTransaction(searchTerm: SearchHistoryEntity) { + if(itemCount(searchTerm.query) > 0) { + deleteTerm(searchTerm.query) + } + insertTerm(searchTerm) + } + + @Query("DELETE FROM search_history WHERE `query` = :searchTerm") + fun deleteTerm(searchTerm: String) + + @Query("SELECT *, `rowid` FROM search_history ORDER BY time DESC") + fun getTerms(): List + + @Query(""" + SELECT *, `rowid` FROM search_history WHERE `query` MATCH :searchQuery + """) + fun getTermsForQuery(searchQuery: String): List } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/data/SearchDto.kt b/app/src/main/java/com/isolaatti/search/data/SearchDto.kt new file mode 100644 index 0000000..fbf44c6 --- /dev/null +++ b/app/src/main/java/com/isolaatti/search/data/SearchDto.kt @@ -0,0 +1,15 @@ +package com.isolaatti.search.data + +import com.isolaatti.posting.posts.data.remote.FeedDto + + +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 HashtagsDto(val result: List) + +data class NewestUsersDto(val result: List) \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/data/SearchHistoryEntity.kt b/app/src/main/java/com/isolaatti/search/data/SearchHistoryEntity.kt index 0bb5177..b0d7b72 100644 --- a/app/src/main/java/com/isolaatti/search/data/SearchHistoryEntity.kt +++ b/app/src/main/java/com/isolaatti/search/data/SearchHistoryEntity.kt @@ -1,10 +1,17 @@ package com.isolaatti.search.data +import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.Fts4 +import androidx.room.Index import androidx.room.PrimaryKey @Entity(tableName = "search_history") +@Fts4 data class SearchHistoryEntity( - @PrimaryKey val query: String, + @PrimaryKey + @ColumnInfo(name = "rowid") + val rowId: Int, + val query: String, val time: Long ) diff --git a/app/src/main/java/com/isolaatti/search/data/SearchRepositoryImpl.kt b/app/src/main/java/com/isolaatti/search/data/SearchRepositoryImpl.kt new file mode 100644 index 0000000..54bd9fe --- /dev/null +++ b/app/src/main/java/com/isolaatti/search/data/SearchRepositoryImpl.kt @@ -0,0 +1,88 @@ +package com.isolaatti.search.data + +import android.util.Log +import com.isolaatti.search.domain.SearchRepository +import com.isolaatti.utils.Resource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import retrofit2.awaitResponse + +class SearchRepositoryImpl(private val searchApi: SearchApi, private val searchDao: SearchDao) : SearchRepository { + companion object { + const val LOG_TAG = "SearchRepositoryImpl" + } + override fun search(query: String): Flow> = flow { + searchDao.insertTermTransaction(SearchHistoryEntity(0, query, System.currentTimeMillis())) + + try { + val response = searchApi.quickSearch(query).awaitResponse() + if(response.isSuccessful) { + val dto = response.body() + if(dto == null) { + Log.e(LOG_TAG, "Emitting error, could not get body") + emit(Resource.Error(Resource.Error.ErrorType.OtherError)) + return@flow + } + emit(Resource.Success(dto)) + + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) + } + } catch(e: Exception) { + Log.e(LOG_TAG, "Error searching: ${e.message}") + emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) + } + } + + override fun getSuggestions(query: String): Flow>> = flow { + emit(Resource.Loading()) + + if(query.isEmpty()) { + emit(Resource.Success(searchDao.getTerms())) + } else { + emit(Resource.Success(searchDao.getTermsForQuery(query))) + } + + } + + override fun removeSuggestion(query: String): Flow> = flow { + searchDao.deleteTerm(query) + emit(Resource.Success(true)) + } + + override fun getTrendingHashtags(): Flow> = flow { + emit(Resource.Loading()) + + try { + val result = searchApi.getTrendingHashtags().awaitResponse() + if(result.isSuccessful) { + val dto = result.body() + dto?.also { emit(Resource.Success(it)) } + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(result.code()))) + } + } catch(e: Exception) { + Log.e(LOG_TAG, "Error getting trending hashtags: ${e.message}") + } + } + + override fun getNewestUsers(): Flow> = flow { + emit(Resource.Loading()) + + try { + val result = searchApi.getNewestUsers(10, null).awaitResponse() + + if(result.isSuccessful) { + val dto = result.body() + if(dto != null) { + emit(Resource.Success(dto)) + } + + } else { + emit(Resource.Error(Resource.Error.mapErrorCode(result.code()))) + } + } catch(e: Exception) { + Log.e(LOG_TAG, "Error getting newest users: ${e.message}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/domain/SearchRepository.kt b/app/src/main/java/com/isolaatti/search/domain/SearchRepository.kt index 81e4611..9e80e38 100644 --- a/app/src/main/java/com/isolaatti/search/domain/SearchRepository.kt +++ b/app/src/main/java/com/isolaatti/search/domain/SearchRepository.kt @@ -1,8 +1,19 @@ package com.isolaatti.search.domain +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.utils.Resource import kotlinx.coroutines.flow.Flow interface SearchRepository { + fun search(query: String): Flow> + fun getSuggestions(query: String): Flow>> + + fun removeSuggestion(query: String): Flow> + + fun getTrendingHashtags(): Flow> + fun getNewestUsers(): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/presentation/SearchSuggestionsAdapter.kt b/app/src/main/java/com/isolaatti/search/presentation/SearchSuggestionsAdapter.kt new file mode 100644 index 0000000..81f3dcb --- /dev/null +++ b/app/src/main/java/com/isolaatti/search/presentation/SearchSuggestionsAdapter.kt @@ -0,0 +1,46 @@ +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 com.isolaatti.databinding.SearchSuggestionItemBinding +import com.isolaatti.search.data.SearchHistoryEntity + +class SearchSuggestionsAdapter( + private val onItemClick: (item: SearchHistoryEntity) -> Unit = {}, + private val onItemDeleteClick: (item: SearchHistoryEntity) -> Unit = {} +) : ListAdapter(itemCallback) { + + inner class SearchSuggestionItemViewHolder(val searchSuggestionItemBinding: SearchSuggestionItemBinding) : ViewHolder(searchSuggestionItemBinding.root) + + companion object { + val itemCallback = object: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: SearchHistoryEntity, newItem: SearchHistoryEntity): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: SearchHistoryEntity, newItem: SearchHistoryEntity): Boolean { + return oldItem.query == newItem.query + } + + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): SearchSuggestionItemViewHolder { + return SearchSuggestionItemViewHolder(SearchSuggestionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + } + + override fun onBindViewHolder(holder: SearchSuggestionItemViewHolder, position: Int) { + val searchSuggestion = getItem(position) + + holder.searchSuggestionItemBinding.suggestionText.text = searchSuggestion.query + + holder.searchSuggestionItemBinding.root.setOnClickListener { onItemClick(searchSuggestion) } + holder.searchSuggestionItemBinding.deleteButton.setOnClickListener { onItemDeleteClick(searchSuggestion) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/presentation/SearchViewModel.kt b/app/src/main/java/com/isolaatti/search/presentation/SearchViewModel.kt index d4a0e80..198ec5c 100644 --- a/app/src/main/java/com/isolaatti/search/presentation/SearchViewModel.kt +++ b/app/src/main/java/com/isolaatti/search/presentation/SearchViewModel.kt @@ -1,20 +1,111 @@ package com.isolaatti.search.presentation -import androidx.lifecycle.LiveData +import android.util.Log +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.isolaatti.search.data.SearchApi -import com.isolaatti.search.data.SearchDao +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.domain.SearchRepository +import com.isolaatti.utils.Resource import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class SearchViewModel @Inject constructor() : ViewModel() { - fun search(query: String) { +class SearchViewModel @Inject constructor(private val searchRepository: SearchRepository) : ViewModel() { + companion object { + const val LOG_TAG = "SearchViewModel" + } + + val searchSuggestions: MutableLiveData> = MutableLiveData() + val searchResults: MutableLiveData = MutableLiveData() + val trendingHashtags: MutableLiveData = MutableLiveData() + val newestUsers: MutableLiveData = MutableLiveData() + + var searchQuery = "" + + fun search() { + Log.d(LOG_TAG, searchQuery) viewModelScope.launch { + if(searchQuery.isNotEmpty()) { + searchRepository.search(searchQuery).onEach { + when(it) { + is Resource.Error -> {} + is Resource.Loading -> {} + is Resource.Success -> { + searchResults.postValue(it.data!!) + } + } + }.flowOn(Dispatchers.IO).launchIn(this) + } + + } } + fun getSuggestions(query: String) { + searchQuery = query + viewModelScope.launch { + searchRepository.getSuggestions(query).onEach { + when(it) { + is Resource.Error -> {} + is Resource.Loading -> {} + is Resource.Success -> { + searchSuggestions.postValue(it.data!!) + } + } + }.flowOn(Dispatchers.IO).launchIn(this) + } + } + + fun deleteSuggestion(suggestion: SearchHistoryEntity) { + viewModelScope.launch { + searchRepository.removeSuggestion(suggestion.query).onEach { resource -> + if(resource is Resource.Success && resource.data == true) { + val searchSuggestionsMutable = searchSuggestions.value?.toMutableList() + searchSuggestionsMutable?.remove(suggestion) + + searchSuggestionsMutable?.also { searchSuggestions.postValue(it) } + } + }.flowOn(Dispatchers.IO).launchIn(this) + } + } + + fun loadTendingHashtags() { + viewModelScope.launch { + searchRepository.getTrendingHashtags().onEach { resource -> + when(resource) { + is Resource.Error -> {} + is Resource.Loading -> {} + is Resource.Success -> { + resource.data?.also { trendingHashtags.postValue(it) } + } + } + }.flowOn(Dispatchers.IO).launchIn(this) + } + } + + fun loadNewestUsers() { + viewModelScope.launch { + searchRepository.getNewestUsers().onEach { resource -> + when(resource) { + is Resource.Error -> {} + is Resource.Loading -> {} + is Resource.Success -> { + resource.data?.also { newestUsers.postValue(it) } + + } + } + }.flowOn(Dispatchers.IO).launchIn(this) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/search/presentation/UserCarouselAdapter.kt b/app/src/main/java/com/isolaatti/search/presentation/UserCarouselAdapter.kt new file mode 100644 index 0000000..a8a21ca --- /dev/null +++ b/app/src/main/java/com/isolaatti/search/presentation/UserCarouselAdapter.kt @@ -0,0 +1,58 @@ +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.UsersCarouselItemBinding +import com.isolaatti.search.data.ProfileSearchDto +import com.isolaatti.utils.UrlGen + +class UserCarouselAdapter( + private val onProfileClick: (profileId: Int) -> Unit = {} +) : ListAdapter(itemCallback) { + + companion object { + val itemCallback = object: DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ProfileSearchDto, + newItem: ProfileSearchDto + ): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: ProfileSearchDto, + newItem: ProfileSearchDto + ): Boolean { + return oldItem == newItem + } + + } + } + + inner class UserCarouselItemViewHolder(val usersCarouselItemBinding: UsersCarouselItemBinding) : ViewHolder(usersCarouselItemBinding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserCarouselItemViewHolder { + return UserCarouselItemViewHolder(UsersCarouselItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + } + + override fun onBindViewHolder(holder: UserCarouselItemViewHolder, position: Int) { + val user = getItem(position) + + holder.usersCarouselItemBinding.userCarouselName.text = user.name + if(user.imageId != null) { + holder.usersCarouselItemBinding.userCarouselImageView.load(UrlGen.imageUrl(user.imageId)) + } else { + holder.usersCarouselItemBinding.userCarouselImageView.load(R.drawable.avatar) + } + + holder.usersCarouselItemBinding.userCarouselCard.setOnClickListener { onProfileClick(user.id) } + + } + + +} \ 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 f3e85bb..d67e85d 100644 --- a/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt @@ -1,19 +1,76 @@ package com.isolaatti.search.ui +import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +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.presentation.SearchSuggestionsAdapter import com.isolaatti.search.presentation.SearchViewModel +import com.isolaatti.search.presentation.UserCarouselAdapter +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class SearchFragment : Fragment() { + companion object { + const val LOG_TAG = "SearchFragment" + } + lateinit var viewBinding: FragmentSearchBinding private val viewModel: SearchViewModel by viewModels() + private var searchSuggestionsAdapter: SearchSuggestionsAdapter? = null + private var newestUsersAdapter: UserCarouselAdapter? = null + + private val searchSuggestionsObserver: Observer> = Observer { + searchSuggestionsAdapter?.submitList(it) + } + + private val searchResultsObserver: Observer = Observer { + Log.d(LOG_TAG, it.toString()) + } + + private val trendingHashtagsObserver: Observer = Observer { + Log.d(LOG_TAG, it.toString()) + + viewBinding.chipGroup.removeAllViews() + it.result.forEach { hashtag -> + viewBinding.chipGroup.addView(Chip(requireContext()).apply { + text = "#$hashtag" + setOnClickListener { + requireContext().startActivity(Intent(requireContext(), HashtagsActivity::class.java)) + } + }) + } + } + + private val newestUsersObserver: Observer = Observer { + newestUsersAdapter?.submitList(it.result) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + viewModel.loadTendingHashtags() + viewModel.loadNewestUsers() + } override fun onCreateView( inflater: LayoutInflater, @@ -24,23 +81,83 @@ class SearchFragment : Fragment() { return viewBinding.root } + private fun showResults(show: Boolean) { + if(show) { + viewBinding.viewAnimator.displayedChild = 1 + viewBinding.searchBar.menu.findItem(R.id.close_button)?.setVisible(true) + } else { + viewBinding.viewAnimator.displayedChild = 0 + viewModel.searchQuery = "" + viewBinding.searchBar.setText("") + viewBinding.searchBar.menu.findItem(R.id.close_button)?.setVisible(false) + } + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupListeners() setupObservers() + searchSuggestionsAdapter = SearchSuggestionsAdapter( + onItemClick = { + viewBinding.searchBar.setText(it.query) + viewModel.searchQuery = it.query + viewBinding.searchView.hide() + viewModel.search() + showResults(true) + }, + onItemDeleteClick = { + viewModel.deleteSuggestion(it) + } + ) + viewBinding.searchHistoryRecyclerView.apply { + adapter = searchSuggestionsAdapter + layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) + } + + newestUsersAdapter = UserCarouselAdapter( + onProfileClick = { + ProfileActivity.startActivity(requireContext(), it) + } + ) + viewBinding.newUsersRecyclerView.apply { + adapter = newestUsersAdapter + layoutManager = CarouselLayoutManager(UncontainedCarouselStrategy()) + } + } private fun setupListeners() { viewBinding.searchView.editText.doAfterTextChanged { searchText -> if(searchText != null) { - viewModel.search(searchText.toString()) + viewModel.getSuggestions(searchText.toString()) } } + + viewBinding.searchView.editText.setOnEditorActionListener { v, actionId, event -> + Log.d(LOG_TAG, "actionId: $actionId; event: $event") + viewBinding.searchBar.setText(viewBinding.searchView.text) + viewBinding.searchView.hide() + viewModel.search() + showResults(true) + true + } + + viewBinding.searchBar.setOnMenuItemClickListener { + if(it.itemId == R.id.close_button) { + showResults(false) + true + } + false + } + + viewBinding.searchView.addTransitionListener { searchView, transitionState, transitionState2 -> } } private fun setupObservers() { - + viewModel.searchSuggestions.observe(viewLifecycleOwner, searchSuggestionsObserver) + viewModel.searchResults.observe(viewLifecycleOwner, searchResultsObserver) + viewModel.trendingHashtags.observe(viewLifecycleOwner, trendingHashtagsObserver) + viewModel.newestUsers.observe(viewLifecycleOwner, newestUsersObserver) } } \ No newline at end of file diff --git a/app/src/main/res/drawable/avatar.xml b/app/src/main/res/drawable/avatar.xml new file mode 100644 index 0000000..65ae8f9 --- /dev/null +++ b/app/src/main/res/drawable/avatar.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/clock_rotate_left_solid.xml b/app/src/main/res/drawable/clock_rotate_left_solid.xml new file mode 100644 index 0000000..8a2984d --- /dev/null +++ b/app/src/main/res/drawable/clock_rotate_left_solid.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/markdown.xml b/app/src/main/res/drawable/markdown.xml new file mode 100644 index 0000000..8984b3e --- /dev/null +++ b/app/src/main/res/drawable/markdown.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index fbac861..c253351 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -2,16 +2,111 @@ - - - + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:hint="@string/searchbar_hint" + app:menu="@menu/search_bar_menu"/> diff --git a/app/src/main/res/layout/search_suggestion_item.xml b/app/src/main/res/layout/search_suggestion_item.xml new file mode 100644 index 0000000..5efbb83 --- /dev/null +++ b/app/src/main/res/layout/search_suggestion_item.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/users_carousel_item.xml b/app/src/main/res/layout/users_carousel_item.xml new file mode 100644 index 0000000..79ee15e --- /dev/null +++ b/app/src/main/res/layout/users_carousel_item.xml @@ -0,0 +1,42 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/search_bar_menu.xml b/app/src/main/res/menu/search_bar_menu.xml new file mode 100644 index 0000000..92ed0c5 --- /dev/null +++ b/app/src/main/res/menu/search_bar_menu.xml @@ -0,0 +1,9 @@ + + + + + \ 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 5e56918..031087b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -198,4 +198,7 @@ Delete notification Remove this notification? An error ocurred + Hashtags + Newest profiles + See all \ No newline at end of file