mirror of
https://github.com/jrpie/Launcher.git
synced 2025-06-08 10:20:15 +02:00
Compare commits
2 commits
72f77c8294
...
da35488ae0
Author | SHA1 | Date | |
---|---|---|---|
da35488ae0 | |||
a964d9de52 |
17 changed files with 474 additions and 4 deletions
|
@ -106,6 +106,7 @@ dependencies {
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
||||||
implementation "eu.jonahbauer:android-preference-annotations:1.1.2"
|
implementation "eu.jonahbauer:android-preference-annotations:1.1.2"
|
||||||
|
implementation 'androidx.activity:activity:1.10.1'
|
||||||
annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2"
|
annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2"
|
||||||
annotationProcessor "com.android.databinding:compiler:$android_plugin_version"
|
annotationProcessor "com.android.databinding:compiler:$android_plugin_version"
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/launcherBaseTheme"
|
android:theme="@style/launcherBaseTheme"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.widgets.SelectWidgetActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.PinShortcutActivity"
|
android:name=".ui.PinShortcutActivity"
|
||||||
android:autoRemoveFromRecents="true"
|
android:autoRemoveFromRecents="true"
|
||||||
|
|
|
@ -12,6 +12,8 @@ import android.os.Build.VERSION_CODES
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import android.appwidget.AppWidgetHost
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import de.jrpie.android.launcher.actions.TorchManager
|
import de.jrpie.android.launcher.actions.TorchManager
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
|
@ -24,9 +26,15 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
const val APP_WIDGET_HOST_ID = 42;
|
||||||
|
|
||||||
|
|
||||||
class Application : android.app.Application() {
|
class Application : android.app.Application() {
|
||||||
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
|
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
|
||||||
val privateSpaceLocked = MutableLiveData<Boolean>()
|
val privateSpaceLocked = MutableLiveData<Boolean>()
|
||||||
|
lateinit var appWidgetHost: AppWidgetHost
|
||||||
|
lateinit var appWidgetManager: AppWidgetManager
|
||||||
|
|
||||||
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
|
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
@ -103,10 +111,15 @@ class Application : android.app.Application() {
|
||||||
torchManager = TorchManager(this)
|
torchManager = TorchManager(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appWidgetHost = AppWidgetHost(this.applicationContext, APP_WIDGET_HOST_ID)
|
||||||
|
appWidgetManager = AppWidgetManager.getInstance(this.applicationContext)
|
||||||
|
|
||||||
|
appWidgetHost.startListening()
|
||||||
|
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
LauncherPreferences.init(preferences, this.resources)
|
LauncherPreferences.init(preferences, this.resources)
|
||||||
|
|
||||||
|
|
||||||
// Try to restore old preferences
|
// Try to restore old preferences
|
||||||
migratePreferencesToNewVersion(this)
|
migratePreferencesToNewVersion(this)
|
||||||
|
|
||||||
|
@ -157,4 +170,10 @@ class Application : android.app.Application() {
|
||||||
apps.postValue(getApps(packageManager, applicationContext))
|
apps.postValue(getApps(packageManager, applicationContext))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTerminate() {
|
||||||
|
appWidgetHost.stopListening()
|
||||||
|
super.onTerminate()
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@ import android.app.role.RoleManager
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProvider
|
||||||
|
import android.appwidget.AppWidgetProviderInfo
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.LauncherApps
|
import android.content.pm.LauncherApps
|
||||||
|
|
|
@ -21,6 +21,8 @@ import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.list.ListActivity
|
import de.jrpie.android.launcher.ui.list.ListActivity
|
||||||
import de.jrpie.android.launcher.ui.settings.SettingsActivity
|
import de.jrpie.android.launcher.ui.settings.SettingsActivity
|
||||||
|
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||||
|
import de.jrpie.android.launcher.ui.widgets.SelectWidgetActivity
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -61,7 +63,10 @@ enum class LauncherAction(
|
||||||
"choose_from_favorites",
|
"choose_from_favorites",
|
||||||
R.string.list_other_list_favorites,
|
R.string.list_other_list_favorites,
|
||||||
R.drawable.baseline_favorite_24,
|
R.drawable.baseline_favorite_24,
|
||||||
{ context -> openAppsList(context, favorite = true) },
|
{ context ->
|
||||||
|
context.startActivity(Intent(context.applicationContext, SelectWidgetActivity::class.java))
|
||||||
|
},
|
||||||
|
//openAppsList(context, favorite = true) },
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
CHOOSE_FROM_PRIVATE_SPACE(
|
CHOOSE_FROM_PRIVATE_SPACE(
|
||||||
|
|
|
@ -8,6 +8,7 @@ import de.jrpie.android.launcher.actions.lock.LockMethod;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
|
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
|
||||||
|
import de.jrpie.android.launcher.preferences.serialization.SetWidgetInfoSerializer;
|
||||||
import de.jrpie.android.launcher.preferences.theme.Background;
|
import de.jrpie.android.launcher.preferences.theme.Background;
|
||||||
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
|
||||||
import de.jrpie.android.launcher.preferences.theme.Font;
|
import de.jrpie.android.launcher.preferences.theme.Font;
|
||||||
|
@ -26,6 +27,7 @@ import eu.jonahbauer.android.preference.annotations.Preferences;
|
||||||
@Preference(name = "started_time", type = long.class),
|
@Preference(name = "started_time", type = long.class),
|
||||||
// see PREFERENCE_VERSION in de.jrpie.android.launcher.preferences.Preferences.kt
|
// see PREFERENCE_VERSION in de.jrpie.android.launcher.preferences.Preferences.kt
|
||||||
@Preference(name = "version_code", type = int.class, defaultValue = "-1"),
|
@Preference(name = "version_code", type = int.class, defaultValue = "-1"),
|
||||||
|
@Preference(name = "widgets", type = Set.class, serializer = SetWidgetInfoSerializer.class)
|
||||||
}),
|
}),
|
||||||
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
|
@PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = {
|
||||||
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
|
@Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class),
|
||||||
|
|
|
@ -4,6 +4,7 @@ package de.jrpie.android.launcher.preferences.serialization
|
||||||
|
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
|
||||||
|
import de.jrpie.android.launcher.widgets.WidgetInfo
|
||||||
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
|
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
|
||||||
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
|
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -28,6 +29,24 @@ class SetAbstractAppInfoPreferenceSerializer :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class SetWidgetInfoSerializer :
|
||||||
|
PreferenceSerializer<java.util.Set<WidgetInfo>?, java.util.Set<java.lang.String>?> {
|
||||||
|
@Throws(PreferenceSerializationException::class)
|
||||||
|
override fun serialize(value: java.util.Set<WidgetInfo>?): java.util.Set<java.lang.String> {
|
||||||
|
return value?.map(WidgetInfo::serialize)
|
||||||
|
?.toHashSet() as java.util.Set<java.lang.String>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(PreferenceSerializationException::class)
|
||||||
|
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<WidgetInfo>? {
|
||||||
|
return value?.map(java.lang.String::toString)?.map(WidgetInfo::deserialize)
|
||||||
|
?.toHashSet() as? java.util.Set<WidgetInfo>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class SetPinnedShortcutInfoPreferenceSerializer :
|
class SetPinnedShortcutInfoPreferenceSerializer :
|
||||||
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> {
|
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> {
|
||||||
|
|
|
@ -9,9 +9,12 @@ import android.os.Bundle
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.window.OnBackInvokedDispatcher
|
import android.window.OnBackInvokedDispatcher
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.marginTop
|
||||||
|
import de.jrpie.android.launcher.Application
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.actions.Action
|
import de.jrpie.android.launcher.actions.Action
|
||||||
import de.jrpie.android.launcher.actions.Gesture
|
import de.jrpie.android.launcher.actions.Gesture
|
||||||
|
@ -20,7 +23,13 @@ import de.jrpie.android.launcher.databinding.HomeBinding
|
||||||
import de.jrpie.android.launcher.openTutorial
|
import de.jrpie.android.launcher.openTutorial
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
|
||||||
|
import de.jrpie.android.launcher.widgets.bindAppWidget
|
||||||
|
import de.jrpie.android.launcher.widgets.createAppWidgetView
|
||||||
|
import de.jrpie.android.launcher.widgets.deleteAllWidgets
|
||||||
|
import de.jrpie.android.launcher.widgets.getAppWidgetProviders
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [HomeActivity] is the actual application Launcher,
|
* [HomeActivity] is the actual application Launcher,
|
||||||
|
@ -73,6 +82,17 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
binding.buttonFallbackSettings.setOnClickListener {
|
binding.buttonFallbackSettings.setOnClickListener {
|
||||||
LauncherAction.SETTINGS.invoke(this)
|
LauncherAction.SETTINGS.invoke(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteAllWidgets(this)
|
||||||
|
|
||||||
|
LauncherPreferences.internal().widgets().forEach { widget ->
|
||||||
|
createAppWidgetView(this, widget)?.let {
|
||||||
|
binding.homeWidgetContainer.addView(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: appWidgetHost.deleteAppWidgetId(appWidgetId)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package de.jrpie.android.launcher.ui.widgets
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
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.ActivitySelectWidgetBinding
|
||||||
|
import de.jrpie.android.launcher.databinding.HomeBinding
|
||||||
|
import de.jrpie.android.launcher.ui.list.ListActivity
|
||||||
|
import de.jrpie.android.launcher.ui.list.other.OtherRecyclerAdapter
|
||||||
|
import de.jrpie.android.launcher.widgets.bindAppWidget
|
||||||
|
import de.jrpie.android.launcher.widgets.getAppWidgetProviders
|
||||||
|
|
||||||
|
class SelectWidgetActivity : AppCompatActivity() {
|
||||||
|
lateinit var binding: ActivitySelectWidgetBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
// Initialise layout
|
||||||
|
binding = ActivitySelectWidgetBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
setContentView(binding.root)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
val viewManager = LinearLayoutManager(this)
|
||||||
|
val viewAdapter = SelectWidgetRecyclerAdapter(this)
|
||||||
|
|
||||||
|
binding.selectWidgetRecycler.apply {
|
||||||
|
// improve performance (since content changes don't change the layout size)
|
||||||
|
setHasFixedSize(true)
|
||||||
|
layoutManager = viewManager
|
||||||
|
adapter = viewAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectWidgetRecyclerAdapter(val activity: Activity) :
|
||||||
|
RecyclerView.Adapter<SelectWidgetRecyclerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private val widgets = getAppWidgetProviders(activity).toTypedArray()
|
||||||
|
|
||||||
|
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
||||||
|
View.OnClickListener {
|
||||||
|
var textView: TextView = itemView.findViewById(R.id.list_widgets_row_name)
|
||||||
|
var iconView: ImageView = itemView.findViewById(R.id.list_widgets_row_icon)
|
||||||
|
var previewView: ImageView = itemView.findViewById(R.id.list_widgets_row_preview)
|
||||||
|
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
val pos = bindingAdapterPosition
|
||||||
|
val content = widgets[pos]
|
||||||
|
|
||||||
|
bindAppWidget(activity, content)
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
||||||
|
val label = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
"${widgets[i].activityInfo.loadLabel(activity.packageManager)} ${widgets[i].loadDescription(activity)}"
|
||||||
|
} else {
|
||||||
|
widgets[i].label
|
||||||
|
}
|
||||||
|
val preview = widgets[i].loadPreviewImage(activity, 100)
|
||||||
|
val icon = widgets[i].loadIcon(activity, 100)
|
||||||
|
|
||||||
|
viewHolder.textView.text = label
|
||||||
|
viewHolder.iconView.setImageDrawable(icon)
|
||||||
|
viewHolder.previewView.setImageDrawable(preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return widgets.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
val view: View = inflater.inflate(R.layout.list_widgets_row, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package de.jrpie.android.launcher.ui.widgets
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
|
||||||
|
// TODO: implement layout logic instead of linear layout
|
||||||
|
class WidgetContainerView(context: Context, attrs: AttributeSet?): LinearLayout(context, attrs) {
|
||||||
|
init {
|
||||||
|
orientation = VERTICAL
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package de.jrpie.android.launcher.widgets;
|
||||||
|
|
||||||
|
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
||||||
|
import kotlinx.serialization.SerialName;
|
||||||
|
import kotlinx.serialization.Serializable;
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("widget")
|
||||||
|
class WidgetInfo(val id: Int, val width: Int, val height: Int) {
|
||||||
|
fun serialize(): String {
|
||||||
|
return Json.encodeToString(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return (other as? WidgetInfo)?.id == id
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
fun deserialize(serialized: String): WidgetInfo {
|
||||||
|
return Json.decodeFromString(serialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt
Normal file
123
app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package de.jrpie.android.launcher.widgets
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Service
|
||||||
|
import android.appwidget.AppWidgetHostView
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProviderInfo
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.UserManager
|
||||||
|
import android.util.Log
|
||||||
|
import de.jrpie.android.launcher.Application
|
||||||
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
fun deleteAllWidgets(activity: Activity) {
|
||||||
|
val appWidgetHost = (activity.application as Application).appWidgetHost
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
appWidgetHost.appWidgetIds.forEach { deleteAppWidget(activity, WidgetInfo(it, 0,0)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bindAppWidget(activity: Activity, providerInfo: AppWidgetProviderInfo): WidgetInfo? {
|
||||||
|
val appWidgetHost = (activity.application as Application).appWidgetHost
|
||||||
|
val appWidgetManager = (activity.application as Application).appWidgetManager
|
||||||
|
val appWidgetId = appWidgetHost.allocateAppWidgetId()
|
||||||
|
|
||||||
|
Log.i("Launcher", "Binding new widget ${appWidgetId}")
|
||||||
|
if (!appWidgetManager.bindAppWidgetIdIfAllowed(
|
||||||
|
appWidgetId,
|
||||||
|
providerInfo.provider
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
requestAppWidgetPermission(activity, appWidgetId, providerInfo)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Log.e("widgets", "configure widget")
|
||||||
|
appWidgetHost.startAppWidgetConfigureActivityForResult(activity, appWidgetId, 0, 1, null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
val widget = WidgetInfo(appWidgetId, 500, 500)
|
||||||
|
LauncherPreferences.internal().widgets(
|
||||||
|
(LauncherPreferences.internal().widgets() ?: HashSet()).also {
|
||||||
|
it.add(widget)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
return widget
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteAppWidget(activity: Activity, widget: WidgetInfo) {
|
||||||
|
Log.i("Launcher", "Deleting widget ${widget.id}")
|
||||||
|
val appWidgetHost = (activity.application as Application).appWidgetHost
|
||||||
|
|
||||||
|
appWidgetHost.deleteAppWidgetId(widget.id)
|
||||||
|
|
||||||
|
LauncherPreferences.internal().widgets(
|
||||||
|
LauncherPreferences.internal().widgets()?.also {
|
||||||
|
it.remove(widget)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createAppWidgetView(activity: Activity, widget: WidgetInfo): AppWidgetHostView? {
|
||||||
|
val appWidgetHost = (activity.application as Application).appWidgetHost
|
||||||
|
val appWidgetManager = (activity.application as Application).appWidgetManager
|
||||||
|
val providerInfo = appWidgetManager.getAppWidgetInfo(widget.id) ?: return null
|
||||||
|
val view = appWidgetHost.createView(activity, widget.id, providerInfo)
|
||||||
|
.apply {
|
||||||
|
setAppWidget(appWidgetId, appWidgetInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val newOptions = Bundle().apply {
|
||||||
|
putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widget.width)
|
||||||
|
putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, widget.width)
|
||||||
|
putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widget.height)
|
||||||
|
putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, widget.height)
|
||||||
|
}
|
||||||
|
appWidgetManager.updateAppWidgetOptions(
|
||||||
|
widget.id,
|
||||||
|
newOptions
|
||||||
|
)
|
||||||
|
//view.minimumWidth = widget.width
|
||||||
|
//view.minimumHeight = widget.height
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAppWidgetProviders(context: Context): List<AppWidgetProviderInfo> {
|
||||||
|
return appWidgetProviders(context, (context.applicationContext as Application).appWidgetManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestAppWidgetPermission(context: Activity, widgetId: Int, info: AppWidgetProviderInfo) {
|
||||||
|
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
|
||||||
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
||||||
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider)
|
||||||
|
}
|
||||||
|
context.startActivityForResult(intent, 0)//REQUEST_CODE_BIND_WIDGET)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun appWidgetProviders(
|
||||||
|
context: Context,
|
||||||
|
appWidgetManager: AppWidgetManager
|
||||||
|
): List<AppWidgetProviderInfo> {
|
||||||
|
val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager
|
||||||
|
return userManager.userProfiles.map {
|
||||||
|
appWidgetManager.getInstalledProvidersForProfile(it)
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
|
fun Activity.bindRandomWidget() {
|
||||||
|
val selectedWidget =
|
||||||
|
getAppWidgetProviders(this).let { it.get(Random.nextInt().absoluteValue % it.size) }
|
||||||
|
bindAppWidget(this, selectedWidget) ?: return
|
||||||
|
}
|
||||||
|
|
73
app/src/main/res/layout/activity_select_widget.xml
Normal file
73
app/src/main/res/layout/activity_select_widget.xml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.widgets.SelectWidgetActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/select_widget_appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
|
||||||
|
android:background="@null"
|
||||||
|
app:elevation="0dp"
|
||||||
|
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/select_widget_heading"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minHeight="?actionBarSize"
|
||||||
|
android:padding="@dimen/appbar_padding"
|
||||||
|
android:text="@string/select_widget_title"
|
||||||
|
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
|
||||||
|
android:textSize="30sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/select_widget_close"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/select_widget_close"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:includeFontPadding="true"
|
||||||
|
android:paddingLeft="16sp"
|
||||||
|
android:paddingRight="16sp"
|
||||||
|
android:src="@drawable/baseline_close_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/select_widget_recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -9,6 +9,15 @@
|
||||||
android:longClickable="false"
|
android:longClickable="false"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".ui.HomeActivity">
|
tools:context=".ui.HomeActivity">
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" >
|
||||||
|
<de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
||||||
|
android:id="@+id/home_widget_container"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
<TextClock
|
<TextClock
|
||||||
android:id="@+id/home_upper_view"
|
android:id="@+id/home_upper_view"
|
||||||
|
|
46
app/src/main/res/layout/list_widgets_row.xml
Normal file
46
app/src/main/res/layout/list_widgets_row.xml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/list_apps_row_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="15sp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/list_widgets_row_preview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:maxWidth="500dp"
|
||||||
|
android:maxHeight="200dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@mipmap/ic_launcher_round"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/list_widgets_row_icon"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/list_widgets_row_preview"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/list_widgets_row_preview"
|
||||||
|
tools:src="@mipmap/ic_launcher_round"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_widgets_row_name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="20sp"
|
||||||
|
android:gravity="start"
|
||||||
|
android:text=""
|
||||||
|
android:textSize="20sp"
|
||||||
|
tools:text="some widget"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/list_widgets_row_preview"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -9,6 +9,7 @@
|
||||||
<string name="settings_internal_started_key" translatable="false">internal.started_before</string>
|
<string name="settings_internal_started_key" translatable="false">internal.started_before</string>
|
||||||
<string name="settings_internal_started_time_key" translatable="false">internal.first_startup</string>
|
<string name="settings_internal_started_time_key" translatable="false">internal.first_startup</string>
|
||||||
<string name="settings_internal_version_code_key" translatable="false">internal.version_code</string>
|
<string name="settings_internal_version_code_key" translatable="false">internal.version_code</string>
|
||||||
|
<string name="settings_internal_widgets_key" translatable="false">internal.widgets</string>
|
||||||
<string name="settings_apps_favorites_key" translatable="false">apps.favorites</string>
|
<string name="settings_apps_favorites_key" translatable="false">apps.favorites</string>
|
||||||
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
|
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
|
||||||
<string name="settings_apps_pinned_shortcuts_key" translatable="false">apps.pinned_shortcuts</string>
|
<string name="settings_apps_pinned_shortcuts_key" translatable="false">apps.pinned_shortcuts</string>
|
||||||
|
|
|
@ -387,5 +387,6 @@
|
||||||
<string name="legal_info_title">Open Source Licenses</string>
|
<string name="legal_info_title">Open Source Licenses</string>
|
||||||
<string name="toast_activity_not_found_search_web">No app found to handle search.</string>
|
<string name="toast_activity_not_found_search_web">No app found to handle search.</string>
|
||||||
<string name="toast_activity_not_found_browser">Can\'t open URL: no browser found.</string>
|
<string name="toast_activity_not_found_browser">Can\'t open URL: no browser found.</string>
|
||||||
|
<string name="select_widget_title">Choose Widget</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue