From e113a9c79517b8c2e96f801db6765e9102b3ce69 Mon Sep 17 00:00:00 2001 From: Jannik Date: Mon, 26 Dec 2022 16:10:38 +0100 Subject: [PATCH] Merge library and search into one fragment closes #55 --- .../teapod/ui/activity/main/MainActivity.kt | 10 +- .../main/fragments/LibraryFragment.kt | 115 +++++++++------ .../main/fragments/MediaFragmentSimilar.kt | 2 +- .../main/fragments/MyListsFragment.kt | 82 +++++++++++ .../activity/main/fragments/SearchFragment.kt | 112 --------------- .../viewmodel/LibraryFragmentViewModel.kt | 131 ++++++++++++++++++ .../ui/components/EmptySubmitSearchView.kt | 31 +++++ .../ic_baseline_bookmark_border_24.xml | 5 + app/src/main/res/layout/fragment_library.xml | 25 +++- ...gment_search.xml => fragment_my_lists.xml} | 28 +--- app/src/main/res/menu/bottom_nav_menu.xml | 10 +- .../main/res/navigation/mobile_navigation.xml | 12 +- app/src/main/res/values-de-rDE/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/values/styles.xml | 10 +- 15 files changed, 374 insertions(+), 203 deletions(-) create mode 100644 app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MyListsFragment.kt delete mode 100644 app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/SearchFragment.kt create mode 100644 app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/LibraryFragmentViewModel.kt create mode 100644 app/src/main/java/org/mosad/teapod/ui/components/EmptySubmitSearchView.kt create mode 100644 app/src/main/res/drawable/ic_baseline_bookmark_border_24.xml rename app/src/main/res/layout/{fragment_search.xml => fragment_my_lists.xml} (51%) diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt index 26b4aae..486e0a2 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt @@ -40,8 +40,8 @@ import org.mosad.teapod.preferences.EncryptedPreferences import org.mosad.teapod.preferences.Preferences import org.mosad.teapod.ui.activity.main.fragments.AccountFragment import org.mosad.teapod.ui.activity.main.fragments.HomeFragment +import org.mosad.teapod.ui.activity.main.fragments.MyListsFragment import org.mosad.teapod.ui.activity.main.fragments.LibraryFragment -import org.mosad.teapod.ui.activity.main.fragments.SearchFragment import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity import org.mosad.teapod.util.DataTypes import org.mosad.teapod.util.metadb.MetaDBController @@ -100,12 +100,12 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen activeBaseFragment = HomeFragment() true } - R.id.navigation_library -> { - activeBaseFragment = LibraryFragment() + R.id.navigation_my_lists -> { + activeBaseFragment = MyListsFragment() true } - R.id.navigation_search -> { - activeBaseFragment = SearchFragment() + R.id.navigation_library -> { + activeBaseFragment = LibraryFragment() true } R.id.navigation_account -> { diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/LibraryFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/LibraryFragment.kt index 0b4f264..f83e067 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/LibraryFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/LibraryFragment.kt @@ -1,30 +1,30 @@ package org.mosad.teapod.ui.activity.main.fragments +import android.annotation.SuppressLint import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.widget.SearchView import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.coroutines.launch import org.mosad.teapod.databinding.FragmentLibraryBinding -import org.mosad.teapod.parser.crunchyroll.Crunchyroll -import org.mosad.teapod.util.ItemMedia +import org.mosad.teapod.ui.activity.main.viewmodel.LibraryFragmentViewModel import org.mosad.teapod.util.adapter.MediaItemListAdapter -import org.mosad.teapod.util.decoration.MediaItemDecoration import org.mosad.teapod.util.showFragment -import org.mosad.teapod.util.toItemMediaList class LibraryFragment : Fragment() { private lateinit var binding: FragmentLibraryBinding private lateinit var adapter: MediaItemListAdapter - - private val itemList = arrayListOf() - private val pageSize = 30 - private var nextItemIndex = 0 + private val model: LibraryFragmentViewModel by viewModels() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = FragmentLibraryBinding.inflate(inflater, container, false) @@ -36,48 +36,79 @@ class LibraryFragment : Fragment() { // TODO replace with pagination3 // https://medium.com/swlh/paging3-recyclerview-pagination-made-easy-333c7dfa8797 - binding.recyclerMediaLibrary.addOnScrollListener(PaginationScrollListener()) + binding.recyclerMediaSearch.addOnScrollListener(PaginationScrollListener()) - // init async - lifecycleScope.launch { - // create and set the adapter, needs context - context?.let { - itemList.addAll(Crunchyroll.browse(n = pageSize).toItemMediaList()) - nextItemIndex += pageSize + adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener { + binding.searchText.clearFocus() + activity?.showFragment(MediaFragment(it.id)) + }) + binding.recyclerMediaSearch.adapter = adapter - adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener { - activity?.showFragment(MediaFragment(it.id)) - }) - binding.recyclerMediaLibrary.adapter = adapter - adapter.submitList(itemList) + binding.searchText.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String?): Boolean { + println("onQueryTextSubmit: $query") + query?.let { model.search(it) } + return false // return false to dismiss the keyboard } - } - } + override fun onQueryTextChange(newText: String?): Boolean { + println("onQueryTextChange: $newText") + newText?.let { model.search(it) } + return false // return false to dismiss the keyboard + } + }) - inner class PaginationScrollListener: RecyclerView.OnScrollListener() { - private var isLoading = false - - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - super.onScrolled(recyclerView, dx, dy) - val layoutManager = recyclerView.layoutManager as GridLayoutManager? - - if (!isLoading) layoutManager?.let { - // itemList.size - 5 to start loading a bit earlier than the actual end - if (layoutManager.findLastCompletelyVisibleItemPosition() >= (itemList.size - 5)) { - // load new browse results async - isLoading = true - lifecycleScope.launch { - val firstNewItemIndex = itemList.lastIndex + 1 - itemList.addAll(Crunchyroll.browse(start = nextItemIndex, n = pageSize).toItemMediaList()) - nextItemIndex += pageSize - - adapter.notifyItemRangeInserted(firstNewItemIndex, pageSize) - isLoading = false + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + model.onUiState(viewLifecycleOwner.lifecycleScope) { uiState -> + when (uiState) { + is LibraryFragmentViewModel.UiState.Browse -> bindUiStateBrowse(uiState) + is LibraryFragmentViewModel.UiState.Search -> bindUiStateSearch(uiState) + is LibraryFragmentViewModel.UiState.Loading -> bindUiStateLoading() + is LibraryFragmentViewModel.UiState.Error -> bindUiStateError(uiState) } } } } } -} \ No newline at end of file + private fun bindUiStateBrowse(uiState: LibraryFragmentViewModel.UiState.Browse) { + adapter.submitList(uiState.itemList) + } + + @SuppressLint("NotifyDataSetChanged") + private fun bindUiStateSearch(uiState: LibraryFragmentViewModel.UiState.Search) { + adapter.submitList(uiState.itemList) + adapter.notifyDataSetChanged() // this is needed, else the adapter will not update + } + + private fun bindUiStateLoading() { + // currently not used + } + + private fun bindUiStateError(uiState: LibraryFragmentViewModel.UiState.Error) { + // currently not used + Log.e(javaClass.name, "A error occurred while loading a UiState: ${uiState.message}") + } + + inner class PaginationScrollListener: RecyclerView.OnScrollListener() { + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + if (!model.isLazyLoading) { + val layoutManager = recyclerView.layoutManager as? GridLayoutManager + layoutManager?.let { + // adapter.itemCount - 10 to start loading a bit earlier than the actual end + if (layoutManager.findLastCompletelyVisibleItemPosition() >= (adapter.itemCount - 10)) { + model.onLazyLoad().invokeOnCompletion { + adapter.notifyItemRangeInserted(adapter.itemCount, model.PAGESIZE) + } + } + } + } + } + + } + +} diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt index da9babb..3c30d29 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt @@ -47,7 +47,7 @@ class MediaFragmentSimilar : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - + binding.recyclerMediaSimilar.adapter = MediaItemListAdapter( MediaItemListAdapter.OnClickListener { activity?.showFragment(MediaFragment(it.id)) diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MyListsFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MyListsFragment.kt new file mode 100644 index 0000000..486831a --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MyListsFragment.kt @@ -0,0 +1,82 @@ +package org.mosad.teapod.ui.activity.main.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch +import org.mosad.teapod.databinding.FragmentMyListsBinding +import org.mosad.teapod.parser.crunchyroll.Crunchyroll +import org.mosad.teapod.util.ItemMedia +import org.mosad.teapod.util.adapter.MediaItemListAdapter +import org.mosad.teapod.util.showFragment +import org.mosad.teapod.util.toItemMediaList + +class MyListsFragment : Fragment() { + + private lateinit var binding: FragmentMyListsBinding + private lateinit var adapter: MediaItemListAdapter + + private val itemList = arrayListOf() + private val pageSize = 30 + private var nextItemIndex = 0 + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragmentMyListsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // TODO replace with pagination3 + // https://medium.com/swlh/paging3-recyclerview-pagination-made-easy-333c7dfa8797 + binding.recyclerMediaLibrary.addOnScrollListener(PaginationScrollListener()) + + // init async + lifecycleScope.launch { + // create and set the adapter, needs context + context?.let { + itemList.addAll(Crunchyroll.browse(n = pageSize).toItemMediaList()) + nextItemIndex += pageSize + + adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener { + activity?.showFragment(MediaFragment(it.id)) + }) + binding.recyclerMediaLibrary.adapter = adapter + adapter.submitList(itemList) + } + + } + } + + inner class PaginationScrollListener: RecyclerView.OnScrollListener() { + private var isLoading = false + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + val layoutManager = recyclerView.layoutManager as GridLayoutManager? + + if (!isLoading) layoutManager?.let { + // itemList.size - 5 to start loading a bit earlier than the actual end + if (layoutManager.findLastCompletelyVisibleItemPosition() >= (itemList.size - 5)) { + // load new browse results async + isLoading = true + lifecycleScope.launch { + val firstNewItemIndex = itemList.lastIndex + 1 + itemList.addAll(Crunchyroll.browse(start = nextItemIndex, n = pageSize).toItemMediaList()) + nextItemIndex += pageSize + + adapter.notifyItemRangeInserted(firstNewItemIndex, pageSize) + isLoading = false + } + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/SearchFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/SearchFragment.kt deleted file mode 100644 index 63ed666..0000000 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/SearchFragment.kt +++ /dev/null @@ -1,112 +0,0 @@ -package org.mosad.teapod.ui.activity.main.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.SearchView -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import org.mosad.teapod.databinding.FragmentSearchBinding -import org.mosad.teapod.parser.crunchyroll.Crunchyroll -import org.mosad.teapod.util.ItemMedia -import org.mosad.teapod.util.adapter.MediaItemListAdapter -import org.mosad.teapod.util.decoration.MediaItemDecoration -import org.mosad.teapod.util.showFragment -import org.mosad.teapod.util.toItemMediaList - -class SearchFragment : Fragment() { - - private lateinit var binding: FragmentSearchBinding - private lateinit var adapter: MediaItemListAdapter - - private val itemList = arrayListOf() - private var searchJob: Job? = null - private var oldSearchQuery = "" - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - binding = FragmentSearchBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.recyclerMediaSearch.addItemDecoration(MediaItemDecoration(9)) - - adapter = MediaItemListAdapter(MediaItemListAdapter.OnClickListener { - binding.searchText.clearFocus() - activity?.showFragment(MediaFragment(it.id)) - }) - binding.recyclerMediaSearch.adapter = adapter - - initActions() - } - - private fun initActions() { - binding.searchText.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - query?.let { search(it) } - return false - } - - override fun onQueryTextChange(newText: String?): Boolean { - newText?.let { search(it) } - return false - } - }) - } - - /** - * Search for a query string at Crunchyroll and submit the results to the adapter. - */ - private fun search(query: String) { - // if the query hasn't changed since the last successful search, return - if (query == oldSearchQuery) return - - // cancel search job if one is already running - if (searchJob?.isActive == true) searchJob?.cancel() - - searchJob = lifecycleScope.async { - // TODO maybe wait a few ms (500ms?) before searching, if the user inputs any other chars - val results = Crunchyroll.search(query, 50) - - itemList.clear() // TODO needs clean up - - // TODO add top results first heading - adapter.submitList( - results.items.firstOrNull()?.items?.toItemMediaList() ?: listOf() - ) - - // TODO currently only tv shows are supported, hence only the first items array - // should be always present - -// // TODO add tv shows heading -// if (results.items.size >= 2) { -// itemList.addAll(results.items[1].items.map { item -> -// ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source) -// }) -// } -// -// // TODO add movies heading -// if (results.items.size >= 3) { -// itemList.addAll(results.items[2].items.map { item -> -// ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source) -// }) -// } -// -// // TODO add episodes heading -// if (results.items.size >= 4) { -// itemList.addAll(results.items[3].items.map { item -> -// ItemMedia(item.id, item.title, item.images.poster_wide[0][0].source) -// }) -// } - - // after successfully searching the query term, add it as old query, to make sure we - // don't search again if the query hasn't changed - oldSearchQuery = query - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/LibraryFragmentViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/LibraryFragmentViewModel.kt new file mode 100644 index 0000000..45e784b --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/LibraryFragmentViewModel.kt @@ -0,0 +1,131 @@ +package org.mosad.teapod.ui.activity.main.viewmodel + +import androidx.lifecycle.LifecycleCoroutineScope +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.mosad.teapod.parser.crunchyroll.Crunchyroll +import org.mosad.teapod.util.ItemMedia +import org.mosad.teapod.util.toItemMediaList + +class LibraryFragmentViewModel : ViewModel() { + + val PAGESIZE = 50 + + private val uiState = MutableStateFlow(UiState.Loading) + private var oldSearchQuery = "" + private var searchJob: Job? = null + var isLazyLoading = false + internal set + + sealed class UiState { + object Loading : UiState() + data class Browse( + val itemList: MutableList + ) : UiState() + data class Search( + val itemList: List + ) : UiState() + data class Error(val message: String?) : UiState() + } + + init { + load() + } + + fun onUiState(scope: LifecycleCoroutineScope, collector: (UiState) -> Unit) { + scope.launch { uiState.collect { collector(it) } } + } + + /** + * initially load the first n browsing items + */ + private fun load() { + viewModelScope.launch { + uiState.emit(UiState.Loading) + + try { + initBrowse() + } catch (ex: Exception) { + uiState.emit(UiState.Error(ex.message)) + } + } + } + + /** + * Search for a query string at Crunchyroll and emit the new ui state. + */ + fun search(query: String) { + // return if nothing has changed + if (query == oldSearchQuery) return + + // update the old query since it has changed + oldSearchQuery = query + + viewModelScope.launch { + + // always cancel a running search job + if (searchJob?.isActive == true) searchJob?.cancel() + + // handle state change: browse <-> search + if (query.isEmpty()) { + // if the query is empty change back to browse state + initBrowse() + } else { + // TODO handle errors + + // if the current ui state is not search, clear the recyclerview + if (uiState.value !is UiState.Search) { + uiState.emit(UiState.Search(emptyList())) + } + + // create a new search job + searchJob = viewModelScope.async { + // wait for a few ms: if the user is typing the task will get canceled + delay(250) + + val results = Crunchyroll.search(query, 50) + .items.firstOrNull()?.items?.toItemMediaList() + ?: listOf() + uiState.emit(UiState.Search(results)) + } + } + } + } + + fun onLazyLoad() = viewModelScope.launch { + isLazyLoading = true + + try { + uiState.update { currentUiState -> + if (currentUiState is UiState.Browse) { + val newBrowseItems = Crunchyroll.browse(start = currentUiState.itemList.size, n = PAGESIZE) + .toItemMediaList() + currentUiState.itemList.addAll(newBrowseItems) + } + currentUiState + } + } catch (ex: Exception) { + uiState.emit(UiState.Error(ex.message)) + } + + isLazyLoading = false + } + + private suspend fun initBrowse() { + try { + val initialBrowseItems = Crunchyroll.browse(n = PAGESIZE) + .toItemMediaList() + .toMutableList() + uiState.emit(UiState.Browse(initialBrowseItems)) + } catch (ex: Exception) { + uiState.emit(UiState.Error(ex.message)) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/ui/components/EmptySubmitSearchView.kt b/app/src/main/java/org/mosad/teapod/ui/components/EmptySubmitSearchView.kt new file mode 100644 index 0000000..42fc6e5 --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/ui/components/EmptySubmitSearchView.kt @@ -0,0 +1,31 @@ +package org.mosad.teapod.ui.components + +import android.content.Context +import android.util.AttributeSet +import android.view.KeyEvent +import android.widget.TextView +import androidx.appcompat.R +import androidx.appcompat.widget.SearchView + +// see https://stackoverflow.com/questions/30046201/android-searchview-empty-query-doesnt-work +class EmptySubmitSearchView : SearchView { + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + + override fun setOnQueryTextListener(listener: OnQueryTextListener?) { + super.setOnQueryTextListener(listener) + + findViewById(R.id.search_src_text).setOnEditorActionListener { _: TextView?, _: Int, event: KeyEvent? -> + if (event != null && event.keyCode == KeyEvent.KEYCODE_ENTER) { + listener?.onQueryTextSubmit(query.toString()) + } else { + listener?.onQueryTextSubmit(query.toString()) + } + + false + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_bookmark_border_24.xml b/app/src/main/res/drawable/ic_baseline_bookmark_border_24.xml new file mode 100644 index 0000000..95cd4cb --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_bookmark_border_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index be6b502..33e69c5 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -7,10 +7,23 @@ android:background="?themePrimary" tools:context=".ui.activity.main.fragments.LibraryFragment"> - + + + + tools:listitem="@layout/item_media"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_my_lists.xml similarity index 51% rename from app/src/main/res/layout/fragment_search.xml rename to app/src/main/res/layout/fragment_my_lists.xml index 7668031..5463863 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_my_lists.xml @@ -5,28 +5,12 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="?themePrimary" - tools:context=".ui.activity.main.fragments.SearchFragment"> - - - - + tools:context=".ui.activity.main.fragments.MyListsFragment"> - - + tools:listitem="@layout/item_media" /> \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml index 0c14574..5f779c8 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -6,15 +6,15 @@ android:icon="@drawable/ic_home_black_24dp" android:title="@string/title_home" /> + + - - + + - - Startseite + Meine Listen Übersicht - Suche Account diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 111b6df..f20c3d1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,8 +1,8 @@ Teapod Home + My Lists Library - Search Account diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 91c332a..5514894 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -5,6 +5,7 @@ @color/colorPrimaryDark @color/colorAccent @style/Widget.App.PopupMenu + @style/SearchViewStyle + + + - - \ No newline at end of file