Improve experience at android tv

This commit is contained in:
Jonas Lochmann 2020-04-20 02:00:00 +02:00
parent 0134dc5e0e
commit 494a82745c
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
22 changed files with 403 additions and 79 deletions

View file

@ -32,9 +32,13 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-feature android:name="android.hardware.telephony" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<application
android:banner="@drawable/banner"
android:name=".Application"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
@ -51,8 +55,8 @@
android:name=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<activity

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* 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
@ -20,7 +20,9 @@ import android.app.AppOpsManager
import android.app.usage.UsageEvents
import android.app.usage.UsageStatsManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import io.timelimit.android.BuildConfig
import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.integration.platform.ForegroundAppSpec
import io.timelimit.android.integration.platform.RuntimePermissionStatus
@ -35,6 +37,7 @@ class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppH
private val usageStatsManager = context.getSystemService(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) Context.USAGE_STATS_SERVICE else "usagestats") as UsageStatsManager
private val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
private val packageManager = context.packageManager
private var lastQueryTime: Long = 0
private var lastPackage: String? = null
@ -94,8 +97,13 @@ class LollipopForegroundAppHelper(private val context: Context) : ForegroundAppH
}
override fun getPermissionStatus(): RuntimePermissionStatus {
if(appOpsManager.checkOpNoThrow("android:get_usage_stats",
android.os.Process.myUid(), context.packageName) == AppOpsManager.MODE_ALLOWED) {
val appOpsStatus = appOpsManager.checkOpNoThrow("android:get_usage_stats", android.os.Process.myUid(), context.packageName)
val packageManagerStatus = packageManager.checkPermission("android.permission.PACKAGE_USAGE_STATS", BuildConfig.APPLICATION_ID)
val allowedUsingSystemSettings = appOpsStatus == AppOpsManager.MODE_ALLOWED
val allowedUsingAdb = appOpsStatus == AppOpsManager.MODE_DEFAULT && packageManagerStatus == PackageManager.PERMISSION_GRANTED
if(allowedUsingSystemSettings || allowedUsingAdb) {
return RuntimePermissionStatus.Granted
} else {
return RuntimePermissionStatus.NotGranted

View file

@ -126,11 +126,15 @@ class ContactsFragment : Fragment() {
}
private fun showContactSelection() {
startActivityForResult(
Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE),
REQ_SELECT_CONTACT
)
try {
startActivityForResult(
Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE),
REQ_SELECT_CONTACT
)
} catch (ex: Exception) {
Snackbar.make(view!!, R.string.error_general, Snackbar.LENGTH_SHORT).show()
}
}
private fun removeItem(item: AllowedContact) {

View file

@ -30,6 +30,7 @@ import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialog
import io.timelimit.android.R
import io.timelimit.android.async.Threads
import io.timelimit.android.data.model.User
import io.timelimit.android.databinding.NewLoginFragmentBinding
import io.timelimit.android.extensions.setOnEnterListenr
@ -168,7 +169,7 @@ class NewLoginFragment: DialogFragment() {
}
}
model.status.observe(this, Observer { status ->
model.status.observe(viewLifecycleOwner, Observer { status ->
when (status) {
LoginDialogDone -> {
dismissAllowingStateLoss()
@ -181,6 +182,10 @@ class NewLoginFragment: DialogFragment() {
}
adapter.data = status.usersToShow
Threads.mainThreadHandler.post { binding.userList.recycler.requestFocus() }
null
}
is ParentUserLogin -> {
if (binding.switcher.displayedChild != PARENT_AUTH) {
@ -189,6 +194,8 @@ class NewLoginFragment: DialogFragment() {
binding.switcher.displayedChild = PARENT_AUTH
}
binding.enterPassword.password.isEnabled = !status.isCheckingPassword
if (!binding.enterPassword.showCustomKeyboard) {
binding.enterPassword.password.requestFocus()
inputMethodManager.showSoftInput(binding.enterPassword.password, 0)
@ -213,8 +220,6 @@ class NewLoginFragment: DialogFragment() {
binding.enterPassword.checkAssignMyself.setOnCheckedChangeListener { _, _ -> bindCanNotKeepLoggedIn() }
}
binding.enterPassword.password.isEnabled = !status.isCheckingPassword
if (status.wasPasswordWrong) {
Toast.makeText(context!!, R.string.login_snackbar_wrong, Toast.LENGTH_SHORT).show()
binding.enterPassword.password.setText("")

View file

@ -52,7 +52,7 @@ object AuthenticationFab {
val highlightObserver = Observer<Boolean> {
if (it == true) {
if (tapTargetView == null && fab.isAttachedToWindow) {
tapTargetView = TapTargetView.showFor(fragment.activity!!,
tapTargetView = MyTapTargetView.showFor(fragment.activity!!,
TapTarget.forView(fab, fragment.getString(R.string.authentication_required_overlay_title), fragment.getString(R.string.authentication_required_overlay_text))
.cancelable(true)
.tintTarget(true)

View file

@ -0,0 +1,56 @@
/*
* 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.main
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewManager
import com.getkeepsafe.taptargetview.TapTarget
import com.getkeepsafe.taptargetview.TapTargetView
@SuppressLint("ViewConstructor")
class MyTapTargetView(
context: Context, parent: ViewManager, boundingParent: ViewGroup?, target: TapTarget?, val userListener: Listener?
): TapTargetView(context, parent, boundingParent, target, userListener) {
companion object {
fun showFor(activity: Activity, target: TapTarget?, listener: Listener?): TapTargetView? {
val decor = activity.window.decorView as ViewGroup
val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
val content = decor.findViewById<View>(android.R.id.content) as ViewGroup
val tapTargetView = MyTapTargetView(activity, decor, content, target, listener)
decor.addView(tapTargetView, layoutParams)
return tapTargetView
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (isVisible && (
keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER)
) {
userListener?.onTargetClick(this)
return true
} else {
return super.onKeyDown(keyCode, event)
}
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.manage.device.manage.permission
import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import io.timelimit.android.BuildConfig
import io.timelimit.android.R
import io.timelimit.android.extensions.showSafe
import io.timelimit.android.integration.platform.android.AdminReceiver
class AdbDeviceAdminDialogFragment: DialogFragment() {
companion object {
private const val DIALOG_TAG = "AdbDeviceAdminDialogFragment"
private val GRANT_COMMAND = "adb shell dpm set-active-admin --user current ${BuildConfig.APPLICATION_ID}/${AdminReceiver::class.java.canonicalName}"
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = AlertDialog.Builder(context!!, theme)
.setTitle(R.string.manage_device_permission_device_admin_title)
.setMessage(getString(R.string.manage_device_permission_no_ui_device_admin, GRANT_COMMAND))
.setPositiveButton(R.string.generic_ok, null)
.create()
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
}

View file

@ -0,0 +1,42 @@
/*
* 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.manage.device.manage.permission
import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import io.timelimit.android.BuildConfig
import io.timelimit.android.R
import io.timelimit.android.extensions.showSafe
class AdbUsageStatsDialogFragment: DialogFragment() {
companion object {
private const val DIALOG_TAG = "AdbUsageStatsDialogFragment"
private const val PERMISSION = "android.permission.PACKAGE_USAGE_STATS"
private const val GRANT_COMMAND = "adb shell pm grant ${BuildConfig.APPLICATION_ID} $PERMISSION"
private const val REVOKE_COMMAND = "adb shell pm revoke ${BuildConfig.APPLICATION_ID} $PERMISSION"
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = AlertDialog.Builder(context!!, theme)
.setTitle(R.string.manage_device_permissions_usagestats_title)
.setMessage(getString(R.string.manage_device_permission_no_ui_usage_stats_text, GRANT_COMMAND, REVOKE_COMMAND))
.setPositiveButton(R.string.generic_ok, null)
.create()
fun show(fragmentManager: FragmentManager) = showSafe(fragmentManager, DIALOG_TAG)
}

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* 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
@ -41,13 +41,17 @@ class InformAboutDeviceOwnerDialogFragment: DialogFragment() {
.setTitle(R.string.inform_about_device_owner_title)
.setMessage(R.string.inform_about_device_owner_text)
.setPositiveButton(R.string.inform_about_device_owner_continue) { _, _ ->
startActivity(
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN,
ComponentName(context!!, AdminReceiver::class.java)
)
)
try {
startActivity(
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN,
ComponentName(context!!, AdminReceiver::class.java)
)
)
} catch (ex: Exception) {
AdbDeviceAdminDialogFragment().show(parentFragmentManager)
}
}
.setNegativeButton(R.string.generic_cancel, null)
.create()

View file

@ -126,10 +126,14 @@ class ManageDevicePermissionsFragment : Fragment(), FragmentWithCustomTitle {
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
startActivity(
Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
try {
startActivity(
Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
AdbUsageStatsDialogFragment().show(parentFragmentManager)
}
}
}
}
@ -143,28 +147,32 @@ class ManageDevicePermissionsFragment : Fragment(), FragmentWithCustomTitle {
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
Toast.makeText(
context,
R.string.error_general,
Toast.LENGTH_SHORT
).show()
Toast.makeText(context, R.string.error_general, Toast.LENGTH_SHORT).show()
}
}
}
override fun openDrawOverOtherAppsScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
startActivity(
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context!!.packageName))
)
try {
startActivity(
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context!!.packageName))
)
} catch (ex: Exception) {
Toast.makeText(context, R.string.error_general, Toast.LENGTH_SHORT).show()
}
}
}
override fun openAccessibilitySettings() {
startActivity(
Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
try {
startActivity(
Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
Toast.makeText(context, R.string.error_general, Toast.LENGTH_SHORT).show()
}
}
override fun manageDeviceAdmin() {
@ -175,19 +183,27 @@ class ManageDevicePermissionsFragment : Fragment(), FragmentWithCustomTitle {
if (InformAboutDeviceOwnerDialogFragment.shouldShow) {
InformAboutDeviceOwnerDialogFragment().show(fragmentManager!!)
} else {
startActivity(
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN,
ComponentName(context!!, AdminReceiver::class.java)
)
)
try {
startActivity(
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN,
ComponentName(context!!, AdminReceiver::class.java)
)
)
} catch (ex: Exception) {
AdbDeviceAdminDialogFragment().show(parentFragmentManager)
}
}
} else {
startActivity(
Intent(Settings.ACTION_SECURITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
try {
startActivity(
Intent(Settings.ACTION_SECURITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
AdbDeviceAdminDialogFragment().show(parentFragmentManager)
}
}
}
}

View file

@ -29,6 +29,7 @@ import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
import io.timelimit.android.R
import io.timelimit.android.async.Threads
import io.timelimit.android.databinding.FragmentSetupDevicePermissionsBinding
import io.timelimit.android.extensions.safeNavigate
import io.timelimit.android.integration.platform.ProtectionLevel
@ -36,6 +37,8 @@ import io.timelimit.android.integration.platform.android.AdminReceiver
import io.timelimit.android.logic.AppLogic
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.ui.help.HelpDialogFragment
import io.timelimit.android.ui.manage.device.manage.permission.AdbDeviceAdminDialogFragment
import io.timelimit.android.ui.manage.device.manage.permission.AdbUsageStatsDialogFragment
import io.timelimit.android.ui.manage.device.manage.permission.InformAboutDeviceOwnerDialogFragment
@ -43,6 +46,16 @@ class SetupDevicePermissionsFragment : Fragment() {
private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) }
private lateinit var binding: FragmentSetupDevicePermissionsBinding
lateinit var refreshStatusRunnable: Runnable
init {
refreshStatusRunnable = Runnable {
refreshStatus()
Threads.mainThreadHandler.postDelayed(refreshStatusRunnable, 2000L)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val navigation = Navigation.findNavController(container!!)
@ -56,19 +69,27 @@ class SetupDevicePermissionsFragment : Fragment() {
if (InformAboutDeviceOwnerDialogFragment.shouldShow) {
InformAboutDeviceOwnerDialogFragment().show(fragmentManager!!)
} else {
startActivity(
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN,
ComponentName(context!!, AdminReceiver::class.java)
)
)
try {
startActivity(
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(
DevicePolicyManager.EXTRA_DEVICE_ADMIN,
ComponentName(context!!, AdminReceiver::class.java)
)
)
} catch (ex: Exception) {
AdbDeviceAdminDialogFragment().show(parentFragmentManager)
}
}
} else {
startActivity(
Intent(Settings.ACTION_SECURITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
try {
startActivity(
Intent(Settings.ACTION_SECURITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
AdbDeviceAdminDialogFragment().show(parentFragmentManager)
}
}
}
@ -87,10 +108,14 @@ class SetupDevicePermissionsFragment : Fragment() {
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
startActivity(
Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
try {
startActivity(
Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
AdbUsageStatsDialogFragment().show(parentFragmentManager)
}
}
}
}
@ -112,18 +137,26 @@ class SetupDevicePermissionsFragment : Fragment() {
override fun openDrawOverOtherAppsScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
startActivity(
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context!!.packageName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
try {
startActivity(
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context!!.packageName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
Toast.makeText(context, R.string.error_general, Toast.LENGTH_SHORT).show()
}
}
}
override fun openAccessibilitySettings() {
startActivity(
Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
try {
startActivity(
Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (ex: Exception) {
Toast.makeText(context, R.string.error_general, Toast.LENGTH_SHORT).show()
}
}
override fun gotoNextStep() {
@ -181,7 +214,14 @@ class SetupDevicePermissionsFragment : Fragment() {
override fun onResume() {
super.onResume()
refreshStatus()
// this additionally schedules it
refreshStatusRunnable.run()
}
override fun onPause() {
super.onPause()
Threads.mainThreadHandler.removeCallbacks(refreshStatusRunnable)
}
}

View file

@ -1,5 +1,5 @@
/*
* TimeLimit Copyright <C> 2019 Jonas Lochmann
* 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
@ -57,7 +57,12 @@ class SelectCustomServerDialogFragment : BottomSheetDialogFragment() {
model.status.observe(this, Observer {
when (it!!) {
SelectCustomServerStatus.Done -> dismiss()
SelectCustomServerStatus.Idle -> binding.isWorking = false
SelectCustomServerStatus.Idle -> {
binding.isWorking = false
binding.executePendingBindings()
binding.url.requestFocus()
}
SelectCustomServerStatus.Working -> binding.isWorking = true
}
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -70,6 +70,7 @@
android:layout_height="wrap_content" />
<SeekBar
android:nextFocusDown="@id/confirm_btn"
android:max="10"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -55,6 +55,7 @@
android:layout_height="wrap_content" />
<Switch
android:nextFocusDown="@id/extra_time_btn_ok"
android:id="@+id/switch_limit_extra_time_to_today"
android:text="@string/category_settings_extra_time_switch_limit_to_day"
android:layout_width="match_parent"

View file

@ -75,6 +75,8 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/usage_stats_access_title"
android:nextFocusDown="@id/usage_stats_access_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
@ -116,6 +118,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/usage_stats_access_btn"
android:nextFocusUp="@id/usage_stats_access_title"
android:nextFocusDown="@id/device_admin_btn"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
@ -123,7 +128,9 @@
android:onClick="@{() -> handlers.openUsageStatsSettings()}"
android:enabled="@{usageStatsAccess != RuntimePermissionStatus.NotRequired}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content">
<requestFocus />
</Button>
</LinearLayout>
</androidx.cardview.widget.CardView>
@ -173,6 +180,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/device_admin_btn"
android:nextFocusUp="@id/usage_stats_access_btn"
android:nextFocusDown="@id/notification_access_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
@ -195,6 +205,9 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/notification_access_title"
android:nextFocusUp="@id/device_admin_btn"
android:nextFocusDown="@id/notification_access_button"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
@ -236,6 +249,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/notification_access_button"
android:nextFocusUp="@id/notification_access_title"
android:nextFocusDown="@id/overlay_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
@ -259,6 +275,9 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/overlay_title"
android:nextFocusUp="@id/notification_access_button"
android:nextFocusDown="@id/overlay_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
@ -300,6 +319,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/overlay_btn"
android:nextFocusUp="@id/overlay_title"
android:nextFocusDown="@id/accessibility_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
@ -323,6 +345,9 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/accessibility_title"
android:nextFocusUp="@id/overlay_btn"
android:nextFocusDown="@id/accessibility_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
@ -354,6 +379,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/accessibility_btn"
android:nextFocusUp="@id/accessibility_title"
android:nextFocusDown="@id/next_btn"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:text="@string/manage_device_permission_btn_modify"
@ -366,6 +394,8 @@
</androidx.cardview.widget.CardView>
<Button
android:nextFocusUp="@id/accessibility_btn"
android:id="@+id/next_btn"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="4dp"

View file

@ -1,5 +1,5 @@
<!--
TimeLimit Copyright <C> 2019 Jonas Lochmann
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.
@ -40,6 +40,7 @@
android:text="@string/setup_select_mode_intro" />
<androidx.cardview.widget.CardView
android:focusable="true"
android:id="@+id/btn_local_mode"
android:foreground="?selectableItemBackground"
app:cardUseCompatPadding="true"
@ -64,9 +65,12 @@
android:layout_height="wrap_content" />
</LinearLayout>
<requestFocus />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:focusable="true"
android:visibility="@{BuildConfig.hasServer ? View.VISIBLE : View.GONE}"
android:id="@+id/btn_parent_mode"
android:foreground="?selectableItemBackground"
@ -95,6 +99,7 @@
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:focusable="true"
android:visibility="@{BuildConfig.hasServer ? View.VISIBLE : View.GONE}"
android:id="@+id/btn_network_child_mode"
android:foreground="?selectableItemBackground"
@ -123,6 +128,7 @@
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:focusable="true"
android:id="@+id/btn_uninstall"
android:foreground="?selectableItemBackground"
app:cardUseCompatPadding="true"

View file

@ -71,6 +71,7 @@
<Button
style="?materialButtonOutlinedStyle"
android:nextFocusDown="@id/btn_accept"
android:id="@+id/custom_server_button"
android:text="@string/select_custom_server_button"
android:layout_width="wrap_content"
@ -94,12 +95,15 @@
android:layout_height="wrap_content">
<Button
android:nextFocusUp="@id/custom_server_button"
android:layout_marginEnd="4dp"
android:layout_gravity="end"
android:id="@+id/btn_accept"
android:text="@string/terms_btn_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content">
<requestFocus />
</Button>
</LinearLayout>
</LinearLayout>

View file

@ -116,6 +116,8 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/device_admin_btn"
android:nextFocusDown="@id/usage_stats_access_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
@ -124,7 +126,9 @@
android:enabled="@{safeUnbox(isThisDevice)}"
android:onClick="@{() -> handlers.manageDeviceAdmin()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content">
<requestFocus />
</Button>
<TextView
android:visibility="@{safeUnbox(isThisDevice) ? View.GONE : View.VISIBLE}"
@ -147,6 +151,9 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/usage_stats_access_title"
android:nextFocusUp="@id/device_admin_btn"
android:nextFocusDown="@id/usage_stats_access_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
@ -188,6 +195,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/usage_stats_access_btn"
android:nextFocusUp="@id/usage_stats_access_title"
android:nextFocusDown="@id/notification_access_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
@ -219,6 +229,9 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/notification_access_title"
android:nextFocusUp="@id/usage_stats_access_btn"
android:nextFocusDown="@id/notification_access_button"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
@ -260,6 +273,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/notification_access_button"
android:nextFocusUp="@id/notification_access_title"
android:nextFocusDown="@id/overlay_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
@ -291,6 +307,9 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/overlay_title"
android:nextFocusUp="@id/notification_access_button"
android:nextFocusDown="@id/overlay_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
@ -332,6 +351,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/overlay_btn"
android:nextFocusUp="@id/overlay_title"
android:nextFocusDown="@id/accessibility_title"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
@ -363,6 +385,9 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/accessibility_title"
android:nextFocusUp="@id/overlay_btn"
android:nextFocusDown="@id/accessibility_btn"
tools:ignore="UnusedAttribute"
android:drawableTint="?colorOnSurface"
android:background="?selectableItemBackground"
@ -394,6 +419,9 @@
android:layout_height="wrap_content" />
<Button
android:id="@+id/accessibility_btn"
android:nextFocusUp="@id/accessibility_title"
android:nextFocusDown="@id/fab"
style="?materialButtonOutlinedStyle"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
@ -423,6 +451,7 @@
</ScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:nextFocusUp="@id/accessibility_btn"
android:onClick="@{() -> handlers.showAuthenticationScreen()}"
android:id="@+id/fab"
app:fabSize="normal"

View file

@ -45,7 +45,9 @@
android:checked="@{safeUnbox(selectedDays.get(0))}"
android:text="@string/day_of_week_monday_short"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content">
<requestFocus />
</com.google.android.material.button.MaterialButton>
<com.google.android.material.button.MaterialButton
style="?materialButtonOutlinedStyle"

View file

@ -108,6 +108,19 @@
<string name="manage_device_permission_btn_modify">Ändern</string>
<string name="manage_device_permission_no_ui_usage_stats_text">
Dieses Gerät hat keine grafische Oberfläche zum Erteilen dieser Berechtigung - diese kann
nur von einem Computer mittels ADB geändert werden.
\n\nAktivieren mittels:\n\n%s
\n\nDeaktivieren mittels:\n\n%s
</string>
<string name="manage_device_permission_no_ui_device_admin">
Dieses Gerät hat keine grafische Oberfläche zum Erteilen dieser Berechtigung - diese kann
nur von einem Computer mittels ADB aktiviert werden:
\n\n%s
\n\nDie Berechtigung kann über die Deinstallations-Funktion von TimeLimit deaktiviert werden.
</string>
<string name="manage_device_downgrade_title">Downgrade</string>
<string name="manage_device_downgrade_text">Auf diesem Gerät wurde eine neuere Version von TimeLimit durch eine ältere ersetzt</string>

View file

@ -107,6 +107,19 @@
<string name="manage_device_permission_btn_modify">Modify</string>
<string name="manage_device_permission_no_ui_usage_stats_text">
This device does not allow changing this permission graphically. You can only
change it from a PC using ADB.
\n\nEnable:\n\n%s
\n\nDisable:\n\n%s
</string>
<string name="manage_device_permission_no_ui_device_admin">
This device does not allow changing this permission graphically. You can only
enable it from a PC using ADB.
\n\n%s
\n\nYou can disable it by using the uninstall feature of TimeLimit.
</string>
<string name="manage_device_downgrade_title">Downgrade</string>
<string name="manage_device_downgrade_text">On this device, a newer version of TimeLimit was replaced by an older one</string>