Consolidate Compose content/theme setting

This commit is contained in:
arkon 2022-07-16 17:08:51 -04:00
parent b034f503f8
commit 46ac9fe970
7 changed files with 130 additions and 271 deletions

View file

@ -3,17 +3,12 @@ package eu.kanade.tachiyomi.ui.base.controller
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.databinding.ComposeControllerBinding import eu.kanade.tachiyomi.databinding.ComposeControllerBinding
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.view.setComposeContent
import nucleus.presenter.Presenter import nucleus.presenter.Presenter
abstract class FullComposeController<P : Presenter<*>>(bundle: Bundle? = null) : abstract class FullComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
@ -27,14 +22,8 @@ abstract class FullComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
super.onViewCreated(view) super.onViewCreated(view)
binding.root.apply { binding.root.apply {
consumeWindowInsets = false setComposeContent {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) ComposeContent()
setContent {
TachiyomiTheme {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
ComposeContent()
}
}
} }
} }
} }
@ -54,15 +43,9 @@ abstract class ComposeController<P : Presenter<*>>(bundle: Bundle? = null) :
super.onViewCreated(view) super.onViewCreated(view)
binding.root.apply { binding.root.apply {
consumeWindowInsets = false setComposeContent {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection() val nestedScrollInterop = rememberNestedScrollInteropConnection()
TachiyomiTheme { ComposeContent(nestedScrollInterop)
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
ComposeContent(nestedScrollInterop)
}
}
} }
} }
} }
@ -82,15 +65,9 @@ abstract class BasicComposeController :
super.onViewCreated(view) super.onViewCreated(view)
binding.root.apply { binding.root.apply {
consumeWindowInsets = false setComposeContent {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection() val nestedScrollInterop = rememberNestedScrollInteropConnection()
TachiyomiTheme { ComposeContent(nestedScrollInterop)
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
ComposeContent(nestedScrollInterop)
}
}
} }
} }
} }
@ -107,15 +84,9 @@ abstract class SearchableComposeController<P : BasePresenter<*>>(bundle: Bundle?
super.onViewCreated(view) super.onViewCreated(view)
binding.root.apply { binding.root.apply {
consumeWindowInsets = false setComposeContent {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
val nestedScrollInterop = rememberNestedScrollInteropConnection() val nestedScrollInterop = rememberNestedScrollInteropConnection()
TachiyomiTheme { ComposeContent(nestedScrollInterop)
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
ComposeContent(nestedScrollInterop)
}
}
} }
} }
} }

View file

@ -3,17 +3,11 @@ package eu.kanade.tachiyomi.ui.library
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
@ -23,7 +17,6 @@ import eu.kanade.presentation.library.components.LibraryComfortableGrid
import eu.kanade.presentation.library.components.LibraryCompactGrid import eu.kanade.presentation.library.components.LibraryCompactGrid
import eu.kanade.presentation.library.components.LibraryCoverOnlyGrid import eu.kanade.presentation.library.components.LibraryCoverOnlyGrid
import eu.kanade.presentation.library.components.LibraryList import eu.kanade.presentation.library.components.LibraryList
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
@ -31,6 +24,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.ComposeControllerBinding import eu.kanade.tachiyomi.databinding.ComposeControllerBinding
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.setComposeContent
import eu.kanade.tachiyomi.widget.RecyclerViewPagerAdapter import eu.kanade.tachiyomi.widget.RecyclerViewPagerAdapter
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -101,80 +95,74 @@ class LibraryAdapter(
*/ */
override fun bindView(view: View, position: Int) { override fun bindView(view: View, position: Int) {
(view as ComposeView).apply { (view as ComposeView).apply {
consumeWindowInsets = false setComposeContent {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) val nestedScrollInterop = rememberNestedScrollInteropConnection()
setContent {
TachiyomiTheme {
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.bodySmall, LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
val nestedScrollInterop = rememberNestedScrollInteropConnection()
val category = presenter.categories[position] val category = presenter.categories[position]
val displayMode = presenter.getDisplayMode(index = position) val displayMode = presenter.getDisplayMode(index = position)
val mangaList by presenter.getMangaForCategory(categoryId = category.id) val mangaList by presenter.getMangaForCategory(categoryId = category.id)
val onClickManga = { manga: LibraryManga -> val onClickManga = { manga: LibraryManga ->
if (presenter.hasSelection().not()) { if (presenter.hasSelection().not()) {
onClickManga(manga) onClickManga(manga)
} else { } else {
presenter.toggleSelection(manga) presenter.toggleSelection(manga)
} }
}
val onLongClickManga = { manga: LibraryManga ->
presenter.toggleSelection(manga)
}
SwipeRefresh(
modifier = Modifier.nestedScroll(nestedScrollInterop),
state = rememberSwipeRefreshState(isRefreshing = false),
onRefresh = {
if (LibraryUpdateService.start(context, category)) {
context.toast(R.string.updating_category)
} }
val onLongClickManga = { manga: LibraryManga -> },
presenter.toggleSelection(manga) indicator = { s, trigger ->
SwipeRefreshIndicator(
state = s,
refreshTriggerDistance = trigger,
)
},
) {
when (displayMode) {
DisplayModeSetting.LIST -> {
LibraryList(
items = mangaList,
selection = presenter.selection,
onClick = onClickManga,
onLongClick = onLongClickManga,
)
} }
DisplayModeSetting.COMPACT_GRID -> {
SwipeRefresh( LibraryCompactGrid(
modifier = Modifier.nestedScroll(nestedScrollInterop), items = mangaList,
state = rememberSwipeRefreshState(isRefreshing = false), columns = presenter.columns,
onRefresh = { selection = presenter.selection,
if (LibraryUpdateService.start(context, category)) { onClick = onClickManga,
context.toast(R.string.updating_category) onLongClick = onLongClickManga,
} )
}, }
indicator = { s, trigger -> DisplayModeSetting.COMFORTABLE_GRID -> {
SwipeRefreshIndicator( LibraryComfortableGrid(
state = s, items = mangaList,
refreshTriggerDistance = trigger, columns = presenter.columns,
) selection = presenter.selection,
}, onClick = onClickManga,
) { onLongClick = onLongClickManga,
when (displayMode) { )
DisplayModeSetting.LIST -> { }
LibraryList( DisplayModeSetting.COVER_ONLY_GRID -> {
items = mangaList, LibraryCoverOnlyGrid(
selection = presenter.selection, items = mangaList,
onClick = onClickManga, columns = presenter.columns,
onLongClick = onLongClickManga, selection = presenter.selection,
) onClick = onClickManga,
} onLongClick = onLongClickManga,
DisplayModeSetting.COMPACT_GRID -> { )
LibraryCompactGrid(
items = mangaList,
columns = presenter.columns,
selection = presenter.selection,
onClick = onClickManga,
onLongClick = onLongClickManga,
)
}
DisplayModeSetting.COMFORTABLE_GRID -> {
LibraryComfortableGrid(
items = mangaList,
columns = presenter.columns,
selection = presenter.selection,
onClick = onClickManga,
onLongClick = onLongClickManga,
)
}
DisplayModeSetting.COVER_ONLY_GRID -> {
LibraryCoverOnlyGrid(
items = mangaList,
columns = presenter.columns,
selection = presenter.selection,
onClick = onClickManga,
onLongClick = onLongClickManga,
)
}
}
} }
} }
} }

View file

@ -3,12 +3,11 @@ package eu.kanade.tachiyomi.ui.setting.track
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.activity.compose.setContent
import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.LoadingScreen
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.view.setComposeContent
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
abstract class BaseOAuthLoginActivity : BaseActivity() { abstract class BaseOAuthLoginActivity : BaseActivity() {
@ -20,10 +19,8 @@ abstract class BaseOAuthLoginActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setComposeContent {
TachiyomiTheme { LoadingScreen()
LoadingScreen()
}
} }
handleResult(intent.data) handleResult(intent.data)

View file

@ -4,8 +4,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.setContent
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.presentation.webview.WebViewScreen import eu.kanade.presentation.webview.WebViewScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
@ -16,6 +14,7 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.setComposeContent
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -44,18 +43,16 @@ class WebViewActivity : BaseActivity() {
headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap() headers = source.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
} }
setContent { setComposeContent {
TachiyomiTheme { WebViewScreen(
WebViewScreen( onUp = { finish() },
onUp = { finish() }, initialTitle = intent.extras?.getString(TITLE_KEY),
initialTitle = intent.extras?.getString(TITLE_KEY), url = url,
url = url, headers = headers,
headers = headers, onShare = this::shareWebpage,
onShare = this::shareWebpage, onOpenInBrowser = this::openInBrowser,
onOpenInBrowser = this::openInBrowser, onClearCookies = this::clearCookies,
onClearCookies = this::clearCookies, )
)
}
} }
} }

View file

@ -5,42 +5,71 @@ package eu.kanade.tachiyomi.util.view
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Point
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.Gravity import android.view.Gravity
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.MenuRes import androidx.annotation.MenuRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.TooltipCompat import androidx.appcompat.widget.TooltipCompat
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.descendants import androidx.core.view.descendants
import androidx.core.view.forEach import androidx.core.view.forEach
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
/** inline fun ComposeView.setComposeContent(crossinline content: @Composable () -> Unit) {
* Returns coordinates of view. consumeWindowInsets = false
* Used for animation setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
* setContent {
* @return coordinates of view TachiyomiTheme {
*/ CompositionLocalProvider(
fun View.getCoordinates() = Point((left + right) / 2, (top + bottom) / 2) LocalTextStyle provides MaterialTheme.typography.bodySmall,
LocalContentColor provides MaterialTheme.colorScheme.onBackground,
) {
content()
}
}
}
}
inline fun ComponentActivity.setComposeContent(
parent: CompositionContext? = null,
crossinline content: @Composable () -> Unit,
) {
setContent(parent) {
TachiyomiTheme {
CompositionLocalProvider(
LocalTextStyle provides MaterialTheme.typography.bodySmall,
LocalContentColor provides MaterialTheme.colorScheme.onBackground,
) {
content()
}
}
}
}
/** /**
* Shows a snackbar in this view. * Shows a snackbar in this view.
@ -164,39 +193,6 @@ inline fun ExtendedFloatingActionButton.shrinkOnScroll(recycler: RecyclerView):
return listener return listener
} }
/**
* Replaces chips in a ChipGroup.
*
* @param items List of strings that are shown as individual chips.
* @param onClick Optional on click listener for each chip.
* @param onLongClick Optional on long click listener for each chip.
*/
inline fun ChipGroup.setChips(
items: List<String>?,
noinline onClick: ((item: String) -> Unit)? = null,
noinline onLongClick: ((item: String) -> Unit)? = null,
) {
removeAllViews()
items?.forEach { item ->
val chip = Chip(context).apply {
text = item
if (onClick != null) { setOnClickListener { onClick(item) } }
if (onLongClick != null) { setOnLongClickListener { onLongClick(item); true } }
}
addView(chip)
}
}
/**
* Sets TextView max lines dynamically. Can only be called when the view is already laid out.
*/
inline fun TextView.setMaxLinesAndEllipsize(_ellipsize: TextUtils.TruncateAt = TextUtils.TruncateAt.END) = post {
maxLines = (measuredHeight - paddingTop - paddingBottom) / lineHeight
ellipsize = _ellipsize
}
/** /**
* Callback will be run immediately when no animation running * Callback will be run immediately when no animation running
*/ */
@ -228,29 +224,6 @@ inline fun <reified T> ViewGroup.findDescendant(): T? {
return descendants.find { it is T } as? T return descendants.find { it is T } as? T
} }
/**
* Returns the active child view of a ViewPager according to the LayoutParams
*/
fun ViewPager.getActivePageView(): View? {
if (null == adapter || adapter?.count == 0 || childCount == 0) {
return null
}
val positionField = ViewPager.LayoutParams::class.java.getDeclaredField("position")
positionField.isAccessible = true
return children.find { child ->
val layoutParams = child.layoutParams as ViewPager.LayoutParams
try {
if (!layoutParams.isDecor && positionField.getInt(layoutParams) == currentItem) {
return@find true
}
} catch (e: NoSuchFieldException) {
} catch (e: IllegalAccessException) {
}
false
}
}
/** /**
* Returns a deep copy of the provided [Drawable] * Returns a deep copy of the provided [Drawable]
*/ */

View file

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_selector_background"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingVertical="10dp"
android:paddingStart="16dp"
android:paddingEnd="5dp">
<ImageView
android:id="@+id/bookmark_icon"
android:layout_width="16dp"
android:layout_height="0dp"
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_filter_bookmarked"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/chapter_title"
app:layout_constraintEnd_toStartOf="@id/chapter_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/chapter_title"
app:srcCompat="@drawable/ic_bookmark_24dp"
app:tint="?attr/colorAccent"
tools:visibility="visible" />
<TextView
android:id="@+id/chapter_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceBodyMedium"
app:layout_constraintBottom_toTopOf="@+id/chapter_description"
app:layout_constraintEnd_toStartOf="@+id/download"
app:layout_constraintStart_toEndOf="@+id/bookmark_icon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Title" />
<TextView
android:id="@+id/chapter_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/download"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chapter_title"
tools:text="22/02/2016 • Scanlator • Page: 45" />
<eu.kanade.tachiyomi.ui.manga.chapter.ChapterDownloadView
android:id="@+id/download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">