diff --git a/app/build.gradle b/app/build.gradle index 54e7b2b..7e92f3b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { minSdkVersion 21 targetSdkVersion 35 compileSdk 35 - versionCode 47 - versionName "0.2.2" + versionCode 45 + versionName "0.2.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 087ec28..e60a85b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,6 @@ tools:ignore="QueryAllPackagesPermission" /> - - @@ -29,7 +27,6 @@ android:exported="false" /> - + \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/Application.kt b/app/src/main/java/de/jrpie/android/launcher/Application.kt index ba47942..3c2e3bc 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -25,10 +25,9 @@ import de.jrpie.android.launcher.preferences.resetPreferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlin.system.exitProcess -const val APP_WIDGET_HOST_ID = 42 +const val APP_WIDGET_HOST_ID = 42; class Application : android.app.Application() { @@ -107,11 +106,6 @@ class Application : android.app.Application() { // TODO Error: Invalid resource ID 0x00000000. // DynamicColors.applyToActivitiesIfAvailable(this) - Thread.setDefaultUncaughtExceptionHandler { _, throwable -> - sendCrashNotification(this@Application, throwable) - exitProcess(1) - } - if (Build.VERSION.SDK_INT >= VERSION_CODES.M) { torchManager = TorchManager(this) @@ -120,6 +114,8 @@ class Application : android.app.Application() { appWidgetHost = AppWidgetHost(this.applicationContext, APP_WIDGET_HOST_ID) appWidgetManager = AppWidgetManager.getInstance(this.applicationContext) + appWidgetHost.startListening() + val preferences = PreferenceManager.getDefaultSharedPreferences(this) LauncherPreferences.init(preferences, this.resources) @@ -161,8 +157,6 @@ class Application : android.app.Application() { removeUnusedShortcuts(this) } loadApps() - - createNotificationChannels(this) } fun getCustomAppNames(): HashMap { @@ -176,4 +170,10 @@ class Application : android.app.Application() { apps.postValue(getApps(packageManager, applicationContext)) } } + + override fun onTerminate() { + appWidgetHost.stopListening() + super.onTerminate() + + } } diff --git a/app/src/main/java/de/jrpie/android/launcher/Functions.kt b/app/src/main/java/de/jrpie/android/launcher/Functions.kt index b6df30b..9679ae5 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -6,6 +6,9 @@ import android.app.role.RoleManager import android.content.ActivityNotFoundException import android.content.ClipData import android.content.ClipboardManager +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.appwidget.AppWidgetProviderInfo import android.content.Context import android.content.Intent import android.content.pm.LauncherApps @@ -224,13 +227,3 @@ fun copyToClipboard(context: Context, text: String) { val clipData = ClipData.newPlainText("Debug Info", text) clipboardManager.setPrimaryClip(clipData) } - -fun writeEmail(context: Context, to: String, subject: String, text: String) { - val intent = Intent(Intent.ACTION_SENDTO) - intent.setData("mailto:".toUri()) - intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(to)) - intent.putExtra(Intent.EXTRA_SUBJECT, subject) - intent.putExtra(Intent.EXTRA_TEXT, text) - context.startActivity(Intent.createChooser(intent, context.getString(R.string.send_email))) -} - diff --git a/app/src/main/java/de/jrpie/android/launcher/Notifications.kt b/app/src/main/java/de/jrpie/android/launcher/Notifications.kt deleted file mode 100644 index 0cf0efb..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/Notifications.kt +++ /dev/null @@ -1,87 +0,0 @@ -package de.jrpie.android.launcher - -import android.app.Activity -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Build -import android.util.Log -import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import de.jrpie.android.launcher.ui.EXTRA_CRASH_LOG -import de.jrpie.android.launcher.ui.ReportCrashActivity -import java.io.PrintWriter -import java.io.StringWriter -import kotlin.random.Random - -private val NOTIFICATION_CHANNEL_CRASH = "launcher:crash" - -fun createNotificationChannels(context: Context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val notificationChannel = NotificationChannel( - NOTIFICATION_CHANNEL_CRASH, - context.getString(R.string.notification_channel_crash), - NotificationManager.IMPORTANCE_HIGH - ) - - val notificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationManager.createNotificationChannel(notificationChannel) - } -} - -fun requestNotificationPermission(activity: Activity) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - return - } - - val permission = - (activity.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) - - if (!permission) { - ActivityCompat.requestPermissions( - activity, - arrayOf( android.Manifest.permission.POST_NOTIFICATIONS ), - 1 - ) - } -} - -fun sendCrashNotification(context: Context, throwable: Throwable) { - val stringWriter = StringWriter() - val printWriter = PrintWriter(stringWriter) - throwable.printStackTrace(printWriter) - - val intent = Intent(context, ReportCrashActivity::class.java) - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - intent.putExtra(EXTRA_CRASH_LOG, stringWriter.toString()) - - val pendingIntent = PendingIntent.getActivity( - context, - Random.nextInt(), - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_CRASH) - .setSmallIcon(R.drawable.baseline_bug_report_24) - .setContentTitle(context.getString(R.string.notification_crash_title)) - .setContentText(context.getString(R.string.notification_crash_explanation)) - .setContentIntent(pendingIntent) - .setAutoCancel(false) - .setPriority(NotificationCompat.PRIORITY_HIGH) - - val notificationManager = NotificationManagerCompat.from(context) - try { - notificationManager.notify( - 0, - builder.build() - ) - } catch (e: SecurityException) { - Log.e("Crash Notification", "Could not send notification") - } -} diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/TorchManager.kt b/app/src/main/java/de/jrpie/android/launcher/actions/TorchManager.kt index a2ea801..7e694c6 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/TorchManager.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/TorchManager.kt @@ -1,6 +1,7 @@ package de.jrpie.android.launcher.actions import android.content.Context +import android.hardware.camera2.CameraAccessException import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager import android.os.Build @@ -77,8 +78,7 @@ class TorchManager(context: Context) { cameraManager.setTorchMode(camera, !torchEnabled) } - } catch (e: Exception) { - // CameraAccessException, IllegalArgumentException + } catch (e: CameraAccessException) { Toast.makeText( context, context.getString(R.string.alert_torch_access_exception), diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/WidgetPanelAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/WidgetPanelAction.kt index 84c4179..d7829a6 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/WidgetPanelAction.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/WidgetPanelAction.kt @@ -1,6 +1,5 @@ package de.jrpie.android.launcher.actions -import android.app.Activity import android.content.Context import android.content.Intent import android.graphics.Rect @@ -26,18 +25,11 @@ class WidgetPanelAction(val widgetPanelId: Int) : Action { override fun invoke(context: Context, rect: Rect?): Boolean { - if (context is WidgetPanelActivity) { - if (context.widgetPanelId == widgetPanelId) { - context.finish() - return true - } - } - - if (WidgetPanel.byId(this.widgetPanelId) == null) { + if (WidgetPanel.byId(widgetPanelId) == null) { Toast.makeText(context, R.string.alert_widget_panel_not_found, Toast.LENGTH_LONG).show() } else { context.startActivity(Intent(context, WidgetPanelActivity::class.java).also { - it.putExtra(EXTRA_PANEL_ID, this.widgetPanelId) + it.putExtra(EXTRA_PANEL_ID, widgetPanelId) }) } return true diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/lock/LockMethod.kt b/app/src/main/java/de/jrpie/android/launcher/actions/lock/LockMethod.kt index 541510a..93b4cbf 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/lock/LockMethod.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/lock/LockMethod.kt @@ -6,6 +6,7 @@ import android.widget.Button import androidx.appcompat.app.AlertDialog import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.R +import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService import de.jrpie.android.launcher.preferences.LauncherPreferences diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt index a4b1f43..34cf569 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt @@ -2,6 +2,7 @@ package de.jrpie.android.launcher.preferences import android.content.Context import android.util.Log +import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.apps.AbstractAppInfo @@ -9,25 +10,21 @@ import de.jrpie.android.launcher.apps.AbstractAppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1 -import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion100 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion2 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion4 import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown -import de.jrpie.android.launcher.sendCrashNotification import de.jrpie.android.launcher.ui.HomeActivity import de.jrpie.android.launcher.widgets.ClockWidget -import de.jrpie.android.launcher.widgets.DebugInfoWidget import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPosition -import de.jrpie.android.launcher.widgets.generateInternalId -import de.jrpie.android.launcher.widgets.getAppWidgetHost +import de.jrpie.android.launcher.widgets.deleteAllWidgets /* Current version of the structure of preferences. * Increase when breaking changes are introduced and write an appropriate case in * `migratePreferencesToNewVersion` */ -const val PREFERENCE_VERSION = 101 +const val PREFERENCE_VERSION = 100 const val UNKNOWN_PREFERENCE_VERSION = -1 private const val TAG = "Launcher - Preferences" @@ -66,10 +63,6 @@ fun migratePreferencesToNewVersion(context: Context) { migratePreferencesFromVersion4(context) Log.i(TAG, "migration of preferences complete (4 -> ${PREFERENCE_VERSION}).") } - 100 -> { - migratePreferencesFromVersion100(context) - Log.i(TAG, "migration of preferences complete (100 -> ${PREFERENCE_VERSION}).") - } else -> { Log.w( @@ -82,7 +75,6 @@ fun migratePreferencesToNewVersion(context: Context) { } } catch (e: Exception) { Log.e(TAG, "Unable to restore preferences:\n${e.stackTrace}") - sendCrashNotification(context, e) resetPreferences(context) } } @@ -91,45 +83,29 @@ fun resetPreferences(context: Context) { Log.i(TAG, "Resetting preferences") LauncherPreferences.clear() LauncherPreferences.internal().versionCode(PREFERENCE_VERSION) - context.getAppWidgetHost().deleteHost() + deleteAllWidgets(context) LauncherPreferences.widgets().widgets( setOf( ClockWidget( - generateInternalId(), + (context.applicationContext as Application).appWidgetHost.allocateAppWidgetId(), WidgetPosition(1, 3, 10, 4), WidgetPanel.HOME.id ) ) ) - if (BuildConfig.DEBUG) { - LauncherPreferences.widgets().widgets( - LauncherPreferences.widgets().widgets().also { - it.add( - DebugInfoWidget( - generateInternalId(), - WidgetPosition(1, 1, 10, 4), - WidgetPanel.HOME.id - ) - ) - } - ) - } val hidden: MutableSet = mutableSetOf() - - if (!BuildConfig.DEBUG) { - val launcher = DetailedAppInfo.fromAppInfo( - AppInfo( - BuildConfig.APPLICATION_ID, - HomeActivity::class.java.name, - INVALID_USER - ), context - ) - launcher?.getRawInfo()?.let { hidden.add(it) } - Log.i(TAG, "Hiding ${launcher?.getRawInfo()}") - } + val launcher = DetailedAppInfo.fromAppInfo( + AppInfo( + BuildConfig.APPLICATION_ID, + HomeActivity::class.java.name, + INVALID_USER + ), context + ) + launcher?.getRawInfo()?.let { hidden.add(it) } + Log.i(TAG,"Hiding ${launcher?.getRawInfo()}") LauncherPreferences.apps().hidden(hidden) Action.resetToDefaultActions(context) diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version100.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version100.kt deleted file mode 100644 index 43e4bc7..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version100.kt +++ /dev/null @@ -1,39 +0,0 @@ -package de.jrpie.android.launcher.preferences.legacy - -import android.content.Context -import de.jrpie.android.launcher.Application -import de.jrpie.android.launcher.preferences.LauncherPreferences -import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION -import de.jrpie.android.launcher.widgets.ClockWidget -import de.jrpie.android.launcher.widgets.DebugInfoWidget -import de.jrpie.android.launcher.widgets.generateInternalId -import de.jrpie.android.launcher.widgets.updateWidget - -fun migratePreferencesFromVersion100(context: Context) { - assert(PREFERENCE_VERSION == 101) - assert(LauncherPreferences.internal().versionCode() == 100) - - val widgets = LauncherPreferences.widgets().widgets() ?: setOf() - widgets.forEach { widget -> - when (widget) { - is ClockWidget -> { - val id = widget.id - val newId = generateInternalId() - (context.applicationContext as Application).appWidgetHost.deleteAppWidgetId(id) - widget.delete(context) - widget.id = newId - updateWidget(widget) - } - is DebugInfoWidget -> { - val id = widget.id - val newId = generateInternalId() - (context.applicationContext as Application).appWidgetHost.deleteAppWidgetId(id) - widget.delete(context) - widget.id = newId - updateWidget(widget) - } - else -> {} - } - } - LauncherPreferences.internal().versionCode(101) -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version4.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version4.kt index fb353d5..aaeeb18 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version4.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/legacy/Version4.kt @@ -1,24 +1,27 @@ package de.jrpie.android.launcher.preferences.legacy import android.content.Context +import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.preferences.LauncherPreferences +import de.jrpie.android.launcher.preferences.PREFERENCE_VERSION import de.jrpie.android.launcher.widgets.ClockWidget import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPosition -import de.jrpie.android.launcher.widgets.generateInternalId fun migratePreferencesFromVersion4(context: Context) { + assert(PREFERENCE_VERSION == 100) assert(LauncherPreferences.internal().versionCode() < 100) LauncherPreferences.widgets().widgets( setOf( ClockWidget( - generateInternalId(), + (context.applicationContext as Application).appWidgetHost.allocateAppWidgetId(), WidgetPosition(1, 3, 10, 4), WidgetPanel.HOME.id ) ) ) + + LauncherPreferences.internal().versionCode(100) - migratePreferencesFromVersion100(context) } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt index 76bf443..192a8e9 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt @@ -1,18 +1,25 @@ package de.jrpie.android.launcher.ui +import android.annotation.SuppressLint +import android.app.Activity import android.content.SharedPreferences +import android.content.res.Configuration import android.content.res.Resources +import android.os.Build import android.os.Bundle +import android.view.KeyEvent +import android.view.MotionEvent import android.view.View +import android.window.OnBackInvokedDispatcher import de.jrpie.android.launcher.Application +import de.jrpie.android.launcher.R import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction -import de.jrpie.android.launcher.databinding.ActivityHomeBinding +import de.jrpie.android.launcher.databinding.HomeBinding import de.jrpie.android.launcher.openTutorial import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity -import de.jrpie.android.launcher.ui.util.LauncherGestureActivity /** * [HomeActivity] is the actual application Launcher, @@ -26,9 +33,10 @@ import de.jrpie.android.launcher.ui.util.LauncherGestureActivity * - Setting global variables (preferences etc.) * - Opening the [TutorialActivity] on new installations */ -class HomeActivity : UIObject, LauncherGestureActivity() { +class HomeActivity : UIObject, Activity() { - private lateinit var binding: ActivityHomeBinding + private lateinit var binding: HomeBinding + private var touchGestureDetector: TouchGestureDetector? = null private var sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> @@ -47,21 +55,35 @@ class HomeActivity : UIObject, LauncherGestureActivity() { } override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + super.onCreate(savedInstanceState) super.onCreate() + // Initialise layout - binding = ActivityHomeBinding.inflate(layoutInflater) + binding = HomeBinding.inflate(layoutInflater) setContentView(binding.root) + // Handle back key / gesture on Android 13+, cf. onKeyDown() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + onBackInvokedDispatcher.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_OVERLAY + ) { + handleBack() + } + } binding.buttonFallbackSettings.setOnClickListener { LauncherAction.SETTINGS.invoke(this) } } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + touchGestureDetector?.updateScreenSize(windowManager) + } + override fun onStart() { - super.onStart() + super.onStart() super.onStart() // If the tutorial was not finished, start it @@ -72,6 +94,15 @@ class HomeActivity : UIObject, LauncherGestureActivity() { LauncherPreferences.getSharedPreferences() .registerOnSharedPreferenceChangeListener(sharedPreferencesListener) + (application as Application).appWidgetHost.startListening() + + } + + + + override fun onStop() { + (application as Application).appWidgetHost.stopListening() + super.onStop() } override fun onWindowFocusChanged(hasFocus: Boolean) { @@ -82,6 +113,7 @@ class HomeActivity : UIObject, LauncherGestureActivity() { } } + private fun updateSettingsFallbackButtonVisibility() { // If µLauncher settings can not be reached from any action bound to an enabled gesture, // show the fallback button. @@ -97,43 +129,90 @@ class HomeActivity : UIObject, LauncherGestureActivity() { } override fun getTheme(): Resources.Theme { - return modifyTheme(super.getTheme()) - } + val mTheme = modifyTheme(super.getTheme()) + mTheme.applyStyle(R.style.backgroundWallpaper, true) + LauncherPreferences.clock().font().applyToTheme(mTheme) + LauncherPreferences.theme().colorTheme().applyToTheme( + mTheme, + LauncherPreferences.theme().textShadow() + ) - override fun onPause() { - try { - (application as Application).appWidgetHost.stopListening() - } catch (e: Exception) { - // Throws a NullPointerException on Android 12 an earlier, see #172 - e.printStackTrace() - } - super.onPause() + return mTheme } override fun onResume() { super.onResume() + + /* This should be initialized in onCreate() + However on some devices there seems to be a bug where the touchGestureDetector + is not working properly after resuming the app. + Reinitializing the touchGestureDetector every time the app is resumed might help to fix that. + (see issue #138) + */ + touchGestureDetector = TouchGestureDetector( + this, 0, 0, + LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f + ).also { + it.updateScreenSize(windowManager) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + binding.root.setOnApplyWindowInsetsListener { _, windowInsets -> + @Suppress("deprecation") // required to support API 29 + val insets = windowInsets.systemGestureInsets + touchGestureDetector?.setSystemGestureInsets(insets) + + windowInsets + } + } updateSettingsFallbackButtonVisibility() binding.homeWidgetContainer.updateWidgets(this@HomeActivity, LauncherPreferences.widgets().widgets() ) - - (application as Application).appWidgetHost.startListening() } - override fun onDestroy() { LauncherPreferences.getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener) super.onDestroy() } - override fun handleBack() { - Gesture.BACK(this) + @SuppressLint("GestureBackNavigation") + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + when (keyCode) { + KeyEvent.KEYCODE_BACK -> { + // Only used pre Android 13, cf. onBackInvokedDispatcher + handleBack() + } + + KeyEvent.KEYCODE_VOLUME_UP -> { + if (Action.forGesture(Gesture.VOLUME_UP) == LauncherAction.VOLUME_UP) { + // Let the OS handle the key event. This works better with some custom ROMs + // and apps like Samsung Sound Assistant. + return false + } + Gesture.VOLUME_UP(this) + } + + KeyEvent.KEYCODE_VOLUME_DOWN -> { + if (Action.forGesture(Gesture.VOLUME_DOWN) == LauncherAction.VOLUME_DOWN) { + // see above + return false + } + Gesture.VOLUME_DOWN(this) + } + } + return true } - override fun getRootView(): View { - return binding.root + override fun onTouchEvent(event: MotionEvent): Boolean { + touchGestureDetector?.onTouchEvent(event) + return true + } + + private fun handleBack() { + Gesture.BACK(this) } override fun isHomeScreen(): Boolean { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt index 70f737f..3dbdda8 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt @@ -1,6 +1,7 @@ package de.jrpie.android.launcher.ui import android.app.AlertDialog +import android.app.Service import android.content.Context import android.content.pm.LauncherApps import android.content.pm.LauncherApps.PinItemRequest @@ -44,7 +45,7 @@ class PinShortcutActivity : AppCompatActivity(), UIObject { binding = ActivityPinShortcutBinding.inflate(layoutInflater) setContentView(binding.root) - val launcherApps = getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps + val launcherApps = getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps val request = launcherApps.getPinItemRequest(intent) this.request = request @@ -55,7 +56,7 @@ class PinShortcutActivity : AppCompatActivity(), UIObject { if (request.requestType == PinItemRequest.REQUEST_TYPE_APPWIDGET) { - // TODO handle app widgets + // TODO request.getAppWidgetProviderInfo(this) // startActivity() finish() diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/ReportCrashActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/ReportCrashActivity.kt deleted file mode 100644 index 5d95769..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/ui/ReportCrashActivity.kt +++ /dev/null @@ -1,57 +0,0 @@ -package de.jrpie.android.launcher.ui - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import de.jrpie.android.launcher.R -import de.jrpie.android.launcher.copyToClipboard -import de.jrpie.android.launcher.databinding.ActivityReportCrashBinding -import de.jrpie.android.launcher.getDeviceInfo -import de.jrpie.android.launcher.openInBrowser -import de.jrpie.android.launcher.writeEmail - -const val EXTRA_CRASH_LOG = "crashLog" - -class ReportCrashActivity : AppCompatActivity() { - // We don't know what caused the crash, so this Activity should use as little functionality as possible. - // In particular it is not a UIObject (and hence looks quite ugly) - private lateinit var binding: ActivityReportCrashBinding - private var report: String? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // Initialise layout - binding = ActivityReportCrashBinding.inflate(layoutInflater) - setContentView(binding.root) - - setTitle(R.string.report_crash_title) - setSupportActionBar(binding.reportCrashAppbar) - supportActionBar?.setDisplayHomeAsUpEnabled(false) - - report = intent.getStringExtra(EXTRA_CRASH_LOG) - - binding.reportCrashButtonCopy.setOnClickListener { - copyToClipboard(this, - "Device Info:\n${getDeviceInfo()}\n\nCrash Log:\n${report}") - } - - binding.reportCrashButtonMail.setOnClickListener { - writeEmail( - this, - getString(R.string.settings_meta_report_bug_mail), - "Crash in μLauncher", - "Hi!\nUnfortunately, μLauncher crashed:\n" + - "\nDevice Info\n\n${getDeviceInfo()}\n\n" + - "\nCrash Log\n\n${report}\n" + - "\nAdditional Information\n\n" + - "[Please add additional information: What did you do when the crash happened? Do you know how to trigger it? ... ]" - ) - } - binding.reportCrashButtonReport.setOnClickListener { - openInBrowser( - getString(R.string.settings_meta_report_bug_link), - this - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt b/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt index 8264752..8e8ed4e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/TouchGestureDetector.kt @@ -17,7 +17,6 @@ import kotlin.math.max import kotlin.math.min import kotlin.math.tan -@Suppress("PrivatePropertyName") class TouchGestureDetector( private val context: Context, var width: Int, @@ -35,13 +34,13 @@ class TouchGestureDetector( private val MIN_TRIANGLE_HEIGHT = 250 + private val longPressHandler = Handler(Looper.getMainLooper()) + private var systemGestureInsetTop = 100 private var systemGestureInsetBottom = 0 private var systemGestureInsetLeft = 0 private var systemGestureInsetRight = 0 - private val longPressHandler = Handler(Looper.getMainLooper()) - data class Vector(val x: Float, val y: Float) { fun absSquared(): Float { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/UIObject.kt b/app/src/main/java/de/jrpie/android/launcher/ui/UIObject.kt index b292425..51324f4 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/UIObject.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/UIObject.kt @@ -10,7 +10,6 @@ import android.view.WindowInsets import android.view.WindowInsetsController import android.view.WindowManager import de.jrpie.android.launcher.preferences.LauncherPreferences -import de.jrpie.android.launcher.preferences.theme.Background /** * An interface implemented by every [Activity], Fragment etc. in Launcher. @@ -66,14 +65,8 @@ interface UIObject { theme, LauncherPreferences.theme().textShadow() ) - - if (isHomeScreen()) { - Background.TRANSPARENT.applyToTheme(theme) - LauncherPreferences.clock().font().applyToTheme(theme) - } else { - LauncherPreferences.theme().background().applyToTheme(theme) - LauncherPreferences.theme().font().applyToTheme(theme) - } + LauncherPreferences.theme().background().applyToTheme(theme) + LauncherPreferences.theme().font().applyToTheme(theme) return theme } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt index 784d6fa..65278ce 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt @@ -237,4 +237,9 @@ class AppsRecyclerAdapter( appFilter.favoritesVisibility = v updateAppsList() } + + fun setHiddenAppsVisibility(v: AppFilter.Companion.AppSetVisibility) { + appFilter.hiddenVisibility = v + updateAppsList() + } } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt index ed26729..a8e59ba 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/ListFragmentApps.kt @@ -96,6 +96,7 @@ class ListFragmentApps : Fragment(), UIObject { if (LauncherPreferences.functionality().searchAutoCloseKeyboard()) { addOnScrollListener(object : RecyclerView.OnScrollListener() { var totalDy: Int = 0 + var threshold = (resources.displayMetrics.density * 100).toInt() override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { totalDy += dy diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt index 1a0e802..dea0bcf 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/meta/SettingsFragmentMeta.kt @@ -79,9 +79,6 @@ class SettingsFragmentMeta : Fragment(), UIObject { // view code bindURL(binding.settingsMetaButtonViewCode, R.string.settings_meta_link_github) - // view documentation - bindURL(binding.settingsMetaButtonViewDocs, R.string.settings_meta_link_docs) - // report a bug binding.settingsMetaButtonReportBug.setOnClickListener { val deviceInfo = getDeviceInfo() @@ -135,12 +132,7 @@ class SettingsFragmentMeta : Fragment(), UIObject { startActivity(Intent(this.context, LegalInfoActivity::class.java)) } - // version binding.settingsMetaTextVersion.text = BuildConfig.VERSION_NAME - binding.settingsMetaTextVersion.setOnClickListener { - val deviceInfo = getDeviceInfo() - copyToClipboard(requireContext(), deviceInfo) - } } } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt index 548c30b..8feaa07 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/tutorial/tabs/TutorialFragment5Finish.kt @@ -5,9 +5,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import de.jrpie.android.launcher.BuildConfig.VERSION_CODE import de.jrpie.android.launcher.databinding.Tutorial5FinishBinding import de.jrpie.android.launcher.preferences.LauncherPreferences -import de.jrpie.android.launcher.requestNotificationPermission import de.jrpie.android.launcher.setDefaultHomeScreen import de.jrpie.android.launcher.ui.UIObject @@ -31,10 +31,8 @@ class TutorialFragment5Finish : Fragment(), UIObject { override fun onStart() { super.onStart() super.onStart() - requestNotificationPermission(requireActivity()) } - override fun setOnClicks() { super.setOnClicks() binding.tutorialFinishButtonStart.setOnClickListener { finishTutorial() } @@ -46,7 +44,6 @@ class TutorialFragment5Finish : Fragment(), UIObject { LauncherPreferences.internal().startedTime(System.currentTimeMillis() / 1000L) } context?.let { setDefaultHomeScreen(it, checkDefault = true) } - activity?.finish() } } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/util/LauncherGestureActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/util/LauncherGestureActivity.kt deleted file mode 100644 index 0be09f9..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/ui/util/LauncherGestureActivity.kt +++ /dev/null @@ -1,104 +0,0 @@ -package de.jrpie.android.launcher.ui.util - -import android.annotation.SuppressLint -import android.app.Activity -import android.content.res.Configuration -import android.os.Build -import android.os.Bundle -import android.view.KeyEvent -import android.view.MotionEvent -import android.view.View -import android.window.OnBackInvokedDispatcher -import de.jrpie.android.launcher.actions.Action -import de.jrpie.android.launcher.actions.Gesture -import de.jrpie.android.launcher.actions.LauncherAction -import de.jrpie.android.launcher.preferences.LauncherPreferences -import de.jrpie.android.launcher.ui.TouchGestureDetector - -/** - * An activity with a [TouchGestureDetector] as well as handling of volume and back keys set up. - */ -abstract class LauncherGestureActivity: Activity() { - protected var touchGestureDetector: TouchGestureDetector? = null - - override fun onTouchEvent(event: MotionEvent): Boolean { - touchGestureDetector?.onTouchEvent(event) - return true - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // Handle back key / gesture on Android 13+, cf. onKeyDown() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - onBackInvokedDispatcher.registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_OVERLAY - ) { - handleBack() - } - } - } - - override fun onResume() { - super.onResume() - - /* This should be initialized in onCreate() - However on some devices there seems to be a bug where the touchGestureDetector - is not working properly after resuming the app. - Reinitializing the touchGestureDetector every time the app is resumed might help to fix that. - (see issue #138) - */ - touchGestureDetector = TouchGestureDetector( - this, 0, 0, - LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f - ).also { - it.updateScreenSize(windowManager) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - getRootView()?.setOnApplyWindowInsetsListener { _, windowInsets -> - @Suppress("deprecation") // required to support API 29 - val insets = windowInsets.systemGestureInsets - touchGestureDetector?.setSystemGestureInsets(insets) - - windowInsets - } - } - } - - @SuppressLint("GestureBackNavigation") - override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { - when (keyCode) { - KeyEvent.KEYCODE_BACK -> { - // Only used pre Android 13, cf. onBackInvokedDispatcher - handleBack() - } - - KeyEvent.KEYCODE_VOLUME_UP -> { - if (Action.forGesture(Gesture.VOLUME_UP) == LauncherAction.VOLUME_UP) { - // Let the OS handle the key event. This works better with some custom ROMs - // and apps like Samsung Sound Assistant. - return false - } - Gesture.VOLUME_UP(this) - } - - KeyEvent.KEYCODE_VOLUME_DOWN -> { - if (Action.forGesture(Gesture.VOLUME_DOWN) == LauncherAction.VOLUME_DOWN) { - // see above - return false - } - Gesture.VOLUME_DOWN(this) - } - } - return true - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - touchGestureDetector?.updateScreenSize(windowManager) - } - - protected abstract fun getRootView(): View? - protected abstract fun handleBack() -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt index cbe5395..33c4888 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/ClockView.kt @@ -6,15 +6,13 @@ import android.view.LayoutInflater import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import de.jrpie.android.launcher.actions.Gesture -import de.jrpie.android.launcher.databinding.WidgetClockBinding +import de.jrpie.android.launcher.databinding.ClockBinding import de.jrpie.android.launcher.preferences.LauncherPreferences -import de.jrpie.android.launcher.widgets.WidgetPanel import java.util.Locale -class ClockView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int, val panelId: Int): ConstraintLayout(context, attrs) { - constructor(context: Context, attrs: AttributeSet?): this(context, attrs, WidgetPanel.HOME.id, -1) +class ClockView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int): ConstraintLayout(context, attrs) { - val binding: WidgetClockBinding = WidgetClockBinding.inflate(LayoutInflater.from(context), this, true) + val binding: ClockBinding = ClockBinding.inflate(LayoutInflater.from(context), this, true) init { initClock() setOnClicks() @@ -59,7 +57,7 @@ class ClockView(context: Context, attrs: AttributeSet? = null, val appWidgetId: binding.clockUpperView.format12Hour = upperFormat } - private fun setOnClicks() { + fun setOnClicks() { binding.clockUpperView.setOnClickListener { if (LauncherPreferences.clock().flipDateTime()) { Gesture.TIME(context) @@ -76,4 +74,7 @@ class ClockView(context: Context, attrs: AttributeSet? = null, val appWidgetId: } } } + + + } \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/DebugInfoView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/DebugInfoView.kt deleted file mode 100644 index d0ab70a..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/DebugInfoView.kt +++ /dev/null @@ -1,17 +0,0 @@ -package de.jrpie.android.launcher.ui.widgets - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import androidx.constraintlayout.widget.ConstraintLayout -import de.jrpie.android.launcher.databinding.WidgetDebugInfoBinding -import de.jrpie.android.launcher.getDeviceInfo - -class DebugInfoView(context: Context, attrs: AttributeSet? = null, val appWidgetId: Int): ConstraintLayout(context, attrs) { - - val binding: WidgetDebugInfoBinding = WidgetDebugInfoBinding.inflate(LayoutInflater.from(context), this, true) - - init { - binding.debugInfoText.text = getDeviceInfo() - } -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt index 5eab32f..5f8adf1 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt @@ -61,7 +61,7 @@ open class WidgetContainerView( it.value.y + it.value.height ).contains(position) == true }.any { - Widget.byId(it.key)?.allowInteraction == false + Widget.byId(context, it.key)?.allowInteraction == false } } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetPanelActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetPanelActivity.kt index 7d51b08..5217bff 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetPanelActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetPanelActivity.kt @@ -1,37 +1,24 @@ package de.jrpie.android.launcher.ui.widgets +import android.app.Activity import android.content.res.Resources import android.os.Bundle -import android.view.View -import androidx.core.view.ViewCompat -import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.R import de.jrpie.android.launcher.databinding.ActivityWidgetPanelBinding import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.UIObject -import de.jrpie.android.launcher.ui.util.LauncherGestureActivity import de.jrpie.android.launcher.ui.widgets.manage.EXTRA_PANEL_ID import de.jrpie.android.launcher.widgets.WidgetPanel -class WidgetPanelActivity : LauncherGestureActivity(), UIObject { - var binding: ActivityWidgetPanelBinding? = null - - var widgetPanelId: Int = WidgetPanel.HOME.id - +class WidgetPanelActivity : Activity(), UIObject { + lateinit var binding: ActivityWidgetPanelBinding + var widgetPanelId: Int = WidgetPanel.Companion.HOME.id override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + super.onCreate(savedInstanceState) super.onCreate() + widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.Companion.HOME.id) val binding = ActivityWidgetPanelBinding.inflate(layoutInflater) setContentView(binding.root) - - widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.HOME.id) - - // The widget container should extend below the status and navigation bars, - // so let's set an empty WindowInsetsListener to prevent it from being moved. - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> - windowInsets - } - binding.widgetPanelWidgetContainer.widgetPanelId = widgetPanelId binding.widgetPanelWidgetContainer.updateWidgets( this, @@ -51,42 +38,12 @@ class WidgetPanelActivity : LauncherGestureActivity(), UIObject { return mTheme } - override fun onWindowFocusChanged(hasFocus: Boolean) { - super.onWindowFocusChanged(hasFocus) - - if (hasFocus && LauncherPreferences.display().hideNavigationBar()) { - hideNavigationBar() - } - } override fun onStart() { - super.onStart() + super.onStart() super.onStart() } - override fun onPause() { - try { - (application as Application).appWidgetHost.stopListening() - } catch (e: Exception) { - // Throws a NullPointerException on Android 12 an earlier, see #172 - e.printStackTrace() - } - super.onPause() - } - - override fun onResume() { - super.onResume() - (application as Application).appWidgetHost.startListening() - } - - override fun getRootView(): View? { - return binding?.root - } - - override fun handleBack() { - finish() - } - override fun isHomeScreen(): Boolean { return true } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetPanelsActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetPanelsActivity.kt index 163777f..cb57fda 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetPanelsActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetPanelsActivity.kt @@ -18,16 +18,13 @@ import de.jrpie.android.launcher.widgets.updateWidgetPanel class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject { - @SuppressLint("NotifyDataSetChanged") private val sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> - if ( - prefKey == LauncherPreferences.widgets().keys().customPanels() - || prefKey == LauncherPreferences.widgets().keys().widgets() - ) { + if (prefKey == LauncherPreferences.widgets().keys().customPanels()) { viewAdapter.widgetPanels = (LauncherPreferences.widgets().customPanels() ?: setOf()).toTypedArray() + @SuppressLint("NotifyDataSetChanged") viewAdapter.notifyDataSetChanged() } } @@ -79,6 +76,7 @@ class ManageWidgetPanelsActivity : AppCompatActivity(), UIObject { ) ) } + true } } diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt index 531cdc1..a841919 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/ManageWidgetsActivity.kt @@ -5,22 +5,21 @@ import android.appwidget.AppWidgetManager import android.content.Intent import android.content.SharedPreferences import android.content.res.Resources +import android.graphics.Rect import android.os.Bundle import android.util.Log -import android.view.ViewGroup import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat -import androidx.core.view.updateLayoutParams +import com.google.android.material.floatingactionbutton.FloatingActionButton import de.jrpie.android.launcher.Application -import de.jrpie.android.launcher.databinding.ActivityManageWidgetsBinding +import de.jrpie.android.launcher.R import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.UIObject +import de.jrpie.android.launcher.ui.widgets.WidgetContainerView import de.jrpie.android.launcher.widgets.AppWidget -import de.jrpie.android.launcher.widgets.GRID_SIZE import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPosition -import kotlin.math.max -import kotlin.math.roundToInt +import kotlin.math.min // http://coderender.blogspot.com/2012/01/hosting-android-widgets-my.html @@ -31,16 +30,14 @@ const val REQUEST_PICK_APPWIDGET = 2 const val EXTRA_PANEL_ID = "widgetPanelId" // We can't use AppCompatActivity, since some AppWidgets don't work there. -class ManageWidgetsActivity : UIObject, Activity() { +class ManageWidgetsActivity : Activity(), UIObject { private var panelId: Int = WidgetPanel.HOME.id - private lateinit var binding: ActivityManageWidgetsBinding private var sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> if (prefKey == LauncherPreferences.widgets().keys().widgets()) { - binding.manageWidgetsContainer.updateWidgets( - this, + findViewById(R.id.manage_widgets_container).updateWidgets(this, LauncherPreferences.widgets().widgets() ) } @@ -49,33 +46,21 @@ class ManageWidgetsActivity : UIObject, Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) super.onCreate() - binding = ActivityManageWidgetsBinding.inflate(layoutInflater) - setContentView(binding.root) + setContentView(R.layout.activity_manage_widgets) panelId = intent.extras?.getInt(EXTRA_PANEL_ID, WidgetPanel.HOME.id) ?: WidgetPanel.HOME.id - binding.manageWidgetsButtonAdd.setOnClickListener { + findViewById(R.id.manage_widgets_button_add).setOnClickListener { selectWidget() } - // The widget container should extend below the status and navigation bars, - // so let's set an empty WindowInsetsListener to prevent it from being moved. - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> - windowInsets + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) + insets } - // The button must not be placed under the navigation bar - ViewCompat.setOnApplyWindowInsetsListener(binding.manageWidgetsButtonAdd) { v, windowInsets -> - val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.updateLayoutParams { - leftMargin = insets.left - bottomMargin = insets.bottom - rightMargin = insets.right - } - WindowInsetsCompat.CONSUMED - } - - binding.manageWidgetsContainer.let { + findViewById(R.id.manage_widgets_container).let { it.widgetPanelId = panelId it.updateWidgets(this, LauncherPreferences.widgets().widgets()) } @@ -90,36 +75,22 @@ class ManageWidgetsActivity : UIObject, Activity() { } - override fun onPause() { - try { - (application as Application).appWidgetHost.stopListening() - } catch (e: Exception) { - // Throws a NullPointerException on Android 12 an earlier, see #172 - e.printStackTrace() - } - super.onPause() - } - override fun onResume() { super.onResume() - (application as Application).appWidgetHost.startListening() - - binding.manageWidgetsContainer.updateWidgets( - this, + findViewById(R.id.manage_widgets_container).updateWidgets(this, LauncherPreferences.widgets().widgets() ) + } - - override fun onWindowFocusChanged(hasFocus: Boolean) { - super.onWindowFocusChanged(hasFocus) - - if (hasFocus && LauncherPreferences.display().hideNavigationBar()) { - hideNavigationBar() - } - } - override fun getTheme(): Resources.Theme { - return modifyTheme(super.getTheme()) + val mTheme = modifyTheme(super.getTheme()) + mTheme.applyStyle(R.style.backgroundWallpaper, true) + LauncherPreferences.clock().font().applyToTheme(mTheme) + LauncherPreferences.theme().colorTheme().applyToTheme( + mTheme, + LauncherPreferences.theme().textShadow() + ) + return mTheme } override fun onDestroy() { @@ -129,9 +100,14 @@ class ManageWidgetsActivity : UIObject, Activity() { } - private fun selectWidget() { + fun selectWidget() { + val appWidgetHost = (application as Application).appWidgetHost startActivityForResult( Intent(this, SelectWidgetActivity::class.java).also { + it.putExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, + appWidgetHost.allocateAppWidgetId() + ) it.putExtra( EXTRA_PANEL_ID, panelId @@ -141,28 +117,25 @@ class ManageWidgetsActivity : UIObject, Activity() { } - private fun createWidget(data: Intent) { + fun createWidget(data: Intent) { Log.i("Launcher", "creating widget") val appWidgetManager = (application as Application).appWidgetManager - val appWidgetHost = (application as Application).appWidgetHost val appWidgetId = data.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return + val provider = appWidgetManager.getAppWidgetInfo(appWidgetId) + val display = windowManager.defaultDisplay - val widgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) - if (widgetInfo == null) { - Log.w("Launcher", "can't access widget") - appWidgetHost.deleteAppWidgetId(appWidgetId) - return - } - - val position = WidgetPosition.findFreeSpace( - WidgetPanel.byId(panelId), - max(3, (GRID_SIZE * (widgetInfo.minWidth) / display.width.toFloat()).roundToInt()), - max(3, (GRID_SIZE * (widgetInfo.minHeight) / display.height.toFloat()).roundToInt()) + val position = WidgetPosition.fromAbsoluteRect( + Rect(0,0, + min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minWidth), + min(400, appWidgetManager.getAppWidgetInfo(appWidgetId).minHeight) + ), + display.width, + display.height ) - val widget = AppWidget(appWidgetId, position, panelId, widgetInfo) + val widget = AppWidget(appWidgetId, position, panelId, provider) LauncherPreferences.widgets().widgets( (LauncherPreferences.widgets().widgets() ?: HashSet()).also { it.add(widget) diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt index eeb98df..a1bd3b5 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/SelectWidgetActivity.kt @@ -14,7 +14,6 @@ import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.R import de.jrpie.android.launcher.databinding.ActivitySelectWidgetBinding import de.jrpie.android.launcher.ui.UIObject @@ -25,7 +24,7 @@ import de.jrpie.android.launcher.widgets.LauncherWidgetProvider import de.jrpie.android.launcher.widgets.WidgetPanel import de.jrpie.android.launcher.widgets.WidgetPosition import de.jrpie.android.launcher.widgets.bindAppWidgetOrRequestPermission -import de.jrpie.android.launcher.widgets.generateInternalId +import de.jrpie.android.launcher.widgets.getAppWidgetHost import de.jrpie.android.launcher.widgets.getAppWidgetProviders import de.jrpie.android.launcher.widgets.updateWidget @@ -39,13 +38,12 @@ private const val REQUEST_WIDGET_PERMISSION = 29 */ class SelectWidgetActivity : AppCompatActivity(), UIObject { lateinit var binding: ActivitySelectWidgetBinding + var widgetId: Int = -1 var widgetPanelId: Int = WidgetPanel.HOME.id private fun tryBindWidget(info: LauncherWidgetProvider) { when (info) { is LauncherAppWidgetProvider -> { - val widgetId = - (applicationContext as Application).appWidgetHost.allocateAppWidgetId() if (bindAppWidgetOrRequestPermission( this, info.info, @@ -64,7 +62,7 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject { } } is LauncherClockWidgetProvider -> { - updateWidget(ClockWidget(generateInternalId(), WidgetPosition(0, 4, 12, 3), widgetPanelId)) + updateWidget(ClockWidget(widgetId, WidgetPosition(0, 4, 12, 3), widgetPanelId)) finish() } } @@ -83,7 +81,11 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject { setContentView(binding.root) + widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) widgetPanelId = intent.getIntExtra(EXTRA_PANEL_ID, WidgetPanel.HOME.id) + if (widgetId == -1) { + widgetId = getAppWidgetHost().allocateAppWidgetId() + } val viewManager = LinearLayoutManager(this) val viewAdapter = SelectWidgetRecyclerAdapter() @@ -93,11 +95,6 @@ class SelectWidgetActivity : AppCompatActivity(), UIObject { layoutManager = viewManager adapter = viewAdapter } - - binding.selectWidgetClose.setOnClickListener { - setResult(RESULT_CANCELED) - finish() - } } override fun getTheme(): Resources.Theme { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt index f49f7da..76a2572 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetManagerView.kt @@ -31,19 +31,19 @@ class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSe WidgetContainerView(widgetPanelId, context, attrs) { constructor(context: Context, attrs: AttributeSet?) : this(WidgetPanel.HOME.id, context, attrs) - val touchSlop: Int - val touchSlopSquare: Int - val longPressTimeout: Long + val TOUCH_SLOP: Int + val TOUCH_SLOP_SQUARE: Int + val LONG_PRESS_TIMEOUT: Long private var overlayViewById = HashMap() init { val configuration = ViewConfiguration.get(context) - touchSlop = configuration.scaledTouchSlop - touchSlopSquare = touchSlop * touchSlop + TOUCH_SLOP = configuration.scaledTouchSlop + TOUCH_SLOP_SQUARE = TOUCH_SLOP * TOUCH_SLOP - longPressTimeout = ViewConfiguration.getLongPressTimeout().toLong() + LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong() } @@ -113,7 +113,7 @@ class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSe height ) selectedWidgetOverlayView = view - selectedWidgetView = widgetViewById[view.widgetId] + selectedWidgetView = widgetViewById[view.widgetId] ?: return true startWidgetPosition = position val positionInView = start.minus(Point(position.left, position.top)) @@ -127,14 +127,14 @@ class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSe view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) endInteraction() } - }, longPressTimeout) + }, LONG_PRESS_TIMEOUT) } if (event.actionMasked == MotionEvent.ACTION_MOVE || event.actionMasked == MotionEvent.ACTION_UP ) { val distanceX = event.x - (currentGestureStart?.x ?: return true) val distanceY = event.y - (currentGestureStart?.y ?: return true) - if (distanceX * distanceX + distanceY * distanceY > touchSlopSquare) { + if (distanceX * distanceX + distanceY * distanceY > TOUCH_SLOP_SQUARE) { longPressHandler.removeCallbacksAndMessages(null) } val view = selectedWidgetOverlayView ?: return true @@ -160,8 +160,9 @@ class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSe } if (event.actionMasked == MotionEvent.ACTION_UP) { + longPressHandler.removeCallbacksAndMessages(null) val id = selectedWidgetOverlayView?.widgetId ?: return true - val widget = Widget.byId(id) ?: return true + val widget = Widget.byId(context, id) ?: return true widget.position = newPosition endInteraction() updateWidget(widget) @@ -175,16 +176,8 @@ class WidgetManagerView(widgetPanelId: Int, context: Context, attrs: AttributeSe } private fun endInteraction() { - synchronized(this) { - longPressHandler.removeCallbacksAndMessages(null) - startWidgetPosition = null - selectedWidgetOverlayView?.mode = null - } - } - - override fun onDetachedFromWindow() { - endInteraction() - super.onDetachedFromWindow() + startWidgetPosition = null + selectedWidgetOverlayView?.mode = null } override fun updateWidgets(activity: Activity, widgets: Collection?) { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt index 0363069..1b8a2d2 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetOverlayView.kt @@ -2,13 +2,11 @@ package de.jrpie.android.launcher.ui.widgets.manage import android.content.Context import android.graphics.Canvas -import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View -import android.view.ViewGroup import android.widget.PopupMenu import androidx.core.graphics.toRectF import de.jrpie.android.launcher.R @@ -22,50 +20,49 @@ private const val HANDLE_EDGE_SIZE = (1.2 * HANDLE_SIZE).toInt() /** * An overlay to show configuration options for a widget in [WidgetManagerView] */ -class WidgetOverlayView : ViewGroup { +class WidgetOverlayView : View { - private val paint = Paint() - private val handlePaint = Paint() - private val selectedHandlePaint = Paint() - - private val popupAnchor = View(context) + val paint = Paint() + val handlePaint = Paint() + val selectedHandlePaint = Paint() var mode: WidgetManagerView.EditMode? = null - class Handle(val mode: WidgetManagerView.EditMode, val position: Rect) init { - addView(popupAnchor) - setWillNotDraw(false) handlePaint.style = Paint.Style.STROKE - handlePaint.color = Color.WHITE - handlePaint.strokeWidth = 2f - handlePaint.setShadowLayer(10f,0f,0f, Color.BLACK) + handlePaint.setARGB(255, 255, 255, 255) selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE selectedHandlePaint.setARGB(100, 255, 255, 255) - handlePaint.setShadowLayer(10f,0f,0f, Color.BLACK) paint.style = Paint.Style.STROKE - paint.color = Color.WHITE - paint.setShadowLayer(10f,0f,0f, Color.BLACK) + paint.setARGB(255, 255, 255, 255) } private var preview: Drawable? = null var widgetId: Int = -1 set(newId) { field = newId - preview = Widget.byId(widgetId)?.getPreview(context) + preview = Widget.byId(context, widgetId)?.getPreview(context) } - constructor(context: Context) : super(context) + constructor(context: Context) : super(context) { + init(null, 0) + } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(attrs, 0) + } constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( context, attrs, defStyle - ) + ) { + init(attrs, defStyle) + } + + private fun init(attrs: AttributeSet?, defStyle: Int) { } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) @@ -78,28 +75,26 @@ class WidgetOverlayView : ViewGroup { } } val bounds = getBounds() - canvas.drawRoundRect(bounds.toRectF(), 5f, 5f, paint) if (mode == null) { return } + //preview?.bounds = bounds //preview?.draw(canvas) - } - override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { - popupAnchor.layout(0,0,0,0) + } fun showPopupMenu() { - val widget = Widget.byId(widgetId)?: return - val menu = PopupMenu(context, popupAnchor) + val widget = Widget.byId(context, widgetId)?: return + val menu = PopupMenu(context, this) menu.menu.let { it.add( context.getString(R.string.widget_menu_remove) ).setOnMenuItemClickListener { _ -> - Widget.byId(widgetId)?.delete(context) + Widget.byId(context, widgetId)?.delete(context) return@setOnMenuItemClickListener true } it.add( @@ -118,7 +113,7 @@ class WidgetOverlayView : ViewGroup { } fun getHandles(): List { - return listOf( + return listOf( Handle(WidgetManagerView.EditMode.TOP, Rect(HANDLE_EDGE_SIZE, 0, width - HANDLE_EDGE_SIZE, HANDLE_SIZE)), Handle(WidgetManagerView.EditMode.BOTTOM, diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetPanelsRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetPanelsRecyclerAdapter.kt index d27ba9a..40c2c2f 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetPanelsRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/manage/WidgetPanelsRecyclerAdapter.kt @@ -26,16 +26,10 @@ class WidgetPanelsRecyclerAdapter( class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var labelView: TextView = itemView.findViewById(R.id.list_widget_panels_label) - var infoView: TextView = itemView.findViewById(R.id.list_widget_panels_info) } override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) { viewHolder.labelView.text = widgetPanels[i].label - val numWidgets = widgetPanels[i].getWidgets().size - viewHolder.infoView.text = context.resources.getQuantityString( - R.plurals.widget_panel_number_of_widgets, - numWidgets, numWidgets - ) viewHolder.itemView.setOnClickListener { onSelectWidgetPanel(widgetPanels[i]) diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt index a968962..22a63eb 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/AppWidget.kt @@ -41,7 +41,7 @@ class AppWidget( id, position, panelId, - panelId != WidgetPanel.HOME.id, + false, widgetProviderInfo.provider.packageName, widgetProviderInfo.provider.className, widgetProviderInfo.profile.hashCode() @@ -78,10 +78,6 @@ class AppWidget( override fun createView(activity: Activity): AppWidgetHostView? { val providerInfo = activity.getAppWidgetManager().getAppWidgetInfo(id) ?: return null - /* TODO: if providerInfo is null, the corresponding app was probably uninstalled. - There does not seem to be a way to recover the widget when the app is installed again, - hence it should be deleted. */ - val view = activity.getAppWidgetHost() .createView(activity, this.id, providerInfo) diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt index 29d9308..d0d1c0e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/ClockWidget.kt @@ -12,14 +12,14 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("widget:clock") class ClockWidget( - override var id: Int, + override val id: Int, override var position: WidgetPosition, override val panelId: Int, override var allowInteraction: Boolean = true ) : Widget() { - override fun createView(activity: Activity): View { - return ClockView(activity, null, id, panelId) + override fun createView(activity: Activity): View? { + return ClockView(activity, null, id) } override fun findView(views: Sequence): ClockView? { diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/DebugInfoWidget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/DebugInfoWidget.kt deleted file mode 100644 index 75ae6d0..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/DebugInfoWidget.kt +++ /dev/null @@ -1,42 +0,0 @@ -package de.jrpie.android.launcher.widgets - -import android.app.Activity -import android.content.Context -import android.graphics.drawable.Drawable -import android.view.View -import de.jrpie.android.launcher.ui.widgets.DebugInfoView -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - - -@Serializable -@SerialName("widget:debuginfo") -class DebugInfoWidget( - override var id: Int, - override var position: WidgetPosition, - override val panelId: Int, - override var allowInteraction: Boolean = true -) : Widget() { - - override fun createView(activity: Activity): View { - return DebugInfoView(activity, null, id) - } - - override fun findView(views: Sequence): DebugInfoView? { - return views.mapNotNull { it as? DebugInfoView }.firstOrNull { it.appWidgetId == id } - } - - override fun getPreview(context: Context): Drawable? { - return null - } - - override fun getIcon(context: Context): Drawable? { - return null - } - - override fun isConfigurable(context: Context): Boolean { - return false - } - - override fun configure(activity: Activity, requestCode: Int) { } -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt index 92f33a9..018b29b 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/LauncherWidgetProvider.kt @@ -37,14 +37,13 @@ class LauncherAppWidgetProvider(val info: AppWidgetProviderInfo) : LauncherWidge } } +class LauncherClockWidgetProvider : LauncherWidgetProvider() { -data object LauncherClockWidgetProvider : LauncherWidgetProvider() { - - override fun loadLabel(context: Context): CharSequence { + override fun loadLabel(context: Context): CharSequence? { return context.getString(R.string.widget_clock_label) } - override fun loadDescription(context: Context): CharSequence { + override fun loadDescription(context: Context): CharSequence? { return context.getString(R.string.widget_clock_description) } @@ -55,4 +54,5 @@ data object LauncherClockWidgetProvider : LauncherWidgetProvider() { override fun loadIcon(context: Context): Drawable? { return AppCompatResources.getDrawable(context, R.drawable.baseline_clock_24) } -} \ No newline at end of file +} + diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt index 73f5d81..e31250b 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widget.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.graphics.drawable.Drawable import android.view.View +import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.preferences.LauncherPreferences import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -27,9 +28,7 @@ sealed class Widget { abstract fun configure(activity: Activity, requestCode: Int) fun delete(context: Context) { - if (id >= 0) { - context.getAppWidgetHost().deleteAppWidgetId(id) - } + context.getAppWidgetHost().deleteAppWidgetId(id) LauncherPreferences.widgets().widgets( LauncherPreferences.widgets().widgets()?.also { @@ -38,6 +37,10 @@ sealed class Widget { ) } + fun getPanel(): WidgetPanel? { + return WidgetPanel.byId(panelId) + } + override fun hashCode(): Int { return id } @@ -53,9 +56,9 @@ sealed class Widget { fun deserialize(serialized: String): Widget { return Json.decodeFromString(serialized) } - fun byId(id: Int): Widget? { + fun byId(context: Context, id: Int): Widget? { // TODO: do some caching - return LauncherPreferences.widgets().widgets().firstOrNull { + return LauncherPreferences.widgets().widgets().firstOrNull() { it.id == id } } diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt index e56983a..93e588d 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPanel.kt @@ -32,12 +32,6 @@ class WidgetPanel(val id: Int, var label: String) { .filter { it.panelId == this.id }.forEach { it.delete(context) } } - fun getWidgets(): List { - return LauncherPreferences.widgets().widgets().filter { - it.panelId == this.id - } - } - companion object { val HOME = WidgetPanel(0, "home") diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt index e51f00c..b575665 100644 --- a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetPosition.kt @@ -11,20 +11,9 @@ const val GRID_SIZE: Short = 12 @Serializable data class WidgetPosition(var x: Short, var y: Short, var width: Short, var height: Short) { - constructor(rect: Rect) : this( - rect.left.toShort(), - rect.top.toShort(), - (rect.right - rect.left).toShort(), - (rect.bottom - rect.top).toShort() - ) - - fun toRect(): Rect { - return Rect(x.toInt(), y.toInt(), x + width, y + height) - } - fun getAbsoluteRect(screenWidth: Int, screenHeight: Int): Rect { val gridWidth = screenWidth / GRID_SIZE.toFloat() - val gridHeight = screenHeight / GRID_SIZE.toFloat() + val gridHeight= screenHeight / GRID_SIZE.toFloat() return Rect( (x * gridWidth).toInt(), @@ -34,33 +23,25 @@ data class WidgetPosition(var x: Short, var y: Short, var width: Short, var heig ) } - companion object { fun fromAbsoluteRect(absolute: Rect, screenWidth: Int, screenHeight: Int): WidgetPosition { val gridWidth = screenWidth / GRID_SIZE.toFloat() - val gridHeight = screenHeight / GRID_SIZE.toFloat() + val gridHeight= screenHeight / GRID_SIZE.toFloat() - val x = (absolute.left / gridWidth).roundToInt().toShort() - .coerceIn(0, (GRID_SIZE - 1).toShort()) - val y = (absolute.top / gridHeight).roundToInt().toShort() - .coerceIn(0, (GRID_SIZE - 1).toShort()) + val x = (absolute.left / gridWidth).roundToInt().toShort().coerceIn(0, (GRID_SIZE-1).toShort()) + val y = (absolute.top / gridHeight).roundToInt().toShort().coerceIn(0, (GRID_SIZE-1).toShort()) val w = max(2, ((absolute.right - absolute.left) / gridWidth).roundToInt()).toShort() val h = max(2, ((absolute.bottom - absolute.top) / gridHeight).roundToInt()).toShort() - return WidgetPosition(x, y, w, h) + return WidgetPosition(x,y,w,h) } - fun center( - minWidth: Int, - minHeight: Int, - screenWidth: Int, - screenHeight: Int - ): WidgetPosition { + fun center(minWidth: Int, minHeight: Int, screenWidth: Int, screenHeight: Int): WidgetPosition { val gridWidth = screenWidth / GRID_SIZE.toFloat() - val gridHeight = screenHeight / GRID_SIZE.toFloat() + val gridHeight= screenHeight / GRID_SIZE.toFloat() val cellsWidth = ceil(minWidth / gridWidth).toInt().toShort() val cellsHeight = ceil(minHeight / gridHeight).toInt().toShort() @@ -71,32 +52,7 @@ data class WidgetPosition(var x: Short, var y: Short, var width: Short, var heig cellsWidth, cellsHeight ) - } - fun findFreeSpace( - widgetPanel: WidgetPanel?, - minWidth: Int, - minHeight: Int - ): WidgetPosition { - val rect = Rect(0, 0, minWidth, minHeight) - if (widgetPanel == null) { - return WidgetPosition(rect) - } - - val widgets = widgetPanel.getWidgets().map { it.position.toRect() } - - for (x in 0..= Build.VERSION_CODES.O) { + context.getAppWidgetHost().appWidgetIds.forEach { AppWidget(it).delete(context) } + } +} /** * Tries to bind [providerInfo] to the id [id]. @@ -25,9 +29,12 @@ import kotlin.math.min * * @return true iff the app widget was bound successfully. */ -fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidgetProviderInfo, appWidgetId: Int, requestCode: Int? = null): Boolean { +fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidgetProviderInfo, id: Int, requestCode: Int? = null): Boolean { + val appWidgetId = if(id == -1) { + activity.getAppWidgetHost().allocateAppWidgetId() + } else { id } - Log.i("Launcher", "Binding new widget $appWidgetId") + Log.i("Launcher", "Binding new widget ${appWidgetId}") if (!activity.getAppWidgetManager().bindAppWidgetIdIfAllowed( appWidgetId, providerInfo.provider @@ -46,7 +53,7 @@ fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidget fun getAppWidgetProviders( context: Context ): List { - val list = mutableListOf(LauncherClockWidgetProvider) + val list = mutableListOf(LauncherClockWidgetProvider()) val appWidgetManager = context.getAppWidgetManager() val profiles = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -55,15 +62,17 @@ fun getAppWidgetProviders( context: Context ): List { (context.getSystemService(Service.USER_SERVICE) as UserManager).userProfiles } list.addAll( - profiles.map { profile -> - appWidgetManager.getInstalledProvidersForProfile(profile) + profiles.map { + appWidgetManager.getInstalledProvidersForProfile(it) .map { LauncherAppWidgetProvider(it) } }.flatten() ) + return list } + fun updateWidget(widget: Widget) { LauncherPreferences.widgets().widgets( (LauncherPreferences.widgets().widgets() ?: setOf()) @@ -72,13 +81,6 @@ fun updateWidget(widget: Widget) { ) } - -// TODO: this needs to be improved -fun generateInternalId(): Int { - val minId = min(-5,(LauncherPreferences.widgets().widgets() ?: setOf()).minOfOrNull { it.id } ?: 0) - return minId -1 -} - fun updateWidgetPanel(widgetPanel: WidgetPanel) { LauncherPreferences.widgets().customPanels( (LauncherPreferences.widgets().customPanels() ?: setOf()) @@ -92,4 +94,4 @@ fun Context.getAppWidgetHost(): AppWidgetHost { } fun Context.getAppWidgetManager(): AppWidgetManager { return (this.applicationContext as Application).appWidgetManager -} \ No newline at end of file +} diff --git a/app/src/main/res/drawable/baseline_bug_report_24.xml b/app/src/main/res/drawable/baseline_bug_report_24.xml deleted file mode 100644 index ef399ba..0000000 --- a/app/src/main/res/drawable/baseline_bug_report_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout/activity_manage_widget_panels.xml b/app/src/main/res/layout/activity_manage_widget_panels.xml index f84f42f..60413a3 100644 --- a/app/src/main/res/layout/activity_manage_widget_panels.xml +++ b/app/src/main/res/layout/activity_manage_widget_panels.xml @@ -40,7 +40,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" - android:contentDescription="@string/content_description_close" android:gravity="center" android:includeFontPadding="true" android:paddingLeft="16sp" @@ -65,7 +64,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" - android:contentDescription="@string/content_description_add_widget_panel" android:src="@drawable/baseline_add_24" app:layout_anchor="@+id/manage_widget_panels_recycler" app:layout_anchorGravity="end|bottom" diff --git a/app/src/main/res/layout/activity_manage_widgets.xml b/app/src/main/res/layout/activity_manage_widgets.xml index 4e63ec9..c77f0e3 100644 --- a/app/src/main/res/layout/activity_manage_widgets.xml +++ b/app/src/main/res/layout/activity_manage_widgets.xml @@ -1,6 +1,5 @@ - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pin_shortcut.xml b/app/src/main/res/layout/activity_pin_shortcut.xml index da724e7..2519374 100644 --- a/app/src/main/res/layout/activity_pin_shortcut.xml +++ b/app/src/main/res/layout/activity_pin_shortcut.xml @@ -44,7 +44,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" - android:contentDescription="@string/content_description_close" android:gravity="center" android:includeFontPadding="true" android:paddingLeft="16sp" diff --git a/app/src/main/res/layout/activity_report_crash.xml b/app/src/main/res/layout/activity_report_crash.xml deleted file mode 100644 index c7f2110..0000000 --- a/app/src/main/res/layout/activity_report_crash.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - -