Compare commits

...

2 commits

Author SHA1 Message Date
da35488ae0
add basic widget selection list 2025-04-16 15:31:45 +02:00
a964d9de52
some tests 2025-04-15 22:11:11 +02:00
17 changed files with 474 additions and 4 deletions

View file

@ -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'

View file

@ -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"

View file

@ -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()
}
} }

View file

@ -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

View file

@ -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(

View file

@ -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),

View file

@ -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>?> {

View file

@ -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) {

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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)
}
}
}

View 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
}

View 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>

View file

@ -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"

View 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>

View file

@ -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>

View file

@ -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>