Refactor widgets
This commit is contained in:
parent
2e43b871c5
commit
956af82f79
@ -8,36 +8,51 @@
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:name="path"
|
||||
android:pathData="M 3 17.46 L 3 20.5 C 3 20.78 3.22 21 3.5 21 L 6.54 21 C 6.67 21 6.8 20.95 6.89 20.85 L 17.81 9.94 L 14.06 6.19 L 3.15 17.1 C 3.05 17.2 3 17.32 3 17.46 Z"
|
||||
android:fillColor="#000"
|
||||
android:strokeWidth="1"/>
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:pathData="M 20.71 7.04 C 21.1 6.65 21.1 6.02 20.71 5.63 L 18.37 3.29 C 17.98 2.9 17.35 2.9 16.96 3.29 L 15.13 5.12 L 18.88 8.87 L 20.71 7.04 Z"
|
||||
android:fillColor="#000000"
|
||||
android:strokeWidth="1"/>
|
||||
<group
|
||||
android:name="group"
|
||||
android:pivotX="12"
|
||||
android:pivotY="12">
|
||||
<path
|
||||
android:name="path"
|
||||
android:pathData="M 3 17.46 L 3 20.5 C 3 20.78 3.22 21 3.5 21 L 6.54 21 C 6.67 21 6.8 20.95 6.89 20.85 L 17.81 9.94 L 14.06 6.19 L 3.15 17.1 C 3.05 17.2 3 17.32 3 17.46 Z M 20.71 7.04 C 21.1 6.65 21.1 6.02 20.71 5.63 L 18.37 3.29 C 17.98 2.9 17.35 2.9 16.96 3.29 L 15.13 5.12 L 18.88 8.87 L 20.71 7.04 Z"
|
||||
android:fillColor="#000"
|
||||
android:strokeWidth="1"/>
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:pathData=""
|
||||
android:fillColor="#000000"/>
|
||||
</group>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
<target android:name="path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:duration="200"
|
||||
android:valueFrom="M 15.894 8.024 C 15.283 7.413 14.671 6.801 14.06 6.19 L 3.15 17.1 C 3.05 17.2 3 17.32 3 17.46 L 3 17.46 C 3 18.473 3 19.487 3 20.5 C 3 20.78 3.22 21 3.5 21 C 4.513 21 5.527 21 6.54 21 C 6.67 21 6.8 20.95 6.89 20.85 C 10.53 17.213 14.17 13.577 17.81 9.94 C 17.171 9.301 16.533 8.663 15.894 8.024"
|
||||
android:valueTo="M 19 12 C 19 11.45 18.55 11 18 11 L 6.014 11 C 6.014 11 6.014 11 6.014 11 L 6 11 C 5.456 11 5.01 11.44 5 11.982 C 5 11.988 5 11.994 5 12 C 5 12.55 5.45 13 6 13 C 6 13 6 13 6 13 C 10 13 14 13 18 13 C 18.55 13 19 12.55 19 12"
|
||||
android:duration="100"
|
||||
android:valueFrom="M 11 18 C 11 14.007 11 9.578 11 6 C 11 5.45 11.45 5 12 5 C 12.55 5 13 5.45 13 6 L 13 18 C 13 18.298 12.868 18.567 12.659 18.751 C 12.482 18.906 12.252 19 12 19 C 12 19 12 19 12 19 C 11.706 19 11.441 18.872 11.257 18.668 C 11.098 18.491 11 18.256 11 18 C 11 18 11 18 11 18 M 12.001 3.13 L 12.001 3.13 L 12.001 3.13 L 12.001 3.13 C 12.001 3.13 12.001 3.13 12.001 3.13 L 12.001 3.13 C 12.001 3.13 12.001 3.13 12.001 3.13"
|
||||
android:valueTo="M 9.35 21.865 C 9.35 16.722 9.35 11.579 9.35 6.436 C 10.233 6.436 11.117 6.436 12.001 6.436 C 12.885 6.436 13.769 6.436 14.653 6.436 L 14.646 21.872 C 14.653 22.006 14.596 22.133 14.504 22.225 C 13.788 22.942 13.071 23.658 12.355 24.375 C 12.157 24.573 11.846 24.573 11.648 24.375 C 10.931 23.658 10.215 22.942 9.498 22.225 C 9.498 22.225 9.498 22.225 9.498 22.225 C 9.399 22.126 9.35 22.006 9.35 21.865 M 14.653 2.334 L 14.653 4.922 L 9.35 4.922 L 9.35 2.334 C 9.35 1.783 9.795 1.337 10.347 1.337 L 13.656 1.337 C 14.207 1.337 14.653 1.783 14.653 2.334"
|
||||
android:valueType="pathType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="group">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="rotation"
|
||||
android:duration="200"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="45"
|
||||
android:valueType="floatType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="path_1">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="pathData"
|
||||
android:duration="200"
|
||||
android:valueFrom="M 18.88 8.87 C 19.49 8.26 20.1 7.65 20.71 7.04 C 20.71 7.04 20.71 7.04 20.71 7.04 C 21.1 6.65 21.1 6.02 20.71 5.63 C 19.93 4.85 19.15 4.07 18.37 3.29 C 17.98 2.9 17.35 2.9 16.96 3.29 C 16.35 3.9 15.74 4.51 15.13 5.12 L 18.88 8.87"
|
||||
android:valueTo="M 11 18 C 11 18.55 11.45 19 12 19 C 12.029 19 12.058 18.999 12.086 18.996 C 12.596 18.952 13 18.521 13 18 C 13 14 13 10 13 6 C 13 5.45 12.55 5 12 5 C 11.45 5 11 5.45 11 6 L 11 18"
|
||||
android:valueFrom="M 18 13 C 14.007 13 9.578 13 6 13 C 5.45 13 5 12.55 5 12 C 5 11.45 5.45 11 6 11 L 18 11 C 18.55 11 19 11.45 19 12 C 19 12.55 18.55 13 18 13 Z"
|
||||
android:valueTo="M 12 13 C 12 13 12 13 12 13 C 11.45 13 11 12.55 11 12 C 11 11.45 11.45 11 12 11 L 12 11 C 12.55 11 13 11.45 13 12 C 13 12.55 12.55 13 12 13 Z"
|
||||
android:valueType="pathType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
</aapt:attr>
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
package de.mm20.launcher2.database
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import de.mm20.launcher2.database.entities.WidgetEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@ -19,6 +21,16 @@ interface WidgetDao {
|
||||
@Insert
|
||||
fun insertAll(widgets: List<WidgetEntity>)
|
||||
|
||||
@Insert
|
||||
fun insert(widget: WidgetEntity)
|
||||
|
||||
@Query("DELETE FROM Widget")
|
||||
fun deleteAll()
|
||||
|
||||
|
||||
@Query("DELETE FROM Widget WHERE data = :data AND type = :type")
|
||||
fun deleteWidget(type: String, data: String)
|
||||
|
||||
@Query("UPDATE Widget SET height = :newHeight WHERE data = :data AND type = :type")
|
||||
fun updateHeight(type: String, data: String, newHeight: Int)
|
||||
}
|
||||
@ -173,7 +173,7 @@ class LauncherScaffoldView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
widgetsViewModel.isEditMode.observe(context) {
|
||||
OneShotLayoutTransition.run(binding.scrollContainer)
|
||||
//OneShotLayoutTransition.run(binding.scrollContainer)
|
||||
if (it) {
|
||||
binding.scrollView.setOnTouchListener(null)
|
||||
binding.searchBar.visibility = View.INVISIBLE
|
||||
|
||||
@ -0,0 +1,164 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets
|
||||
|
||||
import android.app.Activity
|
||||
import android.appwidget.AppWidgetHost
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.gestures.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.LauncherCard
|
||||
import de.mm20.launcher2.ui.launcher.widgets.calendar.CalendarWidget
|
||||
import de.mm20.launcher2.ui.launcher.widgets.external.ExternalWidget
|
||||
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
|
||||
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget
|
||||
import de.mm20.launcher2.widgets.*
|
||||
import java.lang.Integer.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun WidgetItem(
|
||||
widget: Widget,
|
||||
appWidgetHost: AppWidgetHost,
|
||||
modifier: Modifier = Modifier,
|
||||
editMode: Boolean = false,
|
||||
onWidgetResize: (newHeight: Int) -> Unit = {},
|
||||
onWidgetRemove: () -> Unit = {},
|
||||
draggableState: DraggableState = rememberDraggableState {},
|
||||
onDragStopped: () -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var resizeMode by remember(editMode) { mutableStateOf(false) }
|
||||
|
||||
var isDragged by remember { mutableStateOf(false) }
|
||||
val elevation by animateDpAsState(if (isDragged) 8.dp else 2.dp)
|
||||
|
||||
LauncherCard(
|
||||
modifier = modifier.zIndex(if (isDragged) 1f else 0f),
|
||||
elevation = elevation
|
||||
) {
|
||||
Column {
|
||||
AnimatedVisibility(editMode) {
|
||||
Row(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DragIndicator,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.draggable(
|
||||
state = draggableState,
|
||||
orientation = Orientation.Vertical,
|
||||
startDragImmediately = true,
|
||||
onDragStarted = {
|
||||
isDragged = true
|
||||
},
|
||||
onDragStopped = {
|
||||
isDragged = false
|
||||
onDragStopped()
|
||||
}
|
||||
)
|
||||
)
|
||||
Text(
|
||||
text = remember(widget) { widget.loadLabel(context) },
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 8.dp),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1
|
||||
)
|
||||
if (widget is ExternalWidget) {
|
||||
IconButton(onClick = { resizeMode = !resizeMode }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Edit,
|
||||
contentDescription = stringResource(R.string.widget_action_adjust_height)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (widget.isConfigurable) {
|
||||
IconButton({
|
||||
widget.configure(context as Activity, appWidgetHost)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Settings,
|
||||
contentDescription = stringResource(R.string.settings)
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { onWidgetRemove() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Delete,
|
||||
contentDescription = stringResource(R.string.widget_action_remove)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(!editMode || resizeMode) {
|
||||
when (widget) {
|
||||
is WeatherWidget -> {
|
||||
WeatherWidget()
|
||||
}
|
||||
is MusicWidget -> {
|
||||
MusicWidget()
|
||||
}
|
||||
is CalendarWidget -> {
|
||||
CalendarWidget()
|
||||
}
|
||||
is ExternalWidget -> {
|
||||
var height by remember(widget) { mutableStateOf(widget.height) }
|
||||
Column {
|
||||
ExternalWidget(
|
||||
appWidgetHost = appWidgetHost,
|
||||
widgetId = widget.widgetId,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
height = height,
|
||||
)
|
||||
if (resizeMode) {
|
||||
val density = LocalDensity.current
|
||||
val drgStt = rememberDraggableState {
|
||||
height = max(
|
||||
height + (it / density.density).roundToInt(),
|
||||
widget.widgetProviderInfo.minResizeHeight
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DragHandle,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.requiredHeight(24.dp)
|
||||
.fillMaxWidth()
|
||||
.draggable(
|
||||
state = drgStt,
|
||||
orientation = Orientation.Vertical,
|
||||
startDragImmediately = true,
|
||||
onDragStopped = {
|
||||
onWidgetResize(height)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,17 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.liveData
|
||||
import de.mm20.launcher2.widgets.ExternalWidget
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
import de.mm20.launcher2.widgets.WidgetRepository
|
||||
import de.mm20.launcher2.widgets.WidgetType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@ -20,11 +26,47 @@ class WidgetsVM : ViewModel(), KoinComponent {
|
||||
isEditMode.value = editMode
|
||||
}
|
||||
|
||||
fun saveWidgets(widgets: List<Widget>) {
|
||||
fun addWidget(widget: Widget) {
|
||||
widgetRepository.addWidget(widget, widgets.value?.size ?: 0)
|
||||
}
|
||||
|
||||
fun addAppWidget(context: Context, widgetId: Int) {
|
||||
if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) return
|
||||
val appWidget = AppWidgetManager.getInstance(context)
|
||||
.getAppWidgetInfo(widgetId) ?: return
|
||||
val widget = ExternalWidget(
|
||||
widgetProviderInfo = appWidget,
|
||||
height = appWidget.minHeight,
|
||||
widgetId = widgetId,
|
||||
)
|
||||
addWidget(widget)
|
||||
}
|
||||
|
||||
fun removeWidget(widget: Widget) {
|
||||
widgetRepository.removeWidget(widget)
|
||||
}
|
||||
|
||||
fun setWidgetHeight(widget: Widget, newHeight: Int) {
|
||||
widgetRepository.setWidgetHeight(widget, newHeight)
|
||||
}
|
||||
|
||||
fun getAvailableBuiltInWidgets(): List<Widget> {
|
||||
return widgetRepository.getInternalWidgets().filter {
|
||||
widgets.value?.contains(it)?.not() ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun moveUp(index: Int) {
|
||||
val widgets = widgets.value?.toMutableList() ?: return
|
||||
val widget = widgets.removeAt(index)
|
||||
widgets.add(index - 1, widget)
|
||||
widgetRepository.saveWidgets(widgets)
|
||||
}
|
||||
|
||||
fun getInternalWidgets(): List<Widget> {
|
||||
return widgetRepository.getInternalWidgets()
|
||||
fun moveDown(index: Int) {
|
||||
val widgets = widgets.value?.toMutableList() ?: return
|
||||
val widget = widgets.removeAt(index)
|
||||
widgets.add(index + 1, widget)
|
||||
widgetRepository.saveWidgets(widgets)
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,5 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets
|
||||
|
||||
import android.animation.LayoutTransition
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetManager
|
||||
@ -9,259 +7,317 @@ 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
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.iterator
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.balsikandar.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.transition.ChangingLayoutTransition
|
||||
import de.mm20.launcher2.transition.OneShotLayoutTransition
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
|
||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Add
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onPlaced
|
||||
import androidx.compose.ui.layout.positionInParent
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import de.mm20.launcher2.ui.ClockWidget
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.ViewWidgetsBinding
|
||||
import de.mm20.launcher2.ui.base.ProvideSettings
|
||||
import de.mm20.launcher2.ui.ktx.toDp
|
||||
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
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import de.mm20.launcher2.widgets.ExternalWidget
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@OptIn(ExperimentalAnimationGraphicsApi::class)
|
||||
class WidgetsView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : LinearLayout(context, attrs) {
|
||||
|
||||
private val binding = ViewWidgetsBinding.inflate(LayoutInflater.from(context), this)
|
||||
) : FrameLayout(context, attrs) {
|
||||
|
||||
private val widgetHost: AppWidgetHost = AppWidgetHost(context.applicationContext, 44203)
|
||||
|
||||
private val viewModel: WidgetsVM by (context as AppCompatActivity).viewModels()
|
||||
|
||||
private lateinit var widgets: MutableList<Widget>
|
||||
|
||||
private val pickWidgetLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private val clockWidgetHeight = MutableLiveData(0)
|
||||
|
||||
init {
|
||||
context as AppCompatActivity
|
||||
|
||||
|
||||
pickWidgetLauncher = context.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
val data = it.data ?: return@registerForActivityResult
|
||||
val widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||
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) {
|
||||
bindAppWidget(widgetId)
|
||||
viewModel.addAppWidget(context, widgetId)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.widgets.observe(context) {
|
||||
if (it != null && !::widgets.isInitialized) {
|
||||
widgets = it.toMutableList()
|
||||
initWidgets()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.isEditMode.observe(context) {
|
||||
if (it) {
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
binding.clockWidget.visibility = View.GONE
|
||||
val composeView = ComposeView(context)
|
||||
composeView.setContent {
|
||||
MdcLauncherTheme {
|
||||
ProvideSettings {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(8.dp)
|
||||
) {
|
||||
val editMode by viewModel.isEditMode.observeAsState(false)
|
||||
val clockHeight by clockWidgetHeight.observeAsState(0)
|
||||
val scope = rememberCoroutineScope()
|
||||
var showAddDialog by remember { mutableStateOf(false) }
|
||||
|
||||
for (v in binding.widgetList.iterator()) {
|
||||
if (v is WidgetView) {
|
||||
v.editMode = true
|
||||
v.onResizeModeChange = {
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
OneShotLayoutTransition.run(this)
|
||||
AnimatedVisibility(!editMode) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(clockHeight.toDp()),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
ClockWidget(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
val widgets by viewModel.widgets.observeAsState(emptyList())
|
||||
Column {
|
||||
val swapThresholds = remember(widgets) {
|
||||
Array(widgets.size) { floatArrayOf(0f, 0f) }
|
||||
}
|
||||
for ((i, widget) in widgets.withIndex()) {
|
||||
key(if (widget is ExternalWidget) widget.widgetId else widget) {
|
||||
var dragOffsetAfterSwap = remember<Float?> { null }
|
||||
val offsetY = remember(widgets) { Animatable(dragOffsetAfterSwap ?: 0f) }
|
||||
|
||||
LaunchedEffect(widgets) {
|
||||
dragOffsetAfterSwap = null
|
||||
}
|
||||
|
||||
WidgetItem(
|
||||
widget = widget,
|
||||
appWidgetHost = widgetHost,
|
||||
editMode = editMode,
|
||||
onWidgetRemove = {
|
||||
if (widget is ExternalWidget) {
|
||||
widgetHost.deleteAppWidgetId(widget.widgetId)
|
||||
}
|
||||
viewModel.removeWidget(widget)
|
||||
},
|
||||
onWidgetResize = {
|
||||
viewModel.setWidgetHeight(widget, it)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onPlaced {
|
||||
swapThresholds[i][0] = it.positionInParent().y
|
||||
swapThresholds[i][1] = it.positionInParent().y + it.size.height
|
||||
}
|
||||
.padding(top = 8.dp)
|
||||
.offset {
|
||||
IntOffset(0, offsetY.value.toInt())
|
||||
},
|
||||
draggableState = rememberDraggableState {
|
||||
scope.launch {
|
||||
val newOffset = offsetY.value + it
|
||||
offsetY.snapTo(newOffset)
|
||||
if (i > 0 && newOffset < (swapThresholds[i - 1][0] - swapThresholds[i - 1][1])) {
|
||||
if (dragOffsetAfterSwap == null) {
|
||||
dragOffsetAfterSwap = swapThresholds[i - 1][1] - swapThresholds[i - 1][0] + newOffset
|
||||
viewModel.moveUp(i)
|
||||
}
|
||||
}
|
||||
if (i < widgets.lastIndex && newOffset > (swapThresholds[i + 1][1] - swapThresholds[i + 1][0])) {
|
||||
if (dragOffsetAfterSwap == null) {
|
||||
dragOffsetAfterSwap = swapThresholds[i + 1][0] - swapThresholds[i + 1][1] + newOffset
|
||||
viewModel.moveDown(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onDragStopped = {
|
||||
scope.launch {
|
||||
offsetY.animateTo(0f)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val icon =
|
||||
AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_edit_add)
|
||||
ExtendedFloatingActionButton(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
icon = {
|
||||
Icon(
|
||||
painter = rememberAnimatedVectorPainter(
|
||||
animatedImageVector = icon,
|
||||
atEnd = !editMode
|
||||
), contentDescription = null
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
stringResource(
|
||||
if (editMode) R.string.widget_add_widget
|
||||
else R.string.menu_edit_widgets
|
||||
)
|
||||
)
|
||||
}, onClick = {
|
||||
if (!editMode) {
|
||||
viewModel.setEditMode(true)
|
||||
} else {
|
||||
if (viewModel.getAvailableBuiltInWidgets().isEmpty()) {
|
||||
pickWidgetLauncher.launch(
|
||||
Intent(
|
||||
context,
|
||||
PickAppWidgetActivity::class.java
|
||||
)
|
||||
)
|
||||
} else {
|
||||
showAddDialog = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (showAddDialog) {
|
||||
val availableBuiltInWidgets =
|
||||
remember { viewModel.getAvailableBuiltInWidgets() }
|
||||
Dialog(onDismissRequest = { showAddDialog = false }) {
|
||||
Surface(
|
||||
tonalElevation = 16.dp,
|
||||
shadowElevation = 16.dp,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.widget_add_widget),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.padding(
|
||||
start = 24.dp,
|
||||
end = 24.dp,
|
||||
top = 24.dp,
|
||||
bottom = 8.dp
|
||||
)
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
top = 16.dp
|
||||
)
|
||||
) {
|
||||
items(availableBuiltInWidgets) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
viewModel.addWidget(it)
|
||||
showAddDialog = false
|
||||
}
|
||||
.padding(
|
||||
horizontal = 24.dp,
|
||||
vertical = 16.dp
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = it.loadLabel(context),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp)
|
||||
.clickable {
|
||||
pickWidgetLauncher.launch(
|
||||
Intent(
|
||||
context,
|
||||
PickAppWidgetActivity::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
.padding(
|
||||
horizontal = 24.dp,
|
||||
vertical = 16.dp
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Add,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.widget_add_external),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = { showAddDialog = false },
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(bottom = 16.dp, end = 24.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(android.R.string.cancel)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.fabEditWidget.apply {
|
||||
setIconResource(R.drawable.ic_add)
|
||||
setText(R.string.widget_add_widget)
|
||||
setOnClickListener {
|
||||
addWidget()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (::widgets.isInitialized) {
|
||||
viewModel.saveWidgets(widgets)
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
}
|
||||
binding.clockWidget.visibility = View.VISIBLE
|
||||
for (v in binding.widgetList.iterator()) {
|
||||
if (v is WidgetView) {
|
||||
v.editMode = false
|
||||
}
|
||||
}
|
||||
binding.fabEditWidget.apply {
|
||||
setIconResource(R.drawable.ic_edit)
|
||||
setText(R.string.menu_edit_widgets)
|
||||
setOnClickListener {
|
||||
viewModel.setEditMode(true)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.lifecycleScope.launch {
|
||||
context.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
widgetHost.startListening()
|
||||
try {
|
||||
awaitCancellation()
|
||||
} finally {
|
||||
// TODO: find out why there is a NPE thrown sometimes
|
||||
try {
|
||||
widgetHost.stopListening()
|
||||
} catch (e: NullPointerException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.fabEditWidget.setOnClickListener {
|
||||
viewModel.setEditMode(true)
|
||||
}
|
||||
addView(composeView)
|
||||
}
|
||||
|
||||
fun setClockWidgetHeight(height: Int) {
|
||||
val params = binding.clockWidget.layoutParams
|
||||
params.height = height
|
||||
binding.clockWidget.layoutParams = params
|
||||
}
|
||||
|
||||
|
||||
private fun initWidgets() {
|
||||
binding.widgetList.removeAllViews()
|
||||
val params = LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
params.topMargin = (8 * dp).roundToInt()
|
||||
for (w in widgets) {
|
||||
val view = WidgetView(context)
|
||||
view.layoutParams = params
|
||||
if (view.setWidget(w, widgetHost)) {
|
||||
binding.widgetList.addDragView(view, view.getDragHandle())
|
||||
view.onRemove = {
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
binding.widgetList.removeDragView(view)
|
||||
removeWidget(view.widget)
|
||||
}
|
||||
view.onResizeModeChange = {
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.widgetList.setOnViewSwapListener { _, firstPosition, _, secondPosition ->
|
||||
Collections.swap(widgets, firstPosition, secondPosition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun addWidget() {
|
||||
val usedWidgets = widgets.filter { it.type == WidgetType.INTERNAL }.map { it.data }
|
||||
val internalWidgets =
|
||||
viewModel.getInternalWidgets().filter { !usedWidgets.contains(it.data) }
|
||||
if (internalWidgets.isNotEmpty()) {
|
||||
MaterialDialog(context).show {
|
||||
title(R.string.widget_add_widget)
|
||||
listItems(items = internalWidgets.map { it.label }) { dialog, index, _ ->
|
||||
val widget = internalWidgets[index]
|
||||
val view = WidgetView(this@WidgetsView.context)
|
||||
val params = LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
params.topMargin = (8 * dp).roundToInt()
|
||||
view.layoutParams = params
|
||||
if (view.setWidget(widget, widgetHost)) {
|
||||
view.editMode = true
|
||||
binding.widgetList.addDragView(view, view.getDragHandle())
|
||||
view.onRemove = {
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
OneShotLayoutTransition.run(this@WidgetsView)
|
||||
binding.widgetList.removeDragView(view)
|
||||
removeWidget(view.widget)
|
||||
}
|
||||
view.onResizeModeChange = {
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
OneShotLayoutTransition.run(this@WidgetsView)
|
||||
}
|
||||
widgets.add(widget)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
@Suppress("DEPRECATION") // I don't care that neutral buttons are discouraged.
|
||||
neutralButton(R.string.widget_add_external) {
|
||||
pickAppWidget()
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pickAppWidget()
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeWidget(widget: Widget?) {
|
||||
widget ?: return
|
||||
widgets.remove(widget)
|
||||
val id = widget.data.toIntOrNull() ?: return
|
||||
widgetHost.deleteAppWidgetId(id)
|
||||
}
|
||||
|
||||
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(
|
||||
type = WidgetType.THIRD_PARTY,
|
||||
data = widgetId.toString(),
|
||||
height = appWidget.minHeight
|
||||
)
|
||||
val params = LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
params.topMargin = (8 * dp).roundToInt()
|
||||
val view = WidgetView(context)
|
||||
view.layoutParams = params
|
||||
if (view.setWidget(widget, widgetHost)) {
|
||||
view.editMode = true
|
||||
|
||||
binding.widgetList.addDragView(view, view.getDragHandle())
|
||||
view.onRemove = {
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
OneShotLayoutTransition.run(this)
|
||||
binding.widgetList.removeDragView(view)
|
||||
removeWidget(view.widget)
|
||||
}
|
||||
view.onResizeModeChange = {
|
||||
OneShotLayoutTransition.run(binding.widgetList)
|
||||
OneShotLayoutTransition.run(this)
|
||||
}
|
||||
widgets.add(widget)
|
||||
}
|
||||
clockWidgetHeight.value = height
|
||||
}
|
||||
|
||||
}
|
||||
58
ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/ExternalWidget.kt
vendored
Normal file
58
ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/ExternalWidget.kt
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.external
|
||||
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ListView
|
||||
import android.widget.ScrollView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.core.view.iterator
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
|
||||
@Composable
|
||||
fun ExternalWidget(
|
||||
appWidgetHost: AppWidgetHost,
|
||||
widgetId: Int,
|
||||
height: Int,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val widgetInfo = remember(widgetId) {
|
||||
AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId)
|
||||
}
|
||||
val viewHeightPx = height.dp.toPixels()
|
||||
key(widgetId) {
|
||||
AndroidView(
|
||||
modifier = modifier.fillMaxWidth().height(height.dp),
|
||||
factory = {
|
||||
val view = appWidgetHost.createView(it.applicationContext, widgetId, widgetInfo)
|
||||
enableNestedScroll(view)
|
||||
return@AndroidView view
|
||||
},
|
||||
update = {
|
||||
it.updateAppWidgetSize(null, 0, 0, it.width, height)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableNestedScroll(view: View) {
|
||||
if (view is ViewGroup) {
|
||||
for (child in view.iterator()) {
|
||||
enableNestedScroll(child)
|
||||
}
|
||||
}
|
||||
if (view is ListView || view is ScrollView) view.isNestedScrollingEnabled = true
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.component
|
||||
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.core.view.get
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.transition.OneShotLayoutTransition
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.ViewWidgetBinding
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherCardView
|
||||
import de.mm20.launcher2.ui.legacy.widget.*
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
import de.mm20.launcher2.widgets.WidgetType
|
||||
|
||||
class WidgetView : LauncherCardView {
|
||||
|
||||
var onRemove: (() -> Unit)? = null
|
||||
|
||||
|
||||
var widget: Widget? = null
|
||||
|
||||
var widgetView: LauncherWidget? = null
|
||||
|
||||
var editMode = false
|
||||
set(value) {
|
||||
OneShotLayoutTransition.run(this)
|
||||
if (value) {
|
||||
binding.widgetControlPanel.visibility = View.VISIBLE
|
||||
val widget = binding.widgetWrapper[2]
|
||||
widget.visibility = View.GONE
|
||||
binding.widgetName.visibility = View.VISIBLE
|
||||
visibility = View.VISIBLE
|
||||
} else {
|
||||
resizeMode = false
|
||||
binding.widgetControlPanel.visibility = View.GONE
|
||||
val widget = binding.widgetWrapper[2] as LauncherWidget
|
||||
widget.visibility = View.VISIBLE
|
||||
binding.widgetName.visibility = View.GONE
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
private var resizeMode = false
|
||||
set(value) {
|
||||
if (value == field) return
|
||||
onResizeModeChange?.invoke(value)
|
||||
OneShotLayoutTransition.run(this)
|
||||
OneShotLayoutTransition.run(binding.widgetWrapper)
|
||||
if (value) {
|
||||
binding.widgetResizeDragHandle.visibility = View.VISIBLE
|
||||
val widget = binding.widgetWrapper[2]
|
||||
widget.visibility = View.VISIBLE
|
||||
binding.widgetName.visibility = View.GONE
|
||||
} else {
|
||||
binding.widgetResizeDragHandle.visibility = View.GONE
|
||||
if (editMode) {
|
||||
val widget = binding.widgetWrapper[2]
|
||||
widget.visibility = View.GONE
|
||||
binding.widgetName.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
||||
|
||||
private val binding = ViewWidgetBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
init {
|
||||
binding.widgetActionResize.setOnClickListener {
|
||||
resizeMode = !resizeMode
|
||||
}
|
||||
binding.widgetActionRemove.setOnClickListener {
|
||||
onRemove?.invoke()
|
||||
}
|
||||
|
||||
TooltipCompat.setTooltipText(binding.widgetActionResize, context.getString(R.string.widget_action_adjust_height))
|
||||
TooltipCompat.setTooltipText(binding.widgetActionRemove, context.getString(R.string.widget_action_remove))
|
||||
}
|
||||
|
||||
var onResizeModeChange: ((Boolean) -> Unit)? = null
|
||||
|
||||
fun setWidget(widget: Widget, widgetHost: AppWidgetHost): Boolean {
|
||||
if (widget.type == WidgetType.INTERNAL) {
|
||||
widgetView = when (widget.data) {
|
||||
CalendarWidget.ID -> CalendarWidget(context)
|
||||
WeatherWidget.ID -> WeatherWidget(context)
|
||||
MusicWidget.ID -> MusicWidget(context)
|
||||
else -> return false
|
||||
}
|
||||
binding.widgetActionResize.visibility = View.GONE
|
||||
binding.widgetResizeDragHandle.resizeView = widgetView
|
||||
binding.widgetWrapper.addView(widgetView, 2)
|
||||
binding.widgetName.text = widgetView?.name
|
||||
} else {
|
||||
widgetView = ExternalWidget(context, widget, widgetHost)
|
||||
binding.widgetResizeDragHandle.resizeView = widgetView
|
||||
binding.widgetResizeDragHandle.onResize = {
|
||||
widget.height = (it / dp).toInt()
|
||||
}
|
||||
binding.widgetWrapper.addView(widgetView, 2)
|
||||
binding.widgetName.text = widgetView?.name
|
||||
binding.widgetActionResize.visibility = View.VISIBLE
|
||||
}
|
||||
this.widget = widget
|
||||
return true
|
||||
}
|
||||
|
||||
fun getDragHandle(): View {
|
||||
return binding.widgetDragHandle
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.MotionEvent.INVALID_POINTER_ID
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
|
||||
class WidgetResizeDragView : ImageView {
|
||||
|
||||
var resizeView: View? = null
|
||||
|
||||
private var lastY = 0f
|
||||
|
||||
var onResize: ((Int) -> Unit)? = null
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
val y = event.y
|
||||
lastY = y
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val y = event.y
|
||||
val dY = y - lastY
|
||||
val view = resizeView ?: return false
|
||||
val params = view.layoutParams
|
||||
val newHeight = (view.height + dY).toInt()
|
||||
params.height = newHeight
|
||||
onResize?.invoke(newHeight)
|
||||
view.layoutParams = params
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.LocalAbsoluteTonalElevation
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.base.ProvideSettings
|
||||
import de.mm20.launcher2.ui.launcher.widgets.calendar.CalendarWidget
|
||||
|
||||
class CalendarWidget : LauncherWidget {
|
||||
|
||||
override val canResize: Boolean
|
||||
get() = false
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleRes
|
||||
)
|
||||
|
||||
init {
|
||||
val composeView = ComposeView(context)
|
||||
composeView.setContent {
|
||||
MdcLauncherTheme {
|
||||
ProvideSettings {
|
||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides MaterialTheme.colorScheme.onSurface,
|
||||
LocalAbsoluteTonalElevation provides 1.dp
|
||||
) {
|
||||
Column {
|
||||
CalendarWidget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addView(composeView)
|
||||
|
||||
}
|
||||
|
||||
|
||||
override val name: String
|
||||
get() = resources.getString(R.string.widget_name_calendar)
|
||||
|
||||
|
||||
companion object {
|
||||
const val ID = "calendar"
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.widget
|
||||
|
||||
import android.animation.LayoutTransition
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import de.mm20.launcher2.ui.ClockWidget
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
|
||||
class ClockWidget : FrameLayout {
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleRes
|
||||
)
|
||||
|
||||
val view = ComposeView(context)
|
||||
|
||||
init {
|
||||
clipToPadding = false
|
||||
clipChildren = false
|
||||
layoutTransition = LayoutTransition()
|
||||
|
||||
val composeView = ComposeView(context)
|
||||
|
||||
addView(composeView)
|
||||
|
||||
composeView.layoutParams =
|
||||
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
|
||||
|
||||
composeView.setContent {
|
||||
MdcLauncherTheme {
|
||||
ClockWidget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetHostView
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ListView
|
||||
import android.widget.ScrollView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.iterator
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class ExternalWidget(
|
||||
context: Context,
|
||||
val widget: Widget,
|
||||
host: AppWidgetHost
|
||||
) : LauncherWidget(context) {
|
||||
|
||||
val widgetInfo: AppWidgetProviderInfo?
|
||||
|
||||
val widgetView: View
|
||||
|
||||
init {
|
||||
val id = widget.data.toInt()
|
||||
widgetInfo = AppWidgetManager.getInstance(context.applicationContext).getAppWidgetInfo(id)
|
||||
widgetView = host.createView(context.applicationContext, id, widgetInfo)
|
||||
?: View(context)
|
||||
if (widgetView is AppWidgetHostView && widgetView.childCount > 0) {
|
||||
enableNestedScroll(widgetView[0])
|
||||
}
|
||||
val h = widget.height * dp
|
||||
val params = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, h.toInt())
|
||||
val p = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
layoutParams = params
|
||||
widgetView.layoutParams = p
|
||||
addView(widgetView)
|
||||
}
|
||||
|
||||
override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
|
||||
super.setLayoutParams(params)
|
||||
params ?: return
|
||||
doOnNextLayout {
|
||||
val width = if (params.width > 0) params.width else it.width
|
||||
val height = if (params.height > 0) params.height else widgetInfo?.minHeight ?: it.height
|
||||
if (widgetView is AppWidgetHostView) {
|
||||
widgetView.updateAppWidgetSize(Bundle(), 0, 0, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableNestedScroll(view: View) {
|
||||
if (view is ViewGroup) {
|
||||
for (child in view.iterator()) {
|
||||
enableNestedScroll(child)
|
||||
}
|
||||
}
|
||||
if (view is ListView || view is ScrollView) view.isNestedScrollingEnabled = true
|
||||
}
|
||||
|
||||
override val canResize: Boolean
|
||||
get() = true
|
||||
override val name: String
|
||||
get() = widgetInfo?.loadLabel(context.packageManager) ?: ""
|
||||
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.widget
|
||||
|
||||
import android.animation.LayoutTransition
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
|
||||
abstract class LauncherWidget : FrameLayout {
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
||||
|
||||
abstract val canResize: Boolean
|
||||
abstract val name: String
|
||||
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
|
||||
|
||||
class MusicWidget : LauncherWidget {
|
||||
|
||||
override val canResize: Boolean
|
||||
get() = false
|
||||
override val name: String
|
||||
get() = context.getString(R.string.widget_name_music)
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
||||
|
||||
|
||||
init {
|
||||
val composeView = ComposeView(context)
|
||||
composeView.id = FrameLayout.generateViewId()
|
||||
composeView.setContent {
|
||||
MdcLauncherTheme {
|
||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
Column {
|
||||
MusicWidget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addView(composeView)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID = "music"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.launcher.widgets.weather.WeatherWidget
|
||||
|
||||
class WeatherWidget : LauncherWidget {
|
||||
|
||||
|
||||
override val canResize: Boolean
|
||||
get() = false
|
||||
override val name: String
|
||||
get() = resources.getString(R.string.widget_name_weather)
|
||||
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
||||
|
||||
|
||||
init {
|
||||
val composeView = ComposeView(context)
|
||||
composeView.id = FrameLayout.generateViewId()
|
||||
composeView.setContent {
|
||||
MdcLauncherTheme {
|
||||
// TODO: Temporary solution until parent widget card is rewritten in Compose
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
Column {
|
||||
WeatherWidget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addView(composeView)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID = "weather"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -37,8 +37,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:layout_margin="8dp" />
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/widgetWrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widgetControlPanel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widgetDragHandle"
|
||||
style="?iconStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_drag_handle"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widgetActionResize"
|
||||
style="?iconStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="56dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_widget_resize"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widgetActionRemove"
|
||||
style="?iconStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_delete"/>
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widgetName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?textAppearanceTitleLarge"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.WidgetResizeDragView
|
||||
android:id="@+id/widgetResizeDragHandle"
|
||||
style="?iconStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:src="@drawable/ic_resize_drag_handle"
|
||||
android:visibility="gone"/>
|
||||
</LinearLayout>
|
||||
</merge>
|
||||
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:layout_height="wrap_content"
|
||||
tools:layout_width="match_parent">
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.widget.ClockWidget
|
||||
android:id="@+id/clockWidget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.jmedeisis.draglinearlayout.DragLinearLayout
|
||||
android:id="@+id/widgetList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Widgets will be added here -->
|
||||
|
||||
</com.jmedeisis.draglinearlayout.DragLinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/fabEditWidget"
|
||||
app:icon="@drawable/ic_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/menu_edit_widgets" />
|
||||
</merge>
|
||||
@ -5,5 +5,5 @@ import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val widgetsModule = module {
|
||||
single { WidgetRepository(androidContext()) }
|
||||
single<WidgetRepository> { WidgetRepositoryImpl(androidContext(), get()) }
|
||||
}
|
||||
@ -1,32 +1,173 @@
|
||||
package de.mm20.launcher2.widgets
|
||||
|
||||
import android.app.Activity
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import de.mm20.launcher2.database.entities.WidgetEntity
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
|
||||
data class Widget(
|
||||
val type: WidgetType,
|
||||
var data: String,
|
||||
var height: Int,
|
||||
val label: String = ""
|
||||
) {
|
||||
constructor(entity: WidgetEntity) : this(
|
||||
type = if (entity.type == "internal") WidgetType.INTERNAL else WidgetType. THIRD_PARTY,
|
||||
data = entity.data,
|
||||
height = entity.height,
|
||||
label = entity.label
|
||||
)
|
||||
sealed class Widget {
|
||||
abstract fun loadLabel(context: Context): String
|
||||
abstract fun toDatabaseEntity(position: Int = -1): WidgetEntity
|
||||
open val isConfigurable: Boolean = false
|
||||
open fun configure(context: Activity, appWidgetHost: AppWidgetHost) {}
|
||||
|
||||
fun toDatabaseEntity(position: Int): WidgetEntity {
|
||||
companion object {
|
||||
fun fromDatabaseEntity(context: Context, entity: WidgetEntity): Widget? {
|
||||
if (entity.type == WidgetType.INTERNAL.value) {
|
||||
return when (entity.data) {
|
||||
"weather" -> WeatherWidget
|
||||
"music" -> MusicWidget
|
||||
"calendar" -> CalendarWidget
|
||||
else -> null
|
||||
}
|
||||
} else {
|
||||
val widgetId = entity.data.toIntOrNull() ?: return null
|
||||
val widgetInfo =
|
||||
AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId) ?: return null
|
||||
return ExternalWidget(
|
||||
height = entity.height,
|
||||
widgetId = widgetId,
|
||||
widgetProviderInfo = widgetInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object WeatherWidget : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return context.getString(R.string.widget_name_weather)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(position: Int): WidgetEntity {
|
||||
return WidgetEntity(
|
||||
type = if (type == WidgetType.INTERNAL) "internal" else "3rdparty",
|
||||
label = label,
|
||||
position = position,
|
||||
height = height,
|
||||
data = data
|
||||
type = WidgetType.INTERNAL.value,
|
||||
data = "weather",
|
||||
height = -1,
|
||||
position = position
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = true
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(
|
||||
context.getPackageName(),
|
||||
"de.mm20.launcher2.ui.settings.SettingsActivity"
|
||||
)
|
||||
intent.putExtra(
|
||||
"de.mm20.launcher2.settings.ROUTE",
|
||||
"settings/widgets/weather"
|
||||
)
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
object MusicWidget : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return context.getString(R.string.widget_name_music)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(position: Int): WidgetEntity {
|
||||
return WidgetEntity(
|
||||
type = WidgetType.INTERNAL.value,
|
||||
data = "music",
|
||||
height = -1,
|
||||
position = position
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = true
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(
|
||||
context.getPackageName(),
|
||||
"de.mm20.launcher2.ui.settings.SettingsActivity"
|
||||
)
|
||||
intent.putExtra(
|
||||
"de.mm20.launcher2.settings.ROUTE",
|
||||
"settings/widgets/music"
|
||||
)
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object CalendarWidget : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return context.getString(R.string.widget_name_calendar)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(position: Int): WidgetEntity {
|
||||
return WidgetEntity(
|
||||
type = WidgetType.INTERNAL.value,
|
||||
data = "calendar",
|
||||
height = -1,
|
||||
position = position
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = true
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(
|
||||
context.getPackageName(),
|
||||
"de.mm20.launcher2.ui.settings.SettingsActivity"
|
||||
)
|
||||
intent.putExtra(
|
||||
"de.mm20.launcher2.settings.ROUTE",
|
||||
"settings/widgets/calendar"
|
||||
)
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalWidget(
|
||||
var height: Int,
|
||||
val widgetId: Int,
|
||||
val widgetProviderInfo: AppWidgetProviderInfo
|
||||
) : Widget() {
|
||||
override fun loadLabel(context: Context): String {
|
||||
return widgetProviderInfo.loadLabel(context.packageManager)
|
||||
}
|
||||
|
||||
override fun toDatabaseEntity(position: Int): WidgetEntity {
|
||||
return WidgetEntity(
|
||||
type = WidgetType.THIRD_PARTY.value,
|
||||
data = widgetId.toString(),
|
||||
height = height,
|
||||
position = position
|
||||
)
|
||||
}
|
||||
|
||||
override val isConfigurable: Boolean = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
widgetProviderInfo.widgetFeatures and AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE != 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
override fun configure(context: Activity, appWidgetHost: AppWidgetHost) {
|
||||
appWidgetHost.startAppWidgetConfigureActivityForResult(
|
||||
context,
|
||||
widgetId,
|
||||
0,
|
||||
0,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enum class WidgetType {
|
||||
INTERNAL,
|
||||
THIRD_PARTY
|
||||
enum class WidgetType(val value: String) {
|
||||
INTERNAL("internal"),
|
||||
THIRD_PARTY("3rdparty")
|
||||
}
|
||||
@ -1,40 +1,79 @@
|
||||
package de.mm20.launcher2.widgets
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.widgets.R
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class WidgetRepository(
|
||||
val context: Context
|
||||
) {
|
||||
interface WidgetRepository {
|
||||
fun getWidgets(): Flow<List<Widget>>
|
||||
fun getInternalWidgets(): List<Widget>
|
||||
fun saveWidgets(widgets: List<Widget>)
|
||||
fun addWidget(widget: Widget, position: Int)
|
||||
fun removeWidget(widget: Widget)
|
||||
fun setWidgetHeight(widget: Widget, newHeight: Int)
|
||||
}
|
||||
|
||||
internal class WidgetRepositoryImpl(
|
||||
private val context: Context,
|
||||
private val database: AppDatabase,
|
||||
) : WidgetRepository {
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
fun getWidgets(): Flow<List<Widget>> {
|
||||
return AppDatabase.getInstance(context).widgetDao()
|
||||
override fun getWidgets(): Flow<List<Widget>> {
|
||||
return database.widgetDao()
|
||||
.getWidgets()
|
||||
.map { it.map { Widget(it) } }
|
||||
.map { it.mapNotNull { Widget.fromDatabaseEntity(context, it) } }
|
||||
}
|
||||
|
||||
fun getInternalWidgets(): List<Widget> {
|
||||
return listOf(
|
||||
Widget(WidgetType.INTERNAL, "weather", -1, context.getString(R.string.widget_name_weather)),
|
||||
Widget(WidgetType.INTERNAL, "music", -1, context.getString(R.string.widget_name_music)),
|
||||
Widget(WidgetType.INTERNAL, "calendar", -1, context.getString(R.string.widget_name_calendar)),
|
||||
)
|
||||
override fun getInternalWidgets(): List<Widget> {
|
||||
return listOf(WeatherWidget, MusicWidget, CalendarWidget)
|
||||
}
|
||||
|
||||
|
||||
fun saveWidgets(widgets: List<Widget>) {
|
||||
override fun saveWidgets(widgets: List<Widget>) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
AppDatabase.getInstance(context).widgetDao().updateWidgets(widgets.mapIndexed { i, widget -> widget.toDatabaseEntity(i) })
|
||||
database.widgetDao()
|
||||
.updateWidgets(widgets.mapIndexed { i, widget -> widget.toDatabaseEntity(i) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addWidget(widget: Widget, position: Int) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
database.widgetDao()
|
||||
.insert(widget.toDatabaseEntity(position))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeWidget(widget: Widget) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val ent = widget.toDatabaseEntity()
|
||||
database.widgetDao().deleteWidget(
|
||||
ent.type,
|
||||
ent.data
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setWidgetHeight(widget: Widget, newHeight: Int) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val ent = widget.toDatabaseEntity()
|
||||
database.widgetDao().updateHeight(
|
||||
ent.type,
|
||||
ent.data,
|
||||
newHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user