diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ed410de8..27a9e53d 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -28,6 +28,7 @@ android {
versionCode = versionCodeDate()
versionName = "1.3.0"
multiDexEnabled = true
+ signingConfig = signingConfigs.getByName("debug")
}
buildTypes {
release {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 218183f3..1f21b862 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -48,7 +48,7 @@
android:label="@string/title_activity_settings"
android:launchMode="singleTask"
android:exported="true"
- android:parentActivityName=".ui.legacy.activity.LauncherActivity"
+ android:parentActivityName=".ui.launcher.LauncherActivity"
android:screenOrientation="portrait"
android:taskAffinity="de.mm20.launcher2.settings"
android:theme="@style/SettingsTheme">
@@ -58,7 +58,7 @@
+ android:value="de.mm20.launcher2.ui.launcher.LauncherActivity" />
@@ -56,7 +56,7 @@
+ android:value="de.mm20.launcher2.ui.launcher.LauncherActivity" />
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt
new file mode 100644
index 00000000..ff323e8d
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt
@@ -0,0 +1,26 @@
+package de.mm20.launcher2.ui.component
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun LauncherCard(
+ modifier: Modifier = Modifier,
+ elevation: Dp = 2.dp,
+ backgroundOpacity: Float = 1f,
+ content: @Composable () -> Unit = {}
+) {
+ Surface(
+ modifier = modifier,
+ shape = RoundedCornerShape(8.dp),
+ content = content,
+ color = MaterialTheme.colorScheme.surface.copy(alpha = backgroundOpacity.coerceIn(0f, 1f)),
+ shadowElevation = elevation,
+ tonalElevation = elevation
+ )
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/SearchBar.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/SearchBar.kt
index 982f5b35..80750dc8 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/component/SearchBar.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/component/SearchBar.kt
@@ -32,10 +32,9 @@ import androidx.compose.ui.unit.sp
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.PagerState
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.locals.LocalWindowSize
-import org.koin.androidx.compose.getViewModel
import org.koin.androidx.compose.viewModel
/**
@@ -55,7 +54,7 @@ fun SearchBar(
) {
var searchQuery by remember { mutableStateOf("") }
- val viewModel: SearchViewModel by viewModel()
+ val viewModel: SearchVM by viewModel()
LaunchedEffect(searchQuery) {
viewModel.search(searchQuery)
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt
new file mode 100644
index 00000000..620b781d
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt
@@ -0,0 +1,162 @@
+package de.mm20.launcher2.ui.launcher
+
+import android.app.WallpaperManager
+import android.content.Intent
+import android.os.Bundle
+import android.view.*
+import androidx.activity.viewModels
+import androidx.core.view.*
+import com.afollestad.materialdialogs.LayoutMode
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.bottomsheets.BottomSheet
+import com.afollestad.materialdialogs.callbacks.onDismiss
+import com.afollestad.materialdialogs.customview.customView
+import de.mm20.launcher2.icons.DynamicIconController
+import de.mm20.launcher2.icons.IconRepository
+import de.mm20.launcher2.ktx.dp
+import de.mm20.launcher2.ktx.isAtLeastApiLevel
+import de.mm20.launcher2.legacy.helper.ActivityStarter
+import de.mm20.launcher2.preferences.LauncherPreferences
+import de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.base.BaseActivity
+import de.mm20.launcher2.ui.databinding.ActivityLauncherBinding
+import de.mm20.launcher2.ui.launcher.modals.EditFavoritesView
+import de.mm20.launcher2.ui.launcher.modals.HiddenItemsView
+import de.mm20.launcher2.ui.legacy.helper.ThemeHelper
+import de.mm20.launcher2.widgets.Widget
+import de.mm20.launcher2.widgets.WidgetViewModel
+import kotlinx.coroutines.*
+import org.koin.android.ext.android.inject
+import java.util.*
+
+
+class LauncherActivity : BaseActivity() {
+
+ private val viewModel: LauncherActivityVM by viewModels()
+
+ private val preferences = LauncherPreferences.instance
+
+ private var windowBackgroundBlur: Boolean = false
+ set(value) {
+ if (field == value) return
+ field = value
+ if (!isAtLeastApiLevel(31)) return
+ window.attributes = window.attributes.also {
+ if (value) {
+ it.blurBehindRadius = (32 * dp).toInt()
+ it.flags = it.flags or WindowManager.LayoutParams.FLAG_BLUR_BEHIND
+ } else {
+ it.blurBehindRadius = 0
+ it.flags = it.flags and WindowManager.LayoutParams.FLAG_BLUR_BEHIND.inv()
+ }
+ }
+ }
+
+
+ private fun updateSystemBarAppearance() {
+ val allowLightSystemBars = allowsLightSystemBars()
+ val insetsController = WindowInsetsControllerCompat(window, window.decorView)
+ insetsController.isAppearanceLightNavigationBars =
+ allowLightSystemBars && preferences.lightNavBar
+ insetsController.isAppearanceLightStatusBars =
+ allowLightSystemBars && preferences.lightStatusBar
+ }
+
+ private lateinit var binding: ActivityLauncherBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val iconRepository: IconRepository by inject()
+ iconRepository.recreate()
+ ThemeHelper.applyTheme(theme)
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ binding = ActivityLauncherBinding.inflate(LayoutInflater.from(this))
+ setContentView(binding.root)
+
+ var editFavoritesDialog: MaterialDialog? = null
+ viewModel.isEditFavoritesShown.observe(this) {
+ if (it) {
+ val view = EditFavoritesView(this@LauncherActivity)
+ editFavoritesDialog =
+ MaterialDialog(this, BottomSheet(LayoutMode.MATCH_PARENT)).show {
+ customView(view = view)
+ title(res = R.string.menu_item_edit_favs)
+ positiveButton(res = R.string.close) {
+ viewModel.hideEditFavorites()
+ it.dismiss()
+ }
+ onDismiss {
+ view.save()
+ viewModel.hideEditFavorites()
+ }
+ }
+ } else {
+ editFavoritesDialog?.dismiss()
+ editFavoritesDialog = null
+ }
+ }
+
+ var hiddenItemsDialog: MaterialDialog? = null
+ viewModel.isHiddenItemsShown.observe(this) {
+ if (it) {
+ val view = HiddenItemsView(this)
+ hiddenItemsDialog = MaterialDialog(this, BottomSheet(LayoutMode.MATCH_PARENT))
+ .show {
+ title(R.string.menu_hidden_items)
+ customView(view = view)
+ negativeButton(R.string.close) { dismiss() }
+ onDismiss {
+ viewModel.hideHiddenItems()
+ }
+ }
+ } else {
+ hiddenItemsDialog?.dismiss()
+ hiddenItemsDialog = null
+ }
+ }
+
+ if (LauncherPreferences.instance.dimWallpaper) {
+ binding.dimWallpaper.setBackgroundColor(getColor(R.color.wallpaper_dim))
+ }
+
+ val dynamicIconController: DynamicIconController by inject()
+
+ lifecycle.addObserver(dynamicIconController)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ ActivityStarter.resume()
+ ActivityStarter.create(binding.rootView)
+ binding.activityStartOverlay.visibility = View.INVISIBLE
+
+ updateSystemBarAppearance()
+
+ binding.container.doOnNextLayout {
+ WallpaperManager.getInstance(this).setWallpaperOffsets(it.windowToken, 0.5f, 0.5f)
+ }
+ }
+
+ private fun allowsLightSystemBars(): Boolean {
+ val dimWallpaper = LauncherPreferences.instance.dimWallpaper
+ val isDarkTheme = resources.getBoolean(R.bool.is_dark_theme)
+ return !(isDarkTheme && dimWallpaper)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ ActivityStarter.pause()
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ onBackPressed()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ ActivityStarter.destroy()
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivityVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivityVM.kt
new file mode 100644
index 00000000..9322ea29
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivityVM.kt
@@ -0,0 +1,27 @@
+package de.mm20.launcher2.ui.launcher
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class LauncherActivityVM : ViewModel() {
+ val isHiddenItemsShown = MutableLiveData(false)
+ val isEditFavoritesShown = MutableLiveData(false)
+ val dimBackground = MutableLiveData(false)
+
+ fun showEditFavorites() {
+ isEditFavoritesShown.value = true
+ }
+
+ fun hideEditFavorites() {
+ isEditFavoritesShown.value = false
+ }
+
+
+ fun showHiddenItems() {
+ isHiddenItemsShown.value = true
+ }
+
+ fun hideHiddenItems() {
+ isHiddenItemsShown.value = false
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherScaffoldVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherScaffoldVM.kt
new file mode 100644
index 00000000..defb88e8
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherScaffoldVM.kt
@@ -0,0 +1,61 @@
+package de.mm20.launcher2.ui.launcher
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import de.mm20.launcher2.ktx.isBrightColor
+import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
+
+class LauncherScaffoldVM : ViewModel() {
+ val isSearchOpen = MutableLiveData(false)
+ val blurBackground = MutableLiveData(false)
+
+ val statusBarColor = MutableLiveData(0)
+ val darkStatusBarIcons = MutableLiveData(false)
+
+ val searchBarLevel = MutableLiveData(SearchBarLevel.Resting)
+
+ var scrollY = 0
+ set(value) {
+ if (value == 0 && field != 0) {
+ if (isSearchOpen.value == true) {
+ searchBarLevel.value = SearchBarLevel.Active
+ blurBackground.value = true
+ } else {
+ searchBarLevel.value = SearchBarLevel.Resting
+ blurBackground.value = false
+ }
+ } else if (value > 0 && field == 0) {
+ searchBarLevel.value = SearchBarLevel.Raised
+ blurBackground.value = true
+ }
+ field = value
+ }
+
+ fun openSearch() {
+ if (isSearchOpen.value == true) return
+ isSearchOpen.value = true
+ if (scrollY == 0) {
+ searchBarLevel.value = SearchBarLevel.Active
+ blurBackground.value = true
+ }
+ }
+
+ fun closeSearch() {
+ if (isSearchOpen.value == false) return
+ isSearchOpen.value = false
+ if (scrollY == 0) {
+ searchBarLevel.value = SearchBarLevel.Resting
+ blurBackground.value = false
+ }
+ }
+
+ fun toggleSearch() {
+ if (isSearchOpen.value == true) closeSearch()
+ else openSearch()
+ }
+
+ fun setStatusBarColor(color: Int) {
+ statusBarColor.value = color
+ darkStatusBarIcons.value = color.isBrightColor()
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherScaffoldView.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherScaffoldView.kt
new file mode 100644
index 00000000..cc2bff95
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherScaffoldView.kt
@@ -0,0 +1,281 @@
+package de.mm20.launcher2.ui.launcher
+
+import android.animation.AnimatorSet
+import android.animation.LayoutTransition
+import android.animation.ObjectAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Log
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
+import android.widget.FrameLayout
+import androidx.activity.addCallback
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.animation.doOnEnd
+import androidx.core.content.ContextCompat
+import androidx.core.content.getSystemService
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsControllerCompat
+import androidx.core.view.setPadding
+import de.mm20.launcher2.ktx.dp
+import de.mm20.launcher2.ktx.isAtLeastApiLevel
+import de.mm20.launcher2.ktx.isBrightColor
+import de.mm20.launcher2.transition.ChangingLayoutTransition
+import de.mm20.launcher2.transition.OneShotLayoutTransition
+import de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.databinding.ViewLauncherScaffoldBinding
+import de.mm20.launcher2.ui.launcher.search.SearchVM
+import de.mm20.launcher2.ui.launcher.widgets.WidgetsVM
+
+@SuppressLint("ClickableViewAccessibility")
+class LauncherScaffoldView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : FrameLayout(context, attrs) {
+
+ private val binding = ViewLauncherScaffoldBinding.inflate(LayoutInflater.from(context), this)
+
+ private val viewModel: LauncherScaffoldVM by (context as AppCompatActivity).viewModels()
+ private val widgetsViewModel: WidgetsVM by (context as AppCompatActivity).viewModels()
+ private val searchViewModel: SearchVM by (context as AppCompatActivity).viewModels()
+
+ private val scrollViewOnTouchListener = object : OnTouchListener {
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> return true
+ MotionEvent.ACTION_MOVE -> {
+ when {
+ binding.scrollView.scrollY == 0 -> {
+ if (event.historySize > 0) {
+ val dY = event.y - event.getHistoricalY(0)
+ val newTransY = 0.4f * dY + translationY
+ if (newTransY > 0 && newTransY < 48 * dp) {
+ translationY = newTransY
+ } else if (newTransY <= 0) {
+ translationY = 0f
+ } else {
+ translationY = 48 * dp
+
+ }
+
+ if (translationY == 0f) return false
+ }
+ }
+ binding.scrollView.scrollY == binding.scrollContainer.height - binding.scrollView.height && viewModel.isSearchOpen.value == true -> {
+ if (event.historySize > 0) {
+ val dY = event.y - event.getHistoricalY(0)
+ val newTransY = 0.4f * dY + translationY
+
+ if (newTransY <= 0 && newTransY > -48 * dp) {
+ translationY = newTransY
+ } else if (newTransY > 0) {
+ translationY = 0f
+ } else {
+ translationY = -48 * dp
+ }
+
+ if (translationY == 0f) return false
+ }
+ }
+ else -> return false
+ }
+ return true
+ }
+ MotionEvent.ACTION_UP -> {
+ if (translationY >= 48 * dp * 0.6) viewModel.toggleSearch()
+ if (translationY <= -48 * dp) viewModel.closeSearch()
+ animate().translationY(0f).setDuration(200).start()
+ return false
+ }
+ else -> return false
+ }
+ }
+
+ }
+
+ init {
+ context as AppCompatActivity
+
+ context.onBackPressedDispatcher.addCallback {
+ viewModel.closeSearch()
+ widgetsViewModel.setEditMode(false)
+ ObjectAnimator.ofInt(binding.scrollView, "scrollY", 0).setDuration(200).start()
+ }
+
+ binding.scrollContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
+
+ binding.scrollView.scrollY = viewModel.scrollY
+ binding.scrollView.setOnTouchListener(scrollViewOnTouchListener)
+
+ binding.scrollView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY: Int ->
+ viewModel.scrollY = scrollY
+ when {
+ /* Hide searchbar*/
+ scrollY > oldScrollY && ((scrollY > 48 * dp)) -> {
+ var newTransY = binding.searchBar.translationY - scrollY + oldScrollY
+ if (newTransY < -112 * dp) {
+ newTransY = -112 * dp
+ }
+ binding.searchBar.translationY = newTransY
+ }
+ /* Show searchbar*/
+ scrollY < oldScrollY -> {
+ var newTransY = binding.searchBar.translationY - scrollY + oldScrollY
+ if (newTransY > 0f) {
+ newTransY = 0f
+ }
+ binding.searchBar.translationY = newTransY
+ }
+ }
+ }
+
+ viewModel.isSearchOpen.observe(context) {
+ if (it) showSearch()
+ else hideSearch()
+ }
+
+ viewModel.searchBarLevel.observe(context) {
+ binding.searchBar.level = it
+ }
+
+ searchViewModel.websearchResults.observe(context) {
+ binding.searchContainer.setPadding(
+ 0,
+ (if (it.isEmpty()) 48 * dp else 96 * dp).toInt(),
+ 0,
+ 0
+ )
+ }
+
+ widgetsViewModel.isEditMode.observe(context) {
+ if (it) {
+ binding.scrollView.setOnTouchListener(null)
+ OneShotLayoutTransition.run(binding.scrollContainer)
+ binding.searchBar.visibility = View.INVISIBLE
+ binding.editWidgetToolbar
+ .animate()
+ .translationY(0f)
+ .alpha(1f)
+ .withStartAction {
+ binding.editWidgetToolbar.visibility = View.VISIBLE
+ }
+ .start()
+ binding.widgetContainer.setPadding(0, (56 * dp).toInt(), 0, 0)
+ val colorSurface = TypedValue()
+ context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
+ context.window.statusBarColor = colorSurface.data
+ viewModel.setStatusBarColor(colorSurface.data)
+ } else {
+ binding.widgetContainer.layoutTransition = ChangingLayoutTransition()
+ binding.scrollContainer.layoutTransition = ChangingLayoutTransition()
+ binding.scrollView.setOnTouchListener(scrollViewOnTouchListener)
+
+ binding.searchBar.visibility = View.VISIBLE
+ binding.editWidgetToolbar
+ .animate()
+ .translationY(-binding.editWidgetToolbar.height.toFloat())
+ .alpha(0f)
+ .withEndAction {
+ binding.editWidgetToolbar.visibility = View.GONE
+ }
+ .start()
+ binding.widgetContainer.setPadding(0)
+ viewModel.setStatusBarColor(0)
+ }
+ }
+ binding.editWidgetToolbar.apply {
+ navigationIcon =
+ ContextCompat.getDrawable(context, R.drawable.ic_done)?.apply {
+ setTint(ContextCompat.getColor(context, R.color.icon_color))
+ }
+ setNavigationOnClickListener {
+ widgetsViewModel.setEditMode(false)
+ }
+ }
+
+ binding.searchBar.onFocus = {
+ viewModel.openSearch()
+ }
+
+ viewModel.blurBackground.observe(context) { blur ->
+ if (!isAtLeastApiLevel(31)) return@observe
+ context.window.attributes = context.window.attributes.also {
+ if (blur) {
+ it.blurBehindRadius = (32 * dp).toInt()
+ it.flags = it.flags or WindowManager.LayoutParams.FLAG_BLUR_BEHIND
+ } else {
+ it.blurBehindRadius = 0
+ it.flags = it.flags and WindowManager.LayoutParams.FLAG_BLUR_BEHIND.inv()
+ }
+ }
+ }
+
+ viewModel.statusBarColor.observe(context) {
+ context.window.statusBarColor = it
+ }
+ viewModel.darkStatusBarIcons.observe(context) {
+ WindowInsetsControllerCompat(context.window, this).isAppearanceLightStatusBars = it
+ }
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ if (changed) {
+ binding.widgetContainer.setClockWidgetHeight(bottom - top - paddingTop - paddingBottom)
+ }
+ super.onLayout(changed, left, top, right, bottom)
+ }
+
+ private fun hideSearch() {
+ val set = AnimatorSet()
+ set.duration = 300
+ set.doOnEnd {
+ binding.searchContainer.visibility = View.GONE
+ binding.widgetContainer.visibility = View.VISIBLE
+ }
+ set.playTogether(
+ ObjectAnimator.ofFloat(binding.widgetContainer, "translationY", 0f),
+ ObjectAnimator.ofInt(binding.scrollView, "scrollY", 0),
+ ObjectAnimator.ofFloat(
+ binding.searchContainer, "translationY", 0f,
+ if (binding.scrollView.scrollY > binding.searchContainer.height / 2f) -binding.searchContainer.height.toFloat() else binding.scrollView.height.toFloat()
+ )
+ )
+ set.doOnEnd {
+ searchViewModel.search("")
+ }
+ set.start()
+ binding.scrollView.scrollTo(0, 0)
+ context.getSystemService()?.hideSoftInputFromWindow(
+ binding.searchBar.windowToken,
+ 0
+ )
+ }
+
+ private fun showSearch() {
+ binding.searchContainer.visibility = View.VISIBLE
+ binding.widgetContainer.visibility = View.GONE
+ val set = AnimatorSet()
+ set.duration = 300
+ set.playTogether(
+ ObjectAnimator.ofFloat(
+ binding.widgetContainer,
+ "translationY",
+ binding.scrollView.height.toFloat()
+ ),
+ ObjectAnimator.ofInt(binding.scrollView, "scrollY", 0),
+ ObjectAnimator.ofFloat(
+ binding.searchContainer,
+ "translationY",
+ binding.scrollView.height.toFloat(),
+ 0f
+ )
+ )
+ set.start()
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt
new file mode 100644
index 00000000..90e6885a
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt
@@ -0,0 +1,323 @@
+package de.mm20.launcher2.ui.launcher.search
+
+import android.content.ComponentName
+import android.content.Intent
+import android.util.Log
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+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.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Search
+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.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.focus.onFocusEvent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import coil.compose.rememberImagePainter
+import de.mm20.launcher2.ktx.tryStartActivity
+import de.mm20.launcher2.search.data.Websearch
+import de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.component.LauncherCard
+import de.mm20.launcher2.ui.launcher.LauncherActivityVM
+import de.mm20.launcher2.ui.settings.SettingsActivity
+import java.io.File
+
+@Composable
+fun SearchBar(
+ level: SearchBarLevel,
+ onFocus: () -> Unit = {}
+) {
+ val searchViewModel: SearchVM = viewModel()
+ val activityViewModel: LauncherActivityVM = viewModel()
+
+ val context = LocalContext.current
+
+ val query by searchViewModel.searchQuery.observeAsState("")
+
+ val websearches by searchViewModel.websearchResults.observeAsState(emptyList())
+
+ SearchBar(
+ level,
+ websearches,
+ value = query,
+ onValueChange = {
+ searchViewModel.search(it)
+ },
+ overflowMenu = { show, onDismissRequest ->
+ DropdownMenu(expanded = show, onDismissRequest = onDismissRequest) {
+ DropdownMenuItem(onClick = {
+ activityViewModel.showEditFavorites()
+ onDismissRequest()
+ }) {
+ Text(stringResource(R.string.menu_item_edit_favs))
+ }
+ DropdownMenuItem(onClick = {
+ activityViewModel.showHiddenItems()
+ onDismissRequest()
+ }) {
+ Text(stringResource(R.string.menu_hidden_items))
+ }
+ DropdownMenuItem(onClick = {
+ context.startActivity(
+ Intent.createChooser(
+ Intent(Intent.ACTION_SET_WALLPAPER),
+ null
+ )
+ )
+ onDismissRequest()
+ }) {
+ Text(stringResource(R.string.wallpaper))
+ }
+ DropdownMenuItem(onClick = {
+ context.startActivity(Intent(context, SettingsActivity::class.java))
+ onDismissRequest()
+ }) {
+ Text(stringResource(R.string.settings))
+ }
+ DropdownMenuItem(onClick = {
+ context.startActivity(Intent().also {
+ it.component = ComponentName(
+ context.packageName,
+ "de.mm20.launcher2.activity.SettingsActivity"
+ )
+ it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ })
+ onDismissRequest()
+ }) {
+ Text("Legacy Settings")
+ }
+ }
+ },
+ onFocus = onFocus,
+ )
+}
+
+@OptIn(ExperimentalAnimationGraphicsApi::class)
+@Composable
+fun SearchBar(
+ level: SearchBarLevel,
+ websearches: List,
+ overflowMenu: @Composable (show: Boolean, onDismissRequest: () -> Unit) -> Unit = { _, _ -> },
+ value: String,
+ onValueChange: (String) -> Unit,
+ onFocus: () -> Unit = {}
+) {
+ val context = LocalContext.current
+
+ var showOverflowMenu by remember { mutableStateOf(false) }
+
+ val transition = updateTransition(level, label = "Searchbar")
+
+
+ val elevation by transition.animateDp(
+ label = "elevation",
+ transitionSpec = {
+ when {
+ initialState == SearchBarLevel.Resting -> tween(
+ durationMillis = 200,
+ delayMillis = 200
+ )
+ targetState == SearchBarLevel.Resting -> tween(durationMillis = 200)
+ else -> tween(durationMillis = 500)
+ }
+ }
+ ) {
+ when (it) {
+ SearchBarLevel.Resting -> 0.dp
+ SearchBarLevel.Active -> 2.dp
+ SearchBarLevel.Raised -> 8.dp
+ }
+ }
+
+ val backgroundOpacity by transition.animateFloat(label = "backgroundOpacity",
+ transitionSpec = {
+ when {
+ initialState == SearchBarLevel.Resting -> tween(durationMillis = 200)
+ targetState == SearchBarLevel.Resting -> tween(
+ durationMillis = 200,
+ delayMillis = 200
+ )
+ else -> tween(durationMillis = 500)
+ }
+ }) {
+ if (it == SearchBarLevel.Resting) 0f else 1f
+ }
+
+ val contentColor by transition.animateColor(label = "textColor",
+ transitionSpec = {
+ when {
+ initialState == SearchBarLevel.Resting -> tween(durationMillis = 200)
+ targetState == SearchBarLevel.Resting -> tween(
+ durationMillis = 200,
+ delayMillis = 200
+ )
+ else -> tween(durationMillis = 500)
+ }
+ }) {
+ if (it == SearchBarLevel.Resting) Color.White else LocalContentColor.current
+ }
+
+ val rightIcon = AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_menu_clear)
+
+ LauncherCard(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .padding(8.dp),
+ backgroundOpacity = backgroundOpacity,
+ elevation = elevation
+ ) {
+ Column {
+ Row(
+ modifier = Modifier.height(48.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ modifier = Modifier.padding(12.dp),
+ imageVector = Icons.Rounded.Search,
+ contentDescription = null,
+ tint = contentColor
+ )
+ Box(
+ modifier = Modifier.weight(1f)
+ ) {
+ if (value.isEmpty()) {
+ Text(
+ text = stringResource(R.string.edit_text_search_hint),
+ style = MaterialTheme.typography.bodyLarge,
+ color = contentColor
+ )
+ }
+ val focusManager = LocalFocusManager.current
+ LaunchedEffect(level) {
+ if (level == SearchBarLevel.Resting) focusManager.clearFocus()
+ }
+ BasicTextField(
+ modifier = Modifier
+ .onFocusChanged {
+ if (it.hasFocus) onFocus()
+ }
+ .fillMaxWidth(),
+ textStyle = MaterialTheme.typography.bodyLarge.copy(
+ color = contentColor
+ ),
+ singleLine = true,
+ value = value,
+ onValueChange = onValueChange,
+ )
+ }
+ Box {
+ IconButton(onClick = {
+ if (value.isNotBlank()) onValueChange("")
+ else showOverflowMenu = true
+ }) {
+ Icon(
+ painter = rememberAnimatedVectorPainter(
+ rightIcon,
+ atEnd = value.isNotBlank()
+ ),
+ contentDescription = null,
+ tint = contentColor
+ )
+ }
+ overflowMenu(showOverflowMenu) { showOverflowMenu = false }
+ }
+ }
+ AnimatedVisibility(websearches.isNotEmpty()) {
+ LazyRow(
+ modifier = Modifier
+ .height(48.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ items(websearches) {
+ Surface(
+ shape = RoundedCornerShape(4.dp),
+ modifier = Modifier.padding(horizontal = 8.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .height(32.dp)
+ .clickable {
+ it
+ .getLaunchIntent()
+ ?.let {
+ context.tryStartActivity(it)
+ }
+ }
+ .padding(horizontal = 4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ val icon = it.icon
+ if (icon == null) {
+ Icon(
+ imageVector = Icons.Rounded.Search,
+ contentDescription = null,
+ tint = if (it.color == 0) MaterialTheme.colorScheme.primary else Color(
+ it.color
+ )
+ )
+ } else {
+ Image(
+ modifier = Modifier.size(24.dp),
+ painter = rememberImagePainter(File(icon)),
+ contentDescription = null
+ )
+ }
+ Text(
+ it.label,
+ modifier = Modifier.padding(start = 4.dp),
+ style = MaterialTheme.typography.labelMedium
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+enum class SearchBarLevel {
+ /**
+ * The default, "hidden" state, when the launcher is in its initial state (scroll position is 0
+ * and search is closed)
+ */
+ Resting,
+
+ /**
+ * When the search is open but there is no content behind the search bar (scroll position is 0)
+ */
+ Active,
+
+ /**
+ * When there is content below the search bar which requires the search bar to be raised above
+ * this content (scroll position is not 0)
+ */
+ Raised
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchViewModel.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt
similarity index 96%
rename from ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchViewModel.kt
rename to ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt
index af735da5..521f2507 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchViewModel.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt
@@ -21,7 +21,7 @@ import kotlinx.coroutines.flow.collectLatest
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
-class SearchViewModel : ViewModel(), KoinComponent {
+class SearchVM : ViewModel(), KoinComponent {
private val favoritesRepository: FavoritesRepository by inject()
@@ -36,6 +36,7 @@ class SearchViewModel : ViewModel(), KoinComponent {
private val websearchRepository: WebsearchRepository by inject()
val isSearching = MutableLiveData(false)
+ val searchQuery = MutableLiveData("")
val favorites by lazy {
favoritesRepository.getFavorites().asLiveData()
@@ -53,8 +54,13 @@ class SearchViewModel : ViewModel(), KoinComponent {
val hideFavorites = MutableLiveData(false)
+ init {
+ search("")
+ }
+
var searchJob: Job? = null
fun search(query: String) {
+ searchQuery.value = query
try {
searchJob?.cancel()
} catch (e: CancellationException) {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchView.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchView.kt
new file mode 100644
index 00000000..2df3b95e
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchView.kt
@@ -0,0 +1,20 @@
+package de.mm20.launcher2.ui.launcher.search
+
+import android.animation.LayoutTransition
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.LinearLayout
+import de.mm20.launcher2.transition.ChangingLayoutTransition
+import de.mm20.launcher2.ui.databinding.ViewSearchBinding
+
+class SearchView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
+ val binding = ViewSearchBinding.inflate(LayoutInflater.from(context), this)
+
+ init {
+ orientation = VERTICAL
+ layoutTransition = ChangingLayoutTransition()
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetsVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetsVM.kt
new file mode 100644
index 00000000..70f9ad2e
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetsVM.kt
@@ -0,0 +1,35 @@
+package de.mm20.launcher2.ui.launcher.widgets
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.liveData
+import androidx.lifecycle.viewModelScope
+import de.mm20.launcher2.widgets.Widget
+import de.mm20.launcher2.widgets.WidgetRepository
+import kotlinx.coroutines.launch
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+
+class WidgetsVM: ViewModel(), KoinComponent {
+ private val widgetRepository: WidgetRepository by inject()
+
+ val isEditMode = MutableLiveData(false)
+
+ val widgets = liveData?> {
+ emit(widgetRepository.getWidgets())
+ }
+
+ fun setEditMode(editMode: Boolean) {
+ isEditMode.value = editMode
+ }
+
+ fun saveWidgets(widgets: List) {
+ viewModelScope.launch {
+ widgetRepository.saveWidgets(widgets)
+ }
+ }
+
+ fun getInternalWidgets() : List {
+ return widgetRepository.getInternalWidgets()
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetsView.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetsView.kt
new file mode 100644
index 00000000..b6b324d0
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetsView.kt
@@ -0,0 +1,291 @@
+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
+import android.content.Context
+import android.content.Intent
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+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 de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.databinding.ViewWidgetsBinding
+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 kotlinx.coroutines.launch
+import java.util.*
+import kotlin.math.roundToInt
+
+class WidgetsView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
+
+ private val binding = ViewWidgetsBinding.inflate(LayoutInflater.from(context), this)
+
+ private val widgetHost: AppWidgetHost = AppWidgetHost(context.applicationContext, 44203)
+
+ private val viewModel: WidgetsVM by (context as AppCompatActivity).viewModels()
+
+ private lateinit var widgets: MutableList
+
+ private val pickWidgetLauncher: ActivityResultLauncher
+ private val configureWidgetLauncher: ActivityResultLauncher
+
+ init {
+ context as AppCompatActivity
+
+ 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
+ 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)
+ }
+ }
+
+ viewModel.widgets.observe(context) {
+ if (it != null && !::widgets.isInitialized) {
+ widgets = it.toMutableList()
+ initWidgets()
+ }
+ }
+
+ viewModel.isEditMode.observe(context) {
+ if (it) {
+ binding.clockWidget.visibility = View.GONE
+
+ for (v in binding.widgetList.iterator()) {
+ if (v is WidgetView) {
+ v.editMode = true
+ v.onResizeModeChange = {
+ OneShotLayoutTransition.run(binding.widgetList)
+ OneShotLayoutTransition.run(this)
+ }
+ }
+ }
+ OneShotLayoutTransition.run(binding.widgetList)
+ OneShotLayoutTransition.run(this)
+ binding.fabEditWidget.apply {
+ setIconResource(R.drawable.ic_add)
+ setText(R.string.widget_add_widget)
+ setOnClickListener {
+ addWidget()
+ }
+ }
+ } else {
+ if (::widgets.isInitialized) viewModel.saveWidgets(widgets)
+ binding.widgetList.layoutTransition = ChangingLayoutTransition()
+ binding.clockWidget.visibility = View.VISIBLE
+ for (v in binding.widgetList.iterator()) {
+ if (v is WidgetView) {
+ v.editMode = false
+ v.layoutTransition = ChangingLayoutTransition()
+ }
+ }
+ 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)
+ }
+ }
+
+ 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.layoutTransition = ChangingLayoutTransition()
+ 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(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) {
+ val appWidgetId = widgetHost.allocateAppWidgetId()
+ val pickIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK)
+ pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
+ pickWidgetLauncher.launch(pickIntent)
+ it.dismiss()
+ }
+ }
+ } else {
+ val appWidgetId = widgetHost.allocateAppWidgetId()
+ val pickIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK)
+ pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
+ pickWidgetLauncher.launch(pickIntent)
+ }
+ }
+
+ private fun removeWidget(widget: Widget?) {
+ widget ?: return
+ widgets.remove(widget)
+ val id = widget.data.toIntOrNull() ?: return
+ widgetHost.deleteAppWidgetId(id)
+ }
+
+ private fun bindAppWidget(data: Intent) {
+ val widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
+ if (widgetId == -1) 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)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt
deleted file mode 100644
index 2186931c..00000000
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt
+++ /dev/null
@@ -1,743 +0,0 @@
-package de.mm20.launcher2.ui.legacy.activity
-
-import android.animation.AnimatorSet
-import android.animation.LayoutTransition
-import android.animation.ObjectAnimator
-import android.app.Activity
-import android.app.WallpaperManager
-import android.appwidget.AppWidgetHost
-import android.appwidget.AppWidgetManager
-import android.content.ActivityNotFoundException
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.graphics.Color
-import android.graphics.Point
-import android.os.Bundle
-import android.provider.Settings
-import android.util.Log
-import android.util.TypedValue
-import android.view.*
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.inputmethod.InputMethodManager
-import android.widget.LinearLayout
-import android.widget.Toast
-import androidx.activity.viewModels
-import androidx.appcompat.widget.PopupMenu
-import androidx.core.animation.doOnEnd
-import androidx.core.app.NotificationManagerCompat
-import androidx.core.content.ContextCompat
-import androidx.core.view.*
-import androidx.core.widget.NestedScrollView
-import androidx.lifecycle.lifecycleScope
-import com.afollestad.materialdialogs.LayoutMode
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.bottomsheets.BottomSheet
-import com.afollestad.materialdialogs.callbacks.onDismiss
-import com.afollestad.materialdialogs.customview.customView
-import com.afollestad.materialdialogs.list.listItems
-import com.jmedeisis.draglinearlayout.DragLinearLayout
-import de.mm20.launcher2.icons.DynamicIconController
-import de.mm20.launcher2.icons.IconRepository
-import de.mm20.launcher2.ktx.dp
-import de.mm20.launcher2.ktx.isAtLeastApiLevel
-import de.mm20.launcher2.ktx.isBrightColor
-import de.mm20.launcher2.legacy.helper.ActivityStarter
-import de.mm20.launcher2.preferences.LauncherPreferences
-import de.mm20.launcher2.transition.ChangingLayoutTransition
-import de.mm20.launcher2.transition.OneShotLayoutTransition
-import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.base.BaseActivity
-import de.mm20.launcher2.ui.databinding.ActivityLauncherBinding
-import de.mm20.launcher2.ui.launcher.modals.EditFavoritesView
-import de.mm20.launcher2.ui.launcher.modals.HiddenItemsView
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
-import de.mm20.launcher2.ui.legacy.component.WidgetView
-import de.mm20.launcher2.ui.legacy.helper.ThemeHelper
-import de.mm20.launcher2.ui.settings.SettingsActivity
-import de.mm20.launcher2.widgets.Widget
-import de.mm20.launcher2.widgets.WidgetType
-import de.mm20.launcher2.widgets.WidgetViewModel
-import kotlinx.coroutines.*
-import org.koin.android.ext.android.inject
-import org.koin.androidx.viewmodel.ext.android.viewModel
-import java.util.*
-import kotlin.math.roundToInt
-
-
-class LauncherActivity : BaseActivity() {
-
- /**
- * True if the search result list is visible
- */
- private var searchVisibility = false
- set(value) {
- field = value
- windowBackgroundBlur = value
- }
-
- private lateinit var widgetHost: AppWidgetHost
- private val widgets = mutableListOf()
-
- private lateinit var overlayView: ViewGroupOverlay
-
- private val widgetViewModel: WidgetViewModel by viewModel()
-
- private val searchViewModel: SearchViewModel by viewModels()
-
- private val preferences = LauncherPreferences.instance
-
- private var windowBackgroundBlur: Boolean = false
- set(value) {
- if (field == value) return
- field = value
- if (!isAtLeastApiLevel(31)) return
- window.attributes = window.attributes.also {
- if (value) {
- it.blurBehindRadius = (32 * dp).toInt()
- it.flags = it.flags or WindowManager.LayoutParams.FLAG_BLUR_BEHIND
- } else {
- it.blurBehindRadius = 0
- it.flags = it.flags and WindowManager.LayoutParams.FLAG_BLUR_BEHIND.inv()
- }
- }
- }
-
- private var widgetEditMode = false
- set(value) {
- field = value
- if (value) {
- binding.clockWidget.visibility = View.GONE
- binding.searchBar.setRightIcon(R.drawable.ic_done)
- binding.scrollView.setOnTouchListener(null)
- for (v in binding.widgetList.iterator()) {
- if (v is WidgetView) {
- v.editMode = true
- v.onResizeModeChange = {
- OneShotLayoutTransition.run(binding.widgetList)
- OneShotLayoutTransition.run(binding.widgetContainer)
- }
- }
- }
- OneShotLayoutTransition.run(binding.widgetList)
- OneShotLayoutTransition.run(binding.widgetContainer)
- OneShotLayoutTransition.run(binding.scrollContainer)
- binding.fabEditWidget.apply {
- setIconResource(R.drawable.ic_add)
- setText(R.string.widget_add_widget)
- setOnClickListener {
- addWidget()
- }
- }
- val statusBarColor = TypedValue().also {
- theme.resolveAttribute(
- R.attr.colorSurface,
- it,
- true
- )
- }.data
- window.statusBarColor = statusBarColor
- if (statusBarColor.isBrightColor()) {
- val insetsController = WindowInsetsControllerCompat(window, window.decorView)
- insetsController.isAppearanceLightStatusBars = true
- }
- binding.searchBar.visibility = View.INVISIBLE
- binding.editWidgetToolbar
- .animate()
- .translationY(0f)
- .alpha(1f)
- .withStartAction {
- binding.editWidgetToolbar.visibility = View.VISIBLE
- }
- .start()
- } else {
- widgetViewModel.saveWidgets(widgets)
- binding.widgetList.layoutTransition = ChangingLayoutTransition()
- binding.widgetContainer.layoutTransition = ChangingLayoutTransition()
- binding.scrollContainer.layoutTransition = ChangingLayoutTransition()
- binding.searchBar.setRightIcon(R.drawable.ic_more_vert)
- binding.scrollView.setOnTouchListener(scrollViewOnTouchListener)
- binding.clockWidget.visibility = View.VISIBLE
- for (v in binding.widgetList.iterator()) {
- if (v is WidgetView) {
- v.editMode = false
- v.layoutTransition = ChangingLayoutTransition()
- }
- }
- binding.fabEditWidget.apply {
- setIconResource(R.drawable.ic_edit)
- setText(R.string.menu_edit_widgets)
- setOnClickListener {
- widgetEditMode = true
- }
- }
- window.statusBarColor = Color.TRANSPARENT
-
- updateSystemBarAppearance()
-
- binding.searchBar.visibility = View.VISIBLE
- binding.editWidgetToolbar
- .animate()
- .translationY(-binding.editWidgetToolbar.height.toFloat())
- .alpha(0f)
- .withEndAction {
- binding.editWidgetToolbar.visibility = View.GONE
- }
- .start()
- }
- }
-
- private fun updateSystemBarAppearance() {
- val allowLightSystemBars = allowsLightSystemBars()
- val insetsController = WindowInsetsControllerCompat(window, window.decorView)
- insetsController.isAppearanceLightNavigationBars =
- allowLightSystemBars && preferences.lightNavBar
- insetsController.isAppearanceLightStatusBars =
- allowLightSystemBars && preferences.lightStatusBar
- }
-
- private lateinit var binding: ActivityLauncherBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val iconRepository: IconRepository by inject()
- iconRepository.recreate()
- ThemeHelper.applyTheme(theme)
-
- if (LauncherPreferences.instance.firstRunVersion < 1) {
- LauncherPreferences.instance.firstRunVersion = 1
- }
-
- WindowCompat.setDecorFitsSystemWindows(window, false)
-
- binding = ActivityLauncherBinding.inflate(LayoutInflater.from(this))
- setContentView(binding.root)
-
- overlayView = binding.rootView.overlay
-
- if (LauncherPreferences.instance.dimWallpaper) {
- binding.dimWallpaper.setBackgroundColor(getColor(R.color.wallpaper_dim))
- }
-
- binding.scrollContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
- binding.searchContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
- binding.widgetContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
-
- val params = binding.clockWidget.layoutParams
- params.height = Point().also { windowManager.defaultDisplay.getSize(it) }.y
- binding.clockWidget.layoutParams = params
- binding.container.doOnLayout {
- adjustWidgetSpace()
- }
- initWidgets()
- binding.scrollView.setOnTouchListener(scrollViewOnTouchListener)
- binding.scrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
- when {
- /* Hide searchbar*/
- scrollY > oldScrollY && ((scrollY > binding.searchBar.height) || widgetEditMode) -> {
- var newTransY = binding.searchBar.translationY - scrollY + oldScrollY
- if (newTransY < -binding.searchBar.height.toFloat() * 1.5f) {
- newTransY = -binding.searchBar.height.toFloat() * 1.5f
- }
- binding.searchBar.translationY = newTransY
- }
- /* Show searchbar*/
- scrollY < oldScrollY -> {
- var newTransY = binding.searchBar.translationY - scrollY + oldScrollY
- if (newTransY > 0f) {
- newTransY = 0f
- }
- binding.searchBar.translationY = newTransY
- }
- }
- if (scrollY > 0 && (searchVisibility || widgetEditMode)
- ) {
- binding.searchBar.raise()
- } else binding.searchBar.drop()
- if (scrollY == 0) {
- if (!searchVisibility) {
- binding.searchBar.hide()
- windowBackgroundBlur = false
- }
- } else {
- binding.searchBar.show()
- if (!searchVisibility) {
- windowBackgroundBlur = true
- }
- }
-
- }
-
- binding.searchBar.onRightIconClick = onRightIconClick@{
- if (widgetEditMode) widgetEditMode = false
- else {
- val menu = PopupMenu(this, it)
- menu.inflate(R.menu.menu_launcher)
- menu.setOnMenuItemClickListener { item ->
- when (item.itemId) {
- R.id.menu_item_settings -> {
- startActivity(Intent(this, SettingsActivity::class.java))
- }
- R.id.menu_item_wallpaper -> {
- startActivity(
- Intent.createChooser(
- Intent(Intent.ACTION_SET_WALLPAPER),
- null
- )
- )
- }
- R.id.menu_item_hidden -> {
- val view = HiddenItemsView(this)
- MaterialDialog(this, BottomSheet(LayoutMode.MATCH_PARENT))
- .show {
- title(R.string.menu_hidden_items)
- customView(view = view)
- negativeButton(R.string.close) { dismiss() }
- }
- //hiddenAppsActivated = true
- }
- R.id.menu_item_edit_favs -> {
- val view = EditFavoritesView(this@LauncherActivity)
- MaterialDialog(this, BottomSheet(LayoutMode.MATCH_PARENT)).show {
- customView(view = view)
- title(res = R.string.menu_item_edit_favs)
- positiveButton(res = R.string.close) {
- it.dismiss()
- }
- onDismiss {
- view.save()
- }
- }
- }
- R.id.menu_item_settings_old -> {
- finish()
- startActivity(Intent().also {
- it.component = ComponentName(
- packageName,
- "de.mm20.launcher2.activity.SettingsActivity"
- )
- it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
- })
- }
- }
- true
- }
- menu.show()
- }
- }
- binding.searchBar.setOnTouchListener { _, _ ->
- if (!searchVisibility) showSearch()
- false
- }
-
- binding.searchBar.onSearchQueryChanged = {
- search(it)
- }
- binding.widgetList.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
-
- binding.fabEditWidget.setOnClickListener {
- widgetEditMode = true
- }
-
- binding.editWidgetToolbar.apply {
- navigationIcon =
- ContextCompat.getDrawable(this@LauncherActivity, R.drawable.ic_done)?.apply {
- setTint(ContextCompat.getColor(this@LauncherActivity, R.color.icon_color))
- }
- setNavigationOnClickListener {
- widgetEditMode = false
- }
- }
-
- val dynamicIconController: DynamicIconController by inject()
-
- lifecycle.addObserver(dynamicIconController)
-
- lifecycleScope.launch {
- widgets.addAll(widgetViewModel.getWidgets())
- initWidgets()
- }
- }
-
-
- private fun initWidgets() {
- widgetHost = AppWidgetHost(applicationContext, 0xacab)
- binding.widgetList.removeAllViews()
- val params = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
- params.topMargin = (8 * dp).roundToInt()
- for (w in widgets) {
- val view = WidgetView(this)
- view.layoutTransition = ChangingLayoutTransition()
- view.layoutParams = params
- if (view.setWidget(w, widgetHost)) {
- binding.widgetList.addDragView(view, view.getDragHandle())
- view.onRemove = {
- OneShotLayoutTransition.run(binding.widgetList)
- OneShotLayoutTransition.run(binding.widgetContainer)
- binding.widgetList.removeDragView(view)
- removeWidget(view.widget)
- }
- view.onResizeModeChange = {
- OneShotLayoutTransition.run(binding.widgetList)
- OneShotLayoutTransition.run(binding.widgetContainer)
- }
- }
- }
-
- binding.widgetList.setOnViewSwapListener { _, firstPosition, _, secondPosition ->
- Collections.swap(widgets, firstPosition, secondPosition)
- }
- }
-
- private fun addWidget() {
- val usedWidgets = widgets.filter { it.type == WidgetType.INTERNAL }.map { it.data }
- val internalWidgets =
- widgetViewModel.getInternalWidgets().filter { !usedWidgets.contains(it.data) }
- if (internalWidgets.isNotEmpty()) {
- MaterialDialog(this).show {
- val widgetList =
- this@LauncherActivity.findViewById(R.id.widgetList)
- val widgetContainer =
- this@LauncherActivity.findViewById(R.id.widgetContainer)
- title(R.string.widget_add_widget)
- listItems(items = internalWidgets.map { it.label }) { dialog, index, _ ->
- val widget = internalWidgets[index]
- val view = WidgetView(this@LauncherActivity)
- val params = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
- params.topMargin = (8 * dp).roundToInt()
- view.layoutParams = params
- if (view.setWidget(widget, widgetHost)) {
- view.editMode = true
- widgetList.addDragView(view, view.getDragHandle())
- view.onRemove = {
- OneShotLayoutTransition.run(widgetList)
- OneShotLayoutTransition.run(widgetContainer)
- widgetList.removeDragView(view)
- removeWidget(view.widget)
- }
- view.onResizeModeChange = {
- OneShotLayoutTransition.run(widgetList)
- OneShotLayoutTransition.run(widgetContainer)
- }
- widgets.add(widget)
- }
- dialog.dismiss()
- }
- @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)
- startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET)
- it.dismiss()
- }
- }
- } else {
- val appWidgetId = widgetHost.allocateAppWidgetId()
- val pickIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK)
- pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
- startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET)
- }
- }
-
- private fun removeWidget(widget: Widget?) {
- widget ?: return
- widgets.remove(widget)
- val id = widget.data.toIntOrNull() ?: return
- widgetHost.deleteAppWidgetId(id)
- }
-
- private fun adjustWidgetSpace() {
- val height = binding.scrollView.height - binding.searchBar.height - 8 * dp
- binding.clockWidget.layoutParams = binding.clockWidget.layoutParams.also {
- it.height = height.toInt()
- }
- }
-
-
- private fun search(text: String) {
- searchViewModel.search(text)
- if (binding.webSearchViewSpacer.tag != "measured" || binding.webSearchViewSpacer.height == 0) {
- val webSearchView = binding.searchBar.getWebSearchView()
- webSearchView.doOnNextLayout {
- binding.webSearchViewSpacer.layoutParams = binding.webSearchViewSpacer.layoutParams
- .apply { height = webSearchView.height }
- binding.webSearchViewSpacer.tag = "measured"
- }
- }
- binding.webSearchViewSpacer.visibility = if (text.isBlank()) View.GONE else View.VISIBLE
- }
-
- private fun toggleSearch() {
- if (searchVisibility) {
- hideSearch()
- } else {
- showSearch()
- }
- }
-
- private fun hideSearch() {
-
- searchVisibility = false
- val set = AnimatorSet()
- set.duration = 300
- set.doOnEnd {
- binding.searchContainer.visibility = View.GONE
- binding.widgetContainer.visibility = View.VISIBLE
- }
- set.playTogether(
- ObjectAnimator.ofFloat(binding.widgetContainer, "translationY", 0f),
- ObjectAnimator.ofInt(binding.scrollView, "scrollY", 0),
- ObjectAnimator.ofFloat(
- binding.searchContainer, "translationY", 0f,
- if (binding.scrollView.scrollY > binding.searchContainer.height / 2f) -binding.searchContainer.height.toFloat() else binding.scrollView.height.toFloat()
- )
- )
- set.start()
- binding.scrollView.scrollTo(0, 0)
- binding.searchBar.hide()
- if (!binding.searchBar.getSearchQuery().isEmpty()) binding.searchBar.setSearchQuery("")
- (getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
- .hideSoftInputFromWindow(binding.searchBar.windowToken, 0)
- }
-
- private fun showSearch() {
-
- searchVisibility = true
- binding.searchBar.show()
- binding.searchContainer.visibility = View.VISIBLE
- binding.widgetContainer.visibility = View.GONE
- val set = AnimatorSet()
- set.duration = 300
- set.doOnEnd {
- search("")
- }
- set.playTogether(
- ObjectAnimator.ofFloat(
- binding.widgetContainer,
- "translationY",
- binding.scrollView.height.toFloat()
- ),
- ObjectAnimator.ofInt(binding.scrollView, "scrollY", 0),
- ObjectAnimator.ofFloat(
- binding.searchContainer,
- "translationY",
- binding.scrollView.height.toFloat(),
- 0f
- )
- )
- set.start()
- }
-
- override fun onBackPressed() {
- if (widgetEditMode) widgetEditMode = false
- if (searchVisibility) hideSearch()
- else ObjectAnimator.ofInt(binding.scrollView, "scrollY", 0).setDuration(200).start()
-
-
- }
-
- override fun onResume() {
- super.onResume()
- ActivityStarter.resume()
- ActivityStarter.create(binding.rootView)
- binding.activityStartOverlay.visibility = View.INVISIBLE
-
- search(binding.searchBar.getSearchQuery())
-
- updateSystemBarAppearance()
-
- binding.container.doOnNextLayout {
- WallpaperManager.getInstance(this).setWallpaperOffsets(it.windowToken, 0.5f, 0.5f)
- }
-
-
- //getSystemService(Context.INPUT_METHOD_SERVICE)
- // .castTo()
- // .hideSoftInputFromWindow(currentFocus?.windowToken, 0)
-
- //overridePendingTransition(R.anim.app_to_launcher_in, R.anim.app_to_launcher_out)
- }
-
- private fun allowsLightSystemBars(): Boolean {
- val dimWallpaper = LauncherPreferences.instance.dimWallpaper
- val isDarkTheme = resources.getBoolean(R.bool.is_dark_theme)
- return !(isDarkTheme && dimWallpaper)
- }
-
- private fun hasNotificationListenerPermission(): Boolean {
- val listeners = NotificationManagerCompat.getEnabledListenerPackages(this)
- for (listener in listeners) {
- if (listener == packageName) return true
- }
- return false
- }
-
- private val themeListener = { key: String ->
- recreate()
- }
-
- override fun onStart() {
- super.onStart()
- preferences.doOnPreferenceChange("is_light_wallpaper", action = themeListener)
- widgetHost.startListening()
- }
-
- override fun onPause() {
- super.onPause()
- ActivityStarter.pause()
- }
-
- override fun onStop() {
- super.onStop()
- try {
- widgetHost.stopListening()
- } catch (e: NullPointerException) {
- Log.e("MM20", Log.getStackTraceString(e))
- }
- }
-
- override fun onNewIntent(intent: Intent?) {
- super.onNewIntent(intent)
- onBackPressed()
- adjustWidgetSpace()
- }
-
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray
- ) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- search(binding.searchBar.getSearchQuery())
- }
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (data == null) return
- if (requestCode == REQUEST_PICK_APPWIDGET) {
- val widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
- if (widgetId == -1) return
- if (resultCode == Activity.RESULT_OK) {
- val appWidget = AppWidgetManager.getInstance(applicationContext)
- .getAppWidgetInfo(widgetId) ?: return
- if (appWidget.configure != null) {
- val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE)
- intent.component = appWidget.configure
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
- startActivityForResult(intent, REQUEST_BIND_APPWIDGET)
- } else {
- onActivityResult(REQUEST_BIND_APPWIDGET, Activity.RESULT_OK, data)
- }
- } else {
- widgetHost.deleteAppWidgetId(widgetId)
- }
- }
- if (requestCode == REQUEST_CREATE_APPWIDGET) {
- val widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
- if (widgetId == -1) return
- val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND)
- intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
- startActivityForResult(intent, REQUEST_BIND_APPWIDGET)
- }
- if (requestCode == REQUEST_BIND_APPWIDGET && resultCode == Activity.RESULT_OK) {
- val widgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
- if (widgetId == -1) return
- val appWidget = AppWidgetManager.getInstance(applicationContext)
- .getAppWidgetInfo(widgetId) ?: return
- val widget = Widget(
- type = WidgetType.THIRD_PARTY,
- data = widgetId.toString(),
- height = appWidget.minHeight
- )
- val params = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
- params.topMargin = (8 * dp).roundToInt()
- val view = WidgetView(this)
- 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(binding.widgetContainer)
- binding.widgetList.removeDragView(view)
- removeWidget(view.widget)
- }
- view.onResizeModeChange = {
- OneShotLayoutTransition.run(binding.widgetList)
- OneShotLayoutTransition.run(binding.widgetContainer)
- }
- widgets.add(widget)
- }
- }
- }
-
- private val scrollViewOnTouchListener: (View, MotionEvent) -> Boolean = onTouch@{ _, event ->
- when (event.action) {
- MotionEvent.ACTION_DOWN -> true
- MotionEvent.ACTION_MOVE -> {
- when {
- binding.scrollView.scrollY == 0 -> {
- if (event.historySize > 0) {
- val dY = event.y - event.getHistoricalY(0)
- val newTransY = 0.4f * dY + binding.container.translationY
- if (newTransY > 0 && newTransY < binding.searchBar.height) {
- binding.container.translationY = newTransY
- binding.searchBar.show()
- } else if (newTransY <= 0) {
- binding.container.translationY = 0f
- } else {
- binding.container.translationY = binding.searchBar.height.toFloat()
-
- }
- windowBackgroundBlur =
- searchVisibility || newTransY > 0.6 * binding.searchBar.height
-
- if (binding.container.translationY == 0f) return@onTouch false
- }
- }
- binding.scrollView.scrollY == binding.scrollContainer.height - binding.scrollView.height && searchVisibility -> {
- if (event.historySize > 0) {
- val dY = event.y - event.getHistoricalY(0)
- val newTransY = 0.4f * dY + binding.container.translationY
-
- if (newTransY <= 0 && newTransY > -binding.searchBar.height) {
- binding.container.translationY = newTransY
- binding.searchBar.show()
- } else if (newTransY > 0) {
- binding.container.translationY = 0f
- } else {
- binding.container.translationY = -binding.searchBar.height.toFloat()
- }
-
- if (binding.container.translationY == 0f) return@onTouch false
- }
- }
- else -> return@onTouch false
- }
- true
- }
- MotionEvent.ACTION_UP -> {
- if (binding.container.translationY >= binding.searchBar.height * 0.6) toggleSearch()
- if (binding.container.translationY <= -binding.searchBar.height) hideSearch()
- binding.container.animate().translationY(0f).setDuration(200).start()
- if (!searchVisibility && binding.scrollView.scrollY == 0) binding.searchBar.hide()
- false
- }
- else -> false
- }
- }
-
- override fun onDestroy() {
- super.onDestroy()
- ActivityStarter.destroy()
- }
-
- companion object {
- const val REQUEST_PICK_APPWIDGET = 4412
- const val REQUEST_CREATE_APPWIDGET = 4460
- const val REQUEST_BIND_APPWIDGET = 4124
- }
-}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ApplicationView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ApplicationView.kt
index 8a5cc070..782645a1 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ApplicationView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ApplicationView.kt
@@ -11,7 +11,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.*
import de.mm20.launcher2.search.data.Application
import de.mm20.launcher2.ui.databinding.ViewApplicationBinding
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
class ApplicationView : FrameLayout {
@@ -27,7 +27,7 @@ class ApplicationView : FrameLayout {
layoutTransition = LayoutTransition()
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
binding.applicationCard.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
applications = viewModel.appResults
applications.observe(context as AppCompatActivity, Observer> {
visibility = if (it.isEmpty()) View.GONE else View.VISIBLE
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalculatorView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalculatorView.kt
index dd070364..ea39dd8c 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalculatorView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalculatorView.kt
@@ -18,7 +18,7 @@ import androidx.lifecycle.Observer
import de.mm20.launcher2.search.data.Calculator
import de.mm20.launcher2.ui.LegacyLauncherTheme
import de.mm20.launcher2.ui.databinding.ViewCalculatorBinding
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.search.CalculatorItem
class CalculatorView : FrameLayout {
@@ -36,7 +36,7 @@ class CalculatorView : FrameLayout {
private val binding = ViewCalculatorBinding.inflate(LayoutInflater.from(context), this, true)
init {
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
calculator = viewModel.calculatorResult
calculator.observe(context as AppCompatActivity, Observer {
if (it == null) visibility = View.GONE
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt
index 3c9209d3..46df4e98 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt
@@ -15,7 +15,7 @@ import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.MissingPermission
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.legacy.search.SearchListView
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
@@ -40,7 +40,7 @@ class CalendarView : FrameLayout, KoinComponent {
val card = findViewById(R.id.card)
card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
val list = findViewById(R.id.list)
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
calendarEvents = viewModel.calendarResults
calendarEvents.observe(context as AppCompatActivity, {
if (it == null) {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt
index f6664e0e..758ff0e8 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt
@@ -15,7 +15,7 @@ import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.data.MissingPermission
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.legacy.search.SearchListView
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
@@ -38,7 +38,7 @@ class ContactView : FrameLayout, KoinComponent {
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
val card = findViewById(R.id.card)
card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
contacts = viewModel.contactResults
val list = findViewById(R.id.list)
contacts.observe(context as AppCompatActivity, {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FavoritesView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FavoritesView.kt
index 84ce593d..fc108dae 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FavoritesView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FavoritesView.kt
@@ -9,7 +9,7 @@ import android.widget.FrameLayout
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import de.mm20.launcher2.ui.databinding.ViewFavoritesBinding
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
class FavoritesView : FrameLayout {
@@ -21,7 +21,7 @@ class FavoritesView : FrameLayout {
init {
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
val favorites = viewModel.favorites
val hide = viewModel.hideFavorites
favorites.observe(context as AppCompatActivity) {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt
index 8d7699f9..13ccd956 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt
@@ -14,7 +14,7 @@ import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.data.MissingPermission
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.legacy.search.SearchListView
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
@@ -38,7 +38,7 @@ class FileView : FrameLayout, KoinComponent {
val card = findViewById(R.id.card)
card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
val list = findViewById(R.id.list)
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
files = viewModel.fileResults
files.observe(context as AppCompatActivity, {
if (it == null) {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt
index bfba700a..eeadf3f8 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt
@@ -1,256 +1,50 @@
package de.mm20.launcher2.ui.legacy.component
-import android.animation.Animator
-import android.animation.AnimatorSet
-import android.animation.ObjectAnimator
import android.content.Context
-import android.graphics.Color
-import android.text.Editable
-import android.text.TextWatcher
import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.View
-import android.view.animation.AccelerateInterpolator
-import android.view.animation.DecelerateInterpolator
-import androidx.core.content.ContextCompat
-import androidx.core.view.postDelayed
-import com.airbnb.lottie.LottieCompositionFactory
-import com.airbnb.lottie.LottieDrawable
-import de.mm20.launcher2.ktx.dp
-import de.mm20.launcher2.preferences.LauncherPreferences
-import de.mm20.launcher2.preferences.SearchStyles
-import de.mm20.launcher2.transition.ChangingLayoutTransition
+import android.util.Log
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.ComposeView
+import androidx.lifecycle.MutableLiveData
+import de.mm20.launcher2.ui.LegacyLauncherTheme
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.databinding.ViewSearchBarBinding
-import de.mm20.launcher2.ui.legacy.view.LauncherCardView
+import de.mm20.launcher2.ui.launcher.search.SearchBar
+import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
class SearchBar @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.materialCardViewStyle
-) : LauncherCardView(context, attrs, defStyleAttr) {
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = R.attr.materialCardViewStyle
+) : FrameLayout(context, attrs, defStyleAttr) {
- private var raised = false
- private var visible = true
- private var currentAnimator: Animator? = null
+ var level: SearchBarLevel = SearchBarLevel.Resting
+ set(value) {
+ levelState.value = value
+ field = value
+ }
- private val rightDrawable = LottieDrawable().apply {
- composition = LottieCompositionFactory.fromRawResSync(context, R.raw.ic_menu_to_clear).value
- repeatMode = LottieDrawable.REVERSE
- }
+ private val levelState = MutableLiveData(level)
- private val binding = ViewSearchBarBinding.inflate(LayoutInflater.from(context), this)
+ var onFocus: (() -> Unit)? = null
init {
- binding.overflowMenu.setImageDrawable(rightDrawable)
- binding.searchEdit.addTextChangedListener(object : TextWatcher {
- override fun afterTextChanged(s: Editable?) {
- val text = binding.searchEdit.text.toString()
- onSearchQueryChanged?.invoke(binding.searchEdit.text.toString())
- if (text.isEmpty()) {
- if (rightDrawable.frame > rightDrawable.minFrame.toInt()) {
- rightDrawable.speed = -1f
- rightDrawable.resumeAnimation()
- }
- } else {
- if (rightDrawable.frame < rightDrawable.maxFrame.toInt()) {
- rightDrawable.speed = 1f
- rightDrawable.resumeAnimation()
- }
- }
- }
-
- override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
- }
-
- override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
- }
-
- })
-
- binding.overflowMenu.setOnClickListener {
- if (getSearchQuery().isEmpty()) onRightIconClick?.invoke(it)
- else (setSearchQuery(""))
- }
-
- postDelayed(1) {
- hide()
- }
-
- layoutTransition = ChangingLayoutTransition()
- }
-
- fun setRightIcon(iconRes: Int) {
- binding.overflowMenu.setImageResource(iconRes)
- }
-
- var onSearchQueryChanged: ((String) -> Unit)? = null
- var onRightIconClick: ((View) -> Unit)? = null
-
- override fun setOnTouchListener(l: OnTouchListener?) {
- binding.searchEdit.setOnTouchListener(l)
- }
-
- fun setSearchQuery(text: String) {
- binding.searchEdit.setText(text)
- }
-
- fun getSearchQuery(): String {
- return binding.searchEdit.text.toString()
- }
-
- /**
- * Elevates the search bar to be higher than the other cards
- */
- fun raise() {
- if (raised) return
- currentAnimator?.takeIf { it.isStarted }?.end()
- currentAnimator = AnimatorSet().apply {
- duration = 200
- playTogether(
- ObjectAnimator.ofFloat(this@SearchBar, "translationZ", elevation, 7 * dp).apply {
- interpolator = AccelerateInterpolator(3f)
- },
- ObjectAnimator.ofInt(this@SearchBar, "backgroundOpacity", 0xFF).apply {
- interpolator = DecelerateInterpolator(3f)
- }
- )
- }
- currentAnimator?.start()
- raised = true
- }
-
- /**
- * Drops the search bar back down to the other cards niveau
- */
-
- fun drop() {
- if (!raised) return
- currentAnimator?.takeIf { it.isStarted }?.end()
- currentAnimator = AnimatorSet().apply {
- duration = 200
- playTogether(
- ObjectAnimator.ofFloat(this@SearchBar, "translationZ", 0f).apply {
- interpolator = DecelerateInterpolator(3f)
- },
- ObjectAnimator.ofInt(this@SearchBar, "backgroundOpacity", LauncherPreferences.instance.cardOpacity).apply {
- interpolator = AccelerateInterpolator(3f)
- }
- )
- }
- currentAnimator?.start()
- raised = false
- }
-
- fun show() {
- if (visible) return
- currentAnimator?.takeIf { it.isStarted }?.end()
- currentAnimator = getShowAnimator()
- currentAnimator?.start()
- visible = true
- }
-
- fun hide() {
- if (!visible) return
- currentAnimator?.takeIf { it.isStarted }?.end()
- currentAnimator = getHideAnimator()
- currentAnimator?.start()
- visible = false
- }
-
- fun getWebSearchView(): View {
- return binding.webSearchView
- }
-
- private fun getHideAnimator(): AnimatorSet {
- val searchStyle = LauncherPreferences.instance.searchStyle
- return when (searchStyle) {
- SearchStyles.NO_BG -> {
- val iconColor = ContextCompat.getColor(context, R.color.icon_color)
- val cardElevation = resources.getDimension(R.dimen.card_elevation)
- val shadowY = resources.getDimension(R.dimen.elevation_shadow_1dp_y)
- val shadowR = resources.getDimension(R.dimen.elevation_shadow_1dp_radius)
- val shadowC = Color.argb(66, 0, 0, 0)
- binding.searchEdit.setShadowLayer(shadowR, 0f, shadowY, shadowC)
- AnimatorSet().apply {
- duration = 200
- playTogether(
- ObjectAnimator.ofInt(this@SearchBar, "backgroundOpacity", 0).apply {
- interpolator = AccelerateInterpolator(3f)
- },
- ObjectAnimator.ofFloat(this@SearchBar, "translationZ", -elevation).apply {
- interpolator = DecelerateInterpolator(3f)
- duration = 150
- },
- ObjectAnimator.ofArgb(binding.searchEdit, "hintTextColor", binding.searchEdit.hintTextColors.defaultColor, Color.WHITE),
- ObjectAnimator.ofArgb(binding.searchIcon, "colorFilter", iconColor, Color.WHITE),
- ObjectAnimator.ofArgb(binding.overflowMenu, "colorFilter", iconColor, Color.WHITE),
- ObjectAnimator.ofFloat(binding.searchIcon, "alpha", 1f),
- ObjectAnimator.ofFloat(binding.overflowMenu, "alpha", 1f),
- ObjectAnimator.ofFloat(binding.searchIcon, "elevation", cardElevation),
- ObjectAnimator.ofFloat(binding.overflowMenu, "elevation", cardElevation)
- )
- }
- }
- // Solid style
- SearchStyles.SOLID -> {
- AnimatorSet()
- }
- // Hidden style
- else -> {
- AnimatorSet().apply {
- duration = 200
- playTogether(
- ObjectAnimator.ofFloat(this@SearchBar, "alpha", 0f)
+ val view = ComposeView(context)
+ view.setContent {
+ val level by levelState.observeAsState(SearchBarLevel.Resting)
+ LegacyLauncherTheme {
+ Box(contentAlignment = Alignment.TopCenter) {
+ SearchBar(
+ level,
+ onFocus = { onFocus?.invoke() }
)
}
}
}
+ addView(view)
}
-
- private fun getShowAnimator(): AnimatorSet? {
- return when (LauncherPreferences.instance.searchStyle) {
- // Transparent style
- SearchStyles.NO_BG -> {
- val hint = ContextCompat.getColor(context, R.color.text_color_primary_disabled)
- val iconAttrs = context.obtainStyledAttributes(R.style.LauncherTheme_IconStyle, intArrayOf(android.R.attr.alpha))
- val iconAlpha = iconAttrs.getFloat(0, 0f)
- iconAttrs.recycle()
- val iconColor = ContextCompat.getColor(context, R.color.icon_color)
- binding.searchEdit.setShadowLayer(0f, 0f, 0f, 0)
- AnimatorSet().apply {
- duration = 200
- playTogether(
- ObjectAnimator.ofFloat(this@SearchBar, "translationZ", 0f).apply {
- interpolator = AccelerateInterpolator(3f)
- },
- ObjectAnimator.ofInt(this@SearchBar, "backgroundOpacity", LauncherPreferences.instance.cardOpacity).apply {
- interpolator = DecelerateInterpolator(3f)
- },
- ObjectAnimator.ofArgb(binding.searchEdit, "hintTextColor", Color.WHITE, hint),
- ObjectAnimator.ofArgb(binding.searchIcon, "colorFilter", Color.WHITE, iconColor),
- ObjectAnimator.ofArgb(binding.overflowMenu, "colorFilter", Color.WHITE, iconColor),
- ObjectAnimator.ofFloat(binding.searchIcon, "alpha", iconAlpha),
- ObjectAnimator.ofFloat(binding.overflowMenu, "alpha", iconAlpha),
- ObjectAnimator.ofFloat(binding.searchIcon, "elevation", 0f),
- ObjectAnimator.ofFloat(binding.overflowMenu, "elevation", 0f)
- )
- }
- }
- // Solid style
- SearchStyles.SOLID -> {
- null
- }
- // Hidden style
- else -> {
- AnimatorSet().apply {
- duration = 200
- playTogether(
- ObjectAnimator.ofFloat(this@SearchBar, "alpha", 1f)
- )
- }
- }
- }
- }
-
}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/UnitConverterView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/UnitConverterView.kt
index b677043f..68449d8a 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/UnitConverterView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/UnitConverterView.kt
@@ -18,7 +18,7 @@ import androidx.lifecycle.Observer
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.ui.LegacyLauncherTheme
import de.mm20.launcher2.ui.databinding.ViewUnitconverterBinding
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.search.UnitConverterItem
class UnitConverterView : FrameLayout {
@@ -36,7 +36,7 @@ class UnitConverterView : FrameLayout {
private val binding = ViewUnitconverterBinding.inflate(LayoutInflater.from(context), this, true)
init {
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
unitConverter = viewModel.unitConverterResult
unitConverter.observe(context as AppCompatActivity, Observer {
if (it == null) visibility = View.GONE
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebSearchView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebSearchView.kt
index e9c9fd20..81cb95bd 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebSearchView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebSearchView.kt
@@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.legacy.component
import android.content.Context
import android.content.res.ColorStateList
-import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
@@ -22,7 +21,7 @@ import de.mm20.launcher2.legacy.helper.ActivityStarter
import de.mm20.launcher2.search.data.Websearch
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.databinding.ViewWebsearchBinding
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import kotlinx.coroutines.launch
import java.io.File
@@ -40,7 +39,7 @@ class WebSearchView : FrameLayout {
private val binding = ViewWebsearchBinding.inflate(LayoutInflater.from(context), this, true)
init {
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
websearches = viewModel.websearchResults
websearches.observe(context as AppCompatActivity, Observer {
updateWebsearches(it)
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebsiteView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebsiteView.kt
index c52fa541..9f3f3e8f 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebsiteView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebsiteView.kt
@@ -12,7 +12,7 @@ import androidx.lifecycle.Observer
import de.mm20.launcher2.legacy.helper.ActivityStarter
import de.mm20.launcher2.search.data.Website
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
class WebsiteView : FrameLayout {
@@ -35,7 +35,7 @@ class WebsiteView : FrameLayout {
val card = findViewById(R.id.card)
websiteView.layoutParams = params
card.addView(websiteView)
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
website = viewModel.websiteResult
website.observe(context as AppCompatActivity, Observer {
visibility = if (it == null) View.GONE else View.VISIBLE
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WikipediaView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WikipediaView.kt
index 519111ce..e7dfe513 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WikipediaView.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WikipediaView.kt
@@ -11,7 +11,7 @@ import androidx.lifecycle.LiveData
import de.mm20.launcher2.legacy.helper.ActivityStarter
import de.mm20.launcher2.search.data.Wikipedia
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
class WikipediaView : FrameLayout {
@@ -34,7 +34,7 @@ class WikipediaView : FrameLayout {
val card = findViewById(R.id.card)
websiteView.layoutParams = params
card.addView(websiteView)
- val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
+ val viewModel: SearchVM by (context as AppCompatActivity).viewModels()
wikipedia = viewModel.wikipediaResult
wikipedia.observe(context as AppCompatActivity, {
visibility = if (it == null) View.GONE else View.VISIBLE
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/ApplicationResults.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/ApplicationResults.kt
index 7a2f48ab..1ae13d51 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/search/ApplicationResults.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/search/ApplicationResults.kt
@@ -6,11 +6,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.lifecycle.viewmodel.compose.viewModel
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun applicationResults(): LazyListScope.(listState: LazyListState) -> Unit {
- val viewModel: SearchViewModel by viewModel()
+ val viewModel: SearchVM by viewModel()
val apps by viewModel.appResults.observeAsState(emptyList())
return {
SearchableGrid(items = apps, listState = it)
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/CalculatorResults.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/CalculatorResults.kt
index 059e6544..d486310c 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/search/CalculatorResults.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/search/CalculatorResults.kt
@@ -8,11 +8,11 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.component.SectionDivider
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun calculatorItem(): LazyListScope.() -> Unit {
- val viewModel: SearchViewModel by viewModel()
+ val viewModel: SearchVM by viewModel()
val calculator by viewModel.calculatorResult.observeAsState(null)
return {
calculator?.let {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/FavoriteResults.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/FavoriteResults.kt
index 6e9a5517..39ba05c0 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/search/FavoriteResults.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/search/FavoriteResults.kt
@@ -6,11 +6,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.lifecycle.viewmodel.compose.viewModel
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun favoriteResults(): LazyListScope.(listState: LazyListState) -> Unit {
- val viewModel: SearchViewModel by viewModel()
+ val viewModel: SearchVM by viewModel()
val favorites by viewModel.favorites.observeAsState(emptyList())
return {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/FileResults.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/FileResults.kt
index f1d1763f..eaaa7528 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/search/FileResults.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/search/FileResults.kt
@@ -5,11 +5,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.lifecycle.viewmodel.compose.viewModel
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun fileResults(): LazyListScope.() -> Unit {
- val viewModel: SearchViewModel by viewModel()
+ val viewModel: SearchVM by viewModel()
val files by viewModel.fileResults.observeAsState(emptyList())
return {
files?.let { SearchableList(items = it) }
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/WikipediaResult.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/WikipediaResult.kt
index fb297aa2..ceeb8e8a 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/search/WikipediaResult.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/search/WikipediaResult.kt
@@ -7,12 +7,12 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.component.SectionDivider
-import de.mm20.launcher2.ui.launcher.search.SearchViewModel
+import de.mm20.launcher2.ui.launcher.search.SearchVM
import org.koin.androidx.compose.viewModel
@Composable
fun wikipediaResult(): LazyListScope.() -> Unit {
- val viewModel: SearchViewModel by viewModel()
+ val viewModel: SearchVM by viewModel()
val wikipedia by viewModel.wikipediaResult.observeAsState()
return {
wikipedia?.let {
diff --git a/ui/src/main/res/layout/activity_launcher.xml b/ui/src/main/res/layout/activity_launcher.xml
index d624488c..47b0b61e 100644
--- a/ui/src/main/res/layout/activity_launcher.xml
+++ b/ui/src/main/res/layout/activity_launcher.xml
@@ -1,7 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:fitsSystemWindows="true" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/src/main/res/layout/view_search.xml b/ui/src/main/res/layout/view_search.xml
new file mode 100644
index 00000000..592fd430
--- /dev/null
+++ b/ui/src/main/res/layout/view_search.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/src/main/res/layout/view_widgets.xml b/ui/src/main/res/layout/view_widgets.xml
new file mode 100644
index 00000000..9f8f1f35
--- /dev/null
+++ b/ui/src/main/res/layout/view_widgets.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file