From f701e90c474b5917efcb6c82ce239254f91e7aeb Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Tue, 25 Jan 2022 17:55:57 +0100 Subject: [PATCH] Unclutter this mess we call LauncherActivity also migrate search bar to Jetpack Compose --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 4 +- .../launcher2/activity/SettingsActivity.kt | 2 +- ui/build.gradle.kts | 1 + ui/src/main/AndroidManifest.xml | 6 +- .../launcher2/ui/component/LauncherCard.kt | 26 + .../mm20/launcher2/ui/component/SearchBar.kt | 5 +- .../launcher2/ui/launcher/LauncherActivity.kt | 162 ++++ .../ui/launcher/LauncherActivityVM.kt | 27 + .../ui/launcher/LauncherScaffoldVM.kt | 61 ++ .../ui/launcher/LauncherScaffoldView.kt | 281 +++++++ .../launcher2/ui/launcher/search/SearchBar.kt | 323 ++++++++ .../{SearchViewModel.kt => SearchVM.kt} | 8 +- .../ui/launcher/search/SearchView.kt | 20 + .../ui/launcher/widgets/WidgetsVM.kt | 35 + .../ui/launcher/widgets/WidgetsView.kt | 291 +++++++ .../ui/legacy/activity/LauncherActivity.kt | 743 ------------------ .../ui/legacy/component/ApplicationView.kt | 4 +- .../ui/legacy/component/CalculatorView.kt | 4 +- .../ui/legacy/component/CalendarView.kt | 4 +- .../ui/legacy/component/ContactView.kt | 4 +- .../ui/legacy/component/FavoritesView.kt | 4 +- .../launcher2/ui/legacy/component/FileView.kt | 4 +- .../ui/legacy/component/SearchBar.kt | 270 +------ .../ui/legacy/component/UnitConverterView.kt | 4 +- .../ui/legacy/component/WebSearchView.kt | 5 +- .../ui/legacy/component/WebsiteView.kt | 4 +- .../ui/legacy/component/WikipediaView.kt | 4 +- .../launcher2/ui/search/ApplicationResults.kt | 4 +- .../launcher2/ui/search/CalculatorResults.kt | 4 +- .../launcher2/ui/search/FavoriteResults.kt | 4 +- .../mm20/launcher2/ui/search/FileResults.kt | 4 +- .../launcher2/ui/search/WikipediaResult.kt | 4 +- ui/src/main/res/layout/activity_launcher.xml | 163 +--- .../res/layout/view_launcher_scaffold.xml | 61 ++ ui/src/main/res/layout/view_search.xml | 68 ++ ui/src/main/res/layout/view_widgets.xml | 35 + 37 files changed, 1471 insertions(+), 1183 deletions(-) create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/component/LauncherCard.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivityVM.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherScaffoldVM.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherScaffoldView.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchBar.kt rename ui/src/main/java/de/mm20/launcher2/ui/launcher/search/{SearchViewModel.kt => SearchVM.kt} (96%) create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchView.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetsVM.kt create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetsView.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt create mode 100644 ui/src/main/res/layout/view_launcher_scaffold.xml create mode 100644 ui/src/main/res/layout/view_search.xml create mode 100644 ui/src/main/res/layout/view_widgets.xml 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