forked from recloudstream/cloudstream
		
	new quicksearch
This commit is contained in:
		
							parent
							
								
									3b4915444a
								
							
						
					
					
						commit
						30fddb0360
					
				
					 6 changed files with 147 additions and 61 deletions
				
			
		|  | @ -103,11 +103,13 @@ open class WcoStream : ExtractorApi() { | ||||||
|                         ).apmap { serverurl -> |                         ).apmap { serverurl -> | ||||||
|                             val testurl = app.get(serverurl, headers = mapOf("Referer" to url)).text |                             val testurl = app.get(serverurl, headers = mapOf("Referer" to url)).text | ||||||
|                             if (testurl.contains("EXTM3")) { |                             if (testurl.contains("EXTM3")) { | ||||||
|                                 val quality = if (serverurl.contains("H4")) "1080p" |                                 val quality = when { | ||||||
|                                 else if (serverurl.contains("H3")) "720p" |                                     serverurl.contains("H4") -> "1080p" | ||||||
|                                 else if (serverurl.contains("H2")) "480p" |                                     serverurl.contains("H3") -> "720p" | ||||||
|                                 else if (serverurl.contains("H1")) "360p" |                                     serverurl.contains("H2") -> "480p" | ||||||
|                                 else "Auto" |                                     serverurl.contains("H1") -> "360p" | ||||||
|  |                                     else -> "Auto" | ||||||
|  |                                 } | ||||||
|                                 sources.add( |                                 sources.add( | ||||||
|                                     ExtractorLink( |                                     ExtractorLink( | ||||||
|                                         "VidStream", |                                         "VidStream", | ||||||
|  |  | ||||||
|  | @ -474,7 +474,7 @@ class HomeFragment : Fragment() { | ||||||
| 
 | 
 | ||||||
|         home_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { |         home_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { | ||||||
|             override fun onQueryTextSubmit(query: String): Boolean { |             override fun onQueryTextSubmit(query: String): Boolean { | ||||||
|                 QuickSearchFragment.pushSearch(activity, query) |                 QuickSearchFragment.pushSearch(activity, query, currentApiName?.let { arrayOf(it) }) | ||||||
| 
 | 
 | ||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| package com.lagradost.cloudstream3.ui.quicksearch | package com.lagradost.cloudstream3.ui.quicksearch | ||||||
| 
 | 
 | ||||||
| import android.app.Activity | import android.app.Activity | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.res.Configuration | ||||||
| import android.os.Bundle | import android.os.Bundle | ||||||
| import android.view.LayoutInflater | import android.view.LayoutInflater | ||||||
| import android.view.View | import android.view.View | ||||||
|  | @ -8,16 +10,19 @@ import android.view.ViewGroup | ||||||
| import android.view.WindowManager | import android.view.WindowManager | ||||||
| import android.widget.ImageView | import android.widget.ImageView | ||||||
| import androidx.appcompat.widget.SearchView | import androidx.appcompat.widget.SearchView | ||||||
|  | import androidx.core.view.isGone | ||||||
|  | import androidx.core.view.isVisible | ||||||
| import androidx.fragment.app.Fragment | import androidx.fragment.app.Fragment | ||||||
| import androidx.fragment.app.activityViewModels | import androidx.lifecycle.ViewModelProvider | ||||||
| import androidx.recyclerview.widget.GridLayoutManager | import androidx.recyclerview.widget.GridLayoutManager | ||||||
| import androidx.recyclerview.widget.RecyclerView |  | ||||||
| import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia | import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia | ||||||
|  | import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull | ||||||
| import com.lagradost.cloudstream3.HomePageList | import com.lagradost.cloudstream3.HomePageList | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.mvvm.Resource | import com.lagradost.cloudstream3.mvvm.Resource | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.mvvm.observe | import com.lagradost.cloudstream3.mvvm.observe | ||||||
|  | import com.lagradost.cloudstream3.ui.home.HomeFragment | ||||||
| import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList | import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList | ||||||
| import com.lagradost.cloudstream3.ui.home.ParentItemAdapter | import com.lagradost.cloudstream3.ui.home.ParentItemAdapter | ||||||
| import com.lagradost.cloudstream3.ui.search.SearchAdapter | import com.lagradost.cloudstream3.ui.search.SearchAdapter | ||||||
|  | @ -27,19 +32,29 @@ import com.lagradost.cloudstream3.ui.search.SearchHelper | ||||||
| import com.lagradost.cloudstream3.ui.search.SearchViewModel | import com.lagradost.cloudstream3.ui.search.SearchViewModel | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper | import com.lagradost.cloudstream3.utils.UIHelper | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | ||||||
|  | import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.navigate | import com.lagradost.cloudstream3.utils.UIHelper.navigate | ||||||
| import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage | import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage | ||||||
| import kotlinx.android.synthetic.main.fragment_search.* |  | ||||||
| import kotlinx.android.synthetic.main.quick_search.* | import kotlinx.android.synthetic.main.quick_search.* | ||||||
| import java.util.concurrent.locks.ReentrantLock | import java.util.concurrent.locks.ReentrantLock | ||||||
| 
 | 
 | ||||||
| class QuickSearchFragment : Fragment() { | class QuickSearchFragment : Fragment() { | ||||||
|     companion object { |     companion object { | ||||||
|         fun pushSearch(activity: Activity?, autoSearch: String? = null) { |         const val AUTOSEARCH_KEY = "autosearch" | ||||||
|  |         const val PROVIDER_KEY = "providers" | ||||||
|  | 
 | ||||||
|  |         fun pushSearch( | ||||||
|  |             activity: Activity?, | ||||||
|  |             autoSearch: String? = null, | ||||||
|  |             providers: Array<String>? = null | ||||||
|  |         ) { | ||||||
|             activity.navigate(R.id.global_to_navigation_quick_search, Bundle().apply { |             activity.navigate(R.id.global_to_navigation_quick_search, Bundle().apply { | ||||||
|  |                 providers?.let { | ||||||
|  |                     putStringArray(PROVIDER_KEY, it) | ||||||
|  |                 } | ||||||
|                 autoSearch?.let { |                 autoSearch?.let { | ||||||
|                     putString( |                     putString( | ||||||
|                         "autosearch", |                         AUTOSEARCH_KEY, | ||||||
|                         it.trim() |                         it.trim() | ||||||
|                             .removeSuffix("(DUB)") |                             .removeSuffix("(DUB)") | ||||||
|                             .removeSuffix("(SUB)") |                             .removeSuffix("(SUB)") | ||||||
|  | @ -53,7 +68,8 @@ class QuickSearchFragment : Fragment() { | ||||||
|         var clickCallback: ((SearchClickCallback) -> Unit)? = null |         var clickCallback: ((SearchClickCallback) -> Unit)? = null | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private val searchViewModel: SearchViewModel by activityViewModels() |     private var providers: Set<String>? = null | ||||||
|  |     private lateinit var searchViewModel: SearchViewModel | ||||||
| 
 | 
 | ||||||
|     override fun onCreateView( |     override fun onCreateView( | ||||||
|         inflater: LayoutInflater, |         inflater: LayoutInflater, | ||||||
|  | @ -63,6 +79,7 @@ class QuickSearchFragment : Fragment() { | ||||||
|         activity?.window?.setSoftInputMode( |         activity?.window?.setSoftInputMode( | ||||||
|             WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE |             WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE | ||||||
|         ) |         ) | ||||||
|  |         searchViewModel = ViewModelProvider(this)[SearchViewModel::class.java] | ||||||
| 
 | 
 | ||||||
|         return inflater.inflate(R.layout.quick_search, container, false) |         return inflater.inflate(R.layout.quick_search, container, false) | ||||||
|     } |     } | ||||||
|  | @ -72,9 +89,75 @@ class QuickSearchFragment : Fragment() { | ||||||
|         clickCallback = null |         clickCallback = null | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     fun search(context: Context?, query: String, isQuickSearch: Boolean): Boolean { | ||||||
|  |         (providers ?: context?.filterProviderByPreferredMedia(hasHomePageIsRequired = false) | ||||||
|  |             ?.map { it.name }?.toSet())?.let { active -> | ||||||
|  |             searchViewModel.searchAndCancel( | ||||||
|  |                 query = query, | ||||||
|  |                 ignoreSettings = false, | ||||||
|  |                 providersActive = active, | ||||||
|  |                 isQuickSearch = isQuickSearch | ||||||
|  |             ) | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun fixGrid() { | ||||||
|  |         activity?.getSpanCount()?.let { | ||||||
|  |             HomeFragment.currentSpan = it | ||||||
|  |         } | ||||||
|  |         quick_search_autofit_results.spanCount = HomeFragment.currentSpan | ||||||
|  |         HomeFragment.currentSpan = HomeFragment.currentSpan | ||||||
|  |         HomeFragment.configEvent.invoke(HomeFragment.currentSpan) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onConfigurationChanged(newConfig: Configuration) { | ||||||
|  |         super.onConfigurationChanged(newConfig) | ||||||
|  |         fixGrid() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|         context?.fixPaddingStatusbar(quick_search_root) |         context?.fixPaddingStatusbar(quick_search_root) | ||||||
|  |         fixGrid() | ||||||
|  | 
 | ||||||
|  |         arguments?.getStringArray(PROVIDER_KEY)?.let { | ||||||
|  |             providers = it.toSet() | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val isSingleProvider = providers?.size == 1 | ||||||
|  |         val isSingleProviderQuickSearch = if (isSingleProvider) { | ||||||
|  |             getApiFromNameNull(providers?.first())?.hasQuickSearch ?: false | ||||||
|  |         } else false | ||||||
|  | 
 | ||||||
|  |         if (isSingleProvider) { | ||||||
|  |             quick_search_autofit_results.adapter = activity?.let { | ||||||
|  |                 SearchAdapter( | ||||||
|  |                     ArrayList(), | ||||||
|  |                     quick_search_autofit_results, | ||||||
|  |                 ) { callback -> | ||||||
|  |                     SearchHelper.handleSearchClickCallback(activity, callback) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             quick_search_master_recycler?.adapter = | ||||||
|  |                 ParentItemAdapter(mutableListOf(), { callback -> | ||||||
|  |                     SearchHelper.handleSearchClickCallback(activity, callback) | ||||||
|  |                     //when (callback.action) { | ||||||
|  |                     //SEARCH_ACTION_LOAD -> { | ||||||
|  |                     //    clickCallback?.invoke(callback) | ||||||
|  |                     //} | ||||||
|  |                     //    else -> SearchHelper.handleSearchClickCallback(activity, callback) | ||||||
|  |                     //} | ||||||
|  |                 }, { item -> | ||||||
|  |                     activity?.loadHomepageList(item) | ||||||
|  |                 }) | ||||||
|  |             quick_search_master_recycler?.layoutManager = GridLayoutManager(context, 1) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         quick_search_autofit_results?.isVisible = isSingleProvider | ||||||
|  |         quick_search_master_recycler?.isGone = isSingleProvider | ||||||
| 
 | 
 | ||||||
|         val listLock = ReentrantLock() |         val listLock = ReentrantLock() | ||||||
|         observe(searchViewModel.currentSearch) { list -> |         observe(searchViewModel.currentSearch) { list -> | ||||||
|  | @ -97,19 +180,6 @@ class QuickSearchFragment : Fragment() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = |  | ||||||
|             ParentItemAdapter(mutableListOf(), { callback -> |  | ||||||
|                 SearchHelper.handleSearchClickCallback(activity, callback) |  | ||||||
|                 //when (callback.action) { |  | ||||||
|                     //SEARCH_ACTION_LOAD -> { |  | ||||||
|                     //    clickCallback?.invoke(callback) |  | ||||||
|                     //} |  | ||||||
|                 //    else -> SearchHelper.handleSearchClickCallback(activity, callback) |  | ||||||
|                 //} |  | ||||||
|             }, { item -> |  | ||||||
|                 activity?.loadHomepageList(item) |  | ||||||
|             }) |  | ||||||
| 
 |  | ||||||
|         val searchExitIcon = |         val searchExitIcon = | ||||||
|             quick_search?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn) |             quick_search?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn) | ||||||
| 
 | 
 | ||||||
|  | @ -119,25 +189,17 @@ class QuickSearchFragment : Fragment() { | ||||||
|         //searchMagIcon?.scaleX = 0.65f |         //searchMagIcon?.scaleX = 0.65f | ||||||
|         //searchMagIcon?.scaleY = 0.65f |         //searchMagIcon?.scaleY = 0.65f | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|         quick_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { |         quick_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { | ||||||
|             override fun onQueryTextSubmit(query: String): Boolean { |             override fun onQueryTextSubmit(query: String): Boolean { | ||||||
|                 context?.filterProviderByPreferredMedia(hasHomePageIsRequired = false) |                 if (search(context, query, false)) | ||||||
|                     ?.map { it.name }?.toSet()?.let { active -> |                     UIHelper.hideKeyboard(quick_search) | ||||||
|                     searchViewModel.searchAndCancel( |  | ||||||
|                         query = query, |  | ||||||
|                         ignoreSettings = false, |  | ||||||
|                         providersActive = active |  | ||||||
|                     ) |  | ||||||
|                     quick_search?.let { |  | ||||||
|                         UIHelper.hideKeyboard(it) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             override fun onQueryTextChange(newText: String): Boolean { |             override fun onQueryTextChange(newText: String): Boolean { | ||||||
|                 //searchViewModel.quickSearch(newText) |                 if (isSingleProviderQuickSearch) | ||||||
|  |                     search(context, newText, true) | ||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|  | @ -147,9 +209,10 @@ class QuickSearchFragment : Fragment() { | ||||||
|             when (it) { |             when (it) { | ||||||
|                 is Resource.Success -> { |                 is Resource.Success -> { | ||||||
|                     it.value.let { data -> |                     it.value.let { data -> | ||||||
|                         if (data.isNotEmpty()) { |                         println("DATA: $data") | ||||||
|                             (search_autofit_results?.adapter as? SearchAdapter?)?.updateList(data) |                         (quick_search_autofit_results?.adapter as? SearchAdapter?)?.updateList( | ||||||
|                         } |                             data | ||||||
|  |                         ) | ||||||
|                     } |                     } | ||||||
|                     searchExitIcon?.alpha = 1f |                     searchExitIcon?.alpha = 1f | ||||||
|                     quick_search_loading_bar?.alpha = 0f |                     quick_search_loading_bar?.alpha = 0f | ||||||
|  | @ -166,8 +229,6 @@ class QuickSearchFragment : Fragment() { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         quick_search_master_recycler?.adapter = masterAdapter |  | ||||||
|         quick_search_master_recycler?.layoutManager = GridLayoutManager(context, 1) |  | ||||||
| 
 | 
 | ||||||
|         //quick_search.setOnQueryTextFocusChangeListener { _, b -> |         //quick_search.setOnQueryTextFocusChangeListener { _, b -> | ||||||
|         //    if (b) { |         //    if (b) { | ||||||
|  | @ -180,9 +241,9 @@ class QuickSearchFragment : Fragment() { | ||||||
|             activity?.popCurrentPage() |             activity?.popCurrentPage() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         arguments?.getString("autosearch")?.let { |         arguments?.getString(AUTOSEARCH_KEY)?.let { | ||||||
|             quick_search?.setQuery(it, true) |             quick_search?.setQuery(it, true) | ||||||
|             arguments?.remove("autosearch") |             arguments?.remove(AUTOSEARCH_KEY) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -47,10 +47,11 @@ class SearchViewModel : ViewModel() { | ||||||
|     fun searchAndCancel( |     fun searchAndCancel( | ||||||
|         query: String, |         query: String, | ||||||
|         providersActive: Set<String> = setOf(), |         providersActive: Set<String> = setOf(), | ||||||
|         ignoreSettings: Boolean = false |         ignoreSettings: Boolean = false, | ||||||
|  |         isQuickSearch: Boolean = false, | ||||||
|     ) { |     ) { | ||||||
|         onGoingSearch?.cancel() |         onGoingSearch?.cancel() | ||||||
|         onGoingSearch = search(query, providersActive, ignoreSettings) |         onGoingSearch = search(query, providersActive, ignoreSettings, isQuickSearch) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun updateHistory() = viewModelScope.launch { |     fun updateHistory() = viewModelScope.launch { | ||||||
|  | @ -65,7 +66,8 @@ class SearchViewModel : ViewModel() { | ||||||
|     private fun search( |     private fun search( | ||||||
|         query: String, |         query: String, | ||||||
|         providersActive: Set<String>, |         providersActive: Set<String>, | ||||||
|         ignoreSettings: Boolean = false |         ignoreSettings: Boolean = false, | ||||||
|  |         isQuickSearch: Boolean = false, | ||||||
|     ) = |     ) = | ||||||
|         viewModelScope.launch { |         viewModelScope.launch { | ||||||
|             if (query.length <= 1) { |             if (query.length <= 1) { | ||||||
|  | @ -73,6 +75,7 @@ class SearchViewModel : ViewModel() { | ||||||
|                 return@launch |                 return@launch | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             if (!isQuickSearch) { | ||||||
|                 val key = query.hashCode().toString() |                 val key = query.hashCode().toString() | ||||||
|                 setKey( |                 setKey( | ||||||
|                     SEARCH_HISTORY_KEY, |                     SEARCH_HISTORY_KEY, | ||||||
|  | @ -84,6 +87,7 @@ class SearchViewModel : ViewModel() { | ||||||
|                         key = key, |                         key = key, | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             _searchResponse.postValue(Resource.Loading()) |             _searchResponse.postValue(Resource.Loading()) | ||||||
| 
 | 
 | ||||||
|  | @ -93,9 +97,9 @@ class SearchViewModel : ViewModel() { | ||||||
| 
 | 
 | ||||||
|             withContext(Dispatchers.IO) { // This interrupts UI otherwise |             withContext(Dispatchers.IO) { // This interrupts UI otherwise | ||||||
|                 repos.filter { a -> |                 repos.filter { a -> | ||||||
|                     ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name)) |                     (ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name))) && (!isQuickSearch || a.hasQuickSearch) | ||||||
|                 }.apmap { a -> // Parallel |                 }.apmap { a -> // Parallel | ||||||
|                     val search = a.search(query) |                     val search = if (isQuickSearch) a.quickSearch(query) else a.search(query) | ||||||
|                     currentList.add(OnGoingSearch(a.name, search)) |                     currentList.add(OnGoingSearch(a.name, search)) | ||||||
|                     _currentSearch.postValue(currentList) |                     _currentSearch.postValue(currentList) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -384,7 +384,9 @@ object UIHelper { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun hideKeyboard(view: View) { |     fun hideKeyboard(view: View?) { | ||||||
|  |         if(view == null) return | ||||||
|  | 
 | ||||||
|         val inputMethodManager = |         val inputMethodManager = | ||||||
|             view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager? |             view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager? | ||||||
|         inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0) |         inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0) | ||||||
|  |  | ||||||
|  | @ -83,4 +83,21 @@ | ||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
|             android:layout_height="match_parent" |             android:layout_height="match_parent" | ||||||
|             tools:listitem="@layout/homepage_parent" /> |             tools:listitem="@layout/homepage_parent" /> | ||||||
|  | 
 | ||||||
|  |     <com.lagradost.cloudstream3.ui.AutofitRecyclerView | ||||||
|  |             android:visibility="gone" | ||||||
|  |             android:nextFocusLeft="@id/nav_rail_view" | ||||||
|  |             android:descendantFocusability="afterDescendants" | ||||||
|  | 
 | ||||||
|  |             android:background="?attr/primaryBlackBackground" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="match_parent" | ||||||
|  |             android:clipToPadding="false" | ||||||
|  |             android:paddingStart="8dp" | ||||||
|  |             android:paddingTop="5dp" | ||||||
|  |             app:spanCount="3" | ||||||
|  |             android:paddingEnd="8dp" | ||||||
|  |             android:id="@+id/quick_search_autofit_results" | ||||||
|  |             tools:listitem="@layout/search_result_grid" | ||||||
|  |             android:orientation="vertical" /> | ||||||
| </LinearLayout> | </LinearLayout> | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue