mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 17:59:51 +02:00
Improve edge to edge support
This commit is contained in:
parent
679276e3cf
commit
bc0e83b916
6 changed files with 209 additions and 190 deletions
|
@ -183,6 +183,7 @@ dependencies {
|
|||
implementation 'androidx.compose.material:material-icons-extended:1.7.5'
|
||||
debugImplementation "androidx.compose.ui:ui-tooling:1.7.5"
|
||||
implementation 'androidx.fragment:fragment-ktx:1.8.5'
|
||||
implementation 'androidx.fragment:fragment-compose:1.8.5'
|
||||
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||
|
|
|
@ -23,16 +23,30 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.Tab
|
||||
import androidx.compose.material.TabRow
|
||||
import androidx.compose.material.TabRowDefaults
|
||||
import androidx.compose.material.TabRowDefaults.tabIndicatorOffset
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.compose.AndroidFragment
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.map
|
||||
import io.timelimit.android.R
|
||||
import io.timelimit.android.databinding.LockActivityBinding
|
||||
import io.timelimit.android.data.model.UserType
|
||||
import io.timelimit.android.extensions.showSafe
|
||||
import io.timelimit.android.logic.BlockingReason
|
||||
import io.timelimit.android.logic.DefaultAppLogic
|
||||
|
@ -40,11 +54,12 @@ import io.timelimit.android.sync.network.UpdatePrimaryDeviceRequestType
|
|||
import io.timelimit.android.u2f.U2fManager
|
||||
import io.timelimit.android.u2f.protocol.U2FDevice
|
||||
import io.timelimit.android.ui.IsAppInForeground
|
||||
import io.timelimit.android.ui.ScreenScaffold
|
||||
import io.timelimit.android.ui.Theme
|
||||
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
||||
import io.timelimit.android.ui.login.NewLoginFragment
|
||||
import io.timelimit.android.ui.main.ActivityViewModel
|
||||
import io.timelimit.android.ui.main.ActivityViewModelHolder
|
||||
import io.timelimit.android.ui.main.AuthenticationFab
|
||||
import io.timelimit.android.ui.manage.child.primarydevice.UpdatePrimaryDeviceDialogFragment
|
||||
import io.timelimit.android.ui.util.SyncStatusModel
|
||||
|
||||
|
@ -91,8 +106,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
|||
null
|
||||
}
|
||||
|
||||
private val showAuth = MutableLiveData<Boolean>().apply { value = false }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@ -107,19 +120,98 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
|||
)
|
||||
)
|
||||
|
||||
supportActionBar!!.hide()
|
||||
|
||||
U2fManager.setupActivity(this)
|
||||
|
||||
val adapter = LockActivityAdapter(supportFragmentManager, this)
|
||||
val subtitleLive = syncModel.statusText.asFlow()
|
||||
val showTasksLive = model.content.map {
|
||||
val isTimeOver = it is LockscreenContent.Blocked.BlockedCategory && it.blockingHandling.activityBlockingReason == BlockingReason.TimeOver
|
||||
|
||||
val binding = LockActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
isTimeOver
|
||||
}.asFlow()
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
setContent {
|
||||
val subtitle by subtitleLive.collectAsState(null)
|
||||
val showTasks by showTasksLive.collectAsState(false)
|
||||
val pager = rememberPagerState(initialPage = 0, pageCount = {
|
||||
if (showTasks) 3
|
||||
else 2
|
||||
})
|
||||
val isAuthenticated by getActivityViewModel().authenticatedUser
|
||||
.map { it?.second?.type == UserType.Parent }
|
||||
.asFlow().collectAsState(initial = false)
|
||||
|
||||
view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||
Theme {
|
||||
ScreenScaffold(
|
||||
screen = null,
|
||||
title = getString(R.string.app_name),
|
||||
subtitle = subtitle,
|
||||
backStack = emptyList(),
|
||||
snackbarHostState = null,
|
||||
content = { padding ->
|
||||
Column (Modifier.fillMaxSize().padding(padding)) {
|
||||
TabRow(
|
||||
pager.currentPage,
|
||||
indicator = { tabPositions ->
|
||||
// workaround for bug
|
||||
TabRowDefaults.Indicator(
|
||||
Modifier.tabIndicatorOffset(tabPositions[
|
||||
pager.currentPage.coerceAtMost(tabPositions.size - 1)
|
||||
])
|
||||
)
|
||||
}
|
||||
) {
|
||||
Tab(
|
||||
selected = pager.currentPage == 0,
|
||||
onClick = { pager.requestScrollToPage(0) }
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.lock_tab_reason),
|
||||
Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
WindowInsetsCompat.CONSUMED
|
||||
Tab(
|
||||
selected = pager.currentPage == 1,
|
||||
onClick = { pager.requestScrollToPage(1) }
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.lock_tab_action),
|
||||
Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (showTasks) Tab(
|
||||
selected = pager.currentPage == 2,
|
||||
onClick = { pager.requestScrollToPage(2) }
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.lock_tab_task),
|
||||
Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
pager,
|
||||
Modifier.weight(1.0F, fill = true),
|
||||
pageContent = { index ->
|
||||
when (index) {
|
||||
0 -> AndroidFragment<LockReasonFragment>(Modifier.fillMaxSize())
|
||||
1 -> AndroidFragment<LockActionFragment>(Modifier.fillMaxSize())
|
||||
2 -> AndroidFragment<LockTaskFragment>(Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
executeCommand = {},
|
||||
showAuthenticationDialog =
|
||||
if (pager.currentPage == 1 && !isAuthenticated) ({ showAuthenticationScreen() })
|
||||
else null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
syncModel.statusText.observe(this) { supportActionBar?.subtitle = it }
|
||||
|
@ -128,8 +220,6 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
|||
|
||||
model.init(blockedPackageName, blockedActivityName)
|
||||
|
||||
binding.pager.adapter = adapter
|
||||
|
||||
model.content.observe(this) {
|
||||
if (isResumed && it is LockscreenContent.Blocked.BlockedCategory && it.reason == BlockingReason.RequiresCurrentDevice && !model.didOpenSetCurrentDeviceScreen) {
|
||||
model.didOpenSetCurrentDeviceScreen = true
|
||||
|
@ -140,30 +230,12 @@ class LockActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De
|
|||
}
|
||||
}
|
||||
|
||||
AuthenticationFab.manageAuthenticationFab(
|
||||
fab = binding.fab,
|
||||
shouldHighlight = activityModel.shouldHighlightAuthenticationButton,
|
||||
authenticatedUser = activityModel.authenticatedUser,
|
||||
activity = this,
|
||||
doesSupportAuth = showAuth
|
||||
)
|
||||
activityModel.shouldHighlightAuthenticationButton.observe(this) {
|
||||
if (it) {
|
||||
activityModel.shouldHighlightAuthenticationButton.postValue(false)
|
||||
|
||||
binding.fab.setOnClickListener { showAuthenticationScreen() }
|
||||
|
||||
binding.pager.addOnPageChangeListener(object: ViewPager.SimpleOnPageChangeListener() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
|
||||
showAuth.value = position == 1
|
||||
showAuthenticationScreen()
|
||||
}
|
||||
})
|
||||
|
||||
binding.tabs.setupWithViewPager(binding.pager)
|
||||
|
||||
model.content.observe(this) {
|
||||
val isTimeOver = it is LockscreenContent.Blocked.BlockedCategory && it.blockingHandling.activityBlockingReason == BlockingReason.TimeOver
|
||||
|
||||
adapter.showTasksFragment = isTimeOver
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(object: OnBackPressedCallback(true) {
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.timelimit.android.ui.lock
|
||||
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import io.timelimit.android.R
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class LockActivityAdapter(fragmentManager: FragmentManager, private val context: Context): FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
var showTasksFragment: Boolean by Delegates.observable(false) { _, _, _ -> notifyDataSetChanged() }
|
||||
|
||||
override fun getCount(): Int = if (showTasksFragment) 3 else 2
|
||||
|
||||
override fun getItem(position: Int): Fragment = when (position) {
|
||||
0 -> LockReasonFragment()
|
||||
1 -> LockActionFragment()
|
||||
2 -> LockTaskFragment()
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence? = context.getString(when (position) {
|
||||
0 -> R.string.lock_tab_reason
|
||||
1 -> R.string.lock_tab_action
|
||||
2 -> R.string.lock_tab_task
|
||||
else -> throw IllegalArgumentException()
|
||||
})
|
||||
}
|
|
@ -22,14 +22,17 @@ import android.content.res.Configuration
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.map
|
||||
import io.timelimit.android.BuildConfig
|
||||
import io.timelimit.android.R
|
||||
|
@ -40,6 +43,8 @@ import io.timelimit.android.integration.platform.android.AndroidIntegrationApps
|
|||
import io.timelimit.android.logic.DefaultAppLogic
|
||||
import io.timelimit.android.u2f.U2fManager
|
||||
import io.timelimit.android.u2f.protocol.U2FDevice
|
||||
import io.timelimit.android.ui.ScreenScaffold
|
||||
import io.timelimit.android.ui.Theme
|
||||
import io.timelimit.android.ui.backdoor.BackdoorDialogFragment
|
||||
import io.timelimit.android.ui.login.AuthTokenLoginProcessor
|
||||
import io.timelimit.android.ui.login.NewLoginFragment
|
||||
|
@ -71,6 +76,8 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val logic = DefaultAppLogic.with(this)
|
||||
|
||||
val isNightMode =
|
||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
|
||||
Configuration.UI_MODE_NIGHT_YES
|
||||
|
@ -82,40 +89,20 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
|
|||
)
|
||||
)
|
||||
|
||||
U2fManager.setupActivity(this)
|
||||
supportActionBar!!.hide()
|
||||
|
||||
val logic = DefaultAppLogic.with(this)
|
||||
|
||||
val binding = AnnoyActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val systemImageApps = packageManager.getInstalledApplications(0)
|
||||
.filter { it.flags and ApplicationInfo.FLAG_SYSTEM == ApplicationInfo.FLAG_SYSTEM }
|
||||
.map { it.packageName }.toSet()
|
||||
|
||||
val lockTaskPackages = AndroidIntegrationApps.appsToIncludeInLockTasks + setOf(packageName) + systemImageApps
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOG_TAG, "setLockTaskPackages: $lockTaskPackages")
|
||||
}
|
||||
|
||||
if (logic.platformIntegration.setLockTaskPackages(lockTaskPackages.toList())) {
|
||||
startLockTask()
|
||||
}
|
||||
}
|
||||
|
||||
logic.annoyLogic.shouldAnnoyRightNow.observe(this) { shouldRun ->
|
||||
if (!shouldRun) shutdown()
|
||||
}
|
||||
setContent {
|
||||
Theme {
|
||||
ScreenScaffold(
|
||||
screen = null,
|
||||
title = getString(R.string.app_name),
|
||||
subtitle = null,
|
||||
backStack = emptyList(),
|
||||
snackbarHostState = null,
|
||||
content = { padding ->
|
||||
AndroidView(
|
||||
factory = {
|
||||
val binding = AnnoyActivityBinding.inflate(LayoutInflater.from(it))
|
||||
|
||||
logic.annoyLogic.nextManualUnblockCountdown.observe(this) { countdown ->
|
||||
binding.canRequestUnlock = countdown == 0L
|
||||
|
@ -146,6 +133,39 @@ class AnnoyActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.D
|
|||
|
||||
binding.useBackdoorButton.setOnClickListener { BackdoorDialogFragment().show(supportFragmentManager) }
|
||||
|
||||
binding.root
|
||||
},
|
||||
modifier = Modifier.fillMaxSize().padding(padding)
|
||||
)
|
||||
},
|
||||
executeCommand = {},
|
||||
showAuthenticationDialog = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
U2fManager.setupActivity(this)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
val systemImageApps = packageManager.getInstalledApplications(0)
|
||||
.filter { it.flags and ApplicationInfo.FLAG_SYSTEM == ApplicationInfo.FLAG_SYSTEM }
|
||||
.map { it.packageName }.toSet()
|
||||
|
||||
val lockTaskPackages = AndroidIntegrationApps.appsToIncludeInLockTasks + setOf(packageName) + systemImageApps
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(LOG_TAG, "setLockTaskPackages: $lockTaskPackages")
|
||||
}
|
||||
|
||||
if (logic.platformIntegration.setLockTaskPackages(lockTaskPackages.toList())) {
|
||||
startLockTask()
|
||||
}
|
||||
}
|
||||
|
||||
logic.annoyLogic.shouldAnnoyRightNow.observe(this) { shouldRun ->
|
||||
if (!shouldRun) shutdown()
|
||||
}
|
||||
|
||||
model.authenticatedUser.observe(this) { user ->
|
||||
if (user?.second?.type == UserType.Parent) {
|
||||
logic.annoyLogic.doParentTempUnlock()
|
||||
|
|
|
@ -17,16 +17,20 @@ package io.timelimit.android.ui.update
|
|||
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import io.timelimit.android.R
|
||||
import io.timelimit.android.databinding.UpdateActivityBinding
|
||||
import io.timelimit.android.logic.DefaultAppLogic
|
||||
import io.timelimit.android.ui.ScreenScaffold
|
||||
import io.timelimit.android.ui.Theme
|
||||
|
||||
class UpdateActivity: AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -43,15 +47,20 @@ class UpdateActivity: AppCompatActivity() {
|
|||
)
|
||||
)
|
||||
|
||||
val binding = DataBindingUtil.setContentView<UpdateActivityBinding>(this, R.layout.update_activity)
|
||||
supportActionBar!!.hide()
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)
|
||||
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
setContent {
|
||||
Theme {
|
||||
ScreenScaffold(
|
||||
screen = null,
|
||||
title = getString(R.string.app_name),
|
||||
subtitle = null,
|
||||
backStack = emptyList(),
|
||||
snackbarHostState = null,
|
||||
content = { padding ->
|
||||
AndroidView(
|
||||
factory = {
|
||||
val binding = UpdateActivityBinding.inflate(LayoutInflater.from(it))
|
||||
|
||||
UpdateView.bind(
|
||||
view = binding.update,
|
||||
|
@ -59,5 +68,16 @@ class UpdateActivity: AppCompatActivity() {
|
|||
fragmentManager = supportFragmentManager,
|
||||
appLogic = DefaultAppLogic.with(this)
|
||||
)
|
||||
|
||||
binding.root
|
||||
},
|
||||
modifier = Modifier.fillMaxSize().padding(padding)
|
||||
)
|
||||
},
|
||||
executeCommand = {},
|
||||
showAuthenticationDialog = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation version 3 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ui.lock.LockActivity" >
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/tabs"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/pager" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
app:fabSize="normal"
|
||||
android:src="@drawable/ic_lock_open_white_24dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</LinearLayout>
|
Loading…
Add table
Add a link
Reference in a new issue