parent
c66f574862
commit
3aa28671a4
@ -47,6 +47,20 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.mm20.launcher2.ui.launcher.LauncherActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".launcher.widgets.picker.PickAppWidgetActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/title_activity_settings"
|
||||
android:launchMode="singleTask"
|
||||
android:parentActivityName=".launcher.LauncherActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:taskAffinity="de.mm20.launcher2.settings"
|
||||
android:theme="@style/SettingsTheme.NoActionBar">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="de.mm20.launcher2.ui.launcher.LauncherActivity" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -8,6 +8,7 @@ import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -28,6 +29,7 @@ import de.mm20.launcher2.transition.ChangingLayoutTransition
|
||||
import de.mm20.launcher2.transition.OneShotLayoutTransition
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.ViewWidgetsBinding
|
||||
import de.mm20.launcher2.ui.launcher.widgets.picker.PickAppWidgetActivity
|
||||
import de.mm20.launcher2.ui.legacy.component.WidgetView
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
import de.mm20.launcher2.widgets.WidgetType
|
||||
@ -49,7 +51,6 @@ class WidgetsView @JvmOverloads constructor(
|
||||
private lateinit var widgets: MutableList<Widget>
|
||||
|
||||
private val pickWidgetLauncher: ActivityResultLauncher<Intent>
|
||||
private val configureWidgetLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
init {
|
||||
context as AppCompatActivity
|
||||
@ -57,34 +58,14 @@ class WidgetsView @JvmOverloads constructor(
|
||||
layoutTransition = ChangingLayoutTransition()
|
||||
binding.widgetList.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
|
||||
configureWidgetLauncher = context.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
val data = it.data ?: return@registerForActivityResult
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
bindAppWidget(data)
|
||||
}
|
||||
}
|
||||
|
||||
pickWidgetLauncher = context.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
val data = it.data ?: return@registerForActivityResult
|
||||
val widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||
if (widgetId == -1) return@registerForActivityResult
|
||||
val widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||
if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) return@registerForActivityResult
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
val appWidget = AppWidgetManager.getInstance(context)
|
||||
.getAppWidgetInfo(widgetId) ?: return@registerForActivityResult
|
||||
if (appWidget.configure != null) {
|
||||
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE)
|
||||
intent.component = appWidget.configure
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
||||
configureWidgetLauncher.launch(intent)
|
||||
} else {
|
||||
bindAppWidget(data)
|
||||
}
|
||||
} else {
|
||||
widgetHost.deleteAppWidgetId(widgetId)
|
||||
bindAppWidget(widgetId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,18 +212,12 @@ class WidgetsView @JvmOverloads constructor(
|
||||
}
|
||||
@Suppress("DEPRECATION") // I don't care that neutral buttons are discouraged.
|
||||
neutralButton(R.string.widget_add_external) {
|
||||
val appWidgetId = widgetHost.allocateAppWidgetId()
|
||||
val pickIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK)
|
||||
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
pickWidgetLauncher.launch(pickIntent)
|
||||
pickAppWidget()
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val appWidgetId = widgetHost.allocateAppWidgetId()
|
||||
val pickIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK)
|
||||
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
pickWidgetLauncher.launch(pickIntent)
|
||||
pickAppWidget()
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,9 +228,13 @@ class WidgetsView @JvmOverloads constructor(
|
||||
widgetHost.deleteAppWidgetId(id)
|
||||
}
|
||||
|
||||
private fun bindAppWidget(data: Intent) {
|
||||
val widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||
if (widgetId == -1) return
|
||||
private fun pickAppWidget() {
|
||||
val pickIntent = Intent(context, PickAppWidgetActivity::class.java)
|
||||
pickWidgetLauncher.launch(pickIntent)
|
||||
}
|
||||
|
||||
private fun bindAppWidget(widgetId: Int) {
|
||||
if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) return
|
||||
val appWidget = AppWidgetManager.getInstance(context)
|
||||
.getAppWidgetInfo(widgetId) ?: return
|
||||
val widget = Widget(
|
||||
|
||||
@ -0,0 +1,110 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.picker
|
||||
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
import androidx.compose.ui.graphics.nativeCanvas
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.ui.component.LauncherCard
|
||||
import de.mm20.launcher2.ui.ktx.toDp
|
||||
|
||||
@Composable
|
||||
fun AppWidgetList(
|
||||
modifier: Modifier = Modifier,
|
||||
widgets: List<AppWidgetProviderInfo>,
|
||||
onWidgetSelected: (AppWidgetProviderInfo) -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val density = (LocalDensity.current.density * 160).toInt()
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
) {
|
||||
items(widgets) {
|
||||
key(it.provider.toShortString()) {
|
||||
LauncherCard(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
onWidgetSelected(it)
|
||||
}
|
||||
.padding(16.dp),
|
||||
) {
|
||||
val label = remember { it.loadLabel(context.packageManager) }
|
||||
Text(text = label, style = MaterialTheme.typography.titleMedium)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
|
||||
val image: Drawable? = remember {
|
||||
it.loadPreviewImage(context, density) ?: it.loadIcon(
|
||||
context,
|
||||
density
|
||||
)
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
|
||||
val mod =
|
||||
if (image.intrinsicWidth > 0 && image.intrinsicHeight > 0) {
|
||||
Modifier
|
||||
.heightIn(max = image.intrinsicHeight.toDp())
|
||||
.widthIn(max = image.intrinsicWidth.toDp())
|
||||
.aspectRatio(
|
||||
image.intrinsicWidth.toFloat() / image.intrinsicHeight.toFloat(),
|
||||
matchHeightConstraintsFirst = true
|
||||
)
|
||||
} else {
|
||||
Modifier.size(64.dp)
|
||||
}
|
||||
|
||||
Canvas(
|
||||
modifier = mod
|
||||
) {
|
||||
drawIntoCanvas {
|
||||
val aspectRatio =
|
||||
image.intrinsicWidth / image.intrinsicHeight
|
||||
|
||||
|
||||
image.setBounds(
|
||||
0,
|
||||
0,
|
||||
size.width.toInt(),
|
||||
size.height.toInt(),
|
||||
)
|
||||
image.draw(it.nativeCanvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isAtLeastApiLevel(31)) {
|
||||
val description = remember { it.loadDescription(context)?.toString() }
|
||||
if (description != null) {
|
||||
Text(text = description, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.picker
|
||||
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.base.BaseActivity
|
||||
import de.mm20.launcher2.ui.component.ProvideIconShape
|
||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||
import de.mm20.launcher2.ui.locals.LocalFavoritesEnabled
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class PickAppWidgetActivity : BaseActivity() {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val viewModel by viewModels<PickAppWidgetVM>()
|
||||
|
||||
private lateinit var widgetHost: AppWidgetHost
|
||||
private lateinit var appWidgetManager: AppWidgetManager
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
widgetHost = AppWidgetHost(this, 44203)
|
||||
appWidgetManager = AppWidgetManager.getInstance(this)
|
||||
|
||||
val availableWidgets = viewModel.getAvailableWidgets(this)
|
||||
val selectedAppWidget = viewModel.selectedAppWidget
|
||||
setContent {
|
||||
MdcLauncherTheme {
|
||||
val cardStyle by remember {
|
||||
dataStore.data.map { it.cards }.distinctUntilChanged()
|
||||
}.collectAsState(
|
||||
Settings.CardSettings.getDefaultInstance()
|
||||
)
|
||||
val iconShape by remember {
|
||||
dataStore.data.map {
|
||||
if (it.easterEgg) Settings.IconSettings.IconShape.EasterEgg
|
||||
else it.icons.shape
|
||||
}.distinctUntilChanged()
|
||||
}.collectAsState(Settings.IconSettings.IconShape.Circle)
|
||||
|
||||
val favoritesEnabled by remember {
|
||||
dataStore.data.map { it.favorites.enabled }.distinctUntilChanged()
|
||||
}.collectAsState(true)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalCardStyle provides cardStyle,
|
||||
LocalFavoritesEnabled provides favoritesEnabled
|
||||
) {
|
||||
ProvideIconShape(iconShape) {
|
||||
val available by availableWidgets.observeAsState()
|
||||
val selected by selectedAppWidget.observeAsState()
|
||||
val widgets = available
|
||||
if (selected == null) {
|
||||
if (widgets != null) {
|
||||
AppWidgetList(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
widgets = widgets,
|
||||
onWidgetSelected = {
|
||||
selectAppWidget(it)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectAppWidget(widget: AppWidgetProviderInfo) {
|
||||
val appWidgetId = widgetHost.allocateAppWidgetId()
|
||||
viewModel.selectAppWidget(widget, appWidgetId)
|
||||
configureWidget()
|
||||
}
|
||||
|
||||
private fun configureWidget() {
|
||||
val appWidgetId = viewModel.appWidgetId.value ?: return
|
||||
val widget = viewModel.selectedAppWidget.value ?: return
|
||||
val canBind = appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, widget.provider)
|
||||
Log.d("MM20", "Can bind: $canBind")
|
||||
if (canBind) {
|
||||
if (widget.configure != null) {
|
||||
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE)
|
||||
intent.component = widget.configure
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
widgetHost.startAppWidgetConfigureActivityForResult(
|
||||
this,
|
||||
appWidgetId,
|
||||
0,
|
||||
RequestCodeConfigure,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
finishWithResult(appWidgetId)
|
||||
}
|
||||
} else {
|
||||
startActivityForResult(
|
||||
Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, widget.provider)
|
||||
}, RequestCodeBind)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when(requestCode) {
|
||||
RequestCodeBind -> {
|
||||
if (resultCode == RESULT_OK) {
|
||||
configureWidget()
|
||||
} else {
|
||||
viewModel.appWidgetId.value?.let { widgetHost.deleteAppWidgetId(it) }
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
RequestCodeConfigure -> {
|
||||
val widgetId = viewModel.appWidgetId.value ?: return cancel()
|
||||
if (resultCode == RESULT_OK) {
|
||||
finishWithResult(widgetId)
|
||||
} else {
|
||||
widgetHost.deleteAppWidgetId(widgetId)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishWithResult(widgetId: Int) {
|
||||
val data = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
||||
setResult(RESULT_OK, data)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun cancel() {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val RequestCodeConfigure = 1
|
||||
const val RequestCodeBind = 2
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.picker
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class PickAppWidgetVM: ViewModel() {
|
||||
var appWidgetId: MutableLiveData<Int?> = MutableLiveData(null)
|
||||
val selectedAppWidget: MutableLiveData<AppWidgetProviderInfo?> = MutableLiveData(null)
|
||||
|
||||
fun selectAppWidget(appWidget: AppWidgetProviderInfo, appWidgetId: Int) {
|
||||
this.appWidgetId.value = appWidgetId
|
||||
this.selectedAppWidget.value = appWidget
|
||||
}
|
||||
|
||||
fun getAvailableWidgets(context: Context): LiveData<List<AppWidgetProviderInfo>?> = liveData {
|
||||
emit(null)
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val widgets = withContext(Dispatchers.IO) {
|
||||
appWidgetManager.installedProviders.sortedBy { it.loadLabel(context.packageManager) }
|
||||
}
|
||||
emit(widgets)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user