mirror of
https://github.com/jrpie/Launcher.git
synced 2025-04-27 14:20:50 +02:00
some progress
This commit is contained in:
parent
f025ac12c1
commit
e1daa8d9be
26 changed files with 1152 additions and 243 deletions
|
@ -8,6 +8,7 @@
|
||||||
tools:ignore="QueryAllPackagesPermission" />
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES" />
|
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES" />
|
||||||
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
|
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
|
||||||
|
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Application"
|
android:name=".Application"
|
||||||
|
@ -20,7 +21,11 @@
|
||||||
android:theme="@style/launcherBaseTheme"
|
android:theme="@style/launcherBaseTheme"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.widgets.SelectWidgetActivity"
|
android:name=".ui.widgets.manage.ManageWidgetsActivity"
|
||||||
|
android:theme="@style/launcherHomeTheme"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.widgets.manage.SelectWidgetActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.PinShortcutActivity"
|
android:name=".ui.PinShortcutActivity"
|
||||||
|
|
|
@ -15,14 +15,13 @@ import de.jrpie.android.launcher.BuildConfig
|
||||||
import de.jrpie.android.launcher.R
|
import de.jrpie.android.launcher.R
|
||||||
import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService
|
import de.jrpie.android.launcher.actions.lock.LauncherAccessibilityService
|
||||||
import de.jrpie.android.launcher.apps.AppFilter
|
import de.jrpie.android.launcher.apps.AppFilter
|
||||||
import de.jrpie.android.launcher.apps.hidePrivateSpaceWhenLocked
|
|
||||||
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
|
import de.jrpie.android.launcher.apps.isPrivateSpaceSupported
|
||||||
import de.jrpie.android.launcher.apps.togglePrivateSpaceLock
|
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.manage.ManageWidgetsActivity
|
||||||
import de.jrpie.android.launcher.ui.widgets.SelectWidgetActivity
|
import de.jrpie.android.launcher.ui.widgets.manage.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
|
||||||
|
@ -64,7 +63,7 @@ enum class LauncherAction(
|
||||||
R.string.list_other_list_favorites,
|
R.string.list_other_list_favorites,
|
||||||
R.drawable.baseline_favorite_24,
|
R.drawable.baseline_favorite_24,
|
||||||
{ context ->
|
{ context ->
|
||||||
context.startActivity(Intent(context.applicationContext, SelectWidgetActivity::class.java))
|
context.startActivity(Intent(context.applicationContext, ManageWidgetsActivity::class.java))
|
||||||
},
|
},
|
||||||
//openAppsList(context, favorite = true) },
|
//openAppsList(context, favorite = true) },
|
||||||
true
|
true
|
||||||
|
@ -74,12 +73,15 @@ enum class LauncherAction(
|
||||||
R.string.list_other_list_private_space,
|
R.string.list_other_list_private_space,
|
||||||
R.drawable.baseline_security_24,
|
R.drawable.baseline_security_24,
|
||||||
{ context ->
|
{ context ->
|
||||||
|
context.startActivity(Intent(context.applicationContext, SelectWidgetActivity::class.java))
|
||||||
|
},
|
||||||
|
/*{ context ->
|
||||||
if ((context.applicationContext as Application).privateSpaceLocked.value != true
|
if ((context.applicationContext as Application).privateSpaceLocked.value != true
|
||||||
|| !hidePrivateSpaceWhenLocked(context)
|
|| !hidePrivateSpaceWhenLocked(context)
|
||||||
) {
|
) {
|
||||||
openAppsList(context, private = true)
|
openAppsList(context, private = true)
|
||||||
}
|
}
|
||||||
},
|
}, */
|
||||||
available = { _ ->
|
available = { _ ->
|
||||||
isPrivateSpaceSupported()
|
isPrivateSpaceSupported()
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
|
||||||
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
|
||||||
import de.jrpie.android.launcher.ui.HomeActivity
|
import de.jrpie.android.launcher.ui.HomeActivity
|
||||||
|
import de.jrpie.android.launcher.widgets.deleteAllWidgets
|
||||||
|
|
||||||
/* Current version of the structure of preferences.
|
/* Current version of the structure of preferences.
|
||||||
* Increase when breaking changes are introduced and write an appropriate case in
|
* Increase when breaking changes are introduced and write an appropriate case in
|
||||||
|
@ -71,6 +72,7 @@ fun resetPreferences(context: Context) {
|
||||||
Log.i(TAG, "Resetting preferences")
|
Log.i(TAG, "Resetting preferences")
|
||||||
LauncherPreferences.clear()
|
LauncherPreferences.clear()
|
||||||
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
|
||||||
|
deleteAllWidgets(context)
|
||||||
|
|
||||||
|
|
||||||
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
|
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package de.jrpie.android.launcher.ui
|
package de.jrpie.android.launcher.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
@ -9,11 +10,8 @@ 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.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.marginTop
|
|
||||||
import de.jrpie.android.launcher.Application
|
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
|
||||||
|
@ -23,13 +21,7 @@ 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,
|
||||||
|
@ -43,7 +35,7 @@ import kotlin.random.Random
|
||||||
* - Setting global variables (preferences etc.)
|
* - Setting global variables (preferences etc.)
|
||||||
* - Opening the [TutorialActivity] on new installations
|
* - Opening the [TutorialActivity] on new installations
|
||||||
*/
|
*/
|
||||||
class HomeActivity : UIObject, AppCompatActivity() {
|
class HomeActivity : UIObject, Activity() {
|
||||||
|
|
||||||
private lateinit var binding: HomeBinding
|
private lateinit var binding: HomeBinding
|
||||||
private var touchGestureDetector: TouchGestureDetector? = null
|
private var touchGestureDetector: TouchGestureDetector? = null
|
||||||
|
@ -59,10 +51,15 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
if (prefKey?.startsWith("action.") == true) {
|
if (prefKey?.startsWith("action.") == true) {
|
||||||
updateSettingsFallbackButtonVisibility()
|
updateSettingsFallbackButtonVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prefKey?.startsWith("internal.widgets") == true) {
|
||||||
|
binding.homeWidgetContainer.updateWidgets(this)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super<AppCompatActivity>.onCreate(savedInstanceState)
|
super<Activity>.onCreate(savedInstanceState)
|
||||||
super<UIObject>.onCreate()
|
super<UIObject>.onCreate()
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,17 +79,7 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
binding.buttonFallbackSettings.setOnClickListener {
|
binding.buttonFallbackSettings.setOnClickListener {
|
||||||
LauncherAction.SETTINGS.invoke(this)
|
LauncherAction.SETTINGS.invoke(this)
|
||||||
}
|
}
|
||||||
|
binding.homeWidgetContainer.updateWidgets(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) {
|
||||||
|
@ -101,8 +88,7 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super<AppCompatActivity>.onStart()
|
super<Activity>.onStart()
|
||||||
|
|
||||||
super<UIObject>.onStart()
|
super<UIObject>.onStart()
|
||||||
|
|
||||||
// If the tutorial was not finished, start it
|
// If the tutorial was not finished, start it
|
||||||
|
@ -113,6 +99,16 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
LauncherPreferences.getSharedPreferences()
|
LauncherPreferences.getSharedPreferences()
|
||||||
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
||||||
|
|
||||||
|
(application as Application).appWidgetHost.startListening()
|
||||||
|
binding.homeWidgetContainer.updateWidgets(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
(application as Application).appWidgetHost.stopListening()
|
||||||
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
|
@ -252,7 +248,14 @@ class HomeActivity : UIObject, AppCompatActivity() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
// TODO: fix!
|
||||||
|
touchGestureDetector?.onTouchEvent(event)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
android.util.Log.e("Launcher", "on touch")
|
||||||
touchGestureDetector?.onTouchEvent(event)
|
touchGestureDetector?.onTouchEvent(event)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,21 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
|
||||||
|
|
||||||
val request = launcherApps.getPinItemRequest(intent)
|
val request = launcherApps.getPinItemRequest(intent)
|
||||||
this.request = request
|
this.request = request
|
||||||
if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
|
if (request == null) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.requestType == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
request.getAppWidgetProviderInfo(this)
|
||||||
|
// startActivity()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,130 @@
|
||||||
package de.jrpie.android.launcher.ui.widgets
|
package de.jrpie.android.launcher.ui.widgets
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.LinearLayout
|
import android.util.Log
|
||||||
|
import android.util.SizeF
|
||||||
|
import android.view.View.MeasureSpec.makeMeasureSpec
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.size
|
||||||
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
import de.jrpie.android.launcher.widgets.WidgetPosition
|
||||||
|
import de.jrpie.android.launcher.widgets.createAppWidgetView
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
|
||||||
// TODO: implement layout logic instead of linear layout
|
// TODO: implement layout logic instead of linear layout
|
||||||
class WidgetContainerView(context: Context, attrs: AttributeSet?): LinearLayout(context, attrs) {
|
/**
|
||||||
init {
|
* This only works in an Activity, not AppCompatActivity
|
||||||
orientation = VERTICAL
|
*/
|
||||||
|
open class WidgetContainerView(context: Context, attrs: AttributeSet? = null) : ViewGroup(context, attrs) {
|
||||||
|
|
||||||
|
open fun updateWidgets(activity: Activity) {
|
||||||
|
Log.i("WidgetContainer", "updating ${activity.localClassName}")
|
||||||
|
(0..<size).forEach { removeViewAt(0) }
|
||||||
|
val dp = activity.resources.displayMetrics.density
|
||||||
|
val screenWidth = activity.resources.displayMetrics.widthPixels
|
||||||
|
val screenHeight = activity.resources.displayMetrics.heightPixels
|
||||||
|
LauncherPreferences.internal().widgets()?.forEach { widget ->
|
||||||
|
createAppWidgetView(activity, widget)?.let {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val absolutePosition = widget.position.getAbsoluteRect(screenWidth, screenHeight)
|
||||||
|
it.updateAppWidgetSize(Bundle.EMPTY,
|
||||||
|
listOf(SizeF(
|
||||||
|
absolutePosition.width() / dp,
|
||||||
|
absolutePosition.height() / dp
|
||||||
|
)))
|
||||||
|
Log.i("WidgetContainer", "Adding widget ${widget.id} at ${widget.position} ($absolutePosition)")
|
||||||
|
} else {
|
||||||
|
Log.i("WidgetContainer", "Adding widget ${widget.id} at ${widget.position}")
|
||||||
|
}
|
||||||
|
addView(it, WidgetContainerView.Companion.LayoutParams(widget.position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
|
||||||
|
var maxHeight = suggestedMinimumHeight
|
||||||
|
var maxWidth = suggestedMinimumWidth
|
||||||
|
|
||||||
|
val mWidth = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
|
val mHeight = MeasureSpec.getSize(heightMeasureSpec)
|
||||||
|
|
||||||
|
(0..<size).map { getChildAt(it) }.forEach {
|
||||||
|
val position = (it.layoutParams as LayoutParams).position.getAbsoluteRect(mWidth, mHeight)
|
||||||
|
it.measure(makeMeasureSpec(position.width(), MeasureSpec.EXACTLY), makeMeasureSpec(position.height(), MeasureSpec.EXACTLY))
|
||||||
|
Log.e("measure", "$position")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find rightmost and bottom-most child
|
||||||
|
(0..<size).map { getChildAt(it) }.filter { it.visibility != GONE }.forEach {
|
||||||
|
val position = (it.layoutParams as LayoutParams).position.getAbsoluteRect(mWidth, mHeight)
|
||||||
|
maxWidth = max(maxWidth, position.left + it.measuredWidth)
|
||||||
|
maxHeight = max(maxHeight, position.top + it.measuredHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
setMeasuredDimension(
|
||||||
|
resolveSizeAndState(maxWidth.toInt(), widthMeasureSpec, 0),
|
||||||
|
resolveSizeAndState(maxHeight.toInt(), heightMeasureSpec, 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a set of layout parameters with a width of
|
||||||
|
* [ViewGroup.LayoutParams.WRAP_CONTENT],
|
||||||
|
* a height of [ViewGroup.LayoutParams.WRAP_CONTENT]
|
||||||
|
* and with the coordinates (0, 0).
|
||||||
|
*/
|
||||||
|
override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams {
|
||||||
|
return LayoutParams(WidgetPosition(0,0,1,1))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||||
|
for (i in 0..<size) {
|
||||||
|
val child = getChildAt(i)
|
||||||
|
//if (child.visibility != GONE) {
|
||||||
|
val lp = child.layoutParams as LayoutParams
|
||||||
|
|
||||||
|
val position = lp.position.getAbsoluteRect(r - l, b - t)
|
||||||
|
Log.e("onLayout", "$l, $t, $r, $b, absolute rect: $position")
|
||||||
|
child.layout(position.left, position.top, position.right, position.bottom)
|
||||||
|
child.layoutParams.width = position.width()
|
||||||
|
child.layoutParams.height = position.height()
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generateLayoutParams(attrs: AttributeSet?): ViewGroup.LayoutParams {
|
||||||
|
return LayoutParams(context, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override to allow type-checking of LayoutParams.
|
||||||
|
override fun checkLayoutParams(p: ViewGroup.LayoutParams?): Boolean {
|
||||||
|
return p is LayoutParams
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
|
||||||
|
return LayoutParams(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldDelayChildPressedState(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
class LayoutParams : ViewGroup.LayoutParams {
|
||||||
|
var position = WidgetPosition(0,0,4,4)
|
||||||
|
|
||||||
|
|
||||||
|
constructor(position: WidgetPosition) : super(WRAP_CONTENT, WRAP_CONTENT) {
|
||||||
|
this.position = position
|
||||||
|
}
|
||||||
|
constructor(c: Context, attrs: AttributeSet?) : super(c, attrs)
|
||||||
|
constructor(source: ViewGroup.LayoutParams?) : super(source)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
package de.jrpie.android.launcher.ui.widgets.manage
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProviderInfo
|
||||||
|
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 androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
import de.jrpie.android.launcher.Application
|
||||||
|
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.WidgetInfo
|
||||||
|
import de.jrpie.android.launcher.widgets.WidgetPosition
|
||||||
|
import de.jrpie.android.launcher.widgets.deleteAppWidget
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
|
// http://coderender.blogspot.com/2012/01/hosting-android-widgets-my.html
|
||||||
|
|
||||||
|
const val REQUEST_CREATE_APPWIDGET = 1
|
||||||
|
const val REQUEST_PICK_APPWIDGET = 2
|
||||||
|
|
||||||
|
class ManageWidgetsActivity : Activity(), UIObject {
|
||||||
|
|
||||||
|
private var sharedPreferencesListener =
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
|
||||||
|
if (prefKey?.startsWith("internal.widgets") == true) {
|
||||||
|
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super<Activity>.onCreate(savedInstanceState)
|
||||||
|
super<UIObject>.onCreate()
|
||||||
|
setContentView(R.layout.activity_manage_widgets)
|
||||||
|
findViewById<FloatingActionButton>(R.id.manage_widgets_button_add).setOnClickListener {
|
||||||
|
selectWidget()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super<Activity>.onStart()
|
||||||
|
super<UIObject>.onStart()
|
||||||
|
|
||||||
|
LauncherPreferences.getSharedPreferences()
|
||||||
|
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
||||||
|
|
||||||
|
}
|
||||||
|
override fun getTheme(): Resources.Theme {
|
||||||
|
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() {
|
||||||
|
LauncherPreferences.getSharedPreferences()
|
||||||
|
.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener)
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun selectWidget() {
|
||||||
|
val appWidgetHost = (application as Application).appWidgetHost
|
||||||
|
startActivityForResult(
|
||||||
|
Intent(this, SelectWidgetActivity::class.java).also {
|
||||||
|
it.putExtra(
|
||||||
|
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||||
|
appWidgetHost.allocateAppWidgetId()
|
||||||
|
)
|
||||||
|
}, REQUEST_PICK_APPWIDGET
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun createWidget(data: Intent) {
|
||||||
|
Log.i("Launcher", "creating widget")
|
||||||
|
val appWidgetManager = (application as Application).appWidgetManager
|
||||||
|
val appWidgetId = data.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return
|
||||||
|
|
||||||
|
val provider = appWidgetManager.getAppWidgetInfo(appWidgetId)
|
||||||
|
|
||||||
|
val display = windowManager.defaultDisplay
|
||||||
|
|
||||||
|
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 = WidgetInfo(appWidgetId, provider, position)
|
||||||
|
LauncherPreferences.internal().widgets(
|
||||||
|
(LauncherPreferences.internal().widgets() ?: HashSet()).also {
|
||||||
|
it.add(widget)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
findViewById<WidgetContainerView>(R.id.manage_widgets_container).updateWidgets(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureWidget(data: Intent) {
|
||||||
|
val extras = data.extras
|
||||||
|
val appWidgetId = extras!!.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||||
|
val appWidgetHost = (application as Application).appWidgetHost
|
||||||
|
val appWidgetInfo: AppWidgetProviderInfo =
|
||||||
|
(application as Application).appWidgetManager.getAppWidgetInfo(appWidgetId) ?: return
|
||||||
|
if (appWidgetInfo.configure != null) {
|
||||||
|
appWidgetHost.startAppWidgetConfigureActivityForResult(
|
||||||
|
this,
|
||||||
|
appWidgetId,
|
||||||
|
0,
|
||||||
|
REQUEST_CREATE_APPWIDGET,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
createWidget(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(
|
||||||
|
requestCode: Int, resultCode: Int,
|
||||||
|
data: Intent?
|
||||||
|
) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
Log.i("Manage Widgets", "result ok")
|
||||||
|
if (requestCode == REQUEST_PICK_APPWIDGET) {
|
||||||
|
configureWidget(data!!)
|
||||||
|
} else if (requestCode == REQUEST_CREATE_APPWIDGET) {
|
||||||
|
createWidget(data!!)
|
||||||
|
}
|
||||||
|
} else if (resultCode == RESULT_CANCELED && data != null) {
|
||||||
|
Log.i("Manage Widgets", "result canceled")
|
||||||
|
val appWidgetId =
|
||||||
|
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||||
|
if (appWidgetId != -1) {
|
||||||
|
deleteAppWidget(this, WidgetInfo(appWidgetId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a better preview, [ManageWidgetsActivity] should behave exactly like [HomeActivity]
|
||||||
|
*/
|
||||||
|
override fun isHomeScreen(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
package de.jrpie.android.launcher.ui.widgets.manage
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager
|
||||||
|
import android.appwidget.AppWidgetProviderInfo
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import android.util.Log
|
||||||
|
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.databinding.ActivitySelectWidgetBinding
|
||||||
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
import de.jrpie.android.launcher.ui.UIObject
|
||||||
|
import de.jrpie.android.launcher.widgets.bindAppWidgetOrRequestPermission
|
||||||
|
import de.jrpie.android.launcher.widgets.getAppWidgetProviders
|
||||||
|
|
||||||
|
|
||||||
|
private const val REQUEST_WIDGET_PERMISSION = 29
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This activity lets the user pick an app widget to add.
|
||||||
|
* It provides an interface similar to [android.appwidget.AppWidgetManager.ACTION_APPWIDGET_PICK],
|
||||||
|
* but shows more information and also shows widgets from other user profiles.
|
||||||
|
*/
|
||||||
|
class SelectWidgetActivity : AppCompatActivity(), UIObject {
|
||||||
|
lateinit var binding: ActivitySelectWidgetBinding
|
||||||
|
var widgetId: Int = -1
|
||||||
|
|
||||||
|
private fun tryBindWidget(info: AppWidgetProviderInfo) {
|
||||||
|
if(bindAppWidgetOrRequestPermission(this, info, widgetId, REQUEST_WIDGET_PERMISSION)) {
|
||||||
|
setResult(
|
||||||
|
RESULT_OK,
|
||||||
|
Intent().also {
|
||||||
|
it.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super<AppCompatActivity>.onStart()
|
||||||
|
super<UIObject>.onStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super<AppCompatActivity>.onCreate(savedInstanceState)
|
||||||
|
super<UIObject>.onCreate()
|
||||||
|
|
||||||
|
binding = ActivitySelectWidgetBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
|
||||||
|
widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||||
|
|
||||||
|
val viewManager = LinearLayoutManager(this)
|
||||||
|
val viewAdapter = SelectWidgetRecyclerAdapter()
|
||||||
|
|
||||||
|
binding.selectWidgetRecycler.apply {
|
||||||
|
setHasFixedSize(false)
|
||||||
|
layoutManager = viewManager
|
||||||
|
adapter = viewAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTheme(): Resources.Theme {
|
||||||
|
return modifyTheme(super.getTheme())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
|
if (requestCode == REQUEST_WIDGET_PERMISSION && resultCode == RESULT_OK) {
|
||||||
|
data ?: return
|
||||||
|
Log.i("SelectWidget", "permission granted")
|
||||||
|
val provider = (data.getSerializableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER) as? AppWidgetProviderInfo) ?: return
|
||||||
|
tryBindWidget(provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class SelectWidgetRecyclerAdapter() :
|
||||||
|
RecyclerView.Adapter<SelectWidgetRecyclerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private val widgets = getAppWidgetProviders(this@SelectWidgetActivity).toTypedArray()
|
||||||
|
|
||||||
|
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
|
||||||
|
View.OnClickListener {
|
||||||
|
var textView: TextView = itemView.findViewById(R.id.list_widgets_row_name)
|
||||||
|
var descriptionView: TextView = itemView.findViewById(R.id.list_widgets_row_description)
|
||||||
|
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) {
|
||||||
|
tryBindWidget(widgets[bindingAdapterPosition])
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
|
||||||
|
val label = widgets[i].label
|
||||||
|
val description = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
widgets[i].loadDescription(this@SelectWidgetActivity)
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
val preview =
|
||||||
|
widgets[i].loadPreviewImage(this@SelectWidgetActivity, DisplayMetrics.DENSITY_DEFAULT )
|
||||||
|
val icon =
|
||||||
|
widgets[i].loadIcon(this@SelectWidgetActivity, DisplayMetrics.DENSITY_DEFAULT)
|
||||||
|
|
||||||
|
viewHolder.textView.text = label
|
||||||
|
viewHolder.descriptionView.text = description
|
||||||
|
viewHolder.descriptionView.visibility =
|
||||||
|
if (description?.isEmpty() == false) { View.VISIBLE } else { View.GONE }
|
||||||
|
viewHolder.iconView.setImageDrawable(icon)
|
||||||
|
|
||||||
|
viewHolder.previewView.setImageDrawable(preview)
|
||||||
|
viewHolder.previewView.visibility =
|
||||||
|
if (preview != null) { View.VISIBLE } else { View.GONE }
|
||||||
|
|
||||||
|
viewHolder.previewView.requestLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
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,176 @@
|
||||||
|
package de.jrpie.android.launcher.ui.widgets.manage
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.appwidget.AppWidgetHostView
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import androidx.core.graphics.contains
|
||||||
|
import androidx.core.graphics.minus
|
||||||
|
import androidx.core.graphics.toRect
|
||||||
|
import androidx.core.view.children
|
||||||
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
|
import de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
||||||
|
import de.jrpie.android.launcher.widgets.WidgetPosition
|
||||||
|
import de.jrpie.android.launcher.widgets.getWidgetById
|
||||||
|
import de.jrpie.android.launcher.widgets.updateWidget
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variant of the [WidgetContainerView] which allows to manage widgets.
|
||||||
|
*/
|
||||||
|
class WidgetManagerView(context: Context, attrs: AttributeSet? = null) :
|
||||||
|
WidgetContainerView(context, attrs) {
|
||||||
|
|
||||||
|
val TOUCH_SLOP: Int
|
||||||
|
val TOUCH_SLOP_SQUARE: Int
|
||||||
|
val LONG_PRESS_TIMEOUT: Long
|
||||||
|
|
||||||
|
init {
|
||||||
|
val configuration = ViewConfiguration.get(context)
|
||||||
|
TOUCH_SLOP = configuration.scaledTouchSlop
|
||||||
|
TOUCH_SLOP_SQUARE = TOUCH_SLOP * TOUCH_SLOP
|
||||||
|
|
||||||
|
LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enum class EditMode(val resize: (dx: Int, dy: Int, rect: Rect) -> Rect) {
|
||||||
|
MOVE({ dx, dy, rect ->
|
||||||
|
Rect(rect.left + dx, rect.top + dy, rect.right + dx, rect.bottom + dy)
|
||||||
|
}),
|
||||||
|
TOP({ dx, dy, rect ->
|
||||||
|
Rect(rect.left, min(rect.top + dy, rect.bottom - 200), rect.right, rect.bottom)
|
||||||
|
}),
|
||||||
|
BOTTOM({ dx, dy, rect ->
|
||||||
|
Rect(rect.left, rect.top, rect.right, max(rect.top + 200, rect.bottom + dy))
|
||||||
|
}),
|
||||||
|
LEFT({ dx, dy, rect ->
|
||||||
|
Rect(min(rect.left + dx, rect.right - 200), rect.top, rect.right, rect.bottom)
|
||||||
|
}),
|
||||||
|
RIGHT({ dx, dy, rect ->
|
||||||
|
Rect(rect.left, rect.top, max(rect.left + 200, rect.right + dx), rect.bottom)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedWidgetOverlayView: WidgetOverlayView? = null
|
||||||
|
var selectedWidgetView: View? = null
|
||||||
|
var currentGestureStart: Point? = null
|
||||||
|
var startWidgetPosition: Rect? = null
|
||||||
|
var lastPosition = Rect()
|
||||||
|
|
||||||
|
private val longPressHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||||
|
onTouchEvent(ev)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||||
|
if (event == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
synchronized(this) {
|
||||||
|
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||||
|
val start = Point(event.x.toInt(), event.y.toInt())
|
||||||
|
currentGestureStart = start
|
||||||
|
val view = children.mapNotNull { it as? WidgetOverlayView }.firstOrNull {
|
||||||
|
RectF(it.x, it.y, it.x + it.width, it.y + it.height).toRect().contains(start) == true
|
||||||
|
} ?: return false
|
||||||
|
|
||||||
|
val position = (view.layoutParams as Companion.LayoutParams).position.getAbsoluteRect(width, height)
|
||||||
|
selectedWidgetOverlayView = view
|
||||||
|
val widgetView = getWidgetViewById(view.widgetId)
|
||||||
|
selectedWidgetView = widgetView ?: return true
|
||||||
|
widgetView.visibility = GONE
|
||||||
|
startWidgetPosition = position
|
||||||
|
|
||||||
|
val positionInView = start.minus(Point(position.left, position.top))
|
||||||
|
view.mode = view.getHandles().firstOrNull { it.position.contains(positionInView) }?.mode ?: EditMode.MOVE
|
||||||
|
|
||||||
|
longPressHandler.postDelayed({
|
||||||
|
synchronized(this@WidgetManagerView) {
|
||||||
|
view.showPopupMenu()
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||||
|
endInteraction()
|
||||||
|
}
|
||||||
|
}, 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 > TOUCH_SLOP_SQUARE) {
|
||||||
|
longPressHandler.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
val view = selectedWidgetOverlayView ?: return true
|
||||||
|
val start = startWidgetPosition ?: return true
|
||||||
|
val absoluteNewPosition = view.mode?.resize(
|
||||||
|
distanceX.toInt(),
|
||||||
|
distanceY.toInt(),
|
||||||
|
start
|
||||||
|
) ?: return true
|
||||||
|
val newPosition = WidgetPosition.fromAbsoluteRect(
|
||||||
|
absoluteNewPosition, width, height
|
||||||
|
)
|
||||||
|
if (newPosition != lastPosition) {
|
||||||
|
lastPosition = absoluteNewPosition
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(view.layoutParams as Companion.LayoutParams).position = newPosition
|
||||||
|
requestLayout()
|
||||||
|
|
||||||
|
if (event.actionMasked == MotionEvent.ACTION_UP) {
|
||||||
|
longPressHandler.removeCallbacksAndMessages(null)
|
||||||
|
val id = selectedWidgetOverlayView?.widgetId ?: return true
|
||||||
|
val widget = getWidgetById(id) ?: return true
|
||||||
|
widget.position = newPosition
|
||||||
|
endInteraction()
|
||||||
|
updateWidget(widget)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.GESTURE_END)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
private fun endInteraction() {
|
||||||
|
startWidgetPosition = null
|
||||||
|
selectedWidgetOverlayView?.mode = null
|
||||||
|
selectedWidgetView?.visibility = VISIBLE
|
||||||
|
}
|
||||||
|
fun getWidgetViewById(id: Int): AppWidgetHostView? {
|
||||||
|
return children.mapNotNull { it as? AppWidgetHostView }.firstOrNull {
|
||||||
|
it.appWidgetId == id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateWidgets(activity: Activity) {
|
||||||
|
super.updateWidgets(activity)
|
||||||
|
|
||||||
|
LauncherPreferences.internal().widgets()?.forEach { widget ->
|
||||||
|
WidgetOverlayView(activity).let {
|
||||||
|
addView(it)
|
||||||
|
it.widgetId = widget.id
|
||||||
|
(it.layoutParams as Companion.LayoutParams).position = widget.position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
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.util.DisplayMetrics
|
||||||
|
import android.view.ContextMenu
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.PopupMenu
|
||||||
|
import androidx.core.graphics.toRectF
|
||||||
|
import de.jrpie.android.launcher.Application
|
||||||
|
import de.jrpie.android.launcher.widgets.WidgetInfo
|
||||||
|
import de.jrpie.android.launcher.widgets.WidgetPosition
|
||||||
|
import de.jrpie.android.launcher.widgets.deleteAppWidget
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An overlay to show configuration options for a widget.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private const val HANDLE_SIZE = 100
|
||||||
|
private const val HANDLE_EDGE_SIZE = (1.2 * HANDLE_SIZE).toInt()
|
||||||
|
class WidgetOverlayView : View {
|
||||||
|
|
||||||
|
|
||||||
|
val paint = Paint()
|
||||||
|
val handlePaint = Paint()
|
||||||
|
val selectedHandlePaint = Paint()
|
||||||
|
var mode: WidgetManagerView.EditMode? = null
|
||||||
|
class Handle(val mode: WidgetManagerView.EditMode, val position: Rect)
|
||||||
|
init {
|
||||||
|
handlePaint.style = Paint.Style.STROKE
|
||||||
|
handlePaint.setARGB(100, 255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
selectedHandlePaint.style = Paint.Style.FILL_AND_STROKE
|
||||||
|
selectedHandlePaint.setARGB(255, 255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
paint.setARGB(50, 255, 255, 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var preview: Drawable? = null
|
||||||
|
var widgetId: Int = -1
|
||||||
|
get() = field
|
||||||
|
set(newId) {
|
||||||
|
field = newId
|
||||||
|
val appWidgetManager= (context.applicationContext as Application).appWidgetManager
|
||||||
|
|
||||||
|
preview =
|
||||||
|
appWidgetManager.getAppWidgetInfo(newId).loadPreviewImage(context, DisplayMetrics.DENSITY_HIGH) ?:
|
||||||
|
appWidgetManager.getAppWidgetInfo(newId).loadIcon(context, DisplayMetrics.DENSITY_HIGH)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context) {
|
||||||
|
init(null, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
getHandles().forEach {
|
||||||
|
if (it.mode == mode) {
|
||||||
|
canvas.drawRoundRect(it.position.toRectF(), 5f, 5f, selectedHandlePaint)
|
||||||
|
} else {
|
||||||
|
canvas.drawRoundRect(it.position.toRectF(), 5f, 5f, handlePaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val bounds = getBounds()
|
||||||
|
canvas.drawRect(bounds, paint)
|
||||||
|
|
||||||
|
if (mode == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preview?.bounds = bounds
|
||||||
|
preview?.draw(canvas)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showPopupMenu() {
|
||||||
|
val menu = PopupMenu(context, this)
|
||||||
|
menu.menu.let {
|
||||||
|
it.add("Remove").setOnMenuItemClickListener { _ ->
|
||||||
|
deleteAppWidget(context, WidgetInfo(widgetId))
|
||||||
|
return@setOnMenuItemClickListener true
|
||||||
|
}
|
||||||
|
it.add("Allow Interaction").setOnMenuItemClickListener { _ ->
|
||||||
|
return@setOnMenuItemClickListener true
|
||||||
|
}
|
||||||
|
it.add("Add Padding")
|
||||||
|
}
|
||||||
|
menu.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHandles(): List<Handle> {
|
||||||
|
return listOf<Handle>(
|
||||||
|
Handle(WidgetManagerView.EditMode.TOP,
|
||||||
|
Rect(HANDLE_EDGE_SIZE, 0, width - HANDLE_EDGE_SIZE, HANDLE_SIZE)),
|
||||||
|
Handle(WidgetManagerView.EditMode.BOTTOM,
|
||||||
|
Rect(HANDLE_EDGE_SIZE, height - HANDLE_SIZE, width - HANDLE_EDGE_SIZE, height)),
|
||||||
|
Handle(WidgetManagerView.EditMode.LEFT,
|
||||||
|
Rect(0, HANDLE_EDGE_SIZE, HANDLE_SIZE, height - HANDLE_EDGE_SIZE)),
|
||||||
|
Handle(WidgetManagerView.EditMode.RIGHT,
|
||||||
|
Rect(width - HANDLE_SIZE, HANDLE_EDGE_SIZE, width, height - HANDLE_EDGE_SIZE))
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBounds(): Rect {
|
||||||
|
return Rect(0,0, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,14 +1,36 @@
|
||||||
package de.jrpie.android.launcher.widgets;
|
package de.jrpie.android.launcher.widgets;
|
||||||
|
|
||||||
import de.jrpie.android.launcher.apps.AbstractAppInfo
|
import android.appwidget.AppWidgetProviderInfo
|
||||||
import kotlinx.serialization.SerialName;
|
import android.content.Context
|
||||||
import kotlinx.serialization.Serializable;
|
import de.jrpie.android.launcher.Application
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("widget")
|
@SerialName("widget")
|
||||||
class WidgetInfo(val id: Int, val width: Int, val height: Int) {
|
class WidgetInfo(
|
||||||
|
val id: Int,
|
||||||
|
var position: WidgetPosition = WidgetPosition(0,0,1,1),
|
||||||
|
|
||||||
|
// We keep track of packageName, className and user to make it possible to restore the widget
|
||||||
|
// on a new device when restoring settings (currently not implemented)
|
||||||
|
// In normal operation only id and position are used.
|
||||||
|
val packageName: String? = null,
|
||||||
|
val className: String? = null,
|
||||||
|
val user: Int? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
constructor(id: Int, widgetProviderInfo: AppWidgetProviderInfo, position: WidgetPosition) :
|
||||||
|
this(
|
||||||
|
id, position,
|
||||||
|
widgetProviderInfo.provider.packageName,
|
||||||
|
widgetProviderInfo.provider.className,
|
||||||
|
widgetProviderInfo.profile.hashCode()
|
||||||
|
)
|
||||||
|
|
||||||
fun serialize(): String {
|
fun serialize(): String {
|
||||||
return Json.encodeToString(this)
|
return Json.encodeToString(this)
|
||||||
}
|
}
|
||||||
|
@ -20,9 +42,37 @@ class WidgetInfo(val id: Int, val width: Int, val height: Int) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return (other as? WidgetInfo)?.id == id
|
return (other as? WidgetInfo)?.id == id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun deserialize(serialized: String): WidgetInfo {
|
fun deserialize(serialized: String): WidgetInfo {
|
||||||
return Json.decodeFromString(serialized)
|
return Json.decodeFromString(serialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the [AppWidgetProviderInfo] by [id].
|
||||||
|
* If the widget is not installed, use [restoreAppWidgetProviderInfo] instead.
|
||||||
|
*/
|
||||||
|
fun getAppWidgetProviderInfo(context: Context): AppWidgetProviderInfo? {
|
||||||
|
return (context.applicationContext as Application).appWidgetManager
|
||||||
|
.getAppWidgetInfo(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the AppWidgetProviderInfo from [user], [packageName] and [className].
|
||||||
|
* Only use this when the widget is not installed,
|
||||||
|
* in normal operation use [getAppWidgetProviderInfo] instead.
|
||||||
|
*/
|
||||||
|
fun restoreAppWidgetProviderInfo(context: Context): AppWidgetProviderInfo? {
|
||||||
|
return getAppWidgetProviders(context).firstOrNull {
|
||||||
|
it.profile.hashCode() == user
|
||||||
|
&& it.provider.packageName == packageName
|
||||||
|
&& it.provider.className == className
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "WidgetInfo(id=$id, position=$position, packageName=$packageName, className=$className, user=$user)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package de.jrpie.android.launcher.widgets
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("panel")
|
||||||
|
data class WidgetPanel(val id: Int, val label: String) {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = WidgetPanel(0, "home")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package de.jrpie.android.launcher.widgets
|
||||||
|
|
||||||
|
import android.graphics.Rect
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
const val GRID_SIZE: Short = 12
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class WidgetPosition(var x: Short, var y: Short, var width: Short, var height: Short) {
|
||||||
|
|
||||||
|
fun getAbsoluteRect(screenWidth: Int, screenHeight: Int): Rect {
|
||||||
|
val gridWidth = screenWidth / GRID_SIZE.toFloat()
|
||||||
|
val gridHeight= screenHeight / GRID_SIZE.toFloat()
|
||||||
|
|
||||||
|
return Rect(
|
||||||
|
(x * gridWidth).toInt(),
|
||||||
|
(y * gridHeight).toInt(),
|
||||||
|
((x + width) * gridWidth).toInt(),
|
||||||
|
((y + height) * gridHeight).toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromAbsoluteRect(absolute: Rect, screenWidth: Int, screenHeight: Int): WidgetPosition {
|
||||||
|
val gridWidth = screenWidth / 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 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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun center(minWidth: Int, minHeight: Int, screenWidth: Int, screenHeight: Int): WidgetPosition {
|
||||||
|
val gridWidth = screenWidth / GRID_SIZE.toFloat()
|
||||||
|
val gridHeight= screenHeight / GRID_SIZE.toFloat()
|
||||||
|
|
||||||
|
val cellsWidth = ceil(minWidth / gridWidth).toInt().toShort()
|
||||||
|
val cellsHeight = ceil(minHeight / gridHeight).toInt().toShort()
|
||||||
|
|
||||||
|
return WidgetPosition(
|
||||||
|
((GRID_SIZE - cellsWidth) / 2).toShort(),
|
||||||
|
((GRID_SIZE - cellsHeight) / 2).toShort(),
|
||||||
|
cellsWidth,
|
||||||
|
cellsHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,62 +2,48 @@ package de.jrpie.android.launcher.widgets
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
|
import android.appwidget.AppWidgetHost
|
||||||
import android.appwidget.AppWidgetHostView
|
import android.appwidget.AppWidgetHostView
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.appwidget.AppWidgetProviderInfo
|
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.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.UserManager
|
import android.os.UserManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.util.SizeF
|
||||||
import de.jrpie.android.launcher.Application
|
import de.jrpie.android.launcher.Application
|
||||||
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
import de.jrpie.android.launcher.preferences.LauncherPreferences
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
fun deleteAllWidgets(activity: Activity) {
|
fun deleteAllWidgets(context: Context) {
|
||||||
val appWidgetHost = (activity.application as Application).appWidgetHost
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
appWidgetHost.appWidgetIds.forEach { deleteAppWidget(activity, WidgetInfo(it, 0,0)) }
|
context.getAppWidgetHost().appWidgetIds.forEach { deleteAppWidget(context, WidgetInfo(it)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bindAppWidget(activity: Activity, providerInfo: AppWidgetProviderInfo): WidgetInfo? {
|
fun bindAppWidgetOrRequestPermission(activity: Activity, providerInfo: AppWidgetProviderInfo, id: Int, requestCode: Int? = null): Boolean {
|
||||||
val appWidgetHost = (activity.application as Application).appWidgetHost
|
val appWidgetId = if(id == -1) {
|
||||||
val appWidgetManager = (activity.application as Application).appWidgetManager
|
activity.getAppWidgetHost().allocateAppWidgetId()
|
||||||
val appWidgetId = appWidgetHost.allocateAppWidgetId()
|
} else { id }
|
||||||
|
|
||||||
Log.i("Launcher", "Binding new widget ${appWidgetId}")
|
Log.i("Launcher", "Binding new widget ${appWidgetId}")
|
||||||
if (!appWidgetManager.bindAppWidgetIdIfAllowed(
|
if (!activity.getAppWidgetManager().bindAppWidgetIdIfAllowed(
|
||||||
appWidgetId,
|
appWidgetId,
|
||||||
providerInfo.provider
|
providerInfo.provider
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
requestAppWidgetPermission(activity, appWidgetId, providerInfo)
|
Log.e("Launcher", "not allowed to bind widget")
|
||||||
return null
|
requestAppWidgetPermission(activity, appWidgetId, providerInfo, requestCode)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
try {
|
return true
|
||||||
Log.e("widgets", "configure widget")
|
|
||||||
appWidgetHost.startAppWidgetConfigureActivityForResult(activity, appWidgetId, 0, 1, null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val widget = WidgetInfo(appWidgetId, 500, 500)
|
fun deleteAppWidget(context: Context, widget: WidgetInfo) {
|
||||||
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}")
|
Log.i("Launcher", "Deleting widget ${widget.id}")
|
||||||
val appWidgetHost = (activity.application as Application).appWidgetHost
|
val appWidgetHost = (context.applicationContext as Application).appWidgetHost
|
||||||
|
|
||||||
appWidgetHost.deleteAppWidgetId(widget.id)
|
appWidgetHost.deleteAppWidgetId(widget.id)
|
||||||
|
|
||||||
|
@ -69,55 +55,58 @@ fun deleteAppWidget(activity: Activity, widget: WidgetInfo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createAppWidgetView(activity: Activity, widget: WidgetInfo): AppWidgetHostView? {
|
fun createAppWidgetView(activity: Activity, widget: WidgetInfo): AppWidgetHostView? {
|
||||||
val appWidgetHost = (activity.application as Application).appWidgetHost
|
val providerInfo = activity.getAppWidgetManager().getAppWidgetInfo(widget.id) ?: return null
|
||||||
val appWidgetManager = (activity.application as Application).appWidgetManager
|
|
||||||
val providerInfo = appWidgetManager.getAppWidgetInfo(widget.id) ?: return null
|
val dp = activity.resources.displayMetrics.density
|
||||||
val view = appWidgetHost.createView(activity, widget.id, providerInfo)
|
|
||||||
.apply {
|
val view = activity.getAppWidgetHost()
|
||||||
setAppWidget(appWidgetId, appWidgetInfo)
|
.createView(activity, widget.id, providerInfo)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
view.updateAppWidgetSize(Bundle.EMPTY, listOf(SizeF(widget.position.width / dp, widget.position.height / dp)))
|
||||||
}
|
}
|
||||||
|
view.setPadding(0,0,0,0)
|
||||||
|
|
||||||
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
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAppWidgetProviders(context: Context): List<AppWidgetProviderInfo> {
|
fun requestAppWidgetPermission(context: Activity, widgetId: Int, info: AppWidgetProviderInfo, requestCode: Int?) {
|
||||||
return appWidgetProviders(context, (context.applicationContext as Application).appWidgetManager)
|
Log.i("Widgets", "requesting permission for widget")
|
||||||
}
|
|
||||||
|
|
||||||
fun requestAppWidgetPermission(context: Activity, widgetId: Int, info: AppWidgetProviderInfo) {
|
|
||||||
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
|
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
|
||||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
||||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider)
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider)
|
||||||
}
|
}
|
||||||
context.startActivityForResult(intent, 0)//REQUEST_CODE_BIND_WIDGET)
|
context.startActivityForResult(intent, requestCode ?: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun appWidgetProviders(
|
fun getAppWidgetProviders( context: Context ): List<AppWidgetProviderInfo> {
|
||||||
context: Context,
|
val appWidgetManager = context.getAppWidgetManager()
|
||||||
appWidgetManager: AppWidgetManager
|
val profiles =
|
||||||
): List<AppWidgetProviderInfo> {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager
|
(context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps).profiles
|
||||||
return userManager.userProfiles.map {
|
} else {
|
||||||
|
(context.getSystemService(Service.USER_SERVICE) as UserManager).userProfiles
|
||||||
|
}
|
||||||
|
Log.i("Widgets", "profiles: ${profiles.size}, $profiles")
|
||||||
|
|
||||||
|
return profiles.map {
|
||||||
appWidgetManager.getInstalledProvidersForProfile(it)
|
appWidgetManager.getInstalledProvidersForProfile(it)
|
||||||
}.flatten()
|
}.flatten()
|
||||||
}
|
}
|
||||||
fun Activity.bindRandomWidget() {
|
|
||||||
val selectedWidget =
|
fun getWidgetById(id: Int): WidgetInfo? {
|
||||||
getAppWidgetProviders(this).let { it.get(Random.nextInt().absoluteValue % it.size) }
|
return (LauncherPreferences.internal().widgets() ?: setOf()).firstOrNull {
|
||||||
bindAppWidget(this, selectedWidget) ?: return
|
it.id == id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateWidget(widget: WidgetInfo) {
|
||||||
|
var widgets = LauncherPreferences.internal().widgets() ?: setOf()
|
||||||
|
widgets = widgets.minus(widget).plus(widget)
|
||||||
|
LauncherPreferences.internal().widgets(widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.getAppWidgetHost(): AppWidgetHost {
|
||||||
|
return (this.applicationContext as Application).appWidgetHost
|
||||||
|
}
|
||||||
|
private fun Context.getAppWidgetManager(): AppWidgetManager {
|
||||||
|
return (this.applicationContext as Application).appWidgetManager
|
||||||
|
}
|
||||||
|
|
11
app/src/main/res/drawable/baseline_add_24.xml
Normal file
11
app/src/main/res/drawable/baseline_add_24.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="?android:textColor"
|
||||||
|
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||||
|
|
||||||
|
</vector>
|
25
app/src/main/res/layout/activity_manage_widgets.xml
Normal file
25
app/src/main/res/layout/activity_manage_widgets.xml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?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"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
tools:context=".ui.widgets.manage.ManageWidgetsActivity">
|
||||||
|
<de.jrpie.android.launcher.ui.widgets.manage.WidgetManagerView
|
||||||
|
android:id="@+id/manage_widgets_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/manage_widgets_button_add"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/baseline_add_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -2,10 +2,11 @@
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/main"
|
android:id="@+id/select_widget_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.widgets.SelectWidgetActivity">
|
android:fitsSystemWindows="true"
|
||||||
|
tools:context=".ui.widgets.manage.SelectWidgetActivity">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/select_widget_appbar"
|
android:id="@+id/select_widget_appbar"
|
||||||
|
@ -61,13 +62,11 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginLeft="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toBottomOf="@+id/select_widget_appbar" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -9,15 +9,12 @@
|
||||||
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
|
<de.jrpie.android.launcher.ui.widgets.WidgetContainerView
|
||||||
android:id="@+id/home_widget_container"
|
android:id="@+id/home_widget_container"
|
||||||
android:paddingLeft="20dp"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="match_parent" />
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<TextClock
|
<TextClock
|
||||||
android:id="@+id/home_upper_view"
|
android:id="@+id/home_upper_view"
|
||||||
|
|
35
app/src/main/res/layout/list_widgets_header.xml
Normal file
35
app/src/main/res/layout/list_widgets_header.xml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?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:background="@color/cardview_dark_background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="15sp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/list_widgets_header_icon"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:src="@mipmap/ic_launcher_round"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_widgets_header_app_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_header_icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -8,23 +8,13 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="15sp">
|
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
|
<ImageView
|
||||||
android:id="@+id/list_widgets_row_icon"
|
android:id="@+id/list_widgets_row_icon"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/list_widgets_row_preview"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@id/list_widgets_row_preview"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
tools:src="@mipmap/ic_launcher_round"
|
tools:src="@mipmap/ic_launcher_round"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
@ -33,14 +23,38 @@
|
||||||
android:id="@+id/list_widgets_row_name"
|
android:id="@+id/list_widgets_row_name"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="20sp"
|
android:layout_marginStart="10sp"
|
||||||
|
android:layout_marginEnd="10sp"
|
||||||
android:gravity="start"
|
android:gravity="start"
|
||||||
android:text=""
|
android:text=""
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
tools:text="some widget"
|
tools:text="some widget"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintStart_toEndOf="@id/list_widgets_row_icon"
|
||||||
app:layout_constraintStart_toEndOf="@id/list_widgets_row_preview"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/list_widgets_row_description"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="10sp"
|
||||||
|
android:gravity="start"
|
||||||
|
android:text=""
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/list_widgets_row_name"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/list_widgets_row_name"
|
||||||
|
tools:text="a longer description of the widget" />
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/list_widgets_row_preview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:maxHeight="100dp"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginEnd="0dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/list_widgets_row_description"
|
||||||
|
tools:src="@mipmap/ic_launcher_round"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
16
app/src/main/res/layout/sample_widget_overlay_view.xml
Normal file
16
app/src/main/res/layout/sample_widget_overlay_view.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<de.jrpie.android.launcher.ui.widgets.WidgetOverlayView
|
||||||
|
style="@style/Widget.launcherBaseTheme.MyView"
|
||||||
|
android:layout_width="300dp"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
android:paddingLeft="20dp"
|
||||||
|
android:paddingBottom="40dp"
|
||||||
|
app:exampleDimension="24sp"
|
||||||
|
app:exampleDrawable="@android:drawable/ic_menu_add"
|
||||||
|
app:exampleString="Hello, WidgetOverlayView" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
7
app/src/main/res/values-night/styles.xml
Normal file
7
app/src/main/res/values-night/styles.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Widget.launcherBaseTheme.MyView" parent="">
|
||||||
|
<item name="android:background">@color/gray_600</item>
|
||||||
|
<item name="exampleColor">@color/light_blue_600</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
8
app/src/main/res/values/attrs_widget_overlay_view.xml
Normal file
8
app/src/main/res/values/attrs_widget_overlay_view.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="WidgetOverlayView">
|
||||||
|
<attr name="exampleString" format="string" />
|
||||||
|
<attr name="exampleDimension" format="dimension" />
|
||||||
|
<attr name="exampleColor" format="color" />
|
||||||
|
<attr name="exampleDrawable" format="color|reference" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
|
@ -11,5 +11,9 @@
|
||||||
<color name="lightTheme_background_color">#fff</color>
|
<color name="lightTheme_background_color">#fff</color>
|
||||||
<color name="lightTheme_accent_color">#9999ff</color>
|
<color name="lightTheme_accent_color">#9999ff</color>
|
||||||
<color name="lightTheme_text_color">#000</color>
|
<color name="lightTheme_text_color">#000</color>
|
||||||
|
<color name="light_blue_400">#FF29B6F6</color>
|
||||||
|
<color name="light_blue_600">#FF039BE5</color>
|
||||||
|
<color name="gray_400">#FFBDBDBD</color>
|
||||||
|
<color name="gray_600">#FF757575</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|
|
@ -66,12 +66,12 @@
|
||||||
<item name="android:shadowDy">0</item>
|
<item name="android:shadowDy">0</item>
|
||||||
<item name="android:shadowRadius">2</item>
|
<item name="android:shadowRadius">2</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="textShadowLight" parent="textShadow">
|
<style name="textShadowLight" parent="textShadow">
|
||||||
<item name="android:shadowColor">#aaa</item>
|
<item name="android:shadowColor">#aaa</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style name="backgroundWallpaper">
|
<style name="backgroundWallpaper">
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
@ -81,26 +81,30 @@
|
||||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="backgroundSolid">
|
<style name="backgroundSolid"></style>
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<style name="fontSystemDefault">
|
<style name="fontSystemDefault">
|
||||||
<!--<item name="android:textSize">18sp</item>-->
|
<!--<item name="android:textSize">18sp</item>-->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontHack">
|
<style name="fontHack">
|
||||||
<item name="android:fontFamily">@font/hack</item>
|
<item name="android:fontFamily">@font/hack</item>
|
||||||
<!--<item name="android:textSize">18sp</item>-->
|
<!--<item name="android:textSize">18sp</item>-->
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontMonospace">
|
<style name="fontMonospace">
|
||||||
<item name="android:fontFamily">monospace</item>
|
<item name="android:fontFamily">monospace</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontSerifMonospace">
|
<style name="fontSerifMonospace">
|
||||||
<item name="android:fontFamily">serif-monospace</item>
|
<item name="android:fontFamily">serif-monospace</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontSansSerif">
|
<style name="fontSansSerif">
|
||||||
<item name="android:fontFamily">sans-serif</item>
|
<item name="android:fontFamily">sans-serif</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="fontSerif" tools:keep="@style/fontSerif">
|
<style name="fontSerif" tools:keep="@style/fontSerif">
|
||||||
<item name="android:fontFamily">serif</item>
|
<item name="android:fontFamily">serif</item>
|
||||||
</style>
|
</style>
|
||||||
|
@ -126,4 +130,9 @@
|
||||||
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
|
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
|
||||||
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
|
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.launcherBaseTheme.MyView" parent="">
|
||||||
|
<item name="android:background">@color/gray_400</item>
|
||||||
|
<item name="exampleColor">@color/light_blue_400</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue