diff --git a/README.md b/README.md
index 6ffda0c51..75105371a 100644
--- a/README.md
+++ b/README.md
@@ -2,28 +2,33 @@
# Olauncher | Minimal AF Launcher
-AF stands for Ad-Free :D
-
-
-## Install
-
-#### Install from [F-Droid](https://f-droid.org/packages/app.olauncher) or [Play Store](https://play.google.com/store/apps/details?id=app.olauncher) or download the APK from the [latest release](https://github.com/tanujnotes/Olauncher/releases/).
+AF stands for Ad-Free
[
](https://f-droid.org/packages/app.olauncher)
[
](https://play.google.com/store/apps/details?id=app.olauncher)
-To maintain the simplicity of the launcher, a few niche features are available but hidden.
+### Install using [F-Droid](https://f-droid.org/packages/app.olauncher), [Play Store](https://play.google.com/store/apps/details?id=app.olauncher) or the [latest APK](https://github.com/tanujnotes/Olauncher/releases/).
+
+- To maintain the simplicity of the launcher, a few niche features are available but hidden.
-Please check out the [About](https://tanujnotes.substack.com/p/olauncher-minimal-af-launcher?utm_source=github) page in Olauncher settings for the complete feature list and FAQs.
+- Please check out the **[About](https://tanujnotes.substack.com/p/olauncher-minimal-af-launcher?utm_source=github)** page in the Olauncher settings for a complete list of features and **FAQs**.
##
License: [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html)
-Developer: [@tanujnotes](https://twitter.com/tanujnotes)
+Dev: [X/twitter](https://x.com/tanujnotes) • [Bluesky](https://bsky.app/profile/tanujnotes.bsky.social)
+
+##
+
+### My other apps:
+
+- [Pro Launcher](https://play.google.com/store/apps/details?id=app.prolauncher) - Pro version of Olauncher with extra features like widgets, weather, folders, etc.
+
+- [Note to Self](https://play.google.com/store/apps/details?id=com.makenotetoself) - Free and [open source](https://github.com/jeerovan/ntsapp) notes app with chat like interface and end-to-end encryption.
-Thank you and have a wonderful day!
+- [Pentastic](https://play.google.com/store/apps/details?id=app.pentastic) - Minimal todo lists. Free and [open source](https://github.com/tanujnotes/Pentastic).
diff --git a/app/build.gradle b/app/build.gradle
index e31ba8e62..027a501fa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
- compileSdkVersion 33
+ compileSdk 35
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
@@ -15,12 +15,12 @@ android {
defaultConfig {
applicationId "app.olauncher"
- minSdkVersion 23
- targetSdkVersion 33
- versionCode 78
- versionName "v4.2.2"
+ minSdkVersion 24
+ targetSdkVersion 35
+ versionCode 105
+ versionName "v6.4.2"
- resourceConfigurations += ["en", "ar", "de", "es-rES", "es-rUS", "fr", "hr", "hu", "in", "it", "ja", "pl", "pt-rBR", "ru-rRU", "sv", "tr", "uk", "zh"]
+ resourceConfigurations += ["en", "ar", "de", "es-rES", "es-rUS", "fr", "he", "hr", "hu", "in", "it", "ja", "nl", "pl", "pt-rBR", "ru-rRU", "sv", "tr", "uk", "zh"]
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -39,27 +39,29 @@ android {
}
buildFeatures {
viewBinding = true
+ buildConfig = true
}
+ namespace 'app.olauncher'
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
- implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.21"
- implementation 'androidx.core:core-ktx:1.10.1'
- implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation "androidx.recyclerview:recyclerview:1.3.0"
+ implementation libs.kotlin.stdlib
+ implementation libs.core.ktx
+ implementation libs.appcompat
+ implementation libs.recyclerview
// Android lifecycle
- implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
+ implementation libs.lifecycle.extensions
+ implementation libs.lifecycle.viewmodel.ktx
// Navigation
- implementation "androidx.navigation:navigation-fragment-ktx:2.6.0"
+ implementation libs.navigation.fragment.ktx
// Work Manager
- implementation "androidx.work:work-runtime-ktx:2.8.1"
+ implementation libs.work.runtime.ktx
//Material dependency
- implementation 'com.google.android.material:material:1.9.0'
+ implementation libs.material
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 89753941d..91aa7afec 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,18 +1,22 @@
+ xmlns:tools="http://schemas.android.com/tools">
+ tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ profileReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ viewModel.isPrivateSpaceToggling = false
+ viewModel.getPrivateSpaceAppList()
+ }
+ }
+ val filter = IntentFilter().apply {
+ addAction(Intent.ACTION_PROFILE_AVAILABLE)
+ addAction(Intent.ACTION_PROFILE_UNAVAILABLE)
+ }
+ registerReceiver(profileReceiver, filter)
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ restartLauncherOrCheckTheme()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ isResumed = true
+ viewModel.isPrivateSpaceToggling = false
}
override fun onStop() {
+ isResumed = false
backToHomeScreen()
super.onStop()
}
@@ -77,7 +147,10 @@ class MainActivity : AppCompatActivity() {
}
override fun onNewIntent(intent: Intent?) {
+ val alreadyHome = navController.currentDestination?.id == R.id.mainFragment
backToHomeScreen()
+ if (alreadyHome && isResumed && prefs.homeButtonShowRecents)
+ viewModel.showRecentApps.call()
super.onNewIntent(intent)
}
@@ -102,69 +175,91 @@ class MainActivity : AppCompatActivity() {
openLauncherChooser(it)
}
viewModel.resetLauncherLiveData.observe(this) {
-// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
- resetDefaultLauncher()
-// else
-// showLauncherSelector(Constants.REQUEST_CODE_LAUNCHER_SELECTOR)
+ if (isDefaultLauncher() || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
+ resetLauncherViaFakeActivity()
+ else
+ showLauncherSelector(Constants.REQUEST_CODE_LAUNCHER_SELECTOR)
}
viewModel.checkForMessages.observe(this) {
checkForMessages()
}
viewModel.showDialog.observe(this) {
when (it) {
+ Constants.Dialog.ABOUT -> {
+ showMessageDialog(R.string.app_name, R.string.welcome_to_olauncher_settings, R.string.okay) {
+ binding.messageLayout.visibility = View.GONE
+ }
+ }
+
+ Constants.Dialog.WALLPAPER -> {
+ prefs.wallpaperMsgShown = true
+ prefs.userState = Constants.UserState.REVIEW
+ showMessageDialog(R.string.did_you_know, R.string.wallpaper_message, R.string.enable) {
+ prefs.dailyWallpaper = true
+ viewModel.setWallpaperWorker()
+ showToast(getString(R.string.your_wallpaper_will_update_shortly))
+ }
+ }
+
Constants.Dialog.REVIEW -> {
prefs.userState = Constants.UserState.RATE
- showMessageDialog(getString(R.string.did_you_know), getString(R.string.review_message), getString(R.string.leave_a_review)) {
- binding.messageLayout.visibility = View.GONE
+ showMessageDialog(R.string.hey, R.string.review_message, R.string.leave_a_review) {
prefs.rateClicked = true
+ showToast("😇❤️")
rateApp()
}
}
Constants.Dialog.RATE -> {
prefs.userState = Constants.UserState.SHARE
- showMessageDialog(getString(R.string.app_name), getString(R.string.rate_us_message), getString(R.string.rate_now)) {
- binding.messageLayout.visibility = View.GONE
+ showMessageDialog(R.string.app_name, R.string.rate_us_message, R.string.rate_now) {
prefs.rateClicked = true
+ showToast("🤩❤️")
rateApp()
}
}
Constants.Dialog.SHARE -> {
prefs.shareShownTime = System.currentTimeMillis()
- showMessageDialog(getString(R.string.app_name), getString(R.string.share_message), getString(R.string.share_now)) {
- binding.messageLayout.visibility = View.GONE
+ showMessageDialog(R.string.hey, R.string.share_message, R.string.share_now) {
+ showToast("😊❤️")
shareApp()
}
}
Constants.Dialog.HIDDEN -> {
- showMessageDialog(getString(R.string.hidden_apps), getString(R.string.hidden_apps_message), getString(R.string.okay)) {
- binding.messageLayout.visibility = View.GONE
+ showMessageDialog(R.string.hidden_apps, R.string.hidden_apps_message, R.string.okay) {
}
}
Constants.Dialog.KEYBOARD -> {
- showMessageDialog(getString(R.string.app_name), getString(R.string.keyboard_message), getString(R.string.okay)) {
- binding.messageLayout.visibility = View.GONE
+ showMessageDialog(R.string.app_name, R.string.keyboard_message, R.string.okay) {
}
}
Constants.Dialog.DIGITAL_WELLBEING -> {
- showMessageDialog("Hi", getString(R.string.digital_wellbeing_message), getString(R.string.learn_more)) {
- binding.messageLayout.visibility = View.GONE
- openUrl(Constants.URL_DIGITAL_WELLBEING_LEARN_MORE)
+ showMessageDialog(R.string.screen_time, R.string.app_usage_message, R.string.permission) {
+ startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
+ }
+ }
+
+ Constants.Dialog.PRO_MESSAGE -> {
+ showMessageDialog(R.string.hey, R.string.pro_message, R.string.olauncher_pro) {
+ openUrl(Constants.URL_OLAUNCHER_PRO)
}
}
}
}
}
- private fun showMessageDialog(title: String, message: String, action: String, clickListener: () -> Unit) {
- binding.tvTitle.text = title
- binding.tvMessage.text = message
- binding.tvAction.text = action
- binding.tvAction.setOnClickListener { clickListener() }
+ private fun showMessageDialog(title: Int, message: Int, action: Int, clickListener: () -> Unit) {
+ binding.tvTitle.text = getString(title)
+ binding.tvMessage.text = getString(message)
+ binding.tvAction.text = getString(action)
+ binding.tvAction.setOnClickListener {
+ clickListener()
+ binding.messageLayout.visibility = View.GONE
+ }
binding.messageLayout.visibility = View.VISIBLE
}
@@ -172,16 +267,35 @@ class MainActivity : AppCompatActivity() {
if (prefs.firstOpenTime == 0L)
prefs.firstOpenTime = System.currentTimeMillis()
+ val calendar = Calendar.getInstance()
+ val dayOfYear = calendar.get(Calendar.DAY_OF_YEAR)
+ if (dayOfYear == 1 && dayOfYear != prefs.shownOnDayOfYear) {
+ prefs.shownOnDayOfYear = dayOfYear
+ showMessageDialog(R.string.hey, R.string.new_year_wish, R.string.cheers) {}
+ return
+ } else if (dayOfYear == 32 && dayOfYear != prefs.shownOnDayOfYear) {
+ prefs.shownOnDayOfYear = dayOfYear
+ showMessageDialog(R.string.hey, R.string.new_year_wish_1, R.string.cheers) {}
+ return
+ }
+
when (prefs.userState) {
Constants.UserState.START -> {
- if (prefs.firstOpenTime.hasBeenDays(1))
+ if (prefs.firstOpenTime.hasBeenMinutes(10))
+ prefs.userState = Constants.UserState.WALLPAPER
+ }
+
+ Constants.UserState.WALLPAPER -> {
+ if (prefs.wallpaperMsgShown || prefs.dailyWallpaper)
prefs.userState = Constants.UserState.REVIEW
+ else if (isOlauncherDefault(this))
+ viewModel.showDialog.postValue(Constants.Dialog.WALLPAPER)
}
Constants.UserState.REVIEW -> {
if (prefs.rateClicked)
prefs.userState = Constants.UserState.SHARE
- else if (isOlauncherDefault(this))
+ else if (isOlauncherDefault(this) && prefs.firstOpenTime.hasBeenHours(1))
viewModel.showDialog.postValue(Constants.Dialog.REVIEW)
}
@@ -189,15 +303,15 @@ class MainActivity : AppCompatActivity() {
if (prefs.rateClicked)
prefs.userState = Constants.UserState.SHARE
else if (isOlauncherDefault(this)
- && prefs.firstOpenTime.hasBeenDays(7)
- && Calendar.getInstance().get(Calendar.HOUR_OF_DAY) >= 15
+ && prefs.firstOpenTime.isDaySince() >= 7
+ && calendar.get(Calendar.HOUR_OF_DAY) >= 16
) viewModel.showDialog.postValue(Constants.Dialog.RATE)
}
Constants.UserState.SHARE -> {
if (isOlauncherDefault(this) && prefs.firstOpenTime.hasBeenDays(14)
- && prefs.shareShownTime.hasBeenDays(35)
- && Calendar.getInstance().get(Calendar.HOUR_OF_DAY) >= 15
+ && prefs.shareShownTime.isDaySince() >= 70
+ && calendar.get(Calendar.HOUR_OF_DAY) >= 16
) viewModel.showDialog.postValue(Constants.Dialog.SHARE)
}
}
@@ -212,6 +326,7 @@ class MainActivity : AppCompatActivity() {
}
private fun backToHomeScreen() {
+ if (viewModel.isPrivateSpaceToggling) return
binding.messageLayout.visibility = View.GONE
if (navController.currentDestination?.id != R.id.mainFragment)
navController.popBackStack(R.id.mainFragment, false)
@@ -225,16 +340,41 @@ class MainActivity : AppCompatActivity() {
private fun openLauncherChooser(resetFailed: Boolean) {
if (resetFailed) {
- val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
- Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
- else {
- showToast(getString(R.string.search_for_Launcher_or_home_app), Toast.LENGTH_LONG)
- Intent(Settings.ACTION_SETTINGS)
- }
+ val intent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
startActivity(intent)
}
}
+ private fun restartLauncherOrCheckTheme(forceRestart: Boolean = false) {
+ if (forceRestart || prefs.launcherRestartTimestamp.hasBeenHours(4)) {
+ prefs.launcherRestartTimestamp = System.currentTimeMillis()
+ cacheDir.deleteRecursively()
+ recreate()
+ } else
+ checkTheme()
+ }
+
+ private fun checkTheme() {
+ timerJob?.cancel()
+ timerJob = lifecycleScope.launch {
+ delay(200)
+ if ((prefs.appTheme == AppCompatDelegate.MODE_NIGHT_YES && getColorFromAttr(R.attr.primaryColor) != getColor(R.color.white))
+ || (prefs.appTheme == AppCompatDelegate.MODE_NIGHT_NO && getColorFromAttr(R.attr.primaryColor) != getColor(R.color.black))
+ )
+ restartLauncherOrCheckTheme(true)
+ }
+ }
+
+ override fun onDestroy() {
+ profileReceiver?.let {
+ try {
+ unregisterReceiver(it)
+ } catch (_: Exception) {
+ }
+ }
+ super.onDestroy()
+ }
+
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
@@ -245,7 +385,8 @@ class MainActivity : AppCompatActivity() {
}
Constants.REQUEST_CODE_LAUNCHER_SELECTOR -> {
- resetDefaultLauncher()
+ if (resultCode == Activity.RESULT_OK)
+ resetLauncherViaFakeActivity()
}
}
}
diff --git a/app/src/main/java/app/olauncher/MainViewModel.kt b/app/src/main/java/app/olauncher/MainViewModel.kt
index a06a35e5d..7196a7ed3 100644
--- a/app/src/main/java/app/olauncher/MainViewModel.kt
+++ b/app/src/main/java/app/olauncher/MainViewModel.kt
@@ -3,8 +3,11 @@ package app.olauncher
import android.app.Application
import android.content.ComponentName
import android.content.Context
+import android.content.Intent
import android.content.pm.LauncherApps
+import android.os.Build
import android.os.UserHandle
+import android.os.UserManager
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
@@ -19,12 +22,21 @@ import app.olauncher.data.Constants
import app.olauncher.data.Prefs
import app.olauncher.helper.SingleLiveEvent
import app.olauncher.helper.WallpaperWorker
+import app.olauncher.helper.formattedTimeSpent
import app.olauncher.helper.getAppsList
+import app.olauncher.helper.getPrivateSpaceApps
+import app.olauncher.helper.getPrivateSpaceUserHandle
+import app.olauncher.helper.hasBeenMinutes
import app.olauncher.helper.isOlauncherDefault
+import app.olauncher.helper.isPackageInstalled
+import app.olauncher.helper.isPrivateSpaceLocked
import app.olauncher.helper.showToast
+import app.olauncher.helper.usageStats.EventLogWrapper
import kotlinx.coroutines.launch
+import java.util.Calendar
import java.util.concurrent.TimeUnit
+
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val appContext by lazy { application.applicationContext }
private val prefs = Prefs(appContext)
@@ -38,112 +50,288 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val isOlauncherDefault = MutableLiveData()
val launcherResetFailed = MutableLiveData()
val homeAppAlignment = MutableLiveData()
+ val screenTimeValue = MutableLiveData()
+
+ val privateSpaceApps = MutableLiveData?>()
+ val privateSpaceLocked = MutableLiveData()
+ val privateSpaceAvailable = MutableLiveData()
+
+ // Suppress backToHomeScreen during Private Space lock/unlock auth
+ var isPrivateSpaceToggling = false
val showDialog = SingleLiveEvent()
val checkForMessages = SingleLiveEvent()
val resetLauncherLiveData = SingleLiveEvent()
+ val showRecentApps = SingleLiveEvent()
fun selectedApp(appModel: AppModel, flag: Int) {
+ if (appModel is AppModel.PrivateSpaceHeader) return
when (flag) {
Constants.FLAG_LAUNCH_APP -> {
- launchApp(appModel.appPackage, appModel.activityClassName, appModel.user)
- }
-
- Constants.FLAG_HIDDEN_APPS -> {
- launchApp(appModel.appPackage, appModel.activityClassName, appModel.user)
- }
-
- Constants.FLAG_SET_HOME_APP_1 -> {
- prefs.appName1 = appModel.appLabel
- prefs.appPackage1 = appModel.appPackage
- prefs.appUser1 = appModel.user.toString()
- prefs.appActivityClassName1 = appModel.activityClassName
- refreshHome(false)
- }
+ when (appModel) {
+ is AppModel.PinnedShortcut -> launchShortcut(appModel)
+ is AppModel.App ->
+ launchApp(appModel.appPackage, appModel.activityClassName, appModel.user)
- Constants.FLAG_SET_HOME_APP_2 -> {
- prefs.appName2 = appModel.appLabel
- prefs.appPackage2 = appModel.appPackage
- prefs.appUser2 = appModel.user.toString()
- prefs.appActivityClassName2 = appModel.activityClassName
- refreshHome(false)
+ else -> {}
+ }
}
- Constants.FLAG_SET_HOME_APP_3 -> {
- prefs.appName3 = appModel.appLabel
- prefs.appPackage3 = appModel.appPackage
- prefs.appUser3 = appModel.user.toString()
- prefs.appActivityClassName3 = appModel.activityClassName
- refreshHome(false)
+ Constants.FLAG_HIDDEN_APPS -> {
+ if (appModel is AppModel.App) {
+ launchApp(appModel.appPackage, appModel.activityClassName, appModel.user)
+ }
}
- Constants.FLAG_SET_HOME_APP_4 -> {
- prefs.appName4 = appModel.appLabel
- prefs.appPackage4 = appModel.appPackage
- prefs.appUser4 = appModel.user.toString()
- prefs.appActivityClassName4 = appModel.activityClassName
- refreshHome(false)
- }
+ Constants.FLAG_SET_HOME_APP_1 -> saveHomeApp(appModel, 1)
+ Constants.FLAG_SET_HOME_APP_2 -> saveHomeApp(appModel, 2)
+ Constants.FLAG_SET_HOME_APP_3 -> saveHomeApp(appModel, 3)
+ Constants.FLAG_SET_HOME_APP_4 -> saveHomeApp(appModel, 4)
+ Constants.FLAG_SET_HOME_APP_5 -> saveHomeApp(appModel, 5)
+ Constants.FLAG_SET_HOME_APP_6 -> saveHomeApp(appModel, 6)
+ Constants.FLAG_SET_HOME_APP_7 -> saveHomeApp(appModel, 7)
+ Constants.FLAG_SET_HOME_APP_8 -> saveHomeApp(appModel, 8)
+
+ Constants.FLAG_SET_SWIPE_LEFT_APP -> saveSwipeApp(appModel, isLeft = true)
+ Constants.FLAG_SET_SWIPE_RIGHT_APP -> saveSwipeApp(appModel, isLeft = false)
+ Constants.FLAG_SET_CLOCK_APP -> saveClockApp(appModel)
+ Constants.FLAG_SET_CALENDAR_APP -> saveCalendarApp(appModel)
+ Constants.FLAG_SET_SCREEN_TIME_APP -> saveScreenTimeApp(appModel)
+ }
+ }
- Constants.FLAG_SET_HOME_APP_5 -> {
- prefs.appName5 = appModel.appLabel
- prefs.appPackage5 = appModel.appPackage
- prefs.appUser5 = appModel.user.toString()
- prefs.appActivityClassName5 = appModel.activityClassName
- refreshHome(false)
+ private fun launchShortcut(appModel: AppModel.PinnedShortcut) {
+ val launcher = appContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
+ val query = LauncherApps.ShortcutQuery().apply {
+ setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
+ }
+ launcher.getShortcuts(query, appModel.user)?.find { it.id == appModel.shortcutId }
+ ?.let { shortcut ->
+ launcher.startShortcut(shortcut, null, null)
}
+ }
- Constants.FLAG_SET_HOME_APP_6 -> {
- prefs.appName6 = appModel.appLabel
- prefs.appPackage6 = appModel.appPackage
- prefs.appUser6 = appModel.user.toString()
- prefs.appActivityClassName6 = appModel.activityClassName
- refreshHome(false)
+ private fun saveHomeApp(appModel: AppModel, position: Int) {
+ when (appModel) {
+ is AppModel.PrivateSpaceHeader -> return
+ is AppModel.App -> {
+ when (position) {
+ 1 -> {
+ prefs.appName1 = appModel.appLabel
+ prefs.appPackage1 = appModel.appPackage
+ prefs.appUser1 = appModel.user.toString()
+ prefs.appActivityClassName1 = appModel.activityClassName
+ prefs.isShortcut1 = false
+ prefs.shortcutId1 = ""
+ }
+
+ 2 -> {
+ prefs.appName2 = appModel.appLabel
+ prefs.appPackage2 = appModel.appPackage
+ prefs.appUser2 = appModel.user.toString()
+ prefs.appActivityClassName2 = appModel.activityClassName
+ prefs.isShortcut2 = false
+ prefs.shortcutId2 = ""
+ }
+
+ 3 -> {
+ prefs.appName3 = appModel.appLabel
+ prefs.appPackage3 = appModel.appPackage
+ prefs.appUser3 = appModel.user.toString()
+ prefs.appActivityClassName3 = appModel.activityClassName
+ prefs.isShortcut3 = false
+ prefs.shortcutId3 = ""
+ }
+
+ 4 -> {
+ prefs.appName4 = appModel.appLabel
+ prefs.appPackage4 = appModel.appPackage
+ prefs.appUser4 = appModel.user.toString()
+ prefs.appActivityClassName4 = appModel.activityClassName
+ prefs.isShortcut4 = false
+ prefs.shortcutId4 = ""
+ }
+
+ 5 -> {
+ prefs.appName5 = appModel.appLabel
+ prefs.appPackage5 = appModel.appPackage
+ prefs.appUser5 = appModel.user.toString()
+ prefs.appActivityClassName5 = appModel.activityClassName
+ prefs.isShortcut5 = false
+ prefs.shortcutId5 = ""
+ }
+
+ 6 -> {
+ prefs.appName6 = appModel.appLabel
+ prefs.appPackage6 = appModel.appPackage
+ prefs.appUser6 = appModel.user.toString()
+ prefs.appActivityClassName6 = appModel.activityClassName
+ prefs.isShortcut6 = false
+ prefs.shortcutId6 = ""
+ }
+
+ 7 -> {
+ prefs.appName7 = appModel.appLabel
+ prefs.appPackage7 = appModel.appPackage
+ prefs.appUser7 = appModel.user.toString()
+ prefs.appActivityClassName7 = appModel.activityClassName
+ prefs.isShortcut7 = false
+ prefs.shortcutId7 = ""
+ }
+
+ 8 -> {
+ prefs.appName8 = appModel.appLabel
+ prefs.appPackage8 = appModel.appPackage
+ prefs.appUser8 = appModel.user.toString()
+ prefs.appActivityClassName8 = appModel.activityClassName
+ prefs.isShortcut8 = false
+ prefs.shortcutId8 = ""
+ }
+ }
}
- Constants.FLAG_SET_HOME_APP_7 -> {
- prefs.appName7 = appModel.appLabel
- prefs.appPackage7 = appModel.appPackage
- prefs.appUser7 = appModel.user.toString()
- prefs.appActivityClassName7 = appModel.activityClassName
- refreshHome(false)
+ is AppModel.PinnedShortcut -> {
+ when (position) {
+ 1 -> {
+ prefs.appName1 = appModel.appLabel
+ prefs.appPackage1 = appModel.appPackage
+ prefs.appUser1 = appModel.user.toString()
+ prefs.appActivityClassName1 = null
+ prefs.isShortcut1 = true
+ prefs.shortcutId1 = appModel.shortcutId
+ }
+
+ 2 -> {
+ prefs.appName2 = appModel.appLabel
+ prefs.appPackage2 = appModel.appPackage
+ prefs.appUser2 = appModel.user.toString()
+ prefs.appActivityClassName2 = null
+ prefs.isShortcut2 = true
+ prefs.shortcutId2 = appModel.shortcutId
+ }
+
+ 3 -> {
+ prefs.appName3 = appModel.appLabel
+ prefs.appPackage3 = appModel.appPackage
+ prefs.appUser3 = appModel.user.toString()
+ prefs.appActivityClassName3 = null
+ prefs.isShortcut3 = true
+ prefs.shortcutId3 = appModel.shortcutId
+ }
+
+ 4 -> {
+ prefs.appName4 = appModel.appLabel
+ prefs.appPackage4 = appModel.appPackage
+ prefs.appUser4 = appModel.user.toString()
+ prefs.appActivityClassName4 = null
+ prefs.isShortcut4 = true
+ prefs.shortcutId4 = appModel.shortcutId
+ }
+
+ 5 -> {
+ prefs.appName5 = appModel.appLabel
+ prefs.appPackage5 = appModel.appPackage
+ prefs.appUser5 = appModel.user.toString()
+ prefs.appActivityClassName5 = null
+ prefs.isShortcut5 = true
+ prefs.shortcutId5 = appModel.shortcutId
+ }
+
+ 6 -> {
+ prefs.appName6 = appModel.appLabel
+ prefs.appPackage6 = appModel.appPackage
+ prefs.appUser6 = appModel.user.toString()
+ prefs.appActivityClassName6 = null
+ prefs.isShortcut6 = true
+ prefs.shortcutId6 = appModel.shortcutId
+ }
+
+ 7 -> {
+ prefs.appName7 = appModel.appLabel
+ prefs.appPackage7 = appModel.appPackage
+ prefs.appUser7 = appModel.user.toString()
+ prefs.appActivityClassName7 = null
+ prefs.isShortcut7 = true
+ prefs.shortcutId7 = appModel.shortcutId
+ }
+
+ 8 -> {
+ prefs.appName8 = appModel.appLabel
+ prefs.appPackage8 = appModel.appPackage
+ prefs.appUser8 = appModel.user.toString()
+ prefs.appActivityClassName8 = null
+ prefs.isShortcut8 = true
+ prefs.shortcutId8 = appModel.shortcutId
+ }
+ }
}
+ }
+ refreshHome(false)
+ }
- Constants.FLAG_SET_HOME_APP_8 -> {
- prefs.appName8 = appModel.appLabel
- prefs.appPackage8 = appModel.appPackage
- prefs.appUser8 = appModel.user.toString()
- prefs.appActivityClassName8 = appModel.activityClassName
- refreshHome(false)
+ private fun saveSwipeApp(appModel: AppModel, isLeft: Boolean) {
+ when (appModel) {
+ is AppModel.PrivateSpaceHeader -> return
+ is AppModel.App -> {
+ if (isLeft) {
+ prefs.appNameSwipeLeft = appModel.appLabel
+ prefs.appPackageSwipeLeft = appModel.appPackage
+ prefs.appUserSwipeLeft = appModel.user.toString()
+ prefs.appActivityClassNameSwipeLeft = appModel.activityClassName
+ prefs.isShortcutSwipeLeft = false
+ prefs.shortcutIdSwipeLeft = ""
+ } else {
+ prefs.appNameSwipeRight = appModel.appLabel
+ prefs.appPackageSwipeRight = appModel.appPackage
+ prefs.appUserSwipeRight = appModel.user.toString()
+ prefs.appActivityClassNameRight = appModel.activityClassName
+ prefs.isShortcutSwipeRight = false
+ prefs.shortcutIdSwipeRight = ""
+ }
}
- Constants.FLAG_SET_SWIPE_LEFT_APP -> {
- prefs.appNameSwipeLeft = appModel.appLabel
- prefs.appPackageSwipeLeft = appModel.appPackage
- prefs.appUserSwipeLeft = appModel.user.toString()
- prefs.appActivityClassNameSwipeLeft = appModel.activityClassName
- updateSwipeApps()
+ is AppModel.PinnedShortcut -> {
+ if (isLeft) {
+ prefs.appNameSwipeLeft = appModel.appLabel
+ prefs.appPackageSwipeLeft = appModel.appPackage
+ prefs.appUserSwipeLeft = appModel.user.toString()
+ prefs.appActivityClassNameSwipeLeft = null
+ prefs.isShortcutSwipeLeft = true
+ prefs.shortcutIdSwipeLeft = appModel.shortcutId
+ } else {
+ prefs.appNameSwipeRight = appModel.appLabel
+ prefs.appPackageSwipeRight = appModel.appPackage
+ prefs.appUserSwipeRight = appModel.user.toString()
+ prefs.appActivityClassNameRight = null
+ prefs.isShortcutSwipeRight = true
+ prefs.shortcutIdSwipeRight = appModel.shortcutId
+ }
}
+ }
+ updateSwipeApps()
+ }
- Constants.FLAG_SET_SWIPE_RIGHT_APP -> {
- prefs.appNameSwipeRight = appModel.appLabel
- prefs.appPackageSwipeRight = appModel.appPackage
- prefs.appUserSwipeRight = appModel.user.toString()
- prefs.appActivityClassNameRight = appModel.activityClassName
- updateSwipeApps()
- }
+ private fun saveClockApp(appModel: AppModel) {
+ if (appModel is AppModel.App) {
+ prefs.clockAppPackage = appModel.appPackage
+ prefs.clockAppUser = appModel.user.toString()
+ prefs.clockAppClassName = appModel.activityClassName
+ }
+ }
- Constants.FLAG_SET_CLOCK_APP -> {
- prefs.clockAppPackage = appModel.appPackage
- prefs.clockAppUser = appModel.user.toString()
- prefs.clockAppClassName = appModel.activityClassName
- }
+ private fun saveCalendarApp(appModel: AppModel) {
+ if (appModel is AppModel.App) {
+ prefs.calendarAppPackage = appModel.appPackage
+ prefs.calendarAppUser = appModel.user.toString()
+ prefs.calendarAppClassName = appModel.activityClassName
+ }
+ }
- Constants.FLAG_SET_CALENDAR_APP -> {
- prefs.calendarAppPackage = appModel.appPackage
- prefs.calendarAppUser = appModel.user.toString()
- prefs.calendarAppClassName = appModel.activityClassName
- }
+ private fun saveScreenTimeApp(appModel: AppModel) {
+ if (appModel is AppModel.App) {
+ prefs.screenTimeAppPackage = appModel.appPackage
+ prefs.screenTimeAppUser = appModel.user.toString()
+ prefs.screenTimeAppClassName = appModel.activityClassName
}
}
@@ -167,8 +355,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val launcher = appContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val activityInfo = launcher.getActivityList(packageName, userHandle)
- val component = if (activityClassName.isNullOrBlank()) {
- // activityClassName will be null for hidden apps.
+ val isActivityValid = activityClassName.isNullOrBlank().not()
+ && activityInfo.any { it.componentName.className == activityClassName }
+
+ val component = if (isActivityValid)
+ ComponentName(packageName, activityClassName)
+ else {
when (activityInfo.size) {
0 -> {
appContext.showToast(appContext.getString(R.string.app_not_found))
@@ -177,9 +369,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
1 -> ComponentName(packageName, activityInfo[0].name)
else -> ComponentName(packageName, activityInfo[activityInfo.size - 1].name)
- }
- } else {
- ComponentName(packageName, activityClassName)
+ }.also { prefs.updateAppActivityClassName(packageName, it.className) }
}
try {
@@ -197,13 +387,16 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
fun getAppList(includeHiddenApps: Boolean = false) {
viewModelScope.launch {
- appList.value = getAppsList(appContext, prefs, includeRegularApps = true, includeHiddenApps)
+ val apps = getAppsList(appContext, prefs, includeRegularApps = true, includeHiddenApps)
+ appList.value = apps
}
+ getPrivateSpaceAppList()
}
fun getHiddenApps() {
viewModelScope.launch {
- hiddenApps.value = getAppsList(appContext, prefs, includeRegularApps = false, includeHiddenApps = true)
+ hiddenApps.value =
+ getAppsList(appContext, prefs, includeRegularApps = false, includeHiddenApps = true)
}
}
@@ -211,16 +404,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
isOlauncherDefault.value = isOlauncherDefault(appContext)
}
-// fun resetDefaultLauncherApp(context: Context) {
-// resetDefaultLauncher(context)
-// launcherResetFailed.value = getDefaultLauncherPackage(appContext).contains(".")
-// }
-
fun setWallpaperWorker() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
- val uploadWorkRequest = PeriodicWorkRequestBuilder(8, TimeUnit.HOURS)
+ val uploadWorkRequest = PeriodicWorkRequestBuilder(4, TimeUnit.HOURS)
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
@@ -243,4 +431,90 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
prefs.homeAlignment = gravity
homeAppAlignment.value = prefs.homeAlignment
}
+
+ fun getTodaysScreenTime() {
+ if (prefs.screenTimeLastUpdated.hasBeenMinutes(1).not()) return
+
+ val eventLogWrapper = EventLogWrapper(
+ appContext
+ )
+ // Start of today in millis
+ val calendar = Calendar.getInstance().apply {
+ set(Calendar.HOUR_OF_DAY, 0)
+ set(Calendar.MINUTE, 0)
+ set(Calendar.SECOND, 0)
+ set(Calendar.MILLISECOND, 0)
+ }
+ val startTime = calendar.timeInMillis
+ val endTime = System.currentTimeMillis()
+
+ val timeSpent = eventLogWrapper.aggregateSimpleUsageStats(
+ eventLogWrapper.aggregateForegroundStats(
+ eventLogWrapper.getForegroundStatsByTimestamps(startTime, endTime)
+ )
+ )
+ val viewTimeSpent = appContext.formattedTimeSpent(timeSpent)
+ screenTimeValue.postValue(viewTimeSpent)
+ prefs.screenTimeLastUpdated = endTime
+ }
+
+ fun getPrivateSpaceAppList() {
+ viewModelScope.launch {
+ val handle = getPrivateSpaceUserHandle(appContext)
+ privateSpaceAvailable.value = handle != null
+ if (handle != null) {
+ privateSpaceLocked.value = isPrivateSpaceLocked(appContext, handle)
+ privateSpaceApps.value = getPrivateSpaceApps(appContext, prefs)
+ } else {
+ privateSpaceLocked.value = true
+ privateSpaceApps.value = emptyList()
+ }
+ }
+ }
+
+ fun openPrivateSpaceSettings() {
+ try {
+ val intent = Intent("android.settings.PRIVATE_SPACE_SETTINGS")
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ appContext.startActivity(intent)
+ } catch (_: Exception) {
+ try {
+ val intent = Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ appContext.startActivity(intent)
+ } catch (_: Exception) {
+ appContext.showToast(appContext.getString(R.string.unable_to_open_app))
+ }
+ }
+ }
+
+ fun togglePrivateSpaceLock() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) return
+ val handle = getPrivateSpaceUserHandle(appContext) ?: return
+ try {
+ isPrivateSpaceToggling = true
+ val userManager = appContext.getSystemService(Context.USER_SERVICE) as UserManager
+ val currentlyLocked = userManager.isQuietModeEnabled(handle)
+ userManager.requestQuietModeEnabled(!currentlyLocked, handle)
+ } catch (e: Exception) {
+ isPrivateSpaceToggling = false
+ e.printStackTrace()
+ }
+ }
+
+ fun setDefaultClockApp() {
+ viewModelScope.launch {
+ try {
+ Constants.CLOCK_APP_PACKAGES.firstOrNull { appContext.isPackageInstalled(it) }?.let { packageName ->
+ appContext.packageManager.getLaunchIntentForPackage(packageName)?.component?.className?.let {
+ prefs.clockAppPackage = packageName
+ prefs.clockAppClassName = it
+ prefs.clockAppUser = android.os.Process.myUserHandle().toString()
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/data/AppModel.kt b/app/src/main/java/app/olauncher/data/AppModel.kt
index 3064bb97f..6df30835f 100644
--- a/app/src/main/java/app/olauncher/data/AppModel.kt
+++ b/app/src/main/java/app/olauncher/data/AppModel.kt
@@ -3,15 +3,43 @@ package app.olauncher.data
import android.os.UserHandle
import java.text.CollationKey
-data class AppModel(
- val appLabel: String,
- val key: CollationKey?,
- val appPackage: String,
- val activityClassName: String?,
- val user: UserHandle,
-) : Comparable {
+sealed class AppModel : Comparable {
+ abstract val appLabel: String
+ abstract val key: CollationKey?
+ abstract val appPackage: String
+ abstract val user: UserHandle
+ abstract val isNew: Boolean
+
+ data class App(
+ override val appLabel: String,
+ override val key: CollationKey?,
+ override val appPackage: String,
+ val activityClassName: String?,
+ override val isNew: Boolean = false,
+ override val user: UserHandle,
+ ) : AppModel()
+
+ data class PinnedShortcut(
+ override val appLabel: String,
+ override val key: CollationKey?,
+ override val appPackage: String,
+ val shortcutId: String,
+ override val isNew: Boolean = false,
+ override val user: UserHandle,
+ ) : AppModel()
+
+ data class PrivateSpaceHeader(
+ val isLocked: Boolean = true,
+ override val user: UserHandle = android.os.Process.myUserHandle(),
+ ) : AppModel() {
+ override val appLabel: String = ""
+ override val key: CollationKey? = null
+ override val appPackage: String = ""
+ override val isNew: Boolean = false
+ }
+
override fun compareTo(other: AppModel): Int = when {
- key != null && other.key != null -> key.compareTo(other.key)
+ key != null && other.key != null -> key!!.compareTo(other.key)
else -> appLabel.compareTo(other.appLabel, true)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/data/Constants.kt b/app/src/main/java/app/olauncher/data/Constants.kt
index 10509a98d..48bd1480b 100644
--- a/app/src/main/java/app/olauncher/data/Constants.kt
+++ b/app/src/main/java/app/olauncher/data/Constants.kt
@@ -8,16 +8,20 @@ object Constants {
}
object Dialog {
+ const val ABOUT = "ABOUT"
+ const val WALLPAPER = "WALLPAPER"
const val REVIEW = "REVIEW"
const val RATE = "RATE"
const val SHARE = "SHARE"
const val HIDDEN = "HIDDEN"
const val KEYBOARD = "KEYBOARD"
const val DIGITAL_WELLBEING = "DIGITAL_WELLBEING"
+ const val PRO_MESSAGE = "PRO_MESSAGE"
}
object UserState {
const val START = "START"
+ const val WALLPAPER = "WALLPAPER"
const val REVIEW = "REVIEW"
const val RATE = "RATE"
const val SHARE = "SHARE"
@@ -42,16 +46,18 @@ object Constants {
const val NOTIFICATIONS = 2
}
- object TextSize {
- const val ONE = 0.7f
- const val TWO = 0.8f
- const val THREE = 0.9f
- const val FOUR = 1f
- const val FIVE = 1.1f
- const val SIX = 1.2f
- const val SEVEN = 1.3f
+ object CharacterIndicator {
+ const val SHOW = 102
+ const val HIDE = 101
}
+ val CLOCK_APP_PACKAGES = arrayOf(
+ "com.google.android.deskclock", //Google Clock
+ "com.sec.android.app.clockpackage", //Samsung Clock
+ "com.oneplus.deskclock", //OnePlus Clock
+ "com.miui.clock", //Xiaomi Clock
+ )
+
const val WALL_TYPE_LIGHT = "light"
const val WALL_TYPE_DARK = "dark"
@@ -75,6 +81,7 @@ object Constants {
const val FLAG_SET_SWIPE_RIGHT_APP = 12
const val FLAG_SET_CLOCK_APP = 13
const val FLAG_SET_CALENDAR_APP = 14
+ const val FLAG_SET_SCREEN_TIME_APP = 15
const val REQUEST_CODE_ENABLE_ADMIN = 666
const val REQUEST_CODE_LAUNCHER_SELECTOR = 678
@@ -86,7 +93,7 @@ object Constants {
const val ONE_HOUR_IN_MILLIS = 3600000L
const val ONE_MINUTE_IN_MILLIS = 60000L
- const val MIN_ANIM_REFRESH_RATE = 10f
+ const val MIN_ANIM_REFRESH_RATE = 30f
const val URL_ABOUT_OLAUNCHER = "https://tanujnotes.substack.com/p/olauncher-minimal-af-launcher?utm_source=olauncher"
const val URL_OLAUNCHER_PRIVACY = "https://tanujnotes.notion.site/Olauncher-Privacy-Policy-dd6ac5101ddd4b3da9d27057889d44ab"
@@ -95,14 +102,18 @@ object Constants {
const val URL_OLAUNCHER_PLAY_STORE = "https://play.google.com/store/apps/details?id=app.olauncher"
const val URL_OLAUNCHER_PRO = "https://play.google.com/store/apps/details?id=app.prolauncher"
const val URL_PLAY_STORE_DEV = "https://play.google.com/store/apps/dev?id=7198807840081074933"
- const val URL_TWITTER_TANUJ = "https://twitter.com/tanujnotes"
+ const val URL_TWITTER_TANUJ = "https://x.com/tanujnotes"
const val URL_WALLPAPERS = "https://gist.githubusercontent.com/tanujnotes/85e2d0343ace71e76615ac346fbff82b/raw"
+ const val URL_NTS = "https://play.google.com/store/apps/details?id=com.makenotetoself"
+ const val URL_PENTASTIC = "https://play.google.com/store/apps/details?id=app.pentastic"
const val URL_DEFAULT_DARK_WALLPAPER = "https://images.unsplash.com/photo-1512551980832-13df02babc9e"
const val URL_DEFAULT_LIGHT_WALLPAPER = "https://images.unsplash.com/photo-1515549832467-8783363e19b6"
const val URL_DUCK_SEARCH = "https://duck.co/?q="
const val URL_DIGITAL_WELLBEING_LEARN_MORE = "https://tanujnotes.substack.com/p/digital-wellbeing-app-on-android?utm_source=olauncher"
const val DIGITAL_WELLBEING_PACKAGE_NAME = "com.google.android.apps.wellbeing"
+ const val DIGITAL_WELLBEING_ACTIVITY = "com.google.android.apps.wellbeing.settings.TopLevelSettingsActivity"
const val DIGITAL_WELLBEING_SAMSUNG_PACKAGE_NAME = "com.samsung.android.forest"
+ const val DIGITAL_WELLBEING_SAMSUNG_ACTIVITY = "com.samsung.android.forest.launcher.LauncherActivity"
const val WALLPAPER_WORKER_NAME = "WALLPAPER_WORKER_NAME"
}
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/data/DrawerCharacterModel.kt b/app/src/main/java/app/olauncher/data/DrawerCharacterModel.kt
new file mode 100644
index 000000000..565020c39
--- /dev/null
+++ b/app/src/main/java/app/olauncher/data/DrawerCharacterModel.kt
@@ -0,0 +1,6 @@
+package app.olauncher.data
+
+data class DrawerCharacterModel(
+ val character: String = "",
+ val inRange: Boolean = false
+)
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/data/Prefs.kt b/app/src/main/java/app/olauncher/data/Prefs.kt
index c281b8cf5..23a839e9c 100644
--- a/app/src/main/java/app/olauncher/data/Prefs.kt
+++ b/app/src/main/java/app/olauncher/data/Prefs.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.SharedPreferences
import android.view.Gravity
import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.content.edit
class Prefs(context: Context) {
private val PREFS_FILENAME = "app.olauncher"
@@ -19,7 +20,6 @@ class Prefs(context: Context) {
private val KEYBOARD_MESSAGE = "KEYBOARD_MESSAGE"
private val DAILY_WALLPAPER = "DAILY_WALLPAPER"
private val DAILY_WALLPAPER_URL = "DAILY_WALLPAPER_URL"
- private val WALLPAPER_UPDATED_DAY = "WALLPAPER_UPDATED_DAY"
private val HOME_ALIGNMENT = "HOME_ALIGNMENT"
private val HOME_BOTTOM_ALIGNMENT = "HOME_BOTTOM_ALIGNMENT"
private val APP_LABEL_ALIGNMENT = "APP_LABEL_ALIGNMENT"
@@ -27,17 +27,22 @@ class Prefs(context: Context) {
private val DATE_TIME_VISIBILITY = "DATE_TIME_VISIBILITY"
private val SWIPE_LEFT_ENABLED = "SWIPE_LEFT_ENABLED"
private val SWIPE_RIGHT_ENABLED = "SWIPE_RIGHT_ENABLED"
- private val SCREEN_TIMEOUT = "SCREEN_TIMEOUT"
private val HIDDEN_APPS = "HIDDEN_APPS"
private val HIDDEN_APPS_UPDATED = "HIDDEN_APPS_UPDATED"
private val SHOW_HINT_COUNTER = "SHOW_HINT_COUNTER"
private val APP_THEME = "APP_THEME"
private val ABOUT_CLICKED = "ABOUT_CLICKED"
private val RATE_CLICKED = "RATE_CLICKED"
+ private val WALLPAPER_MSG_SHOWN = "WALLPAPER_MSG_SHOWN"
private val SHARE_SHOWN_TIME = "SHARE_SHOWN_TIME"
private val SWIPE_DOWN_ACTION = "SWIPE_DOWN_ACTION"
private val TEXT_SIZE_SCALE = "TEXT_SIZE_SCALE"
- private val HIDE_DIGITAL_WELLBEING = "HIDE_DIGITAL_WELLBEING"
+ private val PRO_MESSAGE_SHOWN = "PRO_MESSAGE_SHOWN"
+ private val HIDE_SET_DEFAULT_LAUNCHER = "HIDE_SET_DEFAULT_LAUNCHER"
+ private val SCREEN_TIME_LAST_UPDATED = "SCREEN_TIME_LAST_UPDATED"
+ private val LAUNCHER_RESTART_TIMESTAMP = "LAUNCHER_RECREATE_TIMESTAMP"
+ private val SHOWN_ON_DAY_OF_YEAR = "SHOWN_ON_DAY_OF_YEAR"
+ private val HOME_BUTTON_SHOW_RECENTS = "HOME_BUTTON_SHOW_RECENTS"
private val APP_NAME_1 = "APP_NAME_1"
private val APP_NAME_2 = "APP_NAME_2"
@@ -86,308 +91,445 @@ class Prefs(context: Context) {
private val CALENDAR_APP_PACKAGE = "CALENDAR_APP_PACKAGE"
private val CALENDAR_APP_USER = "CALENDAR_APP_USER"
private val CALENDAR_APP_CLASS_NAME = "CALENDAR_APP_CLASS_NAME"
-
- private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_FILENAME, 0);
+ private val SCREEN_TIME_APP_PACKAGE = "SCREEN_TIME_APP_PACKAGE"
+ private val SCREEN_TIME_APP_USER = "SCREEN_TIME_APP_USER"
+ private val SCREEN_TIME_APP_CLASS_NAME = "SCREEN_TIME_APP_CLASS_NAME"
+
+ private val IS_SHORTCUT_1 = "IS_SHORTCUT_1"
+ private val SHORTCUT_ID_1 = "SHORTCUT_ID_1"
+ private val IS_SHORTCUT_2 = "IS_SHORTCUT_2"
+ private val SHORTCUT_ID_2 = "SHORTCUT_ID_2"
+ private val IS_SHORTCUT_3 = "IS_SHORTCUT_3"
+ private val SHORTCUT_ID_3 = "SHORTCUT_ID_3"
+ private val IS_SHORTCUT_4 = "IS_SHORTCUT_4"
+ private val SHORTCUT_ID_4 = "SHORTCUT_ID_4"
+ private val IS_SHORTCUT_5 = "IS_SHORTCUT_5"
+ private val SHORTCUT_ID_5 = "SHORTCUT_ID_5"
+ private val IS_SHORTCUT_6 = "IS_SHORTCUT_6"
+ private val SHORTCUT_ID_6 = "SHORTCUT_ID_6"
+ private val IS_SHORTCUT_7 = "IS_SHORTCUT_7"
+ private val SHORTCUT_ID_7 = "SHORTCUT_ID_7"
+ private val IS_SHORTCUT_8 = "IS_SHORTCUT_8"
+ private val SHORTCUT_ID_8 = "SHORTCUT_ID_8"
+
+ private val SHORTCUT_ID_SWIPE_LEFT = "SHORTCUT_ID_SWIPE_LEFT"
+ private val IS_SHORTCUT_SWIPE_LEFT = "IS_SHORTCUT_SWIPE_LEFT"
+ private val SHORTCUT_ID_SWIPE_RIGHT = "SHORTCUT_ID_SWIPE_RIGHT"
+ private val IS_SHORTCUT_SWIPE_RIGHT = "IS_SHORTCUT_SWIPE_RIGHT"
+
+ private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_FILENAME, 0)
var firstOpen: Boolean
get() = prefs.getBoolean(FIRST_OPEN, true)
- set(value) = prefs.edit().putBoolean(FIRST_OPEN, value).apply()
+ set(value) = prefs.edit { putBoolean(FIRST_OPEN, value).apply() }
var firstOpenTime: Long
get() = prefs.getLong(FIRST_OPEN_TIME, 0L)
- set(value) = prefs.edit().putLong(FIRST_OPEN_TIME, value).apply()
+ set(value) = prefs.edit { putLong(FIRST_OPEN_TIME, value).apply() }
var firstSettingsOpen: Boolean
get() = prefs.getBoolean(FIRST_SETTINGS_OPEN, true)
- set(value) = prefs.edit().putBoolean(FIRST_SETTINGS_OPEN, value).apply()
+ set(value) = prefs.edit { putBoolean(FIRST_SETTINGS_OPEN, value).apply() }
var firstHide: Boolean
get() = prefs.getBoolean(FIRST_HIDE, true)
- set(value) = prefs.edit().putBoolean(FIRST_HIDE, value).apply()
+ set(value) = prefs.edit { putBoolean(FIRST_HIDE, value).apply() }
var userState: String
get() = prefs.getString(USER_STATE, Constants.UserState.START).toString()
- set(value) = prefs.edit().putString(USER_STATE, value).apply()
+ set(value) = prefs.edit { putString(USER_STATE, value).apply() }
var lockModeOn: Boolean
get() = prefs.getBoolean(LOCK_MODE, false)
- set(value) = prefs.edit().putBoolean(LOCK_MODE, value).apply()
+ set(value) = prefs.edit { putBoolean(LOCK_MODE, value).apply() }
var autoShowKeyboard: Boolean
get() = prefs.getBoolean(AUTO_SHOW_KEYBOARD, true)
- set(value) = prefs.edit().putBoolean(AUTO_SHOW_KEYBOARD, value).apply()
+ set(value) = prefs.edit { putBoolean(AUTO_SHOW_KEYBOARD, value).apply() }
var keyboardMessageShown: Boolean
get() = prefs.getBoolean(KEYBOARD_MESSAGE, false)
- set(value) = prefs.edit().putBoolean(KEYBOARD_MESSAGE, value).apply()
+ set(value) = prefs.edit { putBoolean(KEYBOARD_MESSAGE, value).apply() }
var dailyWallpaper: Boolean
get() = prefs.getBoolean(DAILY_WALLPAPER, false)
- set(value) = prefs.edit().putBoolean(DAILY_WALLPAPER, value).apply()
+ set(value) = prefs.edit { putBoolean(DAILY_WALLPAPER, value).apply() }
var dailyWallpaperUrl: String
get() = prefs.getString(DAILY_WALLPAPER_URL, "").toString()
- set(value) = prefs.edit().putString(DAILY_WALLPAPER_URL, value).apply()
+ set(value) = prefs.edit { putString(DAILY_WALLPAPER_URL, value).apply() }
var homeAppsNum: Int
get() = prefs.getInt(HOME_APPS_NUM, 4)
- set(value) = prefs.edit().putInt(HOME_APPS_NUM, value).apply()
+ set(value) = prefs.edit { putInt(HOME_APPS_NUM, value).apply() }
var homeAlignment: Int
get() = prefs.getInt(HOME_ALIGNMENT, Gravity.START)
- set(value) = prefs.edit().putInt(HOME_ALIGNMENT, value).apply()
+ set(value) = prefs.edit { putInt(HOME_ALIGNMENT, value).apply() }
var homeBottomAlignment: Boolean
get() = prefs.getBoolean(HOME_BOTTOM_ALIGNMENT, false)
- set(value) = prefs.edit().putBoolean(HOME_BOTTOM_ALIGNMENT, value).apply()
+ set(value) = prefs.edit { putBoolean(HOME_BOTTOM_ALIGNMENT, value).apply() }
var appLabelAlignment: Int
get() = prefs.getInt(APP_LABEL_ALIGNMENT, Gravity.START)
- set(value) = prefs.edit().putInt(APP_LABEL_ALIGNMENT, value).apply()
+ set(value) = prefs.edit { putInt(APP_LABEL_ALIGNMENT, value).apply() }
var showStatusBar: Boolean
get() = prefs.getBoolean(STATUS_BAR, false)
- set(value) = prefs.edit().putBoolean(STATUS_BAR, value).apply()
+ set(value) = prefs.edit { putBoolean(STATUS_BAR, value).apply() }
var dateTimeVisibility: Int
get() = prefs.getInt(DATE_TIME_VISIBILITY, Constants.DateTime.ON)
- set(value) = prefs.edit().putInt(DATE_TIME_VISIBILITY, value).apply()
+ set(value) = prefs.edit { putInt(DATE_TIME_VISIBILITY, value).apply() }
var swipeLeftEnabled: Boolean
get() = prefs.getBoolean(SWIPE_LEFT_ENABLED, true)
- set(value) = prefs.edit().putBoolean(SWIPE_LEFT_ENABLED, value).apply()
+ set(value) = prefs.edit { putBoolean(SWIPE_LEFT_ENABLED, value).apply() }
var swipeRightEnabled: Boolean
get() = prefs.getBoolean(SWIPE_RIGHT_ENABLED, true)
- set(value) = prefs.edit().putBoolean(SWIPE_RIGHT_ENABLED, value).apply()
+ set(value) = prefs.edit { putBoolean(SWIPE_RIGHT_ENABLED, value).apply() }
var appTheme: Int
get() = prefs.getInt(APP_THEME, AppCompatDelegate.MODE_NIGHT_YES)
- set(value) = prefs.edit().putInt(APP_THEME, value).apply()
+ set(value) = prefs.edit { putInt(APP_THEME, value).apply() }
var textSizeScale: Float
get() = prefs.getFloat(TEXT_SIZE_SCALE, 1.0f)
- set(value) = prefs.edit().putFloat(TEXT_SIZE_SCALE, value).apply()
+ set(value) = prefs.edit { putFloat(TEXT_SIZE_SCALE, value).apply() }
+
+ var proMessageShown: Boolean
+ get() = prefs.getBoolean(PRO_MESSAGE_SHOWN, false)
+ set(value) = prefs.edit { putBoolean(PRO_MESSAGE_SHOWN, value).apply() }
+
+ var hideSetDefaultLauncher: Boolean
+ get() = prefs.getBoolean(HIDE_SET_DEFAULT_LAUNCHER, false)
+ set(value) = prefs.edit { putBoolean(HIDE_SET_DEFAULT_LAUNCHER, value).apply() }
- var hideDigitalWellbeing: Boolean
- get() = prefs.getBoolean(HIDE_DIGITAL_WELLBEING, false)
- set(value) = prefs.edit().putBoolean(HIDE_DIGITAL_WELLBEING, value).apply()
+ var screenTimeLastUpdated: Long
+ get() = prefs.getLong(SCREEN_TIME_LAST_UPDATED, 0L)
+ set(value) = prefs.edit { putLong(SCREEN_TIME_LAST_UPDATED, value).apply() }
- var screenTimeout: Int
- get() = prefs.getInt(SCREEN_TIMEOUT, 30000) // Default: 30 seconds
- set(value) = prefs.edit().putInt(SCREEN_TIMEOUT, value).apply()
+ var launcherRestartTimestamp: Long
+ get() = prefs.getLong(LAUNCHER_RESTART_TIMESTAMP, 0L)
+ set(value) = prefs.edit { putLong(LAUNCHER_RESTART_TIMESTAMP, value).apply() }
+
+ var shownOnDayOfYear: Int
+ get() = prefs.getInt(SHOWN_ON_DAY_OF_YEAR, 0)
+ set(value) = prefs.edit { putInt(SHOWN_ON_DAY_OF_YEAR, value).apply() }
+
+ var homeButtonShowRecents: Boolean
+ get() = prefs.getBoolean(HOME_BUTTON_SHOW_RECENTS, false)
+ set(value) = prefs.edit { putBoolean(HOME_BUTTON_SHOW_RECENTS, value).apply() }
var hiddenApps: MutableSet
get() = prefs.getStringSet(HIDDEN_APPS, mutableSetOf()) as MutableSet
- set(value) = prefs.edit().putStringSet(HIDDEN_APPS, value).apply()
+ set(value) = prefs.edit { putStringSet(HIDDEN_APPS, value).apply() }
var hiddenAppsUpdated: Boolean
get() = prefs.getBoolean(HIDDEN_APPS_UPDATED, false)
- set(value) = prefs.edit().putBoolean(HIDDEN_APPS_UPDATED, value).apply()
+ set(value) = prefs.edit { putBoolean(HIDDEN_APPS_UPDATED, value).apply() }
var toShowHintCounter: Int
get() = prefs.getInt(SHOW_HINT_COUNTER, 1)
- set(value) = prefs.edit().putInt(SHOW_HINT_COUNTER, value).apply()
+ set(value) = prefs.edit { putInt(SHOW_HINT_COUNTER, value).apply() }
var aboutClicked: Boolean
get() = prefs.getBoolean(ABOUT_CLICKED, false)
- set(value) = prefs.edit().putBoolean(ABOUT_CLICKED, value).apply()
+ set(value) = prefs.edit { putBoolean(ABOUT_CLICKED, value).apply() }
var rateClicked: Boolean
get() = prefs.getBoolean(RATE_CLICKED, false)
- set(value) = prefs.edit().putBoolean(RATE_CLICKED, value).apply()
+ set(value) = prefs.edit { putBoolean(RATE_CLICKED, value).apply() }
+
+ var wallpaperMsgShown: Boolean
+ get() = prefs.getBoolean(WALLPAPER_MSG_SHOWN, false)
+ set(value) = prefs.edit { putBoolean(WALLPAPER_MSG_SHOWN, value).apply() }
var shareShownTime: Long
get() = prefs.getLong(SHARE_SHOWN_TIME, 0L)
- set(value) = prefs.edit().putLong(SHARE_SHOWN_TIME, value).apply()
+ set(value) = prefs.edit { putLong(SHARE_SHOWN_TIME, value).apply() }
var swipeDownAction: Int
get() = prefs.getInt(SWIPE_DOWN_ACTION, Constants.SwipeDownAction.NOTIFICATIONS)
- set(value) = prefs.edit().putInt(SWIPE_DOWN_ACTION, value).apply()
+ set(value) = prefs.edit { putInt(SWIPE_DOWN_ACTION, value).apply() }
var appName1: String
get() = prefs.getString(APP_NAME_1, "").toString()
- set(value) = prefs.edit().putString(APP_NAME_1, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_1, value).apply() }
var appName2: String
get() = prefs.getString(APP_NAME_2, "").toString()
- set(value) = prefs.edit().putString(APP_NAME_2, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_2, value).apply() }
var appName3: String
get() = prefs.getString(APP_NAME_3, "").toString()
- set(value) = prefs.edit().putString(APP_NAME_3, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_3, value).apply() }
var appName4: String
get() = prefs.getString(APP_NAME_4, "").toString()
- set(value) = prefs.edit().putString(APP_NAME_4, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_4, value).apply() }
var appName5: String
get() = prefs.getString(APP_NAME_5, "").toString()
- set(value) = prefs.edit().putString(APP_NAME_5, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_5, value).apply() }
var appName6: String
get() = prefs.getString(APP_NAME_6, "").toString()
- set(value) = prefs.edit().putString(APP_NAME_6, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_6, value).apply() }
var appName7: String
get() = prefs.getString(APP_NAME_7, "").toString()
- set(value) = prefs.edit().putString(APP_NAME_7, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_7, value).apply() }
var appName8: String
get() = prefs.getString(APP_NAME_8, "").toString()
- set(value) = prefs.edit().putString(APP_NAME_8, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_8, value).apply() }
var appPackage1: String
get() = prefs.getString(APP_PACKAGE_1, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_1, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_1, value).apply() }
var appPackage2: String
get() = prefs.getString(APP_PACKAGE_2, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_2, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_2, value).apply() }
var appPackage3: String
get() = prefs.getString(APP_PACKAGE_3, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_3, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_3, value).apply() }
var appPackage4: String
get() = prefs.getString(APP_PACKAGE_4, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_4, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_4, value).apply() }
var appPackage5: String
get() = prefs.getString(APP_PACKAGE_5, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_5, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_5, value).apply() }
var appPackage6: String
get() = prefs.getString(APP_PACKAGE_6, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_6, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_6, value).apply() }
var appPackage7: String
get() = prefs.getString(APP_PACKAGE_7, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_7, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_7, value).apply() }
var appPackage8: String
get() = prefs.getString(APP_PACKAGE_8, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_8, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_8, value).apply() }
var appActivityClassName1: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_1, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_1, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_1, value).apply() }
var appActivityClassName2: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_2, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_2, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_2, value).apply() }
var appActivityClassName3: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_3, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_3, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_3, value).apply() }
var appActivityClassName4: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_4, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_4, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_4, value).apply() }
var appActivityClassName5: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_5, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_5, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_5, value).apply() }
var appActivityClassName6: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_6, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_6, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_6, value).apply() }
var appActivityClassName7: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_7, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_7, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_7, value).apply() }
var appActivityClassName8: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_8, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_8, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_8, value).apply() }
var appUser1: String
get() = prefs.getString(APP_USER_1, "").toString()
- set(value) = prefs.edit().putString(APP_USER_1, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_1, value).apply() }
var appUser2: String
get() = prefs.getString(APP_USER_2, "").toString()
- set(value) = prefs.edit().putString(APP_USER_2, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_2, value).apply() }
var appUser3: String
get() = prefs.getString(APP_USER_3, "").toString()
- set(value) = prefs.edit().putString(APP_USER_3, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_3, value).apply() }
var appUser4: String
get() = prefs.getString(APP_USER_4, "").toString()
- set(value) = prefs.edit().putString(APP_USER_4, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_4, value).apply() }
var appUser5: String
get() = prefs.getString(APP_USER_5, "").toString()
- set(value) = prefs.edit().putString(APP_USER_5, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_5, value).apply() }
var appUser6: String
get() = prefs.getString(APP_USER_6, "").toString()
- set(value) = prefs.edit().putString(APP_USER_6, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_6, value).apply() }
var appUser7: String
get() = prefs.getString(APP_USER_7, "").toString()
- set(value) = prefs.edit().putString(APP_USER_7, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_7, value).apply() }
var appUser8: String
get() = prefs.getString(APP_USER_8, "").toString()
- set(value) = prefs.edit().putString(APP_USER_8, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_8, value).apply() }
var appNameSwipeLeft: String
get() = prefs.getString(APP_NAME_SWIPE_LEFT, "Camera").toString()
- set(value) = prefs.edit().putString(APP_NAME_SWIPE_LEFT, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_SWIPE_LEFT, value).apply() }
var appNameSwipeRight: String
get() = prefs.getString(APP_NAME_SWIPE_RIGHT, "Phone").toString()
- set(value) = prefs.edit().putString(APP_NAME_SWIPE_RIGHT, value).apply()
+ set(value) = prefs.edit { putString(APP_NAME_SWIPE_RIGHT, value).apply() }
var appPackageSwipeLeft: String
get() = prefs.getString(APP_PACKAGE_SWIPE_LEFT, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_SWIPE_LEFT, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_SWIPE_LEFT, value).apply() }
var appActivityClassNameSwipeLeft: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_SWIPE_LEFT, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_SWIPE_LEFT, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_SWIPE_LEFT, value).apply() }
var appPackageSwipeRight: String
get() = prefs.getString(APP_PACKAGE_SWIPE_RIGHT, "").toString()
- set(value) = prefs.edit().putString(APP_PACKAGE_SWIPE_RIGHT, value).apply()
+ set(value) = prefs.edit { putString(APP_PACKAGE_SWIPE_RIGHT, value).apply() }
var appActivityClassNameRight: String?
get() = prefs.getString(APP_ACTIVITY_CLASS_NAME_SWIPE_RIGHT, "").toString()
- set(value) = prefs.edit().putString(APP_ACTIVITY_CLASS_NAME_SWIPE_RIGHT, value).apply()
+ set(value) = prefs.edit { putString(APP_ACTIVITY_CLASS_NAME_SWIPE_RIGHT, value).apply() }
var appUserSwipeLeft: String
get() = prefs.getString(APP_USER_SWIPE_LEFT, "").toString()
- set(value) = prefs.edit().putString(APP_USER_SWIPE_LEFT, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_SWIPE_LEFT, value).apply() }
var appUserSwipeRight: String
get() = prefs.getString(APP_USER_SWIPE_RIGHT, "").toString()
- set(value) = prefs.edit().putString(APP_USER_SWIPE_RIGHT, value).apply()
+ set(value) = prefs.edit { putString(APP_USER_SWIPE_RIGHT, value).apply() }
var clockAppPackage: String
get() = prefs.getString(CLOCK_APP_PACKAGE, "").toString()
- set(value) = prefs.edit().putString(CLOCK_APP_PACKAGE, value).apply()
+ set(value) = prefs.edit { putString(CLOCK_APP_PACKAGE, value).apply() }
var clockAppUser: String
get() = prefs.getString(CLOCK_APP_USER, "").toString()
- set(value) = prefs.edit().putString(CLOCK_APP_USER, value).apply()
+ set(value) = prefs.edit { putString(CLOCK_APP_USER, value).apply() }
var clockAppClassName: String?
get() = prefs.getString(CLOCK_APP_CLASS_NAME, "").toString()
- set(value) = prefs.edit().putString(CLOCK_APP_CLASS_NAME, value).apply()
+ set(value) = prefs.edit { putString(CLOCK_APP_CLASS_NAME, value).apply() }
var calendarAppPackage: String
get() = prefs.getString(CALENDAR_APP_PACKAGE, "").toString()
- set(value) = prefs.edit().putString(CALENDAR_APP_PACKAGE, value).apply()
+ set(value) = prefs.edit { putString(CALENDAR_APP_PACKAGE, value).apply() }
var calendarAppUser: String
get() = prefs.getString(CALENDAR_APP_USER, "").toString()
- set(value) = prefs.edit().putString(CALENDAR_APP_USER, value).apply()
+ set(value) = prefs.edit { putString(CALENDAR_APP_USER, value).apply() }
var calendarAppClassName: String?
get() = prefs.getString(CALENDAR_APP_CLASS_NAME, "").toString()
- set(value) = prefs.edit().putString(CALENDAR_APP_CLASS_NAME, value).apply()
+ set(value) = prefs.edit { putString(CALENDAR_APP_CLASS_NAME, value).apply() }
+
+ var screenTimeAppPackage: String
+ get() = prefs.getString(SCREEN_TIME_APP_PACKAGE, "").toString()
+ set(value) = prefs.edit { putString(SCREEN_TIME_APP_PACKAGE, value).apply() }
+
+ var screenTimeAppUser: String
+ get() = prefs.getString(SCREEN_TIME_APP_USER, "").toString()
+ set(value) = prefs.edit { putString(SCREEN_TIME_APP_USER, value).apply() }
+
+ var screenTimeAppClassName: String?
+ get() = prefs.getString(SCREEN_TIME_APP_CLASS_NAME, "").toString()
+ set(value) = prefs.edit { putString(SCREEN_TIME_APP_CLASS_NAME, value).apply() }
+
+ var isShortcut1: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_1, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_1, value) }
+
+ var shortcutId1: String
+ get() = prefs.getString(SHORTCUT_ID_1, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_1, value) }
+
+ var isShortcut2: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_2, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_2, value) }
+
+ var shortcutId2: String
+ get() = prefs.getString(SHORTCUT_ID_2, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_2, value) }
+
+ var isShortcut3: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_3, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_3, value) }
+
+ var shortcutId3: String
+ get() = prefs.getString(SHORTCUT_ID_3, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_3, value) }
+
+ var isShortcut4: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_4, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_4, value) }
+
+ var shortcutId4: String
+ get() = prefs.getString(SHORTCUT_ID_4, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_4, value) }
+
+ var isShortcut5: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_5, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_5, value) }
+
+ var shortcutId5: String
+ get() = prefs.getString(SHORTCUT_ID_5, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_5, value) }
+
+ var isShortcut6: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_6, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_6, value) }
+
+ var shortcutId6: String
+ get() = prefs.getString(SHORTCUT_ID_6, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_6, value) }
+
+ var isShortcut7: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_7, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_7, value) }
+
+ var shortcutId7: String
+ get() = prefs.getString(SHORTCUT_ID_7, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_7, value) }
+
+ var isShortcut8: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_8, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_8, value) }
+
+ var shortcutId8: String
+ get() = prefs.getString(SHORTCUT_ID_8, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_8, value) }
+
+ var shortcutIdSwipeLeft: String
+ get() = prefs.getString(SHORTCUT_ID_SWIPE_LEFT, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_SWIPE_LEFT, value) }
+
+ var isShortcutSwipeLeft: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_SWIPE_LEFT, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_SWIPE_LEFT, value) }
+
+ var shortcutIdSwipeRight: String
+ get() = prefs.getString(SHORTCUT_ID_SWIPE_RIGHT, "").toString()
+ set(value) = prefs.edit { putString(SHORTCUT_ID_SWIPE_RIGHT, value) }
+
+ var isShortcutSwipeRight: Boolean
+ get() = prefs.getBoolean(IS_SHORTCUT_SWIPE_RIGHT, false)
+ set(value) = prefs.edit { putBoolean(IS_SHORTCUT_SWIPE_RIGHT, value) }
fun getAppName(location: Int): String {
return when (location) {
@@ -445,7 +587,59 @@ class Prefs(context: Context) {
}
}
+ fun getShortcutId(location: Int): String {
+ return when (location) {
+ 1 -> shortcutId1
+ 2 -> shortcutId2
+ 3 -> shortcutId3
+ 4 -> shortcutId4
+ 5 -> shortcutId5
+ 6 -> shortcutId6
+ 7 -> shortcutId7
+ 8 -> shortcutId8
+ else -> ""
+ }
+ }
+
+ fun getIsShortcut(location: Int): Boolean {
+ return when (location) {
+ 1 -> isShortcut1
+ 2 -> isShortcut2
+ 3 -> isShortcut3
+ 4 -> isShortcut4
+ 5 -> isShortcut5
+ 6 -> isShortcut6
+ 7 -> isShortcut7
+ 8 -> isShortcut8
+ else -> false
+ }
+ }
+
+ fun setAppActivityClassName(location: Int, activityClassName: String) {
+ when (location) {
+ 1 -> appActivityClassName1 = activityClassName
+ 2 -> appActivityClassName2 = activityClassName
+ 3 -> appActivityClassName3 = activityClassName
+ 4 -> appActivityClassName4 = activityClassName
+ 5 -> appActivityClassName5 = activityClassName
+ 6 -> appActivityClassName6 = activityClassName
+ 7 -> appActivityClassName7 = activityClassName
+ 8 -> appActivityClassName8 = activityClassName
+ }
+ }
+
+ fun updateAppActivityClassName(packageName: String, activityClassName: String) {
+ for (i in 1..8) {
+ if (getAppPackage(i) == packageName) setAppActivityClassName(i, activityClassName)
+ }
+ if (clockAppPackage == packageName) clockAppClassName = activityClassName
+ if (calendarAppPackage == packageName) calendarAppClassName = activityClassName
+ if (screenTimeAppPackage == packageName) screenTimeAppClassName = activityClassName
+ if (appPackageSwipeLeft == packageName) appActivityClassNameSwipeLeft = activityClassName
+ if (appPackageSwipeRight == packageName) appActivityClassNameRight = activityClassName
+ }
+
fun getAppRenameLabel(appPackage: String): String = prefs.getString(appPackage, "").toString()
- fun setAppRenameLabel(appPackage: String, renameLabel: String) = prefs.edit().putString(appPackage, renameLabel).apply()
+ fun setAppRenameLabel(appPackage: String, renameLabel: String) = prefs.edit { putString(appPackage, renameLabel) }
}
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/helper/AppFilterHelper.kt b/app/src/main/java/app/olauncher/helper/AppFilterHelper.kt
new file mode 100644
index 000000000..271e96e54
--- /dev/null
+++ b/app/src/main/java/app/olauncher/helper/AppFilterHelper.kt
@@ -0,0 +1,7 @@
+package app.olauncher.helper
+
+import app.olauncher.data.AppModel
+
+interface AppFilterHelper {
+ fun onAppFiltered(items:List)
+}
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/helper/AppUsageStats.kt b/app/src/main/java/app/olauncher/helper/AppUsageStats.kt
new file mode 100644
index 000000000..9b6926906
--- /dev/null
+++ b/app/src/main/java/app/olauncher/helper/AppUsageStats.kt
@@ -0,0 +1,21 @@
+package app.olauncher.helper
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+
+class AppUsageStats(
+ val lastTimeUsedMillis: Long,
+ val totalTimeInForegroundMillis: Long,
+ @get:RequiresApi(Build.VERSION_CODES.Q) val lastTimeForegroundServiceUsedMillis: Long,
+ @get:RequiresApi(Build.VERSION_CODES.Q) val totalTimeForegroundServiceUsedMillis: Long,
+)
+
+class AppUsageStatsBucket {
+ var startMillis: Long = 0L
+ var endMillis: Long = 0L
+ var totalTime: Long = 0L
+
+ fun addTotalTime() {
+ this.totalTime += endMillis - startMillis
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/helper/Extensions.kt b/app/src/main/java/app/olauncher/helper/Extensions.kt
index 3c9bda6c1..ad1c2f44a 100644
--- a/app/src/main/java/app/olauncher/helper/Extensions.kt
+++ b/app/src/main/java/app/olauncher/helper/Extensions.kt
@@ -1,6 +1,7 @@
package app.olauncher.helper
import android.app.Activity
+import android.app.AppOpsManager
import android.app.SearchManager
import android.app.role.RoleManager
import android.content.ComponentName
@@ -8,14 +9,19 @@ import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
+import android.content.res.Resources
import android.net.Uri
import android.os.Build
import android.os.UserHandle
+import android.provider.Settings
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.annotation.RequiresApi
+import app.olauncher.BuildConfig
+import app.olauncher.R
import app.olauncher.data.Constants
+import java.util.Calendar
fun View.hideKeyboard() {
this.clearFocus()
@@ -64,6 +70,16 @@ fun Context.resetDefaultLauncher() {
}
}
+fun Context.isDefaultLauncher(): Boolean {
+ val launcherPackageName = getDefaultLauncherPackage(this)
+ return BuildConfig.APPLICATION_ID == launcherPackageName
+}
+
+fun Context.resetLauncherViaFakeActivity() {
+ resetDefaultLauncher()
+ if (getDefaultLauncherPackage(this).contains("."))
+ startActivity(Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS))
+}
fun Context.openSearch(query: String? = null) {
val intent = Intent(Intent.ACTION_WEB_SEARCH)
@@ -103,8 +119,63 @@ fun Context.searchOnPlayStore(query: String? = null): Boolean {
fun Context.isPackageInstalled(packageName: String, userHandle: UserHandle = android.os.Process.myUserHandle()): Boolean {
val launcher = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val activityInfo = launcher.getActivityList(packageName, userHandle)
- return activityInfo.size > 0
+ return activityInfo.isNotEmpty()
+}
+
+@RequiresApi(Build.VERSION_CODES.Q)
+fun Context.appUsagePermissionGranted(): Boolean {
+ val appOpsManager = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+ return appOpsManager.unsafeCheckOpNoThrow(
+ "android:get_usage_stats",
+ android.os.Process.myUid(),
+ packageName
+ ) == AppOpsManager.MODE_ALLOWED
+}
+
+fun Context.formattedTimeSpent(timeSpent: Long): String {
+ val seconds = timeSpent / 1000
+ val minutes = seconds / 60
+ val hours = minutes / 60
+ val remainingMinutes = minutes % 60
+ return when {
+ timeSpent == 0L -> "0m"
+
+ hours > 0 -> getString(
+ R.string.time_spent_hour,
+ hours.toString(),
+ remainingMinutes.toString()
+ )
+
+ minutes > 0 -> {
+ getString(R.string.time_spent_min, minutes.toString())
+ }
+
+ else -> "<1m"
+ }
+}
+
+fun Long.convertEpochToMidnight(): Long {
+ val calendar = Calendar.getInstance()
+ calendar.timeInMillis = this
+ calendar.set(Calendar.HOUR_OF_DAY, 0)
+ calendar.set(Calendar.MINUTE, 0)
+ calendar.set(Calendar.SECOND, 0)
+ calendar.set(Calendar.MILLISECOND, 0)
+ return calendar.timeInMillis
}
+fun Long.isDaySince(): Int = ((System.currentTimeMillis().convertEpochToMidnight() - this.convertEpochToMidnight())
+ / Constants.ONE_DAY_IN_MILLIS).toInt()
+
fun Long.hasBeenDays(days: Int): Boolean =
((System.currentTimeMillis() - this) / Constants.ONE_DAY_IN_MILLIS) >= days
+
+fun Long.hasBeenHours(hours: Int): Boolean =
+ ((System.currentTimeMillis() - this) / Constants.ONE_HOUR_IN_MILLIS) >= hours
+
+fun Long.hasBeenMinutes(minutes: Int): Boolean =
+ ((System.currentTimeMillis() - this) / Constants.ONE_MINUTE_IN_MILLIS) >= minutes
+
+fun Int.dpToPx(): Int {
+ return (this * Resources.getSystem().displayMetrics.density).toInt()
+}
diff --git a/app/src/main/java/app/olauncher/helper/MyAccessibilityService.kt b/app/src/main/java/app/olauncher/helper/MyAccessibilityService.kt
index b39ec170b..bdcc8c08f 100644
--- a/app/src/main/java/app/olauncher/helper/MyAccessibilityService.kt
+++ b/app/src/main/java/app/olauncher/helper/MyAccessibilityService.kt
@@ -5,7 +5,6 @@ import android.content.Intent
import android.os.Build
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
-import androidx.annotation.RequiresApi
import app.olauncher.R
import app.olauncher.data.Prefs
@@ -20,14 +19,20 @@ class MyAccessibilityService : AccessibilityService() {
super.onServiceConnected()
}
- @RequiresApi(Build.VERSION_CODES.P)
override fun onAccessibilityEvent(event: AccessibilityEvent) {
try {
val source: AccessibilityNodeInfo = event.source ?: return
- if ((source.className == "android.widget.FrameLayout") and
- (source.contentDescription == getString(R.string.lock_layout_description))
- )
- performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN)
+ if (source.className != "android.widget.FrameLayout") return
+
+ when (source.contentDescription) {
+ getString(R.string.lock_layout_description) -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+ performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN)
+ }
+ getString(R.string.recents_layout_description) -> {
+ performGlobalAction(GLOBAL_ACTION_RECENTS)
+ }
+ }
} catch (e: Exception) {
return
}
diff --git a/app/src/main/java/app/olauncher/helper/PinItemActivity.kt b/app/src/main/java/app/olauncher/helper/PinItemActivity.kt
new file mode 100644
index 000000000..8435ebda3
--- /dev/null
+++ b/app/src/main/java/app/olauncher/helper/PinItemActivity.kt
@@ -0,0 +1,51 @@
+package app.olauncher.helper
+
+import android.content.pm.LauncherApps
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+
+class PinItemActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // Set window to be transparent
+ window.setBackgroundDrawable(null)
+
+ val launcherApps = getSystemService(LauncherApps::class.java)
+ val pinItemRequest = launcherApps.getPinItemRequest(intent)
+
+ when (pinItemRequest != null) {
+ true -> handleRequestType(pinItemRequest)
+ false -> showToast("Invalid pin request")
+ }
+
+ finish()
+ }
+
+ private fun handleRequestType(pinItemRequest: LauncherApps.PinItemRequest) {
+ when (pinItemRequest.requestType) {
+ LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ->
+ handleShortcutRequest(pinItemRequest)
+
+ LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET ->
+ showToast("Widgets are not supported")
+
+ else -> showToast("Unknown action not supported")
+ }
+ }
+
+ private fun handleShortcutRequest(pinItemRequest: LauncherApps.PinItemRequest) {
+ val shortcutInfo = pinItemRequest.shortcutInfo
+ if (shortcutInfo != null) {
+ val success = pinItemRequest.accept()
+ val message = when (success) {
+ true -> "Shortcut pinned successfully"
+ false -> "Failed to pin shortcut"
+ }
+ showToast(message)
+ } else {
+ showToast("Invalid shortcut info")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/helper/Utils.kt b/app/src/main/java/app/olauncher/helper/Utils.kt
index cb2541982..45b8521db 100644
--- a/app/src/main/java/app/olauncher/helper/Utils.kt
+++ b/app/src/main/java/app/olauncher/helper/Utils.kt
@@ -5,12 +5,10 @@ import android.app.SearchManager
import android.app.WallpaperManager
import android.content.ClipData
import android.content.ClipboardManager
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherApps
-import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.graphics.Bitmap
@@ -36,7 +34,10 @@ import android.view.animation.LinearInterpolator
import android.widget.Toast
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
+import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.graphics.createBitmap
+import androidx.core.net.toUri
import app.olauncher.BuildConfig
import app.olauncher.R
import app.olauncher.data.AppModel
@@ -65,48 +66,68 @@ fun Context.showToast(stringResource: Int, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, getString(stringResource), duration).show()
}
-suspend fun getAppsList(context: Context, prefs: Prefs, includeRegularApps: Boolean = true, includeHiddenApps: Boolean = false): MutableList {
+suspend fun getAppsList(
+ context: Context,
+ prefs: Prefs,
+ includeRegularApps: Boolean = true,
+ includeHiddenApps: Boolean = false,
+): MutableList {
return withContext(Dispatchers.IO) {
val appList: MutableList = mutableListOf()
+ val seenPackages: MutableSet = mutableSetOf()
try {
if (!Prefs(context).hiddenAppsUpdated) upgradeHiddenApps(Prefs(context))
val hiddenApps = Prefs(context).hiddenApps
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
- val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
+ val launcherApps =
+ context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val collator = Collator.getInstance()
for (profile in userManager.userProfiles) {
+ if (isPrivateSpaceProfile(context, profile)) continue
for (app in launcherApps.getActivityList(null, profile)) {
-
- val appLabelShown = prefs.getAppRenameLabel(app.applicationInfo.packageName).ifBlank { app.label.toString() }
- val appModel = AppModel(
- appLabelShown,
- collator.getCollationKey(app.label.toString()),
- app.applicationInfo.packageName,
- app.componentName.className,
- profile
+ val appLabelShown = prefs.getAppRenameLabel(app.applicationInfo.packageName)
+ .ifBlank { app.label.toString() }
+ val appModel = AppModel.App(
+ appLabel = appLabelShown,
+ key = collator.getCollationKey(app.label.toString()),
+ appPackage = app.applicationInfo.packageName,
+ activityClassName = app.componentName.className,
+ isNew = (System.currentTimeMillis() - app.firstInstallTime) < Constants.ONE_HOUR_IN_MILLIS,
+ user = profile
)
// if the current app is not OLauncher
if (app.applicationInfo.packageName != BuildConfig.APPLICATION_ID) {
+ val packageKey = app.applicationInfo.packageName + "|" + profile.toString()
// is this a hidden app?
- if (hiddenApps.contains(app.applicationInfo.packageName + "|" + profile.toString())) {
- if (includeHiddenApps) {
+ if (hiddenApps.contains(packageKey)) {
+ if (includeHiddenApps && seenPackages.add(packageKey)) {
appList.add(appModel)
}
} else {
// this is a regular app
- if (includeRegularApps) {
+ if (includeRegularApps && seenPackages.add(packageKey)) {
appList.add(appModel)
}
}
}
}
}
- appList.sortBy { it.appLabel.lowercase() }
+ // Add shortcuts if we're getting regular apps
+ if (includeRegularApps && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val pinned = try {
+ getPinnedShortcuts(context, prefs, collator)
+ } catch (e: Exception) {
+ emptyList()
+ }
+ appList.addAll(pinned)
+ }
+
+ appList.sortWith(compareBy(collator) { it.appLabel })
} catch (e: Exception) {
e.printStackTrace()
}
@@ -114,6 +135,48 @@ suspend fun getAppsList(context: Context, prefs: Prefs, includeRegularApps: Bool
}
}
+@RequiresApi(Build.VERSION_CODES.O)
+private suspend fun getPinnedShortcuts(
+ context: Context,
+ prefs: Prefs,
+ collator: Collator,
+): List =
+ withContext(Dispatchers.IO) {
+ val pinnedShortcuts = mutableListOf()
+ val shortcuts = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as? LauncherApps
+ if (shortcuts?.hasShortcutHostPermission() == true) {
+ val query = LauncherApps.ShortcutQuery().apply {
+ setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
+ }
+ shortcuts.profiles.forEach { profile ->
+ if (isPrivateSpaceProfile(context, profile)) return@forEach
+ try {
+ shortcuts.getShortcuts(query, profile)?.forEach { shortcut ->
+ if (shortcut.isPinned && pinnedShortcuts.none { it.shortcutId == shortcut.id }) {
+ val label = prefs.getAppRenameLabel(shortcut.id)
+ .takeIf { it.isNotBlank() }
+ ?: shortcut.shortLabel?.toString()
+ ?: shortcut.longLabel?.toString().orEmpty()
+ pinnedShortcuts.add(
+ AppModel.PinnedShortcut(
+ appLabel = label,
+ key = collator.getCollationKey(label),
+ appPackage = shortcut.`package`,
+ shortcutId = shortcut.id,
+ isNew = false,
+ user = profile
+ )
+ )
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+ pinnedShortcuts
+ }
+
// This is to ensure backward compatibility with older app versions
// which did not support multiple user profiles
private fun upgradeHiddenApps(prefs: Prefs) {
@@ -130,10 +193,72 @@ private fun upgradeHiddenApps(prefs: Prefs) {
fun isPackageInstalled(context: Context, packageName: String, userString: String): Boolean {
val launcher = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val activityInfo = launcher.getActivityList(packageName, getUserHandleFromString(context, userString))
- if (activityInfo.size > 0) return true
+ if (activityInfo.isNotEmpty()) return true
return false
}
+fun isPrivateSpaceProfile(context: Context, userHandle: UserHandle): Boolean {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) return false
+ return try {
+ val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
+ launcherApps.getLauncherUserInfo(userHandle)?.userType == "android.os.usertype.profile.PRIVATE"
+ } catch (e: Exception) {
+ false
+ }
+}
+
+fun isPrivateSpaceLocked(context: Context, userHandle: UserHandle): Boolean {
+ return try {
+ val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
+ userManager.isQuietModeEnabled(userHandle)
+ } catch (e: Exception) {
+ true
+ }
+}
+
+fun getPrivateSpaceUserHandle(context: Context): UserHandle? {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) return null
+ val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
+ for (profile in userManager.userProfiles) {
+ if (isPrivateSpaceProfile(context, profile)) return profile
+ }
+ return null
+}
+
+suspend fun getPrivateSpaceApps(
+ context: Context,
+ prefs: Prefs,
+): MutableList {
+ return withContext(Dispatchers.IO) {
+ val appList: MutableList = mutableListOf()
+ try {
+ val privateSpaceHandle = getPrivateSpaceUserHandle(context) ?: return@withContext appList
+ val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
+ val collator = Collator.getInstance()
+
+ for (app in launcherApps.getActivityList(null, privateSpaceHandle)) {
+ if (app.applicationInfo.packageName == BuildConfig.APPLICATION_ID) continue
+ val appLabelShown = prefs.getAppRenameLabel(app.applicationInfo.packageName)
+ .ifBlank { app.label.toString() }
+ appList.add(
+ AppModel.App(
+ appLabel = appLabelShown,
+ key = collator.getCollationKey(app.label.toString()),
+ appPackage = app.applicationInfo.packageName,
+ activityClassName = app.componentName.className,
+ isNew = false,
+ user = privateSpaceHandle
+ )
+ )
+ }
+ appList.sortWith(compareBy(collator) { it.appLabel })
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ appList
+ }
+}
+
fun getUserHandleFromString(context: Context, userHandleString: String): UserHandle {
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
for (userHandle in userManager.userProfiles) {
@@ -174,7 +299,7 @@ fun setPlainWallpaperByTheme(context: Context, appTheme: Int) {
fun setPlainWallpaper(context: Context, color: Int) {
try {
- val bitmap = Bitmap.createBitmap(1000, 2000, Bitmap.Config.ARGB_8888)
+ val bitmap = createBitmap(1000, 2000)
bitmap.eraseColor(context.getColor(color))
val manager = WallpaperManager.getInstance(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
@@ -230,7 +355,7 @@ suspend fun getBitmapFromURL(src: String?): Bitmap? {
suspend fun getWallpaperBitmap(originalImage: Bitmap, width: Int, height: Int): Bitmap {
return withContext(Dispatchers.IO) {
- val background = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ val background = createBitmap(width, height)
val originalWidth: Float = originalImage.width.toFloat()
val originalHeight: Float = originalImage.height.toFloat()
@@ -260,17 +385,16 @@ suspend fun getWallpaperBitmap(originalImage: Bitmap, width: Int, height: Int):
suspend fun setWallpaper(appContext: Context, url: String): Boolean {
return withContext(Dispatchers.IO) {
val originalImageBitmap = getBitmapFromURL(url) ?: return@withContext false
- val wallpaperManager = WallpaperManager.getInstance(appContext)
+ if (appContext.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && isTablet(appContext).not())
+ return@withContext false
+ val wallpaperManager = WallpaperManager.getInstance(appContext)
val (width, height) = getScreenDimensions(appContext)
val scaledBitmap = getWallpaperBitmap(originalImageBitmap, width, height)
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- wallpaperManager.setBitmap(scaledBitmap, null, false, WallpaperManager.FLAG_SYSTEM)
- wallpaperManager.setBitmap(scaledBitmap, null, false, WallpaperManager.FLAG_LOCK)
- } else
- wallpaperManager.setBitmap(scaledBitmap)
+ wallpaperManager.setBitmap(scaledBitmap, null, false, WallpaperManager.FLAG_SYSTEM)
+ wallpaperManager.setBitmap(scaledBitmap, null, false, WallpaperManager.FLAG_LOCK)
} catch (e: Exception) {
return@withContext false
}
@@ -292,13 +416,17 @@ fun getScreenDimensions(context: Context): Pair {
return Pair(point.x, point.y)
}
-suspend fun getTodaysWallpaper(wallType: String): String {
+suspend fun getTodaysWallpaper(wallType: String, firstOpenTime: Long): String {
return withContext(Dispatchers.IO) {
var wallpaperUrl: String
try {
- val month = SimpleDateFormat("M", Locale.ENGLISH).format(Date()) ?: ""
- val day = SimpleDateFormat("d", Locale.ENGLISH).format(Date()) ?: ""
- val key = String.format("%s_%s", month, day)
+ val key = if (firstOpenTime.isDaySince() < 10)
+ String.format("0_%s", firstOpenTime.isDaySince().toString())
+ else {
+ val month = SimpleDateFormat("M", Locale.ENGLISH).format(Date()) ?: "0"
+ val day = SimpleDateFormat("d", Locale.ENGLISH).format(Date()) ?: "0"
+ String.format("%s_%s", month, day)
+ }
val url = URL(Constants.URL_WALLPAPERS)
val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
@@ -438,12 +566,21 @@ fun Context.openUrl(url: String) {
startActivity(intent)
}
-fun Context.isSystemApp(packageName: String): Boolean {
+fun Context.isSystemApp(packageName: String, user: UserHandle? = null): Boolean {
if (packageName.isBlank()) return true
return try {
- val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
- ((applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0)
- || (applicationInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0))
+ val launcherApps = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
+ val targetUser = user ?: android.os.Process.myUserHandle()
+ val activityList = launcherApps.getActivityList(packageName, targetUser)
+ if (activityList.isNotEmpty()) {
+ val applicationInfo = activityList.first().applicationInfo
+ ((applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0)
+ || (applicationInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0))
+ } else {
+ val applicationInfo = packageManager.getApplicationInfo(packageName, 0)
+ ((applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0)
+ || (applicationInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0))
+ }
} catch (e: Exception) {
e.printStackTrace()
false
@@ -491,10 +628,47 @@ fun Context.shareApp() {
fun Context.rateApp() {
val intent = Intent(
Intent.ACTION_VIEW,
- Uri.parse(Constants.URL_OLAUNCHER_PLAY_STORE)
+ Constants.URL_OLAUNCHER_PLAY_STORE.toUri()
)
var flags = Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
flags = flags or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
intent.addFlags(flags)
startActivity(intent)
-}
\ No newline at end of file
+}
+
+@RequiresApi(Build.VERSION_CODES.N_MR1)
+fun Context.deletePinnedShortcut(packageName: String, shortcutIdToDelete: String, user: UserHandle) {
+ val launcherApps = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
+
+ // 1. Query for existing pinned shortcuts for the package
+ val query = LauncherApps.ShortcutQuery().apply {
+ setPackage(packageName)
+ // Query only for pinned shortcuts
+ setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
+ }
+
+ try {
+ val pinnedShortcuts = launcherApps.getShortcuts(query, user)
+
+ if (pinnedShortcuts != null) {
+ // 2. Filter out the shortcut to be deleted
+ val updatedPinnedIds = pinnedShortcuts
+ .filter { it.id != shortcutIdToDelete }
+ .map { it.id }
+
+ // 3. Re-pin the remaining shortcuts
+ // This replaces the existing set of pinned shortcuts for this package
+ launcherApps.pinShortcuts(packageName, updatedPinnedIds, user)
+ }
+ } catch (e: SecurityException) {
+ // Handle cases where the app doesn't have permission
+ // (e.g., not the default launcher or active voice interaction service)
+ Log.e("ShortcutHelper", "Permission denied to modify pinned shortcuts for $packageName", e)
+ } catch (e: IllegalStateException) {
+ // Handle cases where the user profile is locked or not running
+ Log.e("ShortcutHelper", "User profile unavailable for modifying pinned shortcuts for $packageName", e)
+ } catch (e: Exception) {
+ // Handle other potential exceptions (like RemoteException wrapped)
+ Log.e("ShortcutHelper", "Failed to modify pinned shortcuts for $packageName", e)
+ }
+}
diff --git a/app/src/main/java/app/olauncher/helper/WallpaperWorker.kt b/app/src/main/java/app/olauncher/helper/WallpaperWorker.kt
index 7b944c5db..9ca71c1a7 100644
--- a/app/src/main/java/app/olauncher/helper/WallpaperWorker.kt
+++ b/app/src/main/java/app/olauncher/helper/WallpaperWorker.kt
@@ -14,11 +14,11 @@ class WallpaperWorker(appContext: Context, workerParams: WorkerParameters) : Cor
override suspend fun doWork(): Result = coroutineScope {
val success =
- if (isOlauncherDefault(applicationContext).not())
+ if (prefs.appTheme == AppCompatDelegate.MODE_NIGHT_YES && isOlauncherDefault(applicationContext).not())
true
else if (prefs.dailyWallpaper) {
val wallType = checkWallpaperType()
- val wallpaperUrl = getTodaysWallpaper(wallType)
+ val wallpaperUrl = getTodaysWallpaper(wallType, prefs.firstOpenTime)
if (prefs.dailyWallpaperUrl == wallpaperUrl)
true
else {
diff --git a/app/src/main/java/app/olauncher/helper/usageStats/ComponentForegroundStat.kt b/app/src/main/java/app/olauncher/helper/usageStats/ComponentForegroundStat.kt
new file mode 100644
index 000000000..ca7d033ac
--- /dev/null
+++ b/app/src/main/java/app/olauncher/helper/usageStats/ComponentForegroundStat.kt
@@ -0,0 +1,28 @@
+package app.olauncher.helper.usageStats
+
+import java.util.Date
+
+/**
+ * Object representing a timespan that an application was in the foreground.
+ *
+ * @property beginTime The start of the timespan, in milliseconds since the epoch.
+ * @property endTime The end of the timespan, in milliseconds since the epoch.
+ * @property packageName The package name of the application.
+ */
+data class ComponentForegroundStat(
+ val beginTime: Long,
+ val endTime: Long,
+ val packageName: String
+) {
+ /**
+ * Overriding toString for a more readable log output, similar to the original Java class.
+ * The default data class toString() would also work, but this one is more explicit
+ * about formatting the timestamps as Instants.
+ */
+ override fun toString(): String {
+ return "ComponentForegroundStat(" +
+ "beginTime=${Date(beginTime)}, " +
+ "endTime=${Date(endTime)}, " +
+ "packageName='$packageName')"
+ }
+}
diff --git a/app/src/main/java/app/olauncher/helper/usageStats/EventLogWrapper.kt b/app/src/main/java/app/olauncher/helper/usageStats/EventLogWrapper.kt
new file mode 100644
index 000000000..15eccfa4d
--- /dev/null
+++ b/app/src/main/java/app/olauncher/helper/usageStats/EventLogWrapper.kt
@@ -0,0 +1,391 @@
+package app.olauncher.helper.usageStats
+
+import android.app.ActivityManager
+import android.app.usage.UsageEvents
+import android.app.usage.UsageStatsManager
+import android.content.Context
+import android.icu.util.Calendar
+import android.util.Log
+import java.util.concurrent.TimeUnit
+import java.util.function.BiConsumer
+import kotlin.math.max
+import kotlin.math.min
+
+class EventLogWrapper(private val context: Context) {
+ private val usageStatsManager by lazy { context.getSystemService("usagestats") as UsageStatsManager }
+ private val guardian = UnmatchedCloseEventGuardian(usageStatsManager)
+
+
+ /**
+ * Collects event information from system to calculate and aggregate precise
+ * foreground time statistics for the specified period.
+ *
+ * Comments refer to the cases from
+ * [the documentation.](https://codeberg.org/fynngodau/usageDirect/wiki/Event-log-wrapper-scenarios)
+ *
+ * @param start First point in time to include in results
+ * @param end Last point in time to include in results
+ * @return A list of foreground stats for the specified period
+ */
+ fun getForegroundStatsByTimestamps(start: Long, end: Long): List {
+ var queryStart = start // Can be mutated by DEVICE_STARTUP event
+
+ /*
+ * Because sometimes, open events do not have close events when they should, as a hack / workaround,
+ * we query the apps currently in the foreground and match them against the apps that are currently
+ * in the foreground if the query start date is very recent or in the future. Thus, we are using this
+ * to tell apart True from Faulty unmatched open events.
+ *
+ * We query processes in the beginning of this method call in case querying the event log takes a
+ * little longer.
+ */
+ val foregroundProcesses = mutableListOf()
+ if (end >= System.currentTimeMillis() - 1500) {
+ val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
+ activityManager?.runningAppProcesses?.forEach { appProcess ->
+ if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND ||
+ appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE
+ ) {
+ if (context.packageName != appProcess.processName) {
+ foregroundProcesses.add(appProcess.processName)
+ }
+ }
+ }
+ }
+
+ // Assumption: events are ordered chronologically
+ val events = usageStatsManager.queryEvents(queryStart, end)
+
+ /* …except that sometimes, the events that are close to each other are swapped in a way that
+ * breaks the assumption that all end times which do not have a matching start time have
+ * started before start. We handle those as Duplicate close event and Duplicate open event.
+ * Therefore, we keep null entries in our moveToForegroundMap instead of removing the entries
+ * to prevent apps that had been opened previously in a period from being counted as "opened
+ * before start" (as they are not a True unmatched close event).
+ */
+ // Map components to the last moveToForeground event. Null value means it was closed.
+ val moveToForegroundMap = mutableMapOf()
+ val componentForegroundStats = mutableListOf()
+
+ // Iterate over events
+ val event = UsageEvents.Event()
+
+ while (events.hasNextEvent()) {
+ events.getNextEvent(event)
+ if (context.packageName == event.packageName) continue
+
+ val appClass = AppClass(event.packageName, event.className)
+
+ when (event.eventType) {
+ /*
+ * "An event type denoting that an android.app.Activity moved to the foreground."
+ * (old definition: "An event type denoting that a component moved to the foreground.")
+ */
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ /*
+ * public static final int android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY = 4;
+ * (annotated as @hide)
+ * "An event type denoting that a component was in the foreground the previous day.
+ * This is effectively treated as a MOVE_TO_FOREGROUND."
+ */
+ 4 -> {
+ // Store open timestamp in map, overwriting earlier timestamps in case of Duplicate open event
+ moveToForegroundMap[appClass] = event.timeStamp
+ }
+
+ /*
+ * "An event type denoting that an android.app.Activity moved to the background."
+ * (old definition: "An event type denoting that a component moved to the background.")
+ */
+ UsageEvents.Event.ACTIVITY_PAUSED,
+ /*
+ * "An activity becomes invisible on the UI, corresponding to Activity.onStop()
+ * of the activity's lifecycle."
+ */
+ UsageEvents.Event.ACTIVITY_STOPPED,
+ /*
+ * public static final int android.app.usage.UsageEvents.Event.END_OF_DAY = 3;
+ * (annotated as @hide)
+ * "An event type denoting that a component was in the foreground when the stats
+ * rolled-over. This is effectively treated as a {@link #MOVE_TO_BACKGROUND}."
+ */
+ 3 -> {
+ val eventBeginTime: Long? = moveToForegroundMap[appClass]?.also {
+ // Open and close events in order. Mark as closed.
+ moveToForegroundMap[appClass] = null
+ } ?: if (
+ // App has not been in this query yet (test for Duplicate close event)
+ moveToForegroundMap.keys.none { it.packageName == event.packageName } &&
+ // Test if this unmatched close event is True by asking the Guardian to scan for it
+ guardian.test(event, queryStart)
+ ) {
+ // Identified as True unmatched close event. Take start as a starting timestamp.
+ queryStart
+ } else {
+ null // Ignore Faulty unmatched close event
+ }
+
+ if (eventBeginTime != null) {
+ // Check if another of the app's components have moved to the foreground in the meantime
+ val endTime = moveToForegroundMap.entries
+ .filter { (key, value) -> key.packageName == event.packageName && value != null }
+ .mapNotNull { it.value }
+ .minOrNull() ?: event.timeStamp
+
+ componentForegroundStats.add(
+ ComponentForegroundStat(eventBeginTime, endTime, event.packageName)
+ )
+ }
+ }
+
+ /*
+ * "An event type denoting that the Android runtime underwent a shutdown process..."
+ */
+ UsageEvents.Event.DEVICE_SHUTDOWN -> {
+ // Per docs: iterate over remaining start events and treat them as closed
+ moveToForegroundMap.forEach { (key, value) ->
+ if (value != null) { // If it's a remaining start event
+ componentForegroundStats.add(
+ ComponentForegroundStat(value, event.timeStamp, key.packageName)
+ )
+ // Set entire app to closed
+ moveToForegroundMap.keys
+ .filter { it.packageName == key.packageName }
+ .forEach { samePackageKey -> moveToForegroundMap[samePackageKey] = null }
+ }
+ }
+ }
+
+ /*
+ * "An event type denoting that the Android runtime started up..."
+ */
+ UsageEvents.Event.DEVICE_STARTUP -> {
+ // Per docs: remove pending open events
+ moveToForegroundMap.clear()
+
+ /* No package could be open longer than a reboot. Thus, we set the `start`
+ * timestamp to the boot event's timestamp in case we later assume that a
+ * package has been open "since the start of the period". It is not logical
+ * that this would happen but we can never know with this API.
+ */
+ queryStart = event.timeStamp
+ }
+ }
+ }
+
+ // Iterate over remaining start events
+ moveToForegroundMap.forEach { (key, value) ->
+ if (value != null) { // If it's a remaining start event
+ // Test if it's a foreground app (True unmatched open event)
+ if (foregroundProcesses.any { it.contains(key.packageName) }) {
+ componentForegroundStats.add(
+ ComponentForegroundStat(
+ value,
+ min(System.currentTimeMillis(), end),
+ key.packageName
+ )
+ )
+ }
+ // If app is not in foreground, drop event (Assume Faulty unmatched open event)
+ }
+ }
+
+ /* If nothing happened during the timespan but there is an app in the foreground,
+ * then this app was used the whole period time and there was No event for it.
+ * Because the foreground applications API call is documented as not to be used
+ * for purposes like this, we first query whether the process name is a valid
+ * package name and if not, we drop it.
+ */
+ if (moveToForegroundMap.isEmpty()) {
+ val packageManager = context.packageManager
+ foregroundProcesses.forEach { foregroundProcess ->
+ if (packageManager.getLaunchIntentForPackage(foregroundProcess) != null) {
+ componentForegroundStats.add(
+ ComponentForegroundStat(
+ queryStart,
+ min(System.currentTimeMillis(), end),
+ foregroundProcess
+ )
+ )
+ Log.d("EventLogWrapper", "Assuming that application $foregroundProcess has been used the whole query time")
+ }
+ }
+ }
+
+ return componentForegroundStats
+ }
+
+ /**
+ * Collects event information from system to calculate and aggregate precise
+ * foreground time statistics for the specified relative day.
+ *
+ * @param offset Day to query back in time relative to today
+ */
+ fun getForegroundStatsByRelativeDay(offset: Int): List {
+ val cal = Calendar.getInstance()
+ cal.add(Calendar.DAY_OF_YEAR, -offset)
+ // Set to start of the day
+ cal.set(Calendar.HOUR_OF_DAY, 0)
+ cal.set(Calendar.MINUTE, 0)
+ cal.set(Calendar.SECOND, 0)
+ cal.set(Calendar.MILLISECOND, 0)
+ val beginTime = cal.timeInMillis
+
+ // Set to start of the next day
+ cal.add(Calendar.DAY_OF_YEAR, 1)
+ val endTime = cal.timeInMillis
+
+ return getForegroundStatsByTimestamps(beginTime, endTime)
+ }
+
+ /**
+ * Collects event information from system to calculate and aggregate precise
+ * foreground time statistics starting at `start` and ending at
+ * the end of the day that contains `start`.
+ *
+ * @param start Starting time of query and point in time in day to query
+ */
+ fun getForegroundStatsByPartialDay(start: Long): List {
+ val cal = Calendar.getInstance()
+ cal.timeInMillis = start
+ // Set to start of the next day
+ cal.add(Calendar.DAY_OF_YEAR, 1)
+ cal.set(Calendar.HOUR_OF_DAY, 0)
+ cal.set(Calendar.MINUTE, 0)
+ cal.set(Calendar.SECOND, 0)
+ cal.set(Calendar.MILLISECOND, 0)
+ val endTime = cal.timeInMillis
+ return getForegroundStatsByTimestamps(start, endTime)
+ }
+
+ /**
+ * Takes a list of foreground stats and aggregates them to usage stats.
+ * Assumes all provided usage stats to be on the same day.
+ *
+ * @param endConsumer Consumer that accepts ending times of component
+ * foreground stats with their package name
+ */
+ @JvmOverloads
+ fun aggregateForegroundStats(
+ foregroundStats: List,
+ endConsumer: BiConsumer? = null
+ ): List {
+ if (foregroundStats.isEmpty()) return emptyList()
+
+ // Group by package name, merge overlapping intervals, then sum durations
+ val applicationTotalTime = foregroundStats
+ .groupBy { it.packageName }
+ .mapValues { (_, stats) ->
+ mergeOverlappingIntervals(stats).sumOf { it.endTime - it.beginTime }
+ }
+
+ val firstBeginTime = foregroundStats.first().beginTime
+ val timeZoneOffset = Calendar.getInstance().timeZone.getOffset(firstBeginTime)
+ val day = TimeUnit.MILLISECONDS.toDays(firstBeginTime + timeZoneOffset)
+
+ // Optionally consume end times
+ endConsumer?.let { consumer ->
+ foregroundStats.forEach { consumer.accept(it.packageName, it.endTime) }
+ }
+
+ // Map the aggregated times to SimpleUsageStat objects
+ return applicationTotalTime.map { (packageName, totalTime) ->
+ SimpleUsageStat(day, totalTime, packageName)
+ }
+ }
+
+ /**
+ * Collects all event information from system to calculate and aggregate precise
+ * foreground time statistics for the provided day and presents this information as
+ * [SimpleUsageStat]s.
+ *
+ * @param day Day since epoch
+ */
+ private fun getForegroundStatsByDay(day: Long): List {
+ val cal = Calendar.getInstance()
+ val timeZoneOffset = cal.timeZone.getOffset(System.currentTimeMillis())
+
+ // To convert epoch day to milliseconds, we first get the UTC millis and then adjust for timezone
+ val start = TimeUnit.DAYS.toMillis(day) - timeZoneOffset
+ val end = TimeUnit.DAYS.toMillis(day + 1) - timeZoneOffset
+ return getForegroundStatsByTimestamps(start, end)
+ }
+
+ /**
+ * Collects all event information from system to calculate and aggregate precise
+ * foreground time statistics and presents this information as [SimpleUsageStat]s.
+ * **This method call causes lag** if called with a low since value.
+ *
+ * @param daySince Return data from this day on
+ * @param endConsumer Consumer that accepts ending times of component
+ * foreground stats with their package name
+ */
+ fun getAllSimpleUsageStats(daySince: Long, endConsumer: BiConsumer): List {
+ val usageStats = mutableListOf()
+ var currentDay = daySince
+
+ val timeZoneOffset = Calendar.getInstance().timeZone.getOffset(System.currentTimeMillis())
+ val today = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() + timeZoneOffset)
+
+ // Maximum event log size
+ currentDay = max(today - 10, currentDay)
+
+ while (currentDay <= today) {
+ val foregroundStats = getForegroundStatsByDay(currentDay)
+ usageStats.addAll(aggregateForegroundStats(foregroundStats, endConsumer))
+ currentDay++
+ }
+
+ return usageStats
+ }
+
+ /**
+ * Returns only usage statistics that have not been counted yet for
+ * only the day that contains `timestamp`
+ *
+ * @param endConsumer Consumer that accepts ending times of component
+ * foreground stats with their package name
+ */
+ fun getIncrementalSimpleUsageStats(timestamp: Long, endConsumer: BiConsumer): List {
+ val foregroundStats = getForegroundStatsByPartialDay(timestamp)
+ return aggregateForegroundStats(foregroundStats, endConsumer)
+ }
+
+ fun aggregateSimpleUsageStats(usageStats: List): Long {
+ return usageStats.sumOf { it.timeUsed }
+ }
+ /**
+ * Merges overlapping or adjacent time intervals to prevent double-counting
+ * when multiple components of the same package have overlapping foreground times.
+ */
+ private fun mergeOverlappingIntervals(
+ stats: List
+ ): List {
+ if (stats.size <= 1) return stats
+
+ val sorted = stats.sortedBy { it.beginTime }
+ val merged = mutableListOf()
+ var current = sorted[0]
+
+ for (i in 1 until sorted.size) {
+ val next = sorted[i]
+ if (next.beginTime <= current.endTime) {
+ current = ComponentForegroundStat(
+ current.beginTime,
+ max(current.endTime, next.endTime),
+ current.packageName
+ )
+ } else {
+ merged.add(current)
+ current = next
+ }
+ }
+ merged.add(current)
+ return merged
+ }
+
+ /**
+ * Stores a class name and its corresponding package.
+ */
+ private data class AppClass(val packageName: String, val className: String?)
+}
\ No newline at end of file
diff --git a/app/src/main/java/app/olauncher/helper/usageStats/SimpleUsageStat.kt b/app/src/main/java/app/olauncher/helper/usageStats/SimpleUsageStat.kt
new file mode 100644
index 000000000..93bac4f6c
--- /dev/null
+++ b/app/src/main/java/app/olauncher/helper/usageStats/SimpleUsageStat.kt
@@ -0,0 +1,48 @@
+package app.olauncher.helper.usageStats
+
+import android.app.usage.UsageStats
+import android.icu.util.Calendar
+import java.util.concurrent.TimeUnit
+
+/**
+ * A data class to hold simplified usage statistics.
+ *
+ * @property day The day since epoch that this object concerns.
+ * @property timeUsed The time that the application has been in the foreground in milliseconds on this day.
+ * @property applicationId The package name of the application that this object concerns.
+ */
+data class SimpleUsageStat(
+ val day: Long,
+ val timeUsed: Long,
+ val applicationId: String
+) {
+ /**
+ * Secondary constructor to create a SimpleUsageStat from the system's [UsageStats].
+ */
+ constructor(systemUsageStat: UsageStats) : this(
+ day = getEpochDay(systemUsageStat.lastTimeUsed),
+ timeUsed = systemUsageStat.totalTimeInForeground,
+ applicationId = systemUsageStat.packageName
+ )
+
+ companion object {
+ /**
+ * Converts a list of system [UsageStats] to a list of [SimpleUsageStat].
+ * This function is kept for direct compatibility with the original Java static method.
+ */
+ @JvmStatic
+ fun asSimpleStats(usageStats: List): List {
+ return usageStats.map { SimpleUsageStat(it) }
+ }
+
+ /**
+ * Calculates the epoch day from a timestamp in milliseconds, compatible with API 24+.
+ * It manually calculates the day by using integer division on the milliseconds.
+ */
+ private fun getEpochDay(lastTimeUsed: Long): Long {
+ val timeZoneOffset = Calendar.getInstance().timeZone.getOffset(lastTimeUsed)
+ return TimeUnit.MILLISECONDS.toDays(lastTimeUsed + timeZoneOffset)
+ }
+ }
+}
+
diff --git a/app/src/main/java/app/olauncher/helper/usageStats/UnmatchedCloseEventGuardian.kt b/app/src/main/java/app/olauncher/helper/usageStats/UnmatchedCloseEventGuardian.kt
new file mode 100644
index 000000000..9d550e264
--- /dev/null
+++ b/app/src/main/java/app/olauncher/helper/usageStats/UnmatchedCloseEventGuardian.kt
@@ -0,0 +1,65 @@
+package app.olauncher.helper.usageStats
+
+import android.app.usage.UsageEvents
+import android.app.usage.UsageStatsManager
+import android.util.Log
+
+/**
+ * “…a diminutive Guardian who traveled backward through time…”
+ *
+ * Guards [EventLogWrapper] against Faulty unmatched close events (per
+ * [the documentation](https://codeberg.org/fynngodau/usageDirect/wiki/Event-log-wrapper-scenarios))
+ * by seeking backwards through time and scanning for the open event.
+ */
+class UnmatchedCloseEventGuardian(private val usageStatsManager: UsageStatsManager) {
+
+ companion object {
+ private const val SCAN_INTERVAL = 1000L * 60 * 60 * 24 // 24 hours
+ }
+
+ /**
+ * @param event Event to validate
+ * @param queryStart Timestamp at which original query o
+ * @return True if the event is valid, false otherwise
+ */
+ fun test(event: UsageEvents.Event, queryStart: Long): Boolean {
+ val events = usageStatsManager.queryEvents(queryStart - SCAN_INTERVAL, queryStart)
+
+ // Reusable event object for iteration
+ val e = UsageEvents.Event()
+
+ // Track whether the package is currently in foreground or background
+ var open = false // Not open until opened
+
+ while (events.hasNextEvent()) {
+ events.getNextEvent(e)
+
+ if (e.eventType == UsageEvents.Event.DEVICE_STARTUP) {
+ // Consider all apps closed after startup according to docs
+ open = false
+ }
+
+ // Only consider events concerning our package otherwise
+ if (event.packageName == e.packageName) {
+ when (e.eventType) {
+ // see EventLogWrapper
+ UsageEvents.Event.ACTIVITY_RESUMED, 4 -> {
+ open = true
+ }
+ UsageEvents.Event.ACTIVITY_PAUSED, 3 -> {
+ if (e.timeStamp != event.timeStamp) {
+ // Don't flip to 'false' if we're looking at the original event itself
+ open = false
+ }
+ }
+ }
+ }
+ }
+
+ val result = if (open) "True" else "Faulty"
+ Log.d("Guardian", "Scanned for package ${event.packageName} and determined event to be $result")
+
+ // Event is valid if it was previously opened (within SCAN_INTERVAL)
+ return open
+ }
+}
diff --git a/app/src/main/java/app/olauncher/listener/OnSwipeTouchListener.kt b/app/src/main/java/app/olauncher/listener/OnSwipeTouchListener.kt
index ae09a784f..3197b3c8c 100644
--- a/app/src/main/java/app/olauncher/listener/OnSwipeTouchListener.kt
+++ b/app/src/main/java/app/olauncher/listener/OnSwipeTouchListener.kt
@@ -74,14 +74,14 @@ internal open class OnSwipeTouchListener(c: Context?) : OnTouchListener {
}
override fun onFling(
- event1: MotionEvent,
+ event1: MotionEvent?,
event2: MotionEvent,
velocityX: Float,
velocityY: Float,
): Boolean {
try {
- val diffY = event2.y - event1.y
- val diffX = event2.x - event1.x
+ val diffY = event2.y - (event1?.y ?: 0F)
+ val diffX = event2.x - (event1?.x ?: 0F)
if (abs(diffX) > abs(diffY)) {
if (abs(diffX) > SWIPE_THRESHOLD && abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) onSwipeRight() else onSwipeLeft()
diff --git a/app/src/main/java/app/olauncher/listener/ViewSwipeTouchListener.kt b/app/src/main/java/app/olauncher/listener/ViewSwipeTouchListener.kt
index 881082417..9d7009ee2 100644
--- a/app/src/main/java/app/olauncher/listener/ViewSwipeTouchListener.kt
+++ b/app/src/main/java/app/olauncher/listener/ViewSwipeTouchListener.kt
@@ -58,14 +58,14 @@ internal open class ViewSwipeTouchListener(c: Context?, v: View) : OnTouchListen
}
override fun onFling(
- event1: MotionEvent,
+ event1: MotionEvent?,
event2: MotionEvent,
velocityX: Float,
velocityY: Float,
): Boolean {
try {
- val diffY = event2.y - event1.y
- val diffX = event2.x - event1.x
+ val diffY = event2.y - (event1?.y ?: 0F)
+ val diffX = event2.x - (event1?.x ?: 0F)
if (abs(diffX) > abs(diffY)) {
if (abs(diffX) > SWIPE_THRESHOLD && abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) onSwipeRight() else onSwipeLeft()
diff --git a/app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt b/app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt
index 55dd97367..01234a50f 100644
--- a/app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt
+++ b/app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt
@@ -1,6 +1,7 @@
package app.olauncher.ui
import android.content.Context
+import android.content.pm.LauncherApps
import android.os.UserHandle
import android.text.Editable
import android.text.TextWatcher
@@ -18,6 +19,7 @@ import app.olauncher.R
import app.olauncher.data.AppModel
import app.olauncher.data.Constants
import app.olauncher.databinding.AdapterAppDrawerBinding
+import app.olauncher.databinding.AdapterPrivateSpaceHeaderBinding
import app.olauncher.helper.hideKeyboard
import app.olauncher.helper.isSystemApp
import app.olauncher.helper.showKeyboard
@@ -31,12 +33,26 @@ class AppDrawerAdapter(
private val appDeleteListener: (AppModel) -> Unit,
private val appHideListener: (AppModel, Int) -> Unit,
private val appRenameListener: (AppModel, String) -> Unit,
-) : ListAdapter(DIFF_CALLBACK), Filterable {
+ private val privateSpaceToggleListener: () -> Unit = {},
+ private val privateSpaceSettingsListener: () -> Unit = {},
+) : ListAdapter(DIFF_CALLBACK), Filterable {
companion object {
+ const val VIEW_TYPE_APP = 0
+ const val VIEW_TYPE_PRIVATE_HEADER = 1
+
val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
- override fun areItemsTheSame(oldItem: AppModel, newItem: AppModel): Boolean =
- oldItem.appPackage == newItem.appPackage && oldItem.user == newItem.user
+ override fun areItemsTheSame(oldItem: AppModel, newItem: AppModel): Boolean = when {
+ oldItem is AppModel.App && newItem is AppModel.App ->
+ oldItem.appPackage == newItem.appPackage && oldItem.user == newItem.user
+
+ oldItem is AppModel.PinnedShortcut && newItem is AppModel.PinnedShortcut ->
+ oldItem.shortcutId == newItem.shortcutId && oldItem.user == newItem.user
+
+ oldItem is AppModel.PrivateSpaceHeader && newItem is AppModel.PrivateSpaceHeader -> true
+
+ else -> false
+ }
override fun areContentsTheSame(oldItem: AppModel, newItem: AppModel): Boolean =
oldItem == newItem
@@ -51,24 +67,58 @@ class AppDrawerAdapter(
var appsList: MutableList = mutableListOf()
var appFilteredList: MutableList = mutableListOf()
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
- ViewHolder(AdapterAppDrawerBinding.inflate(LayoutInflater.from(parent.context), parent, false))
+ override fun getItemViewType(position: Int): Int {
+ return when (appFilteredList.getOrNull(position)) {
+ is AppModel.PrivateSpaceHeader -> VIEW_TYPE_PRIVATE_HEADER
+ else -> VIEW_TYPE_APP
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ return when (viewType) {
+ VIEW_TYPE_PRIVATE_HEADER -> PrivateSpaceHeaderViewHolder(
+ AdapterPrivateSpaceHeaderBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+ )
+
+ else -> ViewHolder(
+ AdapterAppDrawerBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+ )
+ }
+ }
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
try {
- if (appFilteredList.size == 0 || position == RecyclerView.NO_POSITION) return
+ if (appFilteredList.isEmpty() || position == RecyclerView.NO_POSITION) return
val appModel = appFilteredList[holder.bindingAdapterPosition]
- holder.bind(
- flag,
- appLabelGravity,
- myUserHandle,
- appModel,
- appClickListener,
- appDeleteListener,
- appInfoListener,
- appHideListener,
- appRenameListener
- )
+ when (holder) {
+ is PrivateSpaceHeaderViewHolder -> {
+ holder.bind(
+ appLabelGravity,
+ privateSpaceToggleListener,
+ privateSpaceSettingsListener,
+ )
+ }
+
+ is ViewHolder -> holder.bind(
+ flag,
+ appLabelGravity,
+ myUserHandle,
+ appModel,
+ appClickListener,
+ appDeleteListener,
+ appInfoListener,
+ appHideListener,
+ appRenameListener
+ )
+ }
} catch (e: Exception) {
e.printStackTrace()
}
@@ -84,9 +134,7 @@ class AppDrawerAdapter(
val appFilteredList = (if (charSearch.isNullOrBlank()) appsList
else appsList.filter { app ->
- appLabelMatches(app.appLabel, charSearch)
-// }.sortedByDescending {
-// charSearch.contentEquals(it.appLabel, true)
+ app !is AppModel.PrivateSpaceHeader && appLabelMatches(app.appLabel, charSearch)
} as MutableList)
val filterResults = FilterResults()
@@ -97,7 +145,8 @@ class AppDrawerAdapter(
@Suppress("UNCHECKED_CAST")
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
results?.values?.let {
- appFilteredList = it as MutableList
+ val items = it as MutableList
+ appFilteredList = items
submitList(appFilteredList) {
autoLaunch()
}
@@ -112,7 +161,8 @@ class AppDrawerAdapter(
&& autoLaunch
&& isBangSearch.not()
&& flag == Constants.FLAG_LAUNCH_APP
- && appFilteredList.size > 0
+ && appFilteredList.isNotEmpty()
+ && appFilteredList[0] !is AppModel.PrivateSpaceHeader
) appClickListener(appFilteredList[0])
} catch (e: Exception) {
e.printStackTrace()
@@ -128,20 +178,45 @@ class AppDrawerAdapter(
}
fun setAppList(appsList: MutableList) {
- // Add empty app for bottom padding in recyclerview
- appsList.add(AppModel("", null, "", "", android.os.Process.myUserHandle()))
+ // Add empty app for bottom padding in recyclerview and assign to list
+ appsList.add(
+ AppModel.App(
+ appLabel = "",
+ key = null,
+ appPackage = "",
+ activityClassName = "",
+ isNew = false,
+ user = android.os.Process.myUserHandle()
+ )
+ )
this.appsList = appsList
this.appFilteredList = appsList
submitList(appsList)
}
fun launchFirstInList() {
- if (appFilteredList.size > 0)
- appClickListener(appFilteredList[0])
+ val first = appFilteredList.firstOrNull { it !is AppModel.PrivateSpaceHeader }
+ if (first != null) appClickListener(first)
}
- class ViewHolder(private val binding: AdapterAppDrawerBinding) : RecyclerView.ViewHolder(binding.root) {
+ class PrivateSpaceHeaderViewHolder(private val binding: AdapterPrivateSpaceHeaderBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ fun bind(
+ appLabelGravity: Int,
+ toggleListener: () -> Unit,
+ settingsListener: () -> Unit,
+ ) = with(binding) {
+ privateSpaceTitle.gravity = appLabelGravity
+ privateSpaceTitle.setOnClickListener { toggleListener() }
+ privateSpaceTitle.setOnLongClickListener {
+ settingsListener()
+ true
+ }
+ }
+ }
+ class ViewHolder(private val binding: AdapterAppDrawerBinding) :
+ RecyclerView.ViewHolder(binding.root) {
fun bind(
flag: Int,
appLabelGravity: Int,
@@ -152,105 +227,130 @@ class AppDrawerAdapter(
appInfoListener: (AppModel) -> Unit,
appHideListener: (AppModel, Int) -> Unit,
appRenameListener: (AppModel, String) -> Unit,
- ) =
- with(binding) {
- appHideLayout.visibility = View.GONE
- renameLayout.visibility = View.GONE
- appTitle.text = appModel.appLabel
- appTitle.gravity = appLabelGravity
- otherProfileIndicator.isVisible = appModel.user != myUserHandle
-
- appTitle.setOnClickListener { clickListener(appModel) }
- appTitle.setOnLongClickListener {
- if (appModel.appPackage.isNotEmpty()) {
- appDelete.alpha = if (root.context.isSystemApp(appModel.appPackage)) 0.5f else 1.0f
- appHide.text = if (flag == Constants.FLAG_HIDDEN_APPS)
- root.context.getString(R.string.adapter_show)
- else
- root.context.getString(R.string.adapter_hide)
- appHideLayout.visibility = View.VISIBLE
- appRename.isVisible = flag != Constants.FLAG_HIDDEN_APPS
+ ) = with(binding) {
+ appHideLayout.visibility = View.GONE
+ renameLayout.visibility = View.GONE
+ appTitle.visibility = View.VISIBLE
+
+ // Show indicators in title based on app type and state
+ appTitle.text = buildString {
+ append(appModel.appLabel)
+ if (appModel.isNew) append(" ✦")
+ }
+ appTitle.gravity = appLabelGravity
+ otherProfileIndicator.isVisible = appModel.user != myUserHandle
+
+ appTitle.setOnClickListener { clickListener(appModel) }
+
+ appTitle.setOnLongClickListener {
+ if (appModel.appPackage.isNotEmpty()) {
+ appDelete.alpha = when (
+ appModel is AppModel.PinnedShortcut || !root.context.isSystemApp(appModel.appPackage, appModel.user)
+ ) {
+ true -> 1.0f
+ false -> 0.5f
}
- true
- }
- appRename.setOnClickListener {
- if (appModel.appPackage.isNotEmpty()) {
- etAppRename.hint = getAppName(etAppRename.context, appModel.appPackage)
- etAppRename.setText(appModel.appLabel)
- etAppRename.setSelectAllOnFocus(true)
- renameLayout.visibility = View.VISIBLE
- appHideLayout.visibility = View.GONE
- etAppRename.showKeyboard()
- etAppRename.imeOptions = EditorInfo.IME_ACTION_DONE;
+ appHide.text = if (flag == Constants.FLAG_HIDDEN_APPS)
+ root.context.getString(R.string.adapter_show)
+ else
+ root.context.getString(R.string.adapter_hide)
+ appTitle.visibility = View.INVISIBLE
+ appHide.alpha = when (appModel is AppModel.PinnedShortcut) {
+ true -> 0.5f
+ false -> 1.0f
}
+ appHideLayout.visibility = View.VISIBLE
+ // Only allow renaming non hidden apps
+ appRename.isVisible = flag != Constants.FLAG_HIDDEN_APPS
}
- etAppRename.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus ->
- if (hasFocus)
- appTitle.visibility = View.INVISIBLE
- else
- appTitle.visibility = View.VISIBLE
+ true
+ }
+
+ // Configure rename behavior
+ appRename.setOnClickListener {
+ if (appModel.appPackage.isNotEmpty()) {
+ etAppRename.hint = getAppName(etAppRename.context, appModel.appPackage, appModel.user)
+ etAppRename.setText(appModel.appLabel)
+ etAppRename.setSelectAllOnFocus(true)
+ renameLayout.visibility = View.VISIBLE
+ appHideLayout.visibility = View.GONE
+ etAppRename.showKeyboard()
+ etAppRename.imeOptions = EditorInfo.IME_ACTION_DONE
+ }
+ }
+ etAppRename.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
+ appTitle.visibility = if (hasFocus) View.INVISIBLE else View.VISIBLE
+ }
+ etAppRename.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) {
+ etAppRename.hint = getAppName(etAppRename.context, appModel.appPackage, appModel.user)
}
- etAppRename.addTextChangedListener(object : TextWatcher {
- override fun afterTextChanged(s: Editable?) {
- etAppRename.hint = getAppName(etAppRename.context, appModel.appPackage)
- }
- override fun beforeTextChanged(
- s: CharSequence?,
- start: Int,
- count: Int,
- after: Int,
- ) {
- }
+ override fun beforeTextChanged(
+ s: CharSequence?,
+ start: Int,
+ count: Int,
+ after: Int,
+ ) {
+ }
- override fun onTextChanged(
- s: CharSequence?,
- start: Int,
- before: Int,
- count: Int,
- ) {
- etAppRename.hint = ""
- }
- })
- etAppRename.setOnEditorActionListener { _, actionCode, _ ->
- if (actionCode == EditorInfo.IME_ACTION_DONE) {
- val renameLabel = etAppRename.text.toString().trim()
- if (renameLabel.isNotBlank() && appModel.appPackage.isNotBlank()) {
- appRenameListener(appModel, renameLabel)
- renameLayout.visibility = View.GONE
- }
- true
- }
- false
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ etAppRename.hint = ""
}
- tvSaveRename.setOnClickListener {
- etAppRename.hideKeyboard()
+ })
+ etAppRename.setOnEditorActionListener { _, actionCode, _ ->
+ if (actionCode == EditorInfo.IME_ACTION_DONE) {
val renameLabel = etAppRename.text.toString().trim()
if (renameLabel.isNotBlank() && appModel.appPackage.isNotBlank()) {
appRenameListener(appModel, renameLabel)
renameLayout.visibility = View.GONE
- } else {
- val packageManager = etAppRename.context.packageManager
- appRenameListener(
- appModel,
- packageManager.getApplicationLabel(
- packageManager.getApplicationInfo(appModel.appPackage, 0)
- ).toString()
- )
- renameLayout.visibility = View.GONE
}
+ true
+ }
+ false
+ }
+ tvSaveRename.setOnClickListener {
+ etAppRename.hideKeyboard()
+ val renameLabel = etAppRename.text.toString().trim()
+ if (renameLabel.isNotBlank() && appModel.appPackage.isNotBlank()) {
+ appRenameListener(appModel, renameLabel)
+ renameLayout.visibility = View.GONE
+ } else {
+ appRenameListener(
+ appModel,
+ getAppName(etAppRename.context, appModel.appPackage, appModel.user)
+ )
+ renameLayout.visibility = View.GONE
}
- appInfo.setOnClickListener { appInfoListener(appModel) }
- appDelete.setOnClickListener { appDeleteListener(appModel) }
- appHideLayout.setOnClickListener { appHideLayout.visibility = View.GONE }
- appHide.setOnClickListener { appHideListener(appModel, bindingAdapterPosition) }
}
+ appInfo.setOnClickListener { appInfoListener(appModel) }
+ appDelete.setOnClickListener { appDeleteListener(appModel) }
+ appMenuClose.setOnClickListener {
+ appHideLayout.visibility = View.GONE
+ appTitle.visibility = View.VISIBLE
+ }
+ appRenameClose.setOnClickListener {
+ renameLayout.visibility = View.GONE
+ appTitle.visibility = View.VISIBLE
+ }
+ appHide.setOnClickListener { appHideListener(appModel, bindingAdapterPosition) }
+ }
- private fun getAppName(context: Context, appPackage: String): String {
- val packageManager = context.packageManager
- return packageManager.getApplicationLabel(
- packageManager.getApplicationInfo(appPackage, 0)
- ).toString()
+ private fun getAppName(context: Context, appPackage: String, user: UserHandle): String {
+ val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
+ return try {
+ val activityList = launcherApps.getActivityList(appPackage, user)
+ if (activityList.isNotEmpty()) {
+ activityList.first().label.toString()
+ } else {
+ val packageManager = context.packageManager
+ packageManager.getApplicationLabel(
+ packageManager.getApplicationInfo(appPackage, 0)
+ ).toString()
+ }
+ } catch (_: Exception) {
+ "" // As a fallback, display an empty string.
+ }
}
}
}
diff --git a/app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt b/app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt
index a8a0b6f7e..2fdf6d533 100644
--- a/app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt
+++ b/app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt
@@ -1,5 +1,6 @@
package app.olauncher.ui
+import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@@ -15,34 +16,44 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Recycler
import app.olauncher.MainViewModel
import app.olauncher.R
+import app.olauncher.data.AppModel
import app.olauncher.data.Constants
import app.olauncher.data.Prefs
import app.olauncher.databinding.FragmentAppDrawerBinding
+import app.olauncher.helper.deletePinnedShortcut
import app.olauncher.helper.hideKeyboard
import app.olauncher.helper.isEinkDisplay
+import app.olauncher.helper.isPrivateSpaceProfile
import app.olauncher.helper.isSystemApp
import app.olauncher.helper.openAppInfo
import app.olauncher.helper.openSearch
import app.olauncher.helper.openUrl
-import app.olauncher.helper.searchOnPlayStore
import app.olauncher.helper.showKeyboard
import app.olauncher.helper.showToast
import app.olauncher.helper.uninstall
-
class AppDrawerFragment : Fragment() {
private lateinit var prefs: Prefs
private lateinit var adapter: AppDrawerAdapter
+ private lateinit var linearLayoutManager: LinearLayoutManager
private var flag = Constants.FLAG_LAUNCH_APP
private var canRename = false
+ private var currentAppList: List? = null
+ private var currentPrivateSpaceApps: List? = null
+ private var currentPrivateSpaceLocked: Boolean = true
+ private var currentPrivateSpaceAvailable: Boolean = false
private val viewModel: MainViewModel by activityViewModels()
private var _binding: FragmentAppDrawerBinding? = null
private val binding get() = _binding!!
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?,
+ ): View {
_binding = FragmentAppDrawerBinding.inflate(inflater, container, false)
return binding.root
}
@@ -54,6 +65,7 @@ class AppDrawerFragment : Fragment() {
flag = it.getInt(Constants.Key.FLAG, Constants.FLAG_LAUNCH_APP)
canRename = it.getBoolean(Constants.Key.RENAME, false)
}
+
initViews()
initSearch()
initAdapter()
@@ -79,7 +91,7 @@ class AppDrawerFragment : Fragment() {
override fun onQueryTextSubmit(query: String?): Boolean {
if (query?.startsWith("!") == true)
requireContext().openUrl(Constants.URL_DUCK_SEARCH + query.replace(" ", "%20"))
- else if (adapter.itemCount == 0 && requireContext().searchOnPlayStore(query?.trim()).not())
+ else if (adapter.itemCount == 0)
requireContext().openSearch(query?.trim())
else
adapter.launchFirstInList()
@@ -89,8 +101,8 @@ class AppDrawerFragment : Fragment() {
override fun onQueryTextChange(newText: String): Boolean {
try {
adapter.filter.filter(newText)
- binding.appDrawerTip.visibility = View.GONE
- binding.appRename.visibility = if (canRename && newText.isNotBlank()) View.VISIBLE else View.GONE
+ binding.appRename.visibility =
+ if (canRename && newText.isNotBlank()) View.VISIBLE else View.GONE
return true
} catch (e: Exception) {
e.printStackTrace()
@@ -104,10 +116,8 @@ class AppDrawerFragment : Fragment() {
adapter = AppDrawerAdapter(
flag,
prefs.appLabelAlignment,
- appClickListener = {
- if (it.appPackage.isEmpty())
- return@AppDrawerAdapter
- viewModel.selectedApp(it, flag)
+ appClickListener = { appModel ->
+ viewModel.selectedApp(appModel, flag)
if (flag == Constants.FLAG_LAUNCH_APP || flag == Constants.FLAG_HIDDEN_APPS)
findNavController().popBackStack(R.id.mainFragment, false)
else
@@ -121,15 +131,36 @@ class AppDrawerFragment : Fragment() {
)
findNavController().popBackStack(R.id.mainFragment, false)
},
- appDeleteListener = {
- requireContext().apply {
- if (isSystemApp(it.appPackage))
- showToast(getString(R.string.system_app_cannot_delete))
- else
- uninstall(it.appPackage)
+ appDeleteListener = { appModel ->
+ when (appModel) {
+ is AppModel.PrivateSpaceHeader -> {}
+ is AppModel.PinnedShortcut ->
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ requireContext().deletePinnedShortcut(
+ packageName = appModel.appPackage,
+ shortcutIdToDelete = appModel.shortcutId,
+ user = appModel.user,
+ )
+ }
+
+ is AppModel.App -> {
+ if (isPrivateSpaceProfile(requireContext(), appModel.user)) {
+ openAppInfo(requireContext(), appModel.user, appModel.appPackage)
+ } else if (requireContext().isSystemApp(appModel.appPackage, appModel.user)) {
+ requireContext().showToast(getString(R.string.system_app_cannot_delete))
+ openAppInfo(requireContext(), appModel.user, appModel.appPackage)
+ } else {
+ requireContext().uninstall(appModel.appPackage)
+ }
+ }
}
+ viewModel.getAppList()
},
appHideListener = { appModel, position ->
+ if (appModel is AppModel.PinnedShortcut) {
+ requireContext().showToast("Hiding pinned shortcuts is not supported")
+ return@AppDrawerAdapter
+ }
adapter.appFilteredList.removeAt(position)
adapter.notifyItemRemoved(position)
adapter.appsList.remove(appModel)
@@ -151,15 +182,33 @@ class AppDrawerFragment : Fragment() {
viewModel.showDialog.postValue(Constants.Dialog.HIDDEN)
findNavController().navigate(R.id.action_appListFragment_to_settingsFragment2)
}
+ viewModel.getAppList()
+ viewModel.getHiddenApps()
},
appRenameListener = { appModel, renameLabel ->
- prefs.setAppRenameLabel(appModel.appPackage, renameLabel)
+ val identifier = when (appModel) {
+ is AppModel.PinnedShortcut -> appModel.shortcutId
+ is AppModel.App -> appModel.appPackage
+ else -> return@AppDrawerAdapter
+ }
+ prefs.setAppRenameLabel(identifier, renameLabel)
viewModel.getAppList()
+ },
+ privateSpaceToggleListener = {
+ viewModel.togglePrivateSpaceLock()
+ },
+ privateSpaceSettingsListener = {
+ viewModel.openPrivateSpaceSettings()
+ findNavController().popBackStack(R.id.mainFragment, false)
}
)
- val linearLayoutManager: LinearLayoutManager = object : LinearLayoutManager(requireContext()) {
- override fun scrollVerticallyBy(dx: Int, recycler: Recycler, state: RecyclerView.State): Int {
+ linearLayoutManager = object : LinearLayoutManager(requireContext()) {
+ override fun scrollVerticallyBy(
+ dx: Int,
+ recycler: Recycler,
+ state: RecyclerView.State,
+ ): Int {
val scrollRange = super.scrollVerticallyBy(dx, recycler, state)
val overScroll = dx - scrollRange
if (overScroll < -10 && binding.recyclerView.scrollState == RecyclerView.SCROLL_STATE_DRAGGING)
@@ -167,6 +216,7 @@ class AppDrawerFragment : Fragment() {
return scrollRange
}
}
+
binding.recyclerView.layoutManager = linearLayoutManager
binding.recyclerView.adapter = adapter
binding.recyclerView.addOnScrollListener(getRecyclerViewOnScrollListener())
@@ -178,26 +228,51 @@ class AppDrawerFragment : Fragment() {
private fun initObservers() {
viewModel.firstOpen.observe(viewLifecycleOwner) {
- if (it && flag == Constants.FLAG_LAUNCH_APP) {
- binding.appDrawerTip.visibility = View.VISIBLE
- binding.appDrawerTip.isSelected = true
- }
}
- if (flag == Constants.FLAG_HIDDEN_APPS)
+ if (flag == Constants.FLAG_HIDDEN_APPS) {
viewModel.hiddenApps.observe(viewLifecycleOwner) {
- it?.let { adapter.setAppList(it.toMutableList()) }
+ it?.let {
+ adapter.setAppList(it.toMutableList())
+ }
}
- else
+ } else {
viewModel.appList.observe(viewLifecycleOwner) {
- it?.let { adapter.setAppList(it.toMutableList()) }
+ currentAppList = it
+ updateCombinedAppList()
+ }
+ if (flag == Constants.FLAG_LAUNCH_APP) {
+ viewModel.privateSpaceAvailable.observe(viewLifecycleOwner) {
+ currentPrivateSpaceAvailable = it
+ updateCombinedAppList()
+ }
+ viewModel.privateSpaceLocked.observe(viewLifecycleOwner) {
+ currentPrivateSpaceLocked = it
+ updateCombinedAppList()
+ }
+ viewModel.privateSpaceApps.observe(viewLifecycleOwner) {
+ currentPrivateSpaceApps = it
+ updateCombinedAppList()
+ }
}
+ }
}
- private fun initClickListeners() {
- binding.appDrawerTip.setOnClickListener {
- binding.appDrawerTip.isSelected = false
- binding.appDrawerTip.isSelected = true
+ private fun updateCombinedAppList() {
+ val apps = currentAppList ?: return
+ val combined = apps.toMutableList()
+
+ if (flag == Constants.FLAG_LAUNCH_APP && currentPrivateSpaceAvailable) {
+ combined.add(AppModel.PrivateSpaceHeader(isLocked = currentPrivateSpaceLocked))
+ if (!currentPrivateSpaceLocked) {
+ currentPrivateSpaceApps?.let { combined.addAll(it) }
+ }
}
+
+ adapter.setAppList(combined)
+ adapter.filter.filter(binding.search.query)
+ }
+
+ private fun initClickListeners() {
binding.appRename.setOnClickListener {
val name = binding.search.query.toString().trim()
if (name.isEmpty()) {
@@ -231,19 +306,16 @@ class AppDrawerFragment : Fragment() {
RecyclerView.SCROLL_STATE_DRAGGING -> {
onTop = !recyclerView.canScrollVertically(-1)
- if (onTop) binding.search.hideKeyboard()
- // if (onTop && !recyclerView.canScrollVertically(1))
- // checkMessageAndExit()
+ if (onTop)
+ binding.search.hideKeyboard()
}
RecyclerView.SCROLL_STATE_IDLE -> {
- if (!recyclerView.canScrollVertically(1)) {
+ if (!recyclerView.canScrollVertically(1))
binding.search.hideKeyboard()
- } else if (!recyclerView.canScrollVertically(-1)) {
- if (!onTop) binding.search.showKeyboard(prefs.autoShowKeyboard)
- // if (onTop) checkMessageAndExit()
- // else binding.search.showKeyboard(prefs.autoShowKeyboard)
- }
+ else if (!recyclerView.canScrollVertically(-1))
+ if (!onTop && isRemoving.not())
+ binding.search.showKeyboard(prefs.autoShowKeyboard)
}
}
}
@@ -270,4 +342,4 @@ class AppDrawerFragment : Fragment() {
super.onDestroyView()
_binding = null
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/app/olauncher/ui/HomeFragment.kt b/app/src/main/java/app/olauncher/ui/HomeFragment.kt
index e64ecd4da..ef7620bd0 100644
--- a/app/src/main/java/app/olauncher/ui/HomeFragment.kt
+++ b/app/src/main/java/app/olauncher/ui/HomeFragment.kt
@@ -2,19 +2,25 @@ package app.olauncher.ui
import android.app.admin.DevicePolicyManager
import android.content.Context
+import android.content.Intent
+import android.content.pm.LauncherApps
+import android.content.res.Configuration
import android.os.BatteryManager
import android.os.Build
import android.os.Bundle
-import android.os.Vibrator
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
+import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
+import androidx.annotation.RequiresApi
+import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
+import androidx.core.view.setPadding
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
@@ -25,6 +31,8 @@ import app.olauncher.data.AppModel
import app.olauncher.data.Constants
import app.olauncher.data.Prefs
import app.olauncher.databinding.FragmentHomeBinding
+import app.olauncher.helper.appUsagePermissionGranted
+import app.olauncher.helper.dpToPx
import app.olauncher.helper.expandNotificationDrawer
import app.olauncher.helper.getChangedAppTheme
import app.olauncher.helper.getUserHandleFromString
@@ -47,7 +55,6 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
private lateinit var prefs: Prefs
private lateinit var viewModel: MainViewModel
private lateinit var deviceManager: DevicePolicyManager
- private lateinit var vibrator: Vibrator
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
@@ -65,7 +72,6 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
} ?: throw Exception("Invalid Activity")
deviceManager = context?.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
- vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
initObservers()
setHomeAlignment(prefs.homeAlignment)
@@ -84,9 +90,12 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
override fun onClick(view: View) {
when (view.id) {
R.id.lock -> {}
+ R.id.recents -> {}
R.id.clock -> openClockApp()
R.id.date -> openCalendarApp()
R.id.setDefaultLauncher -> viewModel.resetLauncherLiveData.call()
+ R.id.tvScreenTime -> openScreenTimeDigitalWellbeing()
+
else -> {
try { // Launch app
val appLocation = view.tag.toString().toInt()
@@ -145,6 +154,22 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
prefs.calendarAppClassName = ""
prefs.calendarAppUser = ""
}
+
+ R.id.tvScreenTime -> {
+ showAppList(Constants.FLAG_SET_SCREEN_TIME_APP)
+ prefs.screenTimeAppPackage = ""
+ prefs.screenTimeAppClassName = ""
+ prefs.screenTimeAppUser = ""
+ }
+
+ R.id.setDefaultLauncher -> {
+ prefs.hideSetDefaultLauncher = true
+ binding.setDefaultLauncher.visibility = View.GONE
+ if (viewModel.isOlauncherDefault.value != true) {
+ requireContext().showToast(R.string.set_as_default_launcher)
+ findNavController().navigate(R.id.action_mainFragment_to_settingsFragment)
+ }
+ }
}
return true
}
@@ -160,16 +185,15 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
}
viewModel.isOlauncherDefault.observe(viewLifecycleOwner, Observer {
if (it != true) {
- if (prefs.dailyWallpaper) {
+ if (prefs.dailyWallpaper && prefs.appTheme == AppCompatDelegate.MODE_NIGHT_YES) {
prefs.dailyWallpaper = false
viewModel.cancelWallpaperWorker()
}
prefs.homeBottomAlignment = false
setHomeAlignment()
}
- if (binding.firstRunTips.visibility == View.VISIBLE) return@Observer
- if (it) binding.setDefaultLauncher.visibility = View.GONE
- else binding.setDefaultLauncher.visibility = View.VISIBLE
+ if (binding.firstRunTips.isVisible) return@Observer
+ binding.setDefaultLauncher.isVisible = it.not() && prefs.hideSetDefaultLauncher.not()
})
viewModel.homeAppAlignment.observe(viewLifecycleOwner) {
setHomeAlignment(it)
@@ -177,6 +201,12 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
viewModel.toggleDateTime.observe(viewLifecycleOwner) {
populateDateTime()
}
+ viewModel.screenTimeValue.observe(viewLifecycleOwner) {
+ it?.let { binding.tvScreenTime.text = it }
+ }
+ viewModel.showRecentApps.observe(viewLifecycleOwner) {
+ binding.recents.performClick()
+ }
}
private fun initSwipeTouchListener() {
@@ -194,11 +224,15 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
private fun initClickListeners() {
binding.lock.setOnClickListener(this)
+ binding.recents.setOnClickListener(this)
binding.clock.setOnClickListener(this)
binding.date.setOnClickListener(this)
binding.clock.setOnLongClickListener(this)
binding.date.setOnLongClickListener(this)
binding.setDefaultLauncher.setOnClickListener(this)
+ binding.setDefaultLauncher.setOnLongClickListener(this)
+ binding.tvScreenTime.setOnClickListener(this)
+ binding.tvScreenTime.setOnLongClickListener(this)
}
private fun setHomeAlignment(horizontalGravity: Int = prefs.homeAlignment) {
@@ -233,70 +267,137 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
binding.date.text = dateText.replace(".,", ",")
}
+ @RequiresApi(Build.VERSION_CODES.Q)
+ private fun populateScreenTime() {
+ if (requireContext().appUsagePermissionGranted().not()) return
+
+ viewModel.getTodaysScreenTime()
+ binding.tvScreenTime.visibility = View.VISIBLE
+
+ val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ val horizontalMargin = if (isLandscape) 64.dpToPx() else 10.dpToPx()
+ val marginTop = if (isLandscape) {
+ if (prefs.dateTimeVisibility == Constants.DateTime.DATE_ONLY) 36.dpToPx() else 56.dpToPx()
+ } else {
+ if (prefs.dateTimeVisibility == Constants.DateTime.DATE_ONLY) 45.dpToPx() else 72.dpToPx()
+ }
+ val params = FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT
+ ).apply {
+ topMargin = marginTop
+ marginStart = horizontalMargin
+ marginEnd = horizontalMargin
+ gravity = if (prefs.homeAlignment == Gravity.END) Gravity.START else Gravity.END
+ }
+ binding.tvScreenTime.layoutParams = params
+ binding.tvScreenTime.setPadding(10.dpToPx())
+ }
+
private fun populateHomeScreen(appCountUpdated: Boolean) {
if (appCountUpdated) hideHomeApps()
populateDateTime()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ populateScreenTime()
+
val homeAppsNum = prefs.homeAppsNum
if (homeAppsNum == 0) return
binding.homeApp1.visibility = View.VISIBLE
- if (!setHomeAppText(binding.homeApp1, prefs.appName1, prefs.appPackage1, prefs.appUser1)) {
+ if (!setHomeAppText(binding.homeApp1, prefs.appName1, prefs.appPackage1, prefs.appUser1, prefs.isShortcut1, prefs.shortcutId1)) {
prefs.appName1 = ""
prefs.appPackage1 = ""
}
if (homeAppsNum == 1) return
binding.homeApp2.visibility = View.VISIBLE
- if (!setHomeAppText(binding.homeApp2, prefs.appName2, prefs.appPackage2, prefs.appUser2)) {
+ if (!setHomeAppText(binding.homeApp2, prefs.appName2, prefs.appPackage2, prefs.appUser2, prefs.isShortcut2, prefs.shortcutId2)) {
prefs.appName2 = ""
prefs.appPackage2 = ""
}
if (homeAppsNum == 2) return
binding.homeApp3.visibility = View.VISIBLE
- if (!setHomeAppText(binding.homeApp3, prefs.appName3, prefs.appPackage3, prefs.appUser3)) {
+ if (!setHomeAppText(binding.homeApp3, prefs.appName3, prefs.appPackage3, prefs.appUser3, prefs.isShortcut3, prefs.shortcutId3)) {
prefs.appName3 = ""
prefs.appPackage3 = ""
}
if (homeAppsNum == 3) return
binding.homeApp4.visibility = View.VISIBLE
- if (!setHomeAppText(binding.homeApp4, prefs.appName4, prefs.appPackage4, prefs.appUser4)) {
+ if (!setHomeAppText(binding.homeApp4, prefs.appName4, prefs.appPackage4, prefs.appUser4, prefs.isShortcut4, prefs.shortcutId4)) {
prefs.appName4 = ""
prefs.appPackage4 = ""
}
if (homeAppsNum == 4) return
binding.homeApp5.visibility = View.VISIBLE
- if (!setHomeAppText(binding.homeApp5, prefs.appName5, prefs.appPackage5, prefs.appUser5)) {
+ if (!setHomeAppText(binding.homeApp5, prefs.appName5, prefs.appPackage5, prefs.appUser5, prefs.isShortcut5, prefs.shortcutId5)) {
prefs.appName5 = ""
prefs.appPackage5 = ""
}
if (homeAppsNum == 5) return
binding.homeApp6.visibility = View.VISIBLE
- if (!setHomeAppText(binding.homeApp6, prefs.appName6, prefs.appPackage6, prefs.appUser6)) {
+ if (!setHomeAppText(binding.homeApp6, prefs.appName6, prefs.appPackage6, prefs.appUser6, prefs.isShortcut6, prefs.shortcutId6)) {
prefs.appName6 = ""
prefs.appPackage6 = ""
}
if (homeAppsNum == 6) return
binding.homeApp7.visibility = View.VISIBLE
- if (!setHomeAppText(binding.homeApp7, prefs.appName7, prefs.appPackage7, prefs.appUser7)) {
+ if (!setHomeAppText(binding.homeApp7, prefs.appName7, prefs.appPackage7, prefs.appUser7, prefs.isShortcut7, prefs.shortcutId7)) {
prefs.appName7 = ""
prefs.appPackage7 = ""
}
if (homeAppsNum == 7) return
binding.homeApp8.visibility = View.VISIBLE
- if (!setHomeAppText(binding.homeApp8, prefs.appName8, prefs.appPackage8, prefs.appUser8)) {
+ if (!setHomeAppText(binding.homeApp8, prefs.appName8, prefs.appPackage8, prefs.appUser8, prefs.isShortcut8, prefs.shortcutId8)) {
prefs.appName8 = ""
prefs.appPackage8 = ""
}
}
- private fun setHomeAppText(textView: TextView, appName: String, packageName: String, userString: String): Boolean {
+ private fun setHomeAppText(
+ textView: TextView,
+ appName: String,
+ packageName: String,
+ userString: String,
+ isShortcut: Boolean,
+ shortcutId: String?,
+ ): Boolean {
+ // Get user handle for the app/shortcut
+ val userHandle = getUserHandleFromString(requireContext(), userString)
+
+ // If it's a shortcut, verify it still exists
+ if (isShortcut) {
+ val launcherApps = requireContext().getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
+
+ // Query for the specific shortcut
+ val query = LauncherApps.ShortcutQuery().apply {
+ setPackage(packageName)
+ setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
+ }
+
+ try {
+ val shortcuts = launcherApps.getShortcuts(query, userHandle)
+ // Check if our shortcut still exists
+ if (shortcuts?.any { it.id == shortcutId } == true) {
+ textView.text = appName
+ return true
+ }
+ textView.text = ""
+ return false
+ } catch (e: Exception) {
+ e.printStackTrace()
+ textView.text = ""
+ return false
+ }
+ }
+
+ // Regular app check
if (isPackageInstalled(requireContext(), packageName, userString)) {
textView.text = appName
return true
@@ -316,29 +417,103 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
binding.homeApp8.visibility = View.GONE
}
- private fun homeAppClicked(location: Int) {
- if (prefs.getAppName(location).isEmpty()) showLongPressToast()
- else launchApp(
- prefs.getAppName(location),
- prefs.getAppPackage(location),
- prefs.getAppActivityClassName(location),
- prefs.getAppUser(location)
+ private fun launchAppOrShortcut(
+ appName: String,
+ packageName: String,
+ activityClassName: String?,
+ shortcutId: String?,
+ isShortcut: Boolean,
+ userString: String,
+ fallback: (() -> Unit)? = null,
+ ) {
+ if (appName.isEmpty()) {
+ showLongPressToast()
+ return
+ }
+ if (isShortcut && !shortcutId.isNullOrEmpty()) {
+ launchShortcut(
+ packageName = packageName,
+ shortcutId = shortcutId,
+ shortcutLabel = appName,
+ userString = userString
+ )
+ } else if (packageName.isNotEmpty()) {
+ launchApp(
+ appName = appName,
+ packageName = packageName,
+ activityClassName = activityClassName,
+ userString = userString
+ )
+ } else {
+ fallback?.invoke()
+ }
+ }
+
+ private fun launchShortcut(shortcutId: String, packageName: String, shortcutLabel: String, userString: String) {
+ viewModel.selectedApp(
+ AppModel.PinnedShortcut(
+ shortcutId = shortcutId,
+ appLabel = shortcutLabel,
+ user = getUserHandleFromString(requireContext(), userString),
+ key = null,
+ appPackage = packageName,
+ isNew = false,
+ ),
+ Constants.FLAG_LAUNCH_APP
)
}
private fun launchApp(appName: String, packageName: String, activityClassName: String?, userString: String) {
viewModel.selectedApp(
- AppModel(
- appName,
- null,
- packageName,
- activityClassName,
- getUserHandleFromString(requireContext(), userString)
+ AppModel.App(
+ appLabel = appName,
+ key = null,
+ appPackage = packageName,
+ activityClassName = activityClassName,
+ isNew = false,
+ user = getUserHandleFromString(requireContext(), userString)
),
Constants.FLAG_LAUNCH_APP
)
}
+ private fun homeAppClicked(location: Int) {
+ launchAppOrShortcut(
+ appName = prefs.getAppName(location),
+ packageName = prefs.getAppPackage(location),
+ activityClassName = prefs.getAppActivityClassName(location),
+ shortcutId = prefs.getShortcutId(location),
+ isShortcut = prefs.getIsShortcut(location),
+ userString = prefs.getAppUser(location)
+ )
+ }
+
+ private fun openSwipeRightApp() {
+ if (!prefs.swipeRightEnabled) return
+ launchAppOrShortcut(
+ appName = prefs.appNameSwipeRight,
+ packageName = prefs.appPackageSwipeRight,
+ activityClassName = prefs.appActivityClassNameRight,
+ shortcutId = prefs.shortcutIdSwipeRight,
+ isShortcut = prefs.isShortcutSwipeRight,
+ userString = prefs.appUserSwipeRight,
+ fallback = { openDialerApp(requireContext()) }
+ )
+ }
+
+ private fun openSwipeLeftApp() {
+ if (!prefs.swipeLeftEnabled) return
+ launchAppOrShortcut(
+ appName = prefs.appNameSwipeLeft,
+ packageName = prefs.appPackageSwipeLeft,
+ activityClassName = prefs.appActivityClassNameSwipeLeft,
+ shortcutId = prefs.shortcutIdSwipeLeft,
+ isShortcut = prefs.isShortcutSwipeLeft,
+ userString = prefs.appUserSwipeLeft,
+ fallback = { openCameraApp(requireContext()) }
+ )
+ }
+
private fun showAppList(flag: Int, rename: Boolean = false, includeHiddenApps: Boolean = false) {
viewModel.getAppList(includeHiddenApps)
try {
@@ -368,30 +543,6 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
}
}
- private fun openSwipeRightApp() {
- if (!prefs.swipeRightEnabled) return
- if (prefs.appPackageSwipeRight.isNotEmpty())
- launchApp(
- prefs.appNameSwipeRight,
- prefs.appPackageSwipeRight,
- prefs.appActivityClassNameRight,
- prefs.appUserSwipeRight
- )
- else openDialerApp(requireContext())
- }
-
- private fun openSwipeLeftApp() {
- if (!prefs.swipeLeftEnabled) return
- if (prefs.appPackageSwipeLeft.isNotEmpty())
- launchApp(
- prefs.appNameSwipeLeft,
- prefs.appPackageSwipeLeft,
- prefs.appActivityClassNameSwipeLeft,
- prefs.appUserSwipeLeft
- )
- else openCameraApp(requireContext())
- }
-
private fun lockPhone() {
requireActivity().runOnUiThread {
try {
@@ -438,6 +589,37 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
requireActivity().recreate()
}
+ private fun openScreenTimeDigitalWellbeing() {
+ if (prefs.screenTimeAppPackage.isNotBlank()) {
+ launchApp(
+ "Screen Time",
+ prefs.screenTimeAppPackage,
+ prefs.screenTimeAppClassName,
+ prefs.screenTimeAppUser
+ )
+ return
+ }
+ val intent = Intent()
+ try {
+ intent.setClassName(
+ Constants.DIGITAL_WELLBEING_PACKAGE_NAME,
+ Constants.DIGITAL_WELLBEING_ACTIVITY
+ )
+ startActivity(intent)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ try {
+ intent.setClassName(
+ Constants.DIGITAL_WELLBEING_SAMSUNG_PACKAGE_NAME,
+ Constants.DIGITAL_WELLBEING_SAMSUNG_ACTIVITY
+ )
+ startActivity(intent)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
private fun showLongPressToast() = requireContext().showToast(getString(R.string.long_press_to_select_app))
private fun textOnClick(view: View) = onClick(view)
@@ -478,9 +660,10 @@ class HomeFragment : Fragment(), View.OnClickListener, View.OnLongClickListener
override fun onDoubleClick() {
super.onDoubleClick()
+ if (!prefs.lockModeOn) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
binding.lock.performClick()
- else if (prefs.lockModeOn)
+ else
lockPhone()
}
diff --git a/app/src/main/java/app/olauncher/ui/SettingsFragment.kt b/app/src/main/java/app/olauncher/ui/SettingsFragment.kt
index 531e2db37..658913c24 100644
--- a/app/src/main/java/app/olauncher/ui/SettingsFragment.kt
+++ b/app/src/main/java/app/olauncher/ui/SettingsFragment.kt
@@ -27,11 +27,13 @@ import app.olauncher.data.Constants
import app.olauncher.data.Prefs
import app.olauncher.databinding.FragmentSettingsBinding
import app.olauncher.helper.animateAlpha
+import app.olauncher.helper.appUsagePermissionGranted
import app.olauncher.helper.getColorFromAttr
import app.olauncher.helper.isAccessServiceEnabled
import app.olauncher.helper.isDarkThemeOn
+import app.olauncher.helper.isEinkDisplay
import app.olauncher.helper.isOlauncherDefault
-import app.olauncher.helper.isPackageInstalled
+import app.olauncher.helper.isTablet
import app.olauncher.helper.openAppInfo
import app.olauncher.helper.openUrl
import app.olauncher.helper.rateApp
@@ -49,6 +51,7 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
private var _binding: FragmentSettingsBinding? = null
private val binding get() = _binding!!
+ private val showPentastic = System.currentTimeMillis() % 2 == 0L
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentSettingsBinding.inflate(inflater, container, false)
@@ -63,14 +66,16 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
} ?: throw Exception("Invalid Activity")
viewModel.isOlauncherDefault()
- deviceManager = context?.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
+ deviceManager = requireContext().getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
componentName = ComponentName(requireContext(), DeviceAdmin::class.java)
checkAdminPermission()
binding.homeAppsNum.text = prefs.homeAppsNum.toString()
- populateDigitalWellbeing()
+ populateProMessage()
populateKeyboardText()
+ populateScreenTimeOnOff()
populateLockSettings()
+ populateHomeButtonRecents()
populateWallpaperText()
populateAppThemeText()
populateTextSize()
@@ -82,6 +87,9 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
populateActionHints()
initClickListeners()
initObservers()
+
+ if (showPentastic)
+ binding.footer.text = getText(R.string.new_app_minimal_todo_lists)
}
override fun onClick(view: View) {
@@ -89,17 +97,23 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
binding.dateTimeSelectLayout.visibility = View.GONE
binding.appThemeSelectLayout.visibility = View.GONE
binding.swipeDownSelectLayout.visibility = View.GONE
- binding.textSizesLayout.visibility = View.GONE
+ if (view.id != R.id.textSizeMinus && view.id != R.id.textSizePlus) {
+ if (binding.textSizesLayout.isVisible) {
+ binding.textSizesLayout.visibility = View.GONE
+ applyTextSizeScale()
+ }
+ }
if (view.id != R.id.alignmentBottom)
binding.alignmentSelectLayout.visibility = View.GONE
when (view.id) {
R.id.olauncherHiddenApps -> showHiddenApps()
- R.id.olauncherPro -> requireContext().openUrl(Constants.URL_OLAUNCHER_PRO)
- R.id.digitalWellbeing -> viewModel.showDialog.postValue(Constants.Dialog.DIGITAL_WELLBEING)
+ R.id.moreFeatures -> viewModel.showDialog.postValue(Constants.Dialog.PRO_MESSAGE)
+ R.id.screenTimeOnOff -> viewModel.showDialog.postValue(Constants.Dialog.DIGITAL_WELLBEING)
R.id.appInfo -> openAppInfo(requireContext(), Process.myUserHandle(), BuildConfig.APPLICATION_ID)
R.id.setLauncher -> viewModel.resetLauncherLiveData.call()
R.id.toggleLock -> toggleLockMode()
+ R.id.homeButtonRecents -> toggleHomeButtonRecents()
R.id.autoShowKeyboard -> toggleKeyboardText()
R.id.homeAppsNum -> binding.appsNumSelectLayout.visibility = View.VISIBLE
R.id.dailyWallpaperUrl -> requireContext().openUrl(prefs.dailyWallpaperUrl)
@@ -135,13 +149,8 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
R.id.maxApps7 -> updateHomeAppsNum(7)
R.id.maxApps8 -> updateHomeAppsNum(8)
- R.id.textSize1 -> updateTextSizeScale(Constants.TextSize.ONE)
- R.id.textSize2 -> updateTextSizeScale(Constants.TextSize.TWO)
- R.id.textSize3 -> updateTextSizeScale(Constants.TextSize.THREE)
- R.id.textSize4 -> updateTextSizeScale(Constants.TextSize.FOUR)
- R.id.textSize5 -> updateTextSizeScale(Constants.TextSize.FIVE)
- R.id.textSize6 -> updateTextSizeScale(Constants.TextSize.SIX)
- R.id.textSize7 -> updateTextSizeScale(Constants.TextSize.SEVEN)
+ R.id.textSizeMinus -> adjustTextSizePreview(-0.1f)
+ R.id.textSizePlus -> adjustTextSizePreview(0.1f)
R.id.swipeLeftApp -> showAppListIfEnabled(Constants.FLAG_SET_SWIPE_LEFT_APP)
R.id.swipeRightApp -> showAppListIfEnabled(Constants.FLAG_SET_SWIPE_RIGHT_APP)
@@ -163,17 +172,16 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
R.id.twitter -> requireContext().openUrl(Constants.URL_TWITTER_TANUJ)
R.id.github -> requireContext().openUrl(Constants.URL_OLAUNCHER_GITHUB)
R.id.privacy -> requireContext().openUrl(Constants.URL_OLAUNCHER_PRIVACY)
- R.id.footer -> requireContext().openUrl(Constants.URL_PLAY_STORE_DEV)
+ R.id.footer -> {
+ requireContext().openUrl(
+ if (showPentastic) Constants.URL_PENTASTIC else Constants.URL_NTS
+ )
+ }
}
}
override fun onLongClick(view: View): Boolean {
when (view.id) {
- R.id.digitalWellbeing -> {
- prefs.hideDigitalWellbeing = true
- binding.digitalWellbeing.visibility = View.GONE
- }
-
R.id.alignment -> {
prefs.appLabelAlignment = prefs.homeAlignment
findNavController().navigate(R.id.action_settingsFragment_to_appListFragment)
@@ -199,11 +207,12 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
binding.appInfo.setOnClickListener(this)
binding.setLauncher.setOnClickListener(this)
binding.aboutOlauncher.setOnClickListener(this)
- binding.olauncherPro.setOnClickListener(this)
- binding.digitalWellbeing.setOnClickListener(this)
+ binding.moreFeatures.setOnClickListener(this)
binding.autoShowKeyboard.setOnClickListener(this)
binding.toggleLock.setOnClickListener(this)
+ binding.homeButtonRecents.setOnClickListener(this)
binding.homeAppsNum.setOnClickListener(this)
+ binding.screenTimeOnOff.setOnClickListener(this)
binding.dailyWallpaperUrl.setOnClickListener(this)
binding.dailyWallpaper.setOnClickListener(this)
binding.alignment.setOnClickListener(this)
@@ -247,15 +256,9 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
binding.maxApps7.setOnClickListener(this)
binding.maxApps8.setOnClickListener(this)
- binding.textSize1.setOnClickListener(this)
- binding.textSize2.setOnClickListener(this)
- binding.textSize3.setOnClickListener(this)
- binding.textSize4.setOnClickListener(this)
- binding.textSize5.setOnClickListener(this)
- binding.textSize6.setOnClickListener(this)
- binding.textSize7.setOnClickListener(this)
+ binding.textSizeMinus.setOnClickListener(this)
+ binding.textSizePlus.setOnClickListener(this)
- binding.digitalWellbeing.setOnLongClickListener(this)
binding.dailyWallpaper.setOnLongClickListener(this)
binding.alignment.setOnLongClickListener(this)
binding.appThemeText.setOnLongClickListener(this)
@@ -266,12 +269,13 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
private fun initObservers() {
if (prefs.firstSettingsOpen) {
+ viewModel.showDialog.postValue(Constants.Dialog.ABOUT)
prefs.firstSettingsOpen = false
}
viewModel.isOlauncherDefault.observe(viewLifecycleOwner) {
if (it) {
binding.setLauncher.text = getString(R.string.change_default_launcher)
- prefs.toShowHintCounter = prefs.toShowHintCounter + 1
+ prefs.toShowHintCounter += 1
}
}
viewModel.homeAppAlignment.observe(viewLifecycleOwner) {
@@ -393,11 +397,11 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
private fun toggleLockMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- toggleAccessibilityVisibility(true)
- if (prefs.lockModeOn) {
- prefs.lockModeOn = false
- removeActiveAdmin()
+ if (!prefs.lockModeOn && !isAccessServiceEnabled(requireContext())) {
+ toggleAccessibilityVisibility(true)
+ return
}
+ prefs.lockModeOn = !prefs.lockModeOn
} else {
val isAdmin: Boolean = deviceManager.isAdminActive(componentName)
if (isAdmin) {
@@ -426,7 +430,13 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
}
private fun removeWallpaper() {
- setPlainWallpaper(requireContext(), android.R.color.black)
+ if (requireContext().isEinkDisplay()) {
+ prefs.appTheme = AppCompatDelegate.MODE_NIGHT_NO
+ setPlainWallpaper(requireContext(), android.R.color.white)
+ } else {
+ prefs.appTheme = AppCompatDelegate.MODE_NIGHT_YES
+ setPlainWallpaper(requireContext(), android.R.color.black)
+ }
if (!prefs.dailyWallpaper) return
prefs.dailyWallpaper = false
populateWallpaperText()
@@ -434,7 +444,7 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
}
private fun toggleDailyWallpaperUpdate() {
- if (prefs.dailyWallpaper.not() && viewModel.isOlauncherDefault.value == false) {
+ if (prefs.dailyWallpaper.not() && prefs.appTheme == AppCompatDelegate.MODE_NIGHT_YES && viewModel.isOlauncherDefault.value == false) {
requireContext().showToast(R.string.set_as_default_launcher_first)
return
}
@@ -460,9 +470,27 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
viewModel.refreshHome(true)
}
- private fun updateTextSizeScale(sizeScale: Float) {
- if (prefs.textSizeScale == sizeScale) return
- prefs.textSizeScale = sizeScale
+ private var pendingTextSizeScale: Float = -1f
+
+ private fun adjustTextSizePreview(delta: Float) {
+ val maxScale = if (isTablet(requireContext())) 2.0f else 1.5f
+ val current = if (pendingTextSizeScale > 0) pendingTextSizeScale else prefs.textSizeScale
+ val newScale = Math.round((current + delta) * 10f) / 10f
+ val clamped = newScale.coerceIn(0.5f, maxScale)
+ if (clamped == current) return
+ pendingTextSizeScale = clamped
+ val formatted = String.format("%.1f", clamped)
+ binding.textSizeValue.text = formatted
+ binding.textSizeCurrent.text = formatted
+ }
+
+ private fun applyTextSizeScale() {
+ if (pendingTextSizeScale < 0 || prefs.textSizeScale == pendingTextSizeScale) {
+ pendingTextSizeScale = -1f
+ return
+ }
+ prefs.textSizeScale = pendingTextSizeScale
+ pendingTextSizeScale = -1f
requireActivity().recreate()
}
@@ -513,16 +541,16 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
}
private fun populateTextSize() {
- binding.textSizeValue.text = when (prefs.textSizeScale) {
- Constants.TextSize.ONE -> 1
- Constants.TextSize.TWO -> 2
- Constants.TextSize.THREE -> 3
- Constants.TextSize.FOUR -> 4
- Constants.TextSize.FIVE -> 5
- Constants.TextSize.SIX -> 6
- Constants.TextSize.SEVEN -> 7
- else -> "--"
- }.toString()
+ val formatted = String.format("%.1f", prefs.textSizeScale)
+ binding.textSizeValue.text = formatted
+ binding.textSizeCurrent.text = formatted
+ }
+
+ private fun populateScreenTimeOnOff() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ if (requireContext().appUsagePermissionGranted()) binding.screenTimeOnOff.text = getString(R.string.on)
+ else binding.screenTimeOnOff.text = getString(R.string.off)
+ } else binding.screenTimeLayout.visibility = View.GONE
}
private fun populateKeyboardText() {
@@ -556,10 +584,26 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
else getString(R.string.bottom_off)
}
+ private fun toggleHomeButtonRecents() {
+ if (!prefs.homeButtonShowRecents && !isAccessServiceEnabled(requireContext())) {
+ toggleAccessibilityVisibility(true)
+ return
+ }
+ prefs.homeButtonShowRecents = !prefs.homeButtonShowRecents
+ populateHomeButtonRecents()
+ }
+
+ private fun populateHomeButtonRecents() {
+ binding.homeButtonRecents.text = getString(
+ if (prefs.homeButtonShowRecents && isAccessServiceEnabled(requireContext())) R.string.on
+ else R.string.off
+ )
+ }
+
private fun populateLockSettings() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
binding.toggleLock.text = getString(
- if (isAccessServiceEnabled(requireContext())) R.string.on
+ if (prefs.lockModeOn && isAccessServiceEnabled(requireContext())) R.string.on
else R.string.off
)
} else {
@@ -592,11 +636,11 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
binding.swipeRightApp.setTextColor(requireContext().getColorFromAttr(R.attr.primaryColorTrans50))
}
- private fun populateDigitalWellbeing() {
- binding.digitalWellbeing.isVisible = requireContext().isPackageInstalled(Constants.DIGITAL_WELLBEING_PACKAGE_NAME).not()
- && requireContext().isPackageInstalled(Constants.DIGITAL_WELLBEING_SAMSUNG_PACKAGE_NAME).not()
- && prefs.hideDigitalWellbeing.not()
- }
+// private fun populateDigitalWellbeing() {
+// binding.digitalWellbeing.isVisible = requireContext().isPackageInstalled(Constants.DIGITAL_WELLBEING_PACKAGE_NAME).not()
+// && requireContext().isPackageInstalled(Constants.DIGITAL_WELLBEING_SAMSUNG_PACKAGE_NAME).not()
+// && prefs.hideDigitalWellbeing.not()
+// }
private fun showAppListIfEnabled(flag: Int) {
if ((flag == Constants.FLAG_SET_SWIPE_LEFT_APP) and !prefs.swipeLeftEnabled) {
@@ -615,11 +659,20 @@ class SettingsFragment : Fragment(), View.OnClickListener, View.OnLongClickListe
}
private fun populateActionHints() {
+ if (prefs.aboutClicked.not())
+ binding.aboutOlauncher.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_info, 0)
if (viewModel.isOlauncherDefault.value != true) return
- if (prefs.rateClicked.not() && prefs.toShowHintCounter > Constants.HINT_RATE_US && prefs.toShowHintCounter < Constants.HINT_RATE_US + 10)
+ if (prefs.rateClicked.not() && prefs.toShowHintCounter > Constants.HINT_RATE_US && prefs.toShowHintCounter < Constants.HINT_RATE_US + 100)
binding.rate.setCompoundDrawablesWithIntrinsicBounds(0, android.R.drawable.arrow_down_float, 0, 0)
}
+ private fun populateProMessage() {
+ if (prefs.proMessageShown.not() && prefs.userState == Constants.UserState.SHARE) {
+ prefs.proMessageShown = true
+ viewModel.showDialog.postValue(Constants.Dialog.PRO_MESSAGE)
+ }
+ }
+
override fun onDestroyView() {
super.onDestroyView()
_binding = null
diff --git a/app/src/main/res/drawable/bg_shade_circular.xml b/app/src/main/res/drawable/bg_shade_circular.xml
new file mode 100644
index 000000000..f0b124240
--- /dev/null
+++ b/app/src/main/res/drawable/bg_shade_circular.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml
new file mode 100644
index 000000000..53bb39ee2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_check.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml
index e5c82ddda..022cd025f 100644
--- a/app/src/main/res/drawable/ic_close.xml
+++ b/app/src/main/res/drawable/ic_close.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/app/src/main/res/drawable/rounded_rectangle_dark.xml b/app/src/main/res/drawable/rounded_rectangle_dark.xml
index 9138e962e..4c0758a54 100644
--- a/app/src/main/res/drawable/rounded_rectangle_dark.xml
+++ b/app/src/main/res/drawable/rounded_rectangle_dark.xml
@@ -4,6 +4,6 @@
diff --git a/app/src/main/res/layout-land/fragment_app_drawer.xml b/app/src/main/res/layout-land/fragment_app_drawer.xml
index 4749e24b4..2bf46f995 100644
--- a/app/src/main/res/layout-land/fragment_app_drawer.xml
+++ b/app/src/main/res/layout-land/fragment_app_drawer.xml
@@ -1,16 +1,22 @@
-
+ android:background="?attr/primaryShadeDarkColor"
+ android:orientation="vertical">
+
+
@@ -27,7 +33,7 @@
app:closeIcon="@null"
app:iconifiedByDefault="false"
app:queryBackground="@null"
- app:queryHint="__"
+ app:queryHint="___"
app:searchIcon="@null"
app:theme="@style/AppSearchText" />
@@ -45,33 +51,21 @@
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/fragment_home.xml b/app/src/main/res/layout-land/fragment_home.xml
index 0165e156d..ad48c44b6 100644
--- a/app/src/main/res/layout-land/fragment_home.xml
+++ b/app/src/main/res/layout-land/fragment_home.xml
@@ -15,6 +15,13 @@
android:layout_height="1dp"
android:contentDescription="@string/lock_layout_description" />
+
+
+
+
+
+
@@ -172,6 +194,6 @@
android:layout_marginBottom="56dp"
android:ellipsize="end"
android:maxLines="1"
- android:text="@string/set_as_default_launcher"
+ android:text="@string/set_as_default_launcher_u"
android:visibility="gone" />
\ No newline at end of file
diff --git a/app/src/main/res/layout-land/fragment_settings.xml b/app/src/main/res/layout-land/fragment_settings.xml
index 0f513133a..5bd84c2b2 100644
--- a/app/src/main/res/layout-land/fragment_settings.xml
+++ b/app/src/main/res/layout-land/fragment_settings.xml
@@ -4,14 +4,14 @@
android:id="@+id/mainActivityLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:animateLayoutChanges="true">
+ android:animateLayoutChanges="true"
+ android:background="?attr/primaryShadeDarkColor">
-
-
+ android:text="@string/more_features" />
@@ -119,7 +109,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
- android:text="@string/appearance"
+ android:text="@string/home_screen"
android:textColor="?attr/primaryColor" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tools:text="1.0" />
-
+
-
-
-
-
-
-
-
-
+ android:text="\u2212" />
+ tools:text="1.0" />
+ android:text="+" />
@@ -873,7 +875,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
- android:text="@string/double_tap_to_lock_screen"
+ android:text="@string/gestures"
android:textColor="?attr/primaryColor" />
+
+
+
+
+
+
+
+ android:text="@string/new_app_note_safe" />
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index de1dc4cfb..12a0fb259 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -53,6 +53,7 @@
android:layout_gravity="end|center_vertical"
android:alpha="0.5"
android:background="?attr/selectableItemBackgroundBorderless"
+ android:contentDescription="@string/close"
android:padding="6dp"
android:src="@drawable/ic_close" />
diff --git a/app/src/main/res/layout/adapter_app_drawer.xml b/app/src/main/res/layout/adapter_app_drawer.xml
index 9fe11754a..9168741fb 100644
--- a/app/src/main/res/layout/adapter_app_drawer.xml
+++ b/app/src/main/res/layout/adapter_app_drawer.xml
@@ -12,16 +12,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
- android:paddingHorizontal="20dp"
+ android:paddingHorizontal="24dp"
android:paddingVertical="@dimen/app_padding_vertical"
android:text="@string/app" />
+
+
-
+ android:textSize="12sp"
+ app:drawableTopCompat="@drawable/ic_check" />
+
diff --git a/app/src/main/res/layout/adapter_private_space_header.xml b/app/src/main/res/layout/adapter_private_space_header.xml
new file mode 100644
index 000000000..992ca2cac
--- /dev/null
+++ b/app/src/main/res/layout/adapter_private_space_header.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_app_drawer.xml b/app/src/main/res/layout/fragment_app_drawer.xml
index 81e4422cb..7935654ed 100644
--- a/app/src/main/res/layout/fragment_app_drawer.xml
+++ b/app/src/main/res/layout/fragment_app_drawer.xml
@@ -1,15 +1,21 @@
-
+ android:background="?attr/primaryShadeDarkColor"
+ android:orientation="vertical">
+
+
@@ -17,16 +23,17 @@
android:id="@+id/search"
android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:layout_marginEnd="16dp"
android:layout_weight="1"
android:gravity="end"
android:imeOptions="actionSearch"
- android:paddingHorizontal="3dp"
+ android:paddingHorizontal="7dp"
android:paddingVertical="0dp"
android:textCursorDrawable="@null"
app:closeIcon="@null"
app:iconifiedByDefault="false"
app:queryBackground="@null"
- app:queryHint="___"
+ app:queryHint=" ___"
app:searchIcon="@null"
app:theme="@style/AppSearchText" />
@@ -44,32 +51,20 @@
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index 3cbd920c0..c1dd3de1e 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -15,12 +15,19 @@
android:layout_height="1dp"
android:contentDescription="@string/lock_layout_description" />
+
+
+
+
+
+
@@ -155,7 +178,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
- android:layout_marginStart="20dp"
+ android:layout_marginStart="24dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="56dp"
android:text="@string/swipe_up_for_apps"
@@ -173,6 +196,6 @@
android:layout_marginBottom="56dp"
android:ellipsize="end"
android:maxLines="1"
- android:text="@string/set_as_default_launcher"
+ android:text="@string/set_as_default_launcher_u"
android:visibility="gone" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
index f4d41425f..a461d0eab 100644
--- a/app/src/main/res/layout/fragment_settings.xml
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
- android:background="?attr/primaryColorInverseTrans50">
+ android:background="?attr/primaryShadeDarkColor">
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="4dp">
-
-
+ android:text="@string/more_features" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tools:text="1.0" />
-
+
-
-
-
-
-
-
-
-
+ android:text="\u2212" />
+ tools:text="1.0" />
+ android:text="+" />
@@ -729,8 +739,8 @@
@@ -903,12 +914,38 @@
android:padding="10dp"
android:text="@string/off" />
+
+
+
+
+
+
+
@@ -971,7 +1008,7 @@
android:gravity="center"
android:letterSpacing="0.03"
android:padding="12dp"
- android:text="@string/digital_minimalism" />
+ android:text="@string/new_app_note_safe" />
@@ -1005,7 +1042,7 @@
style="@style/TextLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/double_tap_lock"
+ android:text="@string/gestures"
android:textColor="?attr/primaryColor" />
+
- App
+ تطبيق
تعيين كمشغل افتراضي
+ تعيين كمشغل افتراضي
تعيين كمشغل افتراضي
صفر
واحد
@@ -13,79 +14,81 @@
سبعة
ثمانية
نصيحة: ابدأ في كتابة اسم التطبيق لتشغيله تلقائيًا
- تمام
+ حسنًا
رائع!
- نصيحة: ابدأ الكتابة لخيار التطبيق RENAME
- سيتم تحديث ورق الحائط الخاص بك قريبًا
+ نصيحة: ابدأ الكتابة بتغيير اسم التطبيق
+ سيتم تحديث الخلفية الخاصة بك قريبًا
Olauncher ليس المشغل الافتراضي. قد يفشل التحديث اليومي للخلفية.
- اضغط لفترة طويلة للتمكين
+ اضغط مطولًا للتمكين
لا توجد تطبيقات مخفية
تطبيقات مخفية
- انتقاد اليسار تعطيل التطبيق
- انتقاد الحق التطبيق تعطيل
- ملاحظة: اضغط على \"Olauncher\" أعلى صفحة الإعدادات هذه للعثور على تطبيقاتك المخفية. المزيد من التفاصيل متوفرة في صفحة \"حول\" أدناه.
+ التمرير لليسار معطّل
+ التمرير لليمين معطّل
+ ملاحظة: اضغط على \"Olauncher\" أعلى الصفحة للعثور على تطبيقاتك المخفية. المزيد من التفاصيل متوفرة في صفحة \"حول\" أدناه.
اسحب لأسفل من أجل
يبحث
إشعارات
- يرجى تشغيل أو إيقاف تشغيل خدمة الوصول في Olauncher في إعدادات جهازك لتمكين هذه الميزة أو تعطيلها. يرجى ملاحظة أن هذه الخدمة تُستخدم فقط للسماح لك بإيقاف تشغيل شاشة جهازك باستخدام إيماءة النقر المزدوج. خدمة الوصول لدينا لا تجمع أو تشارك أي بيانات.
+ يرجى تشغيل خدمة الوصول في Olauncher في إعدادات جهازك لتمكين إيماءة النقر المزدوج للقفل أو زر الشاشة الرئيسية لعرض التطبيقات الأخيرة.
+ \n\nيرجى ملاحظة أن هذه الخدمة تُستخدم فقط لهذه الإيماءات. خدمة الوصول لدينا لا تجمع أو تشارك أي بيانات.
يقبل
إذن
- انقر نقرًا مزدوجًا للقفل
- يُمكَِن
- إبطال
+ النقر المزدوج للقفل
+ زر الرئيسية للتطبيقات الأخيرة
+ تشغيل
+ إيقاف
لا يعمل؟
معلومات
يمسح
حجم الخط
- ممكن
- عاجز
- يرجى تشغيل النقر المزدوج للقفل
+ ممكّن
+ مُطفأ
+ يرجى تشغيل النقر المزدوج لفتح القفل
فشل Olauncher في قفل الجهاز. يرجى التحقق من إعدادات التطبيق الخاص بك.
- اضغط لفترة طويلة لتحديد التطبيق
- تعذر فتح معلومات التطبيق
- نسخ
- تطبيق النظام ، لا يمكن الحذف
- اكتب اسم تطبيق جديد أولاً
+ اضغط مطوّلًا لاختيار التطبيق
+ تعذر فتح التطبيق
+ نُسخ
+ ﻻ يمكن حذف تطبيقات النظام
+ اكتب اسمًا جديدًا للتطبيق أولاً
يعرض
يخفي
لون الخط
- انقر نقرًا مزدوجًا للقفل
+ انقر مرتين لقفل الشاشة
يعد Olauncher باستخدام إذن قفل الشاشة بشكل مسؤول. لا يقوم Olauncher بجمع أو مشاركة أي بيانات.
- عن
- عن
- على
+ حول
+ ﻻ
+ نعم
أسود
أبيض
تغيير المشغل الافتراضي
خصوصية
- يشارك
- يشارك
- معدل
- معدل
- يتبع
- بريد إلكتروني
- يتبرع
- 1. مرر لأعلى لجميع التطبيقات\n2. اضغط لفترة طويلة في أي مكان للإعدادات
- تطبيقات على الشاشة الرئيسية
- تطبيقات على الشاشة الرئيسية
- غادر
+ شارك
+ يشارك اﻵن
+ قيّم
+ قيّم اﻵن
+ تابع
+ بريد
+ تبرّع
+ 1. مرر لأعلى لجميع التطبيقات\n2. اضغط مطوّلًا في أي مكان للإعدادات
+ التطبيقات على الشاشة الرئيسية
+ تحديث الخلفية يوميّا
+ يسار
يمين
مركز
محاذاة التطبيق
- انتقاد اليسار التطبيق
- انتقد التطبيق الصحيح
- لوحة مفاتيح العرض التلقائي
+ افتح عند التمرير لليسار
+ افتح عند التمرير لليمين
+ العرض التلقائي للوحة المفاتيح
يخفي
أظهر شريط الحالة
إعادة تسمية
إظهار التاريخ والوقت
- مظهر
+ المظهر
إيماءات
- وضع الموضوع
+ النمط
نظام
مظلم
- ضوء
- تحتوي صفحة \"حول\" في الأسفل على إجابات لجميع أسئلتك. لا تنسى. شكرًا لك.
+ مضيء
+ مرحبًا بك في الإعدادات!\n\nتذكر، إذا كان لديك أي سؤال، فإن صفحة "حول" لدينا تحتوي على الإجابة.
مرحبًا ، لن ترى هذه الرسالة مرة أخرى.
\n\nإذا كنت تحب Olauncher ، فهل يمكنك أن تعطينا تصنيف 5 نجوم؟ يساعدنا ذلك على تحسين تصنيفنا ويجعلنا سعداء. شكرًا لك!
مرحبا، نحن بحاجة لمساعدتكم! في العام الماضي تجاوزنا مليون عملية تنزيل. نريد هذا العام أن نصل إلى مليون آخرين ولكن لا يمكننا القيام بذلك بمفردنا.
@@ -93,7 +96,13 @@
مرحبًا ، لوحة المفاتيح هي أسرع طريقة للعثور على التطبيقات وتشغيلها في Olauncher. هل يمكننا أن نطلب منك الاحتفاظ بهذا الأمر لبضعة أيام أخرى ثم اتخاذ القرار؟ الاختيار دائما لك.
إذا كنت تريد تعيين حدود زمنية لتطبيقات معينة، فيمكن أن تساعدك ميزة الرفاهية الرقمية في Android على القيام بذلك.
\n\nيرجى البحث عن الرفاهية الرقمية في إعدادات هاتفك وتمكينها.
- إذن الوصول مطلوب لتمكين النقر المزدوج للقفل. أي بيانات حصل عليها Olauncher عبر هذه الخدمة لن تترك جهازك.
+ لعرض/إخفاء وقت استخدام الشاشة على الشاشة الرئيسية، يرجى السماح أو منع إذن استخدام التطبيق.
+ \n\nملاحظة: لا يتم جمع أي بيانات حول استخدام التطبيقات. يتم معالجة كل شيء على جهازك.
+ يحتوي Olauncher أيضًا على إصدار احترافي يسمى Pro Launcher. يحتوي على ميزات إضافية مثل الطقس، والأدوات، والخطوط، والمجلدات، وخلفيات AMOLED، وما إلى ذلك.
+ \n\nإنه أيضًا أفضل طريقة لدعم مطوري Olauncher. تحقق من ذلك!
+ يقدم Olauncher خلفيات جميلة جديدة يوميًا. امنح شاشتك الرئيسية مظهرًا جديدًا كل يوم، مجانًا!
+ \n\nهل ترغب في تجربته؟
+ إذن الوصول مطلوب لتمكين النقر المزدوج للقفل وزر الشاشة الرئيسية لعرض التطبيقات الأخيرة. أي بيانات حصل عليها Olauncher عبر هذه الخدمة لن تترك جهازك.
ادعم Olauncher مجانًا باستخدام رابط الشراكة التابع لشركة Amazon.
في كل مرة تشتري شيئًا باستخدام الرابط الخاص بنا ، نحصل على عمولة صغيرة. يرجى حفظ الرابط واستخدامه في أي وقت تتسوق فيه عبر الإنترنت. شكرًا لك!
افتح الرابط
@@ -106,20 +115,29 @@
خريطة الطريق
تاريخ
التاريخ فقط
- القاع: تشغيل
- القاع: إيقاف
+ المحاذاة ﻷسفل
+ المحاذاة ﻷعلى
يرجى تعيين Olauncher كمشغل افتراضي أولاً
شركة تابعة
التطبيق غير موجود
تعذر تشغيل التطبيق
ابحث عن Launcher أو Home app
- انتقاد اليسار التطبيق تمكين
- انتقاد الحق التطبيق تمكين
+ التمرير لليسار مفعّل
+ التمرير لليمين مفعّل
هل تستخدم هاتفك أم أن هاتفك يستخدمك؟
عن Olauncher
- يتعلم أكثر
+ تعلم أكثر
هل كنت تعلم؟
- ترك التعليق
+ أخبرنا رأيك
Olauncher هو وسيظل دائمًا مجانيًا تمامًا. ليس لديها إعلانات. لا يجمع أي بيانات. والأهم من ذلك أنه يساعدك على تقليل وقت الشاشة والعيش حياة رقمية أكثر صحة.
\n\nنحن نعتقد أن التحول إلى الحد الأدنى من المشغل هو قرار ممتاز. على الرغم من أننا متحيزون بشكل واضح، إلا أننا نأمل أن تستمتع بالتجربة. لا تتردد في مشاركة أفكارك معنا!
-
\ No newline at end of file
+ وقت الشاشة
+ تم تغيير المحاذاة ✅
+ الشاشة الرئيسية
+ مرحبا
+ مزايا إضافية…
+ إليك عامًا أفضل من كل ما سبقه. سنة جديدة سعيدة!
+ لا يهم كيف مضى شهر يناير. لا يزال لديك بقية العام لتقدم أداءً أفضل من أي وقت مضى.
+ هتافات!
+ شريط الإشعارات
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 12865b01a..ef4e6ad54 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -1,7 +1,8 @@
-
+
App
Als Standard-Launcher festlegen
+ Als Standard-Launcher festlegen
Als Standard-Launcher festlegen
0
1
@@ -26,11 +27,11 @@
Wischen Sie nach unten für
Suchen
Benachrichtigungen
- Bitte schalten Sie den Barrierefreiheitsdienst von Olauncher in den Einstellungen Ihres Geräts ein oder aus, um diese Funktion zu aktivieren oder zu deaktivieren.
- \n\nBitte beachten Sie, dass dieser Dienst nur dazu dient, den Bildschirm Ihres Geräts mit der Doppeltipp-Geste auszuschalten. Unser Barrierefreiheitsdienst erfasst oder gibt keine Daten weiter.
+ Bitte schalten Sie den Barrierefreiheitsdienst von Olauncher in den Einstellungen Ihres Geräts ein, um die Gesten Doppeltippen zum Sperren oder Home-Taste für letzte Apps zu aktivieren.\n\nBitte beachten Sie, dass dieser Dienst nur für diese Gesten verwendet wird. Unser Barrierefreiheitsdienst sammelt oder teilt keine Daten.
Akzeptieren
Erlaubnis
Zum Sperren zweimal tippen
+ Home-Taste für letzte Apps
Aktivieren
Deaktivieren
Funktioniert nicht?
@@ -86,7 +87,8 @@
System
Dunkel
Hell
- Auf unserer About-Seite unten finden Sie Antworten auf alle Ihre Fragen. Vergiss es nicht. :)\n\nDankeschön.
+ Willkommen in den Einstellungen!
+ \n\nDenken Sie daran, wenn Sie Fragen haben, unsere Seite „Über“ hat die Antwort.
Hallo, diese Nachricht wird Ihnen nicht mehr angezeigt.
\n\nWenn Ihnen Olauncher gefällt, können Sie uns bitte eine 5-Sterne-Bewertung geben? Es hilft uns, unser Ranking zu verbessern und macht uns glücklich. Dankeschön!
Hallo, wir brauchen deine Hilfe! Letztes Jahr haben wir die Marke von einer Million Downloads überschritten. Dieses Jahr wollen wir eine Million mehr erreichen, aber alleine schaffen wir das nicht.
@@ -95,7 +97,13 @@
\n\nDie Wahl liegt immer bei Ihnen.
Wenn Sie Zeitlimits für bestimmte Apps festlegen möchten, kann Ihnen die Digital Wellbeing-Funktion von Android dabei helfen.
\n\nBitte suchen Sie in Ihren Telefoneinstellungen nach Digital Wellbeing und aktivieren Sie es.
- Die Zugriffsberechtigung ist erforderlich, um das Sperren durch Doppeltippen zu ermöglichen. Die von Olauncher über diesen Dienst erfassten Daten verlassen Ihr Gerät nicht.
+ Um die Bildschirmzeit auf dem Startbildschirm anzuzeigen oder zu verbergen, erteilen Sie bitte die Erlaubnis für die App-Nutzung oder verweigern Sie sie.
+ \n\nHinweis: Es werden keine Daten zur App-Nutzung gesammelt. Alles wird auf Ihrem Gerät verarbeitet.
+ Olauncher hat auch eine Pro-Version namens Pro Launcher. Sie bietet zusätzliche Funktionen wie Wetter, Widgets, Schriftarten, Ordner, AMOLED-Hintergründe usw.
+ \n\nEs ist auch der beste Weg, die Entwickler von Olauncher zu unterstützen. Schau es dir an!
+ Olauncher bietet täglich wunderschöne neue Wallpapers. Verleihe deinem Homescreen jeden Tag ein frisches Aussehen – kostenlos!
+ \n\nMöchtest du es ausprobieren?
+ Die Zugriffsberechtigung ist erforderlich, um das Sperren durch Doppeltippen und die Home-Taste zum Anzeigen der letzten Apps zu ermöglichen. Die von Olauncher über diesen Dienst erfassten Daten verlassen Ihr Gerät nicht.
Unterstützen Sie Olauncher kostenlos, indem Sie unseren Amazon-Partnerlink verwenden.
Jedes Mal, wenn Sie über unseren Link etwas kaufen, erhalten wir eine kleine Provision. Bitte speichern Sie den Link und verwenden Sie ihn bei jedem Online-Einkauf. Dankeschön!
Verbindung öffnen
@@ -120,10 +128,19 @@
App nach links wischen deaktiviert
App nach rechts wischen deaktiviert
Benutzen Sie Ihr Telefon, oder nutzt Ihr Telefon Sie?
+ Bildschirmzeit
Über Olauncher
Erfahren Sie mehr
Wussten Sie?
Rezension
Olauncher ist und bleibt völlig kostenlos. Es gibt keine Werbung. Es werden keine Daten erfasst. Am wichtigsten ist, dass es Ihnen dabei hilft, Ihre Bildschirmzeit zu verkürzen und ein gesünderes digitales Leben zu führen.
\n\nWir glauben, dass der Wechsel zu einem Minimal Launcher eine ausgezeichnete Entscheidung ist. Obwohl wir offensichtlich voreingenommen sind, hoffen wir, dass Ihnen die Erfahrung gefällt. Teilen Sie uns gerne Ihre Gedanken mit!
+ Ausrichtung geändert ✅
+ Startbildschirm
+ Hey
+ Weitere Funktionen…
+ Auf ein Jahr, das besser ist als alle zuvor. Frohes neues Jahr!
+ Egal, wie der Januar gelaufen ist. Du hast noch den Rest des Jahres Zeit, um besser zu sein als je zuvor.
+ Prost!
+ Benachrichtigungsleiste
diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml
index ccf642ca1..a1649c76a 100644
--- a/app/src/main/res/values-es-rES/strings.xml
+++ b/app/src/main/res/values-es-rES/strings.xml
@@ -1,7 +1,8 @@
-
+
App
Establecer como lanzador principal
+ Establecer como lanzador principal
Establecer como lanzador principal
Color del texto
Tocar dos veces para bloquear
@@ -53,7 +54,8 @@
Sistema
Oscuro
Claro
- Nuestra página \'Acerca de\' en la parte inferior tiene respuestas a todas sus preguntas. no lo olvides :)\n\nGracias.
+ ¡Bienvenido a la configuración!
+ \n\nRecuerda, si tienes alguna pregunta, nuestra página Acerca de tiene la respuesta.
Hola, no volverás a ver este mensaje.
\n\nSi te gusta Olauncher, ¿puedes darnos una calificación de 5 estrellas? Nos ayuda a mejorar nuestra clasificación y nos hace felices. ¡Gracias!
Hola, necesitamos tu ayuda! El año pasado superamos el millón de descargas. Este año queremos llegar a un millón más, pero no podemos hacerlo solos.
@@ -62,7 +64,13 @@
\n\nLa decisión siempre es vuestra.
Si deseas establecer límites de tiempo para determinadas aplicaciones, la función Bienestar digital de Android puede ayudarte a hacerlo.
\n\nBusca Bienestar digital en la configuración de su teléfono y habilítelo.
- "Se requiere el permiso de accesibilidad para habilitar el doble toque para bloquear. Cualquier dato adquirido por Olauncher a través de este servicio no saldrá de su dispositivo."
+ Para mostrar/ocultar el tiempo de pantalla en la pantalla de inicio, permita o deniegue el permiso de uso de la aplicación.
+ \n\nNota: No se recopilan datos de uso de la aplicación. Todo se procesa en su dispositivo.
+ Olauncher también tiene una versión pro llamada Pro Launcher. Tiene funciones adicionales como clima, widgets, fuentes, carpetas, fondos AMOLED, etc.
+ \n\nTambién es la mejor manera de apoyar a los desarrolladores de Olauncher. ¡Échale un vistazo!
+ Olauncher ofrece hermosos fondos de pantalla nuevos a diario. ¡Dale un nuevo look a tu pantalla de inicio cada día, gratis!
+ \n\n¿Quieres probarlo?
+ Se requiere el permiso de accesibilidad para habilitar el doble toque para bloquear y el botón de inicio para mostrar aplicaciones recientes. Cualquier dato adquirido por Olauncher a través de este servicio no saldrá de su dispositivo.
Apoya a Olauncher de forma gratuita utilizando nuestro enlace de afiliado de Amazon.
Cada vez que compre algo usando nuestro enlace, obtendremos una pequeña comisión. Guarde el enlace y utilícelo cada vez que compre en línea.\n¡Gracias!
Abrir enlace
@@ -85,11 +93,11 @@
Deslizar hacia abajo para
Búsqueda
Notificaciones
- Activa o desactiva el servicio de accesibilidad de Olauncher en la configuración de su dispositivo para activar o desactivar esta función.
- \n\nTen en cuenta que este servicio solo se utiliza para permitir apagar la pantalla de tu dispositivo con el gesto de doble toque. Nuestro servicio de accesibilidad no recopila ni comparte ningún dato.
+ Active el servicio de accesibilidad de Olauncher en la configuración de su dispositivo para habilitar los gestos de doble toque para bloquear o botón de inicio para aplicaciones recientes.\n\nTenga en cuenta que este servicio solo se utiliza para estos gestos. Nuestro servicio de accesibilidad no recopila ni comparte ningún dato.
Aceptar
Permiso
Tocar dos veces para bloquear
+ Botón de inicio para recientes
Activar
Desactivar
¿No funciona?
@@ -123,7 +131,16 @@
Acerca de Olauncher
Aprende más
¿Sabías?
+ Tiempo de pantalla
Dejar un comentario
Olauncher es y será siempre completamente gratuito. No tiene anuncios. No recopila datos. Lo más importante es que te ayuda a reducir el tiempo que pasa frente a la pantalla y a vivir una vida digital más saludable.
\n\nCreemos que cambiar a un lanzador mínimo es una excelente decisión. Aunque obviamente somos parciales, esperamos que disfrutes la experiencia. ¡No dudes en compartir tus pensamientos con nosotros!
-
+ Alineación cambiada ✅
+ Pantalla de inicio
+ Hola
+ Más funciones…
+ Por un año mejor que cualquiera anterior. ¡Feliz año nuevo!
+ No importa cómo haya ido enero. Todavía tienes el resto del año para hacerlo mejor que nunca.
+ ¡Salud!
+ Barra de notificaciones
+
diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml
index 3ee1884a2..33e235288 100644
--- a/app/src/main/res/values-es-rUS/strings.xml
+++ b/app/src/main/res/values-es-rUS/strings.xml
@@ -1,7 +1,8 @@
-
+
App
Establecer como lanzador principal
+ Establecer como lanzador principal
Primero pongalo como lanzador principal
Color del texto
Tocar dos veces para bloquear
@@ -53,7 +54,8 @@
Sistema
Oscuro
Claro
- Nuestra página Acerca de en la parte inferior tiene respuestas a todas tus preguntas. No lo olvides. :)
+ ¡Bienvenido a la configuración!
+ \n\nRecuerda, si tienes alguna pregunta, nuestra página Acerca de tiene la respuesta.
Hola, no volverá a ver este mensaje.
\n\nSi le gusta Olauncher, ¿puede darnos una calificación de 5 estrellas? Nos ayuda a mejorar nuestra clasificación y nos hace felices. ¡Gracias!
Hola, necesitamos su ayuda! El año pasado superamos el millón de descargas. Este año queremos llegar a un millón más pero no podemos hacerlo solos.
@@ -61,7 +63,13 @@
Hola, el teclado es la forma más rápida de encontrar y abrir aplicaciones en Olauncher. ¿Podemos pedirle que mantenga esto activado por unos días más y luego decidir? La decisión siempre es suya.
Si desea establecer límites de tiempo para determinadas aplicaciones, la función Bienestar Digital de Android puede ayudarle a hacerlo.
\n\nBusque Bienestar Digital en la configuración de su teléfono y habilítelo.
- Se necesita el permiso de Accesibilidad para activar el bloqueo con doble toque. Cualquier dato recolectado por Olauncher no saldrá de su dispositivo.
+ Para mostrar/ocultar el tiempo de pantalla en la pantalla de inicio, permita o deniegue el permiso de uso de la aplicación.
+ \n\nNota: No se recopilan datos de uso de la aplicación. Todo se procesa en su dispositivo.
+ Olauncher también tiene una versión pro llamada Pro Launcher. Tiene funciones adicionales como clima, widgets, fuentes, carpetas, fondos AMOLED, etc.
+ \n\nTambién es la mejor manera de apoyar a los desarrolladores de Olauncher. ¡Échale un vistazo!
+ Olauncher ofrece hermosos fondos de pantalla nuevos a diario. ¡Dale un nuevo look a tu pantalla de inicio cada día, gratis!
+ \n\n¿Quieres probarlo?
+ Se necesita el permiso de Accesibilidad para activar el bloqueo con doble toque y el botón de inicio para mostrar aplicaciones recientes. Cualquier dato recolectado por Olauncher no saldrá de su dispositivo.
Apoye Olauncher de forma gratuita utilizando nuestro enlace de afiliado de Amazon.
Cada vez que compre algo usando nuestro enlace, obtenemos una pequeña comisión. Por favor, guarde el enlace y úselo cada vez que compre en línea. ¡Gracias!
Abrir enlace
@@ -83,10 +91,11 @@
Deslize el dedo hacia abajo para
Buscar
Notificaciones
- Active o desactive el servicio de accesibilidad de Olauncher en la configuración de su dispositivo para habilitar o deshabilitar esta función. Tenga en cuenta que este servicio solo se utiliza para permitirle apagar el dispositivo
+ Active el servicio de accesibilidad de Olauncher en la configuración de su dispositivo para habilitar los gestos de doble toque para bloquear o botón de inicio para aplicaciones recientes.\n\nTenga en cuenta que este servicio solo se utiliza para estos gestos. Nuestro servicio de accesibilidad no recopila ni comparte ningún dato.
Aceptar
Permiso
Tocar dos veces para bloquear
+ Botón de inicio para recientes
Habilitar
Inutilizar
¿No funciona?
@@ -119,8 +128,17 @@
¿Está usando su teléfono, o el teléfono lo usa a usted?
Acerca de Olauncher
Aprenda más
+ Tiempo de pantalla
¿Sabía qué?
Dejar un comentario
Olauncher es y será siempre completamente gratuito. No tiene anuncios. No recopila datos. Lo más importante es que le ayuda a reducir el tiempo que pasa frente a la pantalla y a vivir una vida digital más saludable.
\n\nCreemos que cambiar a un lanzador mínimo es una excelente decisión. Aunque obviamente somos parciales, esperamos que disfrutes la experiencia. ¡No dude en compartir sus pensamientos con nosotros!
-
+ Alineación cambiada ✅
+ Pantalla de inicio
+ Hola
+ Más funciones…
+ Por un año mejor que cualquiera anterior. ¡Feliz año nuevo!
+ No importa cómo haya ido enero. Todavía tienes el resto del año para hacerlo mejor que nunca.
+ ¡Salud!
+ Barra de notificaciones
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 0a47cbd62..f70571d76 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -1,7 +1,8 @@
-
+
App
Définir comme lanceur par défaut
+ Définir comme lanceur par défaut
Définir comme lanceur par défaut
Couleur du texte
Double appui pour verrouiller
@@ -53,7 +54,8 @@
Système
Sombre
Clair
- Notre page \"À propos\" en bas a des réponses à toutes vos questions. :)\n\nMerci!
+ Bienvenue dans les paramètres !
+ \n\nN\'oubliez pas, si vous avez des questions, notre page À propos a la réponse.
Hé, trouvez-vous Olauncher utile? Si oui, une note de 5 étoiles est le meilleur moyen de nous soutenir.
\n\nMerci, vous ne verrez plus ce message.
Bonjour, nous avons besoin de votre aide ! L\'année dernière, nous avons franchi le million de téléchargements. Cette année, nous voulons atteindre un million de plus, mais nous ne pouvons pas le faire seuls.
@@ -61,7 +63,13 @@
Bonjour, le clavier est le moyen le plus rapide de trouver et de lancer des applications dans Olauncher. Pouvons-nous vous demander de garder cela activé pendant quelques jours de plus, puis de décider? Le choix vous appartient toujours.
Si vous souhaitez définir des limites de temps pour certaines applications, la fonctionnalité Digital Wellbeing d\'Android peut vous aider à le faire.
\n\nVeuillez rechercher Digital Wellbeing dans les paramètres de votre téléphone et l\'activer.
- L\'autorisation d\'accessibilité est requise pour activer le double appui pour verrouiller. Toutes les données acquises par Olauncher via ce service ne quitteront pas votre appareil.
+ Pour afficher/masquer le temps d\'écran sur l\'écran d\'accueil, autorisez ou refusez l\'autorisation d\'utilisation de l\'application.
+ \n\nRemarque : Aucune donnée d\'utilisation de l\'application n\'est collectée. Tout est traité sur votre appareil.
+ Olauncher a également une version pro appelée Pro Launcher. Elle dispose de fonctionnalités supplémentaires telles que la météo, les widgets, les polices, les dossiers, les fonds d\'écran AMOLED, etc.
+ \n\nC\'est aussi le meilleur moyen de soutenir les développeurs d\'Olauncher. Jetez-y un œil !
+ Olauncher propose de superbes fonds d\'écran nouveaux chaque jour. Donne un nouveau look à ton écran d\'accueil chaque jour, gratuitement!
+ \n\nTu veux essayer?
+ L\'autorisation d\'accessibilité est requise pour activer le double appui pour verrouiller et le bouton d\'accueil pour afficher les applications récentes. Toutes les données acquises par Olauncher via ce service ne quitteront pas votre appareil.
Soutenez Olauncher gratuitement en utilisant notre lien d\'affiliation Amazon.
Chaque fois que vous achetez quelque chose en utilisant notre lien, nous recevons une petite commission. Enregistrez le lien et utilisez le chaque fois que vous commandez en ligne. Merci!
Ouvrir le lien
@@ -83,18 +91,16 @@
Balayer vers le bas pour
Rechercher
Notifications
- Veuillez activer ou désactiver le service d\'accessibilité d\'Olauncher dans les paramètres de votre téléphone pour utiliser ou non cette fonctionnalité.
- \n\nVeuillez noter que ce service n\'est utilisé que pour vous permettre d\'éteindre l\'écran de votre téléphone avec le geste du double appui.
- Notre service d\'accessibilité ne collecte ni ne partage aucune donnée.
+ Veuillez activer le service d\'accessibilité d\'Olauncher dans les paramètres de votre appareil pour utiliser les gestes Double appui pour verrouiller ou Bouton d\'accueil pour les applications récentes.\n\nVeuillez noter que ce service est utilisé uniquement pour ces gestes. Notre service d\'accessibilité ne collecte ni ne partage aucune donnée.
Accepter
Autorisation
Appuyer deux fois pour verrouiller
+ Bouton d\'accueil pour les récents
Activer
Désactiver
Ne fonctionne pas?
Info
Supprimer
- %s, %d%%
Taille du texte
Activé
Désactivé
@@ -124,8 +130,17 @@
Utilisez-vous votre téléphone, ou votre téléphone vous utilise-t-il?
À propos Olauncher
Apprendre encore plus
+ Temps d\'écran
Saviez-vous?
Laisser un commentaire
Olauncher est et sera toujours entièrement gratuit. Il n\'y a pas de publicité. Il ne collecte aucune donnée. Plus important encore, cela vous aide à réduire votre temps d’écran et à vivre une vie numérique plus saine.
\n\nNous pensons que passer à un lanceur minimal est une excellente décision. Même si nous sommes évidemment partiaux, nous espérons que vous appréciez l\'expérience. N\'hésitez pas à partager vos idées avec nous!
-
+ Alignement changé ✅
+ Écran d\'accueil
+ Salut
+ Plus de fonctionnalités…
+ À une année meilleure que toutes les précédentes. Bonne année !
+ " Peu importe comment s'est passé le mois de janvier. Tu as encore le reste de l'année pour faire mieux que jamais."
+ Santé!
+ Barre de notifications
+
diff --git a/app/src/main/res/values-hdpi/dimens.xml b/app/src/main/res/values-hdpi/dimens.xml
index 288f259ff..96a19250a 100644
--- a/app/src/main/res/values-hdpi/dimens.xml
+++ b/app/src/main/res/values-hdpi/dimens.xml
@@ -1,11 +1,10 @@
- 24sp
+ 26sp
14sp
10dp
8dp
50sp
14sp
-
\ No newline at end of file
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
new file mode 100644
index 000000000..f7d21ba8e
--- /dev/null
+++ b/app/src/main/res/values-he/strings.xml
@@ -0,0 +1,138 @@
+
+ יישום
+ הגדרה בתור משגר ברירת המחדל
+ הגדר תחילה את Olauncher כברירת מחדל
+ הגדר תחילה את Olauncher כברירת מחדל
+ צבע הטקסט
+ נעילת המסך בהקשה כפולה
+ יישום Olauncher ישתמש בהרשאה של נעילת המסך באופן מתון. יישום Olauncher אינו אוסף או משתף נתונים כלל.
+ מידע כללי
+ כבוי
+ מופעל
+ שחור
+ לבן
+ שינוי משגר ברירת המחדל
+ פרטיות
+ שיתוף
+ שתף עכשיו
+ דירוג
+ דרג עכשיו
+ עקבו אחריי
+ דואר אלקטרוני
+ תרומה
+ 1. החלקה כלפי מעלה תפתח את הרשימה של כל היישומים\n2. לפתיחת הגדרות מסך הבית יש ללחוץ לחיצה ארוכה מכל מקום על גבי המסך.
+ יישומים במסך הבית
+ רקע חדש בכל יום
+ ימין
+ שמאל
+ מרכז
+ יישור היישומים
+ יישום לפתיחה בהחלקה שמאלה
+ יישום לפתיחה בהחלקה ימינה
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ עצה: אפשר להתחיל להקליד שם של יישום מסוים ואז הוא ייפתח אוטומטית
+ אישור
+ מגניב!
+ הצגת המקלדת באופן אוטומטי
+ הסתרה
+ הצגת שורת המצב
+ שינוי שם
+ Show date time
+ מראה
+ מחוות
+ ערכת נושא
+ מערכת
+ כהה
+ בהירה
+ למטה מופיע קישור לעמוד "מידע כללי", ובו אפשר למצוא תשובות לכל השאלות. לא לשכוח :)
+ \n\nThank you.
+ היי, לא נציג לך הודעה זו בשנית.
+ \n\nאם כיף לך להשתמש ב־Olauncher, האם נוכל בבקשה לקבל ממך דירוג של 5 כוכבים? זה יעזור לנו להתקדם בדירוג ויעשה לנו טוב על הלב. תודה מראש!
+ שלום, יש לנו משימה ענקית! אנחנו רוצים להגיע למיליון הורדות לפני סוף שנת 2023, ואנו זקוקים לעזרתך.
+ \n\nהיית רוצה אולי לספר לחברים שלך על Olauncher? זה יכול לסייע לנו מאוד. תודה רבה!
+ היי, יותר מהיר למצוא ולפתוח יישומים ב־Olauncher בעזרת ההקלדה. אולי בכל זאת היית רוצה להשאיר את ההגדרה הזאת ולתת לה עוד הזדמנות?
+ \n\nThe choice is always yours.
+ Olauncher מציע טפטים חדשים ויפים מדי יום. תנו למסך הבית שלכם מראה חדש ורענן בכל יום, בחינם! רוצים לנסות?
+ הרשאת הנגישות נחוצה להפעלת הנעילה בהקשה כפולה ולחצן הבית להצגת אפליקציות אחרונות. כל הנתונים שייאספו על ידי Olauncher דרך השירות הזה לא יועברו הלאה ממכשירך.
+ אפשר לתמוך ב־Olauncher מבלי לשלם שקל, בעזרת שימוש בקישור ה־Amazon affiliate שלנו.
+ על כל רכישה שתבוצע דרך הקישור שלנו אנחנו נקבל עמלה קטנה. נשמח אם תשמרו את הקישור ותשתמשו בו בקניות מהאינטרנט.\nתודה רבה!
+ פתיחת קישור
+ העתקה
+ סגירה
+ לא כעת
+ Olauncher public roadmap
+ אינסטגרם
+ המפתח
+ Roadmap
+ תאריך
+ תאריך בלבד
+ הצגה למטה: מופעל
+ הצגה למטה: כבוי
+ ראשית, נא להגדיר את Olauncher בתור ברירת המחדל למסך בית.
+ Affiliate
+ עצה: אפשר להתחיל להקליד בשביל להגיע לאפשרות לשינוי שם של יישומים
+ הערה: בשביל להגיע ליישומים המוסתרים, יש ללחוץ על הכיתוב \'Olauncher\' שנמצא בראש עמוד ההגדרות.\n\nפרטים נוספים אפשר למצוא בעמוד ה"מידע כללי" שלנו למטה.
+ פעולה בעת החלקה למטה
+ חיפוש
+ התראות
+ נא להפעיל את שירות הנגישות של Olauncher בהגדרות המכשיר שלך כדי להפעיל את מחוות הנעילה בהקשה כפולה או לחצן הבית לאפליקציות אחרונות.\n\nשימו לב ששירות זה משמש רק למחוות אלה. שירות הנגישות שלנו אינו אוסף או משתף נתונים כלשהם.
+ הסכמה
+ הרשאה
+ לחיצה כפולה לנעילת המסך
+ כפתור הבית לאחרונים
+ הפעלה
+ השבתהe
+ לא עובד?
+ מידע
+ מחיקה
+ גודל הטקסט
+ מופעל
+ מושבת
+ נא להפעיל את מחוות ההקשה הכפולה בשביל לצורך הנעילה
+ ל־Olauncher אין אפשרות לנעול את המסך.\nנא לבדוק את הגדרות היישום.
+ לבחירת יישום יש ללחוץ לחיצה ארוכה
+ אין אפשרות לפתוח את המידע על היישום
+ הועתק ללוח
+ יישום מערכת, ולכן אי אפשר למחוק אותו
+ נא לתת שם חדש ליישום תחילה
+ היישום לא נמצא
+ אין אפשרות להפעיל את היישום
+ חיפוש יישום מסך בית או משגר
+ פתיחת היישום בהחלקה שמאלה מופעלת
+ פתיחת היישום בהחלקה ימינה מופעלת
+ פתיחת היישום בהחלקה שמאלה כבויה
+ פתיחת היישום בהחלקה ימינה כבויה
+ הרקע שלך תכף יתעדכן
+ יישום Olauncher לא מוגדר בתור ברירת מחדל.\nייתכן שעדכון הרקע היומי לא יצליח.
+ לחיצה ארוכה להפעלה
+ אין יישומים מוסתרים
+ אפליקציות מוסתרות
+ הצגה
+ הסתרה
+ האם אתם משתמשים בטלפון, או שהוא משתמש בכם?
+ היי
+ עוד תכונות…
+ לעבר שנה טובה יותר מכל מה שקדם. שנה טובה!
+ לא משנה איך עבר ינואר. עדיין יש לך את שאר השנה להצליח יותר מתמיד
+ לחיים!
+ אודות ושאלות נפוצות
+ זמן מסך
+ למדו עוד
+ האם ידעת?
+ השאירו ביקורת
+ Olauncher חינמית ותמיד תהיה. אין בה פרסומות, היא אינה אוספת נתונים, והיא מפחיתה את זמן המסך שלכם – מה שמוביל לחיים בריאים יותר.
+ היישור השתנה ✅
+ מסך הבית
+ אם ברצונך להגדיר מגבלות זמן עבור אפליקציות מסוימות, תכונת Digital Wellbeing של אנדרואיד יכולה לעזור לך לעשות זאת. חפש את Digital Wellbeing בהגדרות הטלפון שלך והפעל אותה.
+ כדי להציג/להסתיר את זמן המסך שלך במסך הבית, אנא אפשר או בטל את הרשאת השימוש באפליקציה. הערה: לא נאספים נתוני שימוש באפליקציה. הכל מעובד במכשיר שלך.
+ ל-Olauncher יש גם גרסת Pro בשם Pro Launcher. יש לה תכונות נוספות כמו מזג אוויר, ווידג\'טים, גופנים, תיקיות, טפטים של AMOLED וכן הלאה. זוהי גם הדרך הטובה ביותר לתמוך במפתחי Olauncher. תבדקו!
+ שורת ההתראות
+
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index fda7d01aa..a083d2b09 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -1,7 +1,8 @@
+
- Olauncher
Aplikacija
Postavi kao zadani pokretač
+ Postavi kao zadani pokretač
Postavi kao zadani pokretač
Boja teksta
Dvostruki dodir za zaključavanje
@@ -19,7 +20,6 @@
Ocijeni
Prati
E-pošta
- Github
Doniraj
1. Povuci prema gore za aplikacije\n2. Dugo pritisnite bilo gdje za postavke
Aplikacije na početnoj stranici
@@ -41,6 +41,7 @@
8
Savjet: Povuci prema dolje za zatvaranje. Pomakni zaslon za zatvaranje tipkovnice.
U redu
+ Cool!
Automatski prikaži tipkovnicu
Sakrij
Prikaži statusnu traku
@@ -52,21 +53,29 @@
Sustav
Tamna
Svijetla
- Naša stranica s informacijama na dnu zaslona ima odgovore na sva tvoja pitanja. Nemoj zaboraviti to. :)
- \n\nHvala ti.
+ Dobrodošli u postavke!
+ \n\nZapamtite, ako imate bilo kakvih pitanja, naša stranica O nama ima odgovor.
Ovu poruku više nećeš vidjeti.
\n\nBok, ako uživaš u korištenju Olaunchera, možeš li nam dati recenziju s 5 zvijezdica? To nam donosi više korisnika i usrećuje nas.\nHvala ti i sretan ostatak dana!
Bok, trebamo tvoju pomoć! Prošle godine prešli smo milijun preuzimanja. Ove godine želimo doseći milijun više, ali ne možemo sami.
\n\nHoćete li, molim vas, biti ljubazni podijeliti Olauncher sa svojim prijateljima? Hvala i ugodan dan!
+ Bok, tipkovnica je najbrži način za pronalaženje i pokretanje aplikacija u Olauncheru. Možemo li vas zamoliti da to zadržite uključeno još nekoliko dana, a zatim odlučite?
+ \n\nIzbor je uvijek vaš.
Ako želite postaviti vremenska ograničenja za određene aplikacije, značajka Digital Wellbeing na Androidu može vam pomoći u tome.
\n\nPotražite Digital Wellbeing u postavkama telefona i omogućite ga.
- Dozvola za pristupačnost potrebna je za omogućavanje zaključavanja dvostrukim dodirom. Bilo koji podatci prikupljeni ovim servisom neće napustiti tvoj uređaj.
- lock layout description to be used a unique id to lock screen
+ Da biste prikazali/sakrili vrijeme korištenja ekrana na početnom zaslonu, molimo dozvolite ili onemogućite dopuštenje korištenja aplikacije.
+ \n\nNapomena: Ne prikupljaju se podaci o korištenju aplikacije. Sve se obrađuje na vašem uređaju.
+ Olauncher također ima pro verziju pod nazivom Pro Launcher. Ima dodatne značajke poput vremena, widgeta, fontova, mapa, AMOLED pozadina itd.
+ \n\nTo je također najbolji način da podržite programere Olauncher-a. Pogledajte!
+ Olauncher nudi predivne nove pozadine svaki dan. Daj svom početnom ekranu svjež izgled svaki dan, besplatno!
+ \n\nŽeliš li isprobati?
+ Dozvola za pristupačnost potrebna je za omogućavanje zaključavanja dvostrukim dodirom i tipke Početna za prikaz nedavnih aplikacija. Bilo koji podatci prikupljeni ovim servisom neće napustiti tvoj uređaj.
Besplatno podrži Olauncher korištenjem naše sponzorske poveznice za Amazon.
Svaki puta kada kupiš nešto pomoću naše poveznice, mi primimo malu naknadu. Molimo spremi poveznicu i koristi ju svaki puta kada kupuješ online.\nHvala ti!
Otvori poveznicu
Kopiraj
Zatvori
+ Ne sad
Javni plan razvoja Olaunchera
Instagram
Twitter
@@ -84,7 +93,53 @@
Obavijesti
Saznajte više
Dali si znao?
+ Vrijeme na ekranu
Ostavite recenziju
Olauncher jest i uvijek će biti potpuno besplatan. Nema reklama. Ne prikuplja podatke. Što je najvažnije, pomaže vam u smanjenju vremena koje provodite pred ekranom i živite zdravijim digitalnim životom.
\n\nVjerujemo da je prelazak na minimalni launcher izvrsna odluka. Iako smo očito pristrani, nadamo se da uživate u iskustvu. Slobodno podijelite svoje misli s nama!
-
+
+ Molimo vas da uključite Olauncherovu uslugu pristupa u postavkama vašeg uređaja kako biste omogućili geste dvostrukog dodira za zaključavanje ili tipke Početna za prikaz nedavnih aplikacija.\n\nImajte na umu da se ova usluga koristi samo za te geste. Naša usluga pristupa ne prikuplja niti dijeli bilo kakve podatke.
+ Prihvati
+ Dozvola
+ Dvostruki dodir za zaključavanje
+ Tipka za početnu stranicu za nedavne
+ Omogući
+ Onemogući
+ Ne radi?
+ Informacije
+ Izbriši
+ Veličina teksta
+ Omogućeno
+ Onemogućeno
+ Molimo vas da uključite dvostruki dodir za otključavanje
+ Olauncher nije uspio zaključati uređaj.\nMolimo provjerite postavke aplikacije.
+ Dugo pritisnite za odabir aplikacije
+ Nije moguće otvoriti informacije o aplikaciji
+ Kopirano
+ Sistemska aplikacija, ne može se izbrisati
+ Prvo upišite novo ime aplikacije
+ Aplikacija nije pronađena
+ Nije moguće pokrenuti aplikaciju
+ Pretraži Launcher ili Home aplikaciju
+ Povucite lijevo, aplikacija omogućena
+ Povucite desno, aplikacija omogućena
+ Povucite lijevo, aplikacija onemogućena
+ Povucite desno, aplikacija onemogućena
+ Vaša pozadina će se uskoro ažurirati
+ Olauncher nije zadani. Ažuriranje pozadine će ne uspjeti.
+ Dugo pritisnite za omogućavanje
+ Nema skrivenih aplikacija
+ Skrivene aplikacije
+ Prikaži
+ Sakrij
+ Koristite li svoj telefon ili vaš telefon koristi vas?
+ O Olauncher-u i ČPP
+ Poravnanje promijenjeno ✅
+ Početni ekran
+ Bok
+ Više značajki…
+ Neka je ova godina bolja od svih prije. Sretna nova godina!
+ Nije važno kako je prošao siječanj. Još uvijek imate ostatak godine da budete bolji nego ikad.
+ Živjeli!
+ Traka obavijesti
+
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index bc227121e..6498f10fa 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -1,6 +1,8 @@
+
Alkalmazás
- Beállítás alapértelmezett indítóként
+ Beállítás alapért. indítóként
+ Beállítás alapért. indítóként
Beállítás alapértelmezett indítóként
Szövegszín
Dupla koppintás a képernyő lezárásához
@@ -37,10 +39,10 @@
6
7
8
- Tipp: Kezdje el begépelni egy alkalmazás nevét az automatikus indításához
+ Tipp: Kezdje el begépelni egy alkalmazás nevét annak automatikus indításához
Rendben
Szuper!
- Billentyűzet automatikus megjelenítlse
+ Billentyűzet auto. megjelenítése
Elrejtés
Állapotsor megjelenítése fent
Átnevezés
@@ -51,20 +53,28 @@
Rendszer
Sötét
Világos
- A Névjegy oldalunk alján megtalálja a válaszokat minden kérdésére. Ne feledje el. :)
- \n\nKöszönjük!
+ Üdvözöljük a beállításokban!
+ \n\nNe feledje, ha bármilyen kérdése van, az „Rólunk” oldalunk tartalmazza a választ.
Üdv, ezt az üzenetet nem fogja újra látni.
- \n\nHa tetszik az Olauncher, kérjük, adjon nekünk 5 csillagos értékelést! Ez segít javítani a helyezésünket, és nagyon boldoggá tesz minket. Köszönjük!
- Sziasztok, segítségre van szükségünk! Tavaly átléptük az egymillió letöltést. Idén egy millióval többet szeretnénk elérni, de egyedül nem tudjuk megtenni.
- \n\nLenne szíves megosztani az Olaunchert a barátaival, kérem? Köszönöm és további szép napot!
- Üdv, a billentyűzet a leggyorsabb módja a alkalmazások megtalálásához és elindításához az Olauncherben. Kérhetjük, hogy hagyja bekapcsolva még néhány napig, és utána döntsön.
- \n\nA választás mindig az Öné.
+ \n\nHa tetszik az Olauncher, kérjük, adjon nekünk 5 csillagos értékelést! Ez segít javítani a helyezésünket, és nagyon boldoggá tesz minket. Köszönjük!
+ A segítségére van szükségünk! Tavaly átléptük az egymillió letöltést. Idén egy millióval többet szeretnénk elérni, de egyedül nem tudjuk megtenni.
+ \n\nAz Olauncher-nek emberekre van szüksége, az embereknek pedig az Olauncher-re. Köszönettel vennénk, ha megosztaná másokkal is az Olaunchert!
+
+
+ Üdvözöljük! A billentyűzet a leggyorsabb módja az alkalmazások megtalálásához és elindításához az Olauncherben. Megkérhetjük, hogy hagyja bekapcsolva még néhány napig, és utána döntsön?
+ \n\nA választás mindig az Öné.
Ha időkorlátokat szeretne beállítani bizonyos alkalmazásokhoz, az Android Digital Wellbeing funkciója segíthet ebben.
- \n\nKeresse meg a Digitális jólét elemet a telefon beállításai között, és engedélyezze.
- A dupla koppintás zárolásához szükséges a hozzáférhetőségi engedély. Az Olauncher által ezen a szolgáltatáson keresztül megszerzett adatok nem hagyják el a készüléket.
+ \n\nKeresse meg a Digitális jólét elemet a telefon beállításai között, és engedélyezze.
+ A képernyőidő megjelenítéséhez/elrejtéséhez a kezdőképernyőn engedélyezze vagy tagadja meg az alkalmazás használati engedélyét.
+ \n\nMegjegyzés: Nem gyűjtünk alkalmazás-használati adatokat. Minden az Ön eszközén kerül feldolgozásra.
+ Az Olauncher-nek van egy pro verziója is, amely Pro Launcher néven ismert. További funkciókat kínál, mint például időjárás, widgetek, betűtípusok, mappák, AMOLED háttérképek stb.
+ \n\nEz a legjobb módja annak, hogy támogassa az Olauncher fejlesztőit. Tekintse meg őket!
+ Az Olauncher minden nap gyönyörű, új háttérképeket kínál, amelyekkel frissen tarthatja kezdőképernyőjét, ingyen!
+ \n\nSzeretné kipróbálni?
+ A hozzáférhetőségi engedély szükséges a dupla koppintás általi zároláshoz és a kezdőgomb megnyomásával a legutóbbi alkalmazások megjelenítéséhez. Az Olauncher által ezen a szolgáltatáson keresztül megszerzett adatok nem hagyják el a készüléket.
Támogasson minket ingyenesen az Amazon affiliate linkünk használatával.
Minden alkalommal, amikor vásárol valamit a linkünket használva, egy kis jutalékot kapunk. Kérjük, mentse el a linket, és használja fel, amikor csak online vásárol.
- \n\nKöszönjük!
+ \n\nKöszönjük!
Link megnyitása
Másolás
Bezárás
@@ -77,7 +87,7 @@
Csak dátum
Alsó: Be
Alsó: Ki
- Kérjük állítsa be az Olauncher alkalmazást alapértelmezett indítóként
+ Kérjük, állítsa be az Olauncher-t alapértelmezett indítóként
Affiliate
Tipp: Kezdjen el gépelni az alkalmazás ÁTNEVEZÉSE opcióhoz
Megjegyzés: A rejtett alkalmazások megtalálásához érintse meg az "Olauncher" elemet ennek a beállítás oldalnak a tetején.
@@ -85,11 +95,11 @@
Simítson lefelé a következőért
Keresés
Értesítések
- Kérjük, kapcsolja be vagy ki az Olauncher hozzáférhetőségi szolgáltatását az eszköz beállításaiban, hogy engedélyezze vagy letiltsa ezt a funkciót.
- \n\nKérjük, vegye figyelembe, hogy ezt a szolgáltatást csak a dupla koppintással történő képernyőzárás engedélyezésére használjuk. A hozzáférhetőségi szolgáltatásunk nem gyűjt vagy oszt meg semmilyen adatot.
+ Kérjük, kapcsolja be az Olauncher hozzáférhetőségi szolgáltatását az eszköz beállításaiban a dupla koppintás zároláshoz vagy a kezdőgomb a legutóbbi alkalmazásokhoz gesztusok engedélyezéséhez.\n\nKérjük, vegye figyelembe, hogy ez a szolgáltatás csak ezekhez a gesztusokhoz használatos. Hozzáférhetőségi szolgáltatásunk nem gyűjt és nem oszt meg semmilyen adatot.
Elfogad
Engedély
Dupla koppintás a lezáráshoz
+ Kezdőlap gomb a legutóbbiakhoz
Engedélyezés
Letiltás
Nem működik?
@@ -119,11 +129,20 @@
Rejtett alkalmazások
Mutasd!
Rejtsd el!
- Ön használja a telefonját, vagy a telefona használja Önt?
+ Ön használja a telefonját, vagy a telefonja használja Önt?
Az Olauncher-ről
- Tudj meg többet
- Tudtad?
+ Képernyőidő
+ Tudjon meg többet
+ Tudta?
Hagyjon véleményt
- Az Olauncher teljesen ingyenes és mindig is ingyenes lesz. Nincsenek hirdetései. Nem gyűjt adatokat. A legfontosabb, hogy segít csökkenteni a képernyő előtt töltött időt és egészségesebb digitális életet élni.
- \n\nÚgy gondoljuk, hogy a minimális indítóra váltás kiváló döntés. Bár nyilvánvalóan elfogultak vagyunk, reméljük, hogy élvezni fogja az élményt. Nyugodtan oszd meg velünk gondolataidat!
-
\ No newline at end of file
+ Az Olauncher teljesen ingyenes és mindig is az lesz. Nincsenek hirdetései, nem gyűjt adatokat, és segít csökkenteni a képernyő előtt töltött időt egy egészségesebb digitális élet végett.
+ \n\nÚgy gondoljuk, hogy egy minimális indítóra váltás kiváló döntés, és reméljük, hogy élvezni fogja. Ossza meg másokkal is tapasztalatait!
+ Az igazítás megváltozott ✅
+ Kezdőképernyő
+ Üdvözöljük!
+ További funkciók…
+ Egy évre, amely jobb lesz minden eddiginél. Boldog új évet!
+ Nem számít, hogyan telt a január. Még mindig van hátra az évből, hogy minden eddiginél jobban teljesíts.
+ Egészségére!
+ Értesítési sáv
+
\ No newline at end of file
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 13c80186f..24de0ecf2 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -1,7 +1,8 @@
-
+
Aplikasi
Tetapkan sebagai peluncur default
+ Tetapkan sebagai peluncur default
Tetapkan sebagai peluncur default
Warna teks
Ketuk dua kali untuk mengunci
@@ -53,7 +54,8 @@
Sistem
Gelap
Terang
- Halaman Tentang kami di bagian bawah memiliki jawaban atas semua pertanyaan Anda. Jangan lupa. :)
+ Selamat datang di pengaturan!
+ \n\nIngat, jika Anda memiliki pertanyaan, halaman Tentang kami memiliki jawabannya.
Hai, Anda tidak akan melihat pesan ini lagi.
\n\nJika Anda menyukai Olauncher, bisakah Anda memberi kami peringkat bintang 5? Ini membantu kami meningkatkan peringkat kami dan membuat kami bahagia. Terima kasih!
Hai, kami membutuhkan bantuan Anda! Tahun lalu kami melampaui satu juta unduhan. Tahun ini kami ingin menjangkau satu juta orang lagi tetapi kami tidak dapat melakukannya sendiri.
@@ -61,7 +63,13 @@
Hai, keyboard adalah cara tercepat untuk menemukan dan meluncurkan aplikasi di Olauncher. Bisakah kami meminta Anda untuk mengaktifkannya selama beberapa hari lagi dan kemudian memutuskan? Pilihan selalu menjadi milik Anda.
Jika Anda ingin menetapkan batas waktu untuk aplikasi tertentu, fitur Digital Wellbeing Android dapat membantu Anda melakukannya.
\n\nSilakan cari Digital Wellbeing di pengaturan ponsel Anda dan aktifkan.
- Izin aksesibilitas diperlukan untuk mengaktifkan ketuk dua kali untuk mengunci. Data apa pun yang diperoleh Olauncher melalui layanan ini tidak akan meninggalkan perangkat Anda.
+ Untuk menampilkan/menyembunyikan waktu layar di layar utama, izinkan atau tolak izin penggunaan aplikasi.
+ \n\nCatatan: Tidak ada data penggunaan aplikasi yang dikumpulkan. Semua diproses di perangkat Anda.
+ Olauncher juga memiliki versi pro yang disebut Pro Launcher. Ini memiliki fitur tambahan seperti cuaca, widget, font, folder, wallpaper AMOLED, dan banyak lagi.
+ \n\nIni juga merupakan cara terbaik untuk mendukung pengembang Olauncher. Cek it out!
+ Olauncher menawarkan wallpaper baru yang indah setiap hari. Berikan tampilan baru pada layar utama Anda setiap hari, secara gratis!
+ \n\nIngin mencobanya?
+ Izin aksesibilitas diperlukan untuk mengaktifkan ketuk dua kali untuk mengunci dan tombol beranda untuk menampilkan aplikasi terbaru. Data apa pun yang diperoleh Olauncher melalui layanan ini tidak akan meninggalkan perangkat Anda.
Dukung Olauncher secara gratis dengan menggunakan tautan afiliasi Amazon kami.
Setiap kali Anda membeli sesuatu menggunakan tautan kami, kami mendapat komisi kecil. Harap simpan tautan dan gunakan setiap kali Anda berbelanja online.\nTerima kasih!
Buka tautan
@@ -83,10 +91,11 @@
Geser ke bawah untuk
Cari
Notifikasi
- Harap aktifkan atau nonaktifkan layanan aksesibilitas Olauncher di pengaturan perangkat Anda untuk mengaktifkan atau menonaktifkan fitur ini.\n\nHarap perhatikan bahwa layanan ini hanya digunakan untuk memungkinkan Anda mematikan layar perangkat menggunakan gerakan ketuk dua kali. Layanan aksesibilitas kami tidak mengumpulkan atau membagikan data apa pun.
+ Harap aktifkan layanan aksesibilitas Olauncher di pengaturan perangkat Anda untuk mengaktifkan gerakan ketuk dua kali untuk mengunci atau tombol beranda untuk aplikasi terbaru.\n\nHarap perhatikan bahwa layanan ini hanya digunakan untuk gerakan-gerakan ini. Layanan aksesibilitas kami tidak mengumpulkan atau membagikan data apa pun.
Terima
Izin
Ketuk dua kali untuk mengunci
+ Tombol beranda untuk terbaru
Aktifkan
Nonaktifkan
Tidak bekerja?
@@ -119,8 +128,17 @@
Apakah Anda menggunakan ponsel Anda, atau ponsel Anda menggunakan Anda?
Tentang Olauncher
Pelajari lebih lanjut
+ Waktu layar
Tahukah kamu?
Tinggalkan ulasan
Olauncher sepenuhnya gratis dan akan selalu gratis. Tidak ada iklan. Tidak mengumpulkan data pribadi. Yang terpenting, ini membantu Anda mengurangi waktu pemakaian perangkat dan menjalani kehidupan digital yang lebih sehat.
\n\nKami percaya bahwa beralih ke peluncur minimal adalah keputusan yang sangat baik. Meskipun kami jelas-jelas bias, kami harap Anda menikmati pengalaman ini. Jangan ragu untuk berbagi pemikiran Anda dengan kami!
-
\ No newline at end of file
+ Penyelarasan diubah ✅
+ Layar beranda
+ Hai
+ Fitur lainnya…
+ Untuk tahun yang lebih baik dari sebelumnya. Selamat tahun baru!
+ Tidak peduli bagaimana bulan Januari berlalu. Anda masih punya sisa tahun untuk melakukan yang lebih baik dari sebelumnya.
+ Bersulang!
+ Bilah pemberitahuan
+
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 639f447f7..1080795d5 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -1,6 +1,8 @@
+
App
Imposta come launcher predefinito
+ Imposta come launcher predefinito
Imposta come launcher predefinito
Colore del testo
Tocca due volte per bloccare
@@ -52,8 +54,8 @@
di sistema
Scuro
Chiaro
- La nostra pagina Informazioni in basso ha le risposto a tutte le tue domande. Non dimenticare. :)
- \n\nGrazie.
+ Benvenuto nelle impostazioni!
+ \n\nRicorda, se hai domande, la nostra pagina Informazioni ha la risposta.
Ehi, trovi utile Olauncher? Se sì, una valutazione a 5 stelle è il modo migliore per sostenerci.
\n\nGrazie e non lo vedrai più.
Ciao, abbiamo bisogno del tuo aiuto! L\'anno scorso abbiamo superato il milione di download. Quest’anno vogliamo raggiungere un milione in più ma non possiamo farcela da soli.
@@ -62,7 +64,13 @@
\n\nSei sempre tu a scegliere.
Se desideri impostare limiti di tempo per determinate app, la funzionalità Benessere digitale di Android può aiutarti a farlo.
\n\nCerca Benessere digitale nelle impostazioni del telefono e attivalo.
- L\'autorizzazione di accessibilità è necessaria per abilitare il doppio tocco per il blocco schermo. Qualsiasi dato acquisito da Olauncher tramite questo servizio rimarrà all’interno del telefono
+ Per visualizzare/nascondere il tempo di utilizzo dello schermo sulla schermata iniziale, conceda o neghi il permesso di utilizzo dell\'app.
+ \n\nNota: Non vengono raccolti dati sull\'utilizzo dell\'app. Tutto viene elaborato sul tuo dispositivo.
+ Olauncher ha anche una versione pro chiamata Pro Launcher. Ha funzionalità extra come meteo, widget, font, cartelle, sfondi AMOLED, e così via.
+ \n\nÈ anche il modo migliore per supportare gli sviluppatori di Olauncher. Dai un\'occhiata!
+ Olauncher offre bellissimi sfondi nuovi ogni giorno. Dai un aspetto fresco alla tua schermata principale ogni giorno, gratuitamente!
+ \n\nVuoi provarlo?
+ L\’autorizzazione di accessibilità è necessaria per abilitare il doppio tocco per il blocco schermo e il tasto Home per mostrare le app recenti. Qualsiasi dato acquisito da Olauncher tramite questo servizio rimarrà all\’interno del telefono.
Supporta Olauncher gratuitamente utilizzando il nostro link di affiliazione su Amazon.
Ogni volta che acquisti qualcosa utilizzando il nostro link, riceviamo una piccola commissione. Salva il link e usalo ogni volta che fai acquisti online.\n Grazie!
Link aperto
@@ -85,11 +93,11 @@
Scorri verso il basso per
Ricerca
Notifiche
- Attiva o disattiva il servizio di accessibilità di Olauncher nelle impostazioni del tuo dispositivo per abilitare o disabilitare questa funzione.
- \n\n Questo servizio viene utilizzato solo per consentirti di spegnere lo schermo del tuo dispositivo utilizzando il gesto del doppio tocco. Il nostro servizio di accessibilità non raccoglie né condivide alcun dato.
+ Attiva il servizio di accessibilità di Olauncher nelle impostazioni del tuo dispositivo per abilitare i gesti Doppio tocco per bloccare o Tasto Home per le app recenti.\n\nTieni presente che questo servizio viene utilizzato solo per questi gesti. Il nostro servizio di accessibilità non raccoglie né condivide alcun dato.
Accetta
Autorizza
Tocca due volte per bloccare
+ Tasto Home per i recenti
Abilita
disattiva
Problemi?
@@ -122,8 +130,17 @@
Stai usando il tuo telefono, o è il tuo telefono che sta usando te?
Info Olauncher
Saperne di più
+ Tempo di schermo
Lo sapevate?
Lascia una recensione
Olauncher è e sarà sempre completamente gratuito. Non ha pubblicità. Non raccoglie dati. Ancora più importante, ti aiuta a ridurre il tempo trascorso davanti allo schermo e a vivere una vita digitale più sana.
\n\nCrediamo che passare a un launcher minimale sia un\'ottima decisione. Anche se siamo ovviamente di parte, ci auguriamo che l\'esperienza ti piaccia. Sentitevi liberi di condividere i tuoi pensieri con noi!
-
\ No newline at end of file
+ Allineamento cambiato ✅
+ Schermata principale
+ Ciao
+ Altre funzionalità…
+ A un anno migliore di tutti i precedenti. Buon anno nuovo!
+ " Non importa come sia andato gennaio. Hai ancora il resto dell'anno per fare meglio che mai."
+ Saluti!
+ Barra delle notifiche
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 7b66dc38c..4a0df9276 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -1,7 +1,8 @@
-
+
アプリ
デフォルトのランチャーとして設定
+ デフォルトのランチャーとして設定
デフォルトのランチャーとして設定
〇
一
@@ -27,11 +28,11 @@
下にスワイプして
検索
通知
- この機能を有効または無効にするには、デバイスの設定で Olauncher のアクセシビリティ サービスをオンまたはオフにしてください。
- \n\nこのサービスは、ダブルタップ ジェスチャを使用してデバイスの画面をオフにするためにのみ使用されることに注意してください。 当社のアクセシビリティ サービスは、いかなるデータも収集または共有しません。
+ ダブルタップでロックまたはホームボタンで最近のアプリのジェスチャーを有効にするには、デバイスの設定で Olauncher のアクセシビリティ サービスをオンにしてください。\n\nこのサービスはこれらのジェスチャーにのみ使用されます。アクセシビリティ サービスはデータを収集したり共有したりすることはありません。
受け入れる
許可
ダブルタップしてロックします
+ ホームボタンで最近のアプリ
有効
無効にする
機能しない場合
@@ -87,14 +88,21 @@
システム
ダーク
ライト
- 下部にある当社の概要ページには、すべての質問への回答が記載されています。忘れないで。 :)\n\nありがとう。
+ 設定へようこそ!
+ \n\n質問がある場合は、私たちの「About」ページに答えがありますので、覚えておいてください。
ねえ、Olauncher は役に立つと思いますか? 「はい」の場合、5 つ星の評価が私たちをサポートする最善の方法です。\n\nありがとう、もうこれを見ることはないだろう。
こんにちは、あなたの助けが必要です!昨年、ダウンロード数が 100 万件を突破しました。今年はさらに 100 万人を達成したいと考えていますが、私たちだけでは達成できません。
\n\nOlauncher をお友達と共有していただけますか?ありがとう、そして素晴らしい一日をお過ごしください!
こんにちは、キーボードは Olauncher でアプリを見つけて起動する最も速い方法です。もう数日間これをオンにしてから判断していただくようお願いできますか?選択は常にあなた次第です。
特定のアプリに時間制限を設定したい場合は、Android の Digital Wellbeing 機能を使用すると便利です。
\n\n携帯電話の設定で Digital Wellbeing を検索し、有効にしてください。
- ダブルタップしてロックを有効にするには、アクセシビリティ許可が必要です。このサービスを通じて Olauncher が取得したデータは、デバイスから流出することはありません。
+ ホーム画面で画面時間を表示/非表示するには、アプリの使用権限を許可または拒否してください。
+ \n\n注意: アプリの使用データは収集されません。すべての処理はあなたのデバイスで行われます。
+ OlauncherにはPro Launcherというプロ版もあります。天気、ウィジェット、フォント、フォルダー、AMOLED壁紙などの追加機能があります。
+ \n\nOlauncherの開発者をサポートするための最良の方法でもあります。チェックしてみてください!
+ Olauncherは毎日美しい新しい壁紙を提供します。毎日ホーム画面に新しい外観を無料で提供!
+ \n\n試してみますか?
+ ダブルタップしてロックおよびホームボタンで最近のアプリを表示するには、アクセシビリティ許可が必要です。このサービスを通じて Olauncher が取得したデータは、デバイスから流出することはありません。
Amazon アフィリエイト リンクを使用して、Olauncher を無料でサポートしてください。
あなたが私たちのリンクを使用して何かを購入するたびに、私たちは少額の手数料を受け取ります。リンクを保存し、オンライン ショッピングの際には必ずご利用ください。 ありがとう!
リンクを開く
@@ -117,6 +125,7 @@
アプリを左にスワイプ有効化
アプリを右にスワイプ有効
左にスワイプするとアプリが無効になります
+ スクリーンタイム
アプリを右にスワイプすると無効になります
あなたは電話を使っていますか、それともあなたの電話があなたを使っていますか?
アバウト Olauncher
@@ -125,4 +134,12 @@
レビューを残す
Olauncher は現在も、今後も完全に無料です。広告はありません。データは収集されません。最も重要なことは、スクリーンタイムを短縮し、より健康的なデジタル ライフを送るのに役立つことです。
\n\n私たちは、最小限のランチャーに切り替えるのは素晴らしい決断だと信じています。明らかに偏見がありますが、楽しんでいただければ幸いです。ぜひご意見をお聞かせください。
-
\ No newline at end of file
+ アライメントが変更されました ✅
+ ホーム画面
+ やあ
+ その他の機能…
+ これまでのどの年よりも良い一年になりますように。明けましておめでとうございます!
+ 1月がどうだったかは関係ありません。これまで以上に良い結果を出すために、まだ今年の残りの期間があります。
+ 乾杯!
+ 通知バー
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ldpi/dimens.xml b/app/src/main/res/values-ldpi/dimens.xml
index eef407ce8..7bad3dfdf 100644
--- a/app/src/main/res/values-ldpi/dimens.xml
+++ b/app/src/main/res/values-ldpi/dimens.xml
@@ -1,11 +1,10 @@
- 24sp
+ 26sp
14sp
8dp
6dp
50sp
14sp
-
\ No newline at end of file
diff --git a/app/src/main/res/values-mdpi/dimens.xml b/app/src/main/res/values-mdpi/dimens.xml
index d6c1fbefb..7bad3dfdf 100644
--- a/app/src/main/res/values-mdpi/dimens.xml
+++ b/app/src/main/res/values-mdpi/dimens.xml
@@ -1,6 +1,6 @@
- 24sp
+ 26sp
14sp
8dp
6dp
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
index 2085d29c7..250a22a44 100644
--- a/app/src/main/res/values-night/styles.xml
+++ b/app/src/main/res/values-night/styles.xml
@@ -51,7 +51,7 @@