Lawnchair: Port and rewrite HotseatQsb and Smartspace from R

This commit is contained in:
Kshitij Gupta
2021-03-18 22:33:18 +05:30
committed by Patryk Michalik
parent c6f8d713bd
commit 2f91a8f956
9 changed files with 454 additions and 0 deletions
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path android:pathData="M0 0h24v24H0z" />
<path
android:fillColor="?android:attr/textColorPrimary"
android:pathData="M7.5 5.6L10 7 8.6 4.5 10 2 7.5 3.4 5 2l1.4 2.5L5 7zm12 9.8L17 14l1.4 2.5L17
19l2.5-1.4L22 19l-1.4-2.5L22 14zM22 2l-2.5 1.4L17 2l1.4 2.5L17 7l2.5-1.4L22
7l-1.4-2.5zm-7.63 5.29c-0.39-0.39-1.02-0.39-1.41 0L1.29 18.96c-0.39 0.39 -0.39 1.02 0
1.41l2.34 2.34c0.39 0.39 1.02 0.39 1.41 0L16.7 11.05c0.39-0.39 0.39 -1.02
0-1.41l-2.33-2.35zm-1.03 5.49l-2.12-2.12 2.44-2.44 2.12 2.12-2.44 2.44z" />
</vector>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<ch.deletescape.nexuslauncher.HotseatQsb
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@id/search_container_all_apps"
android:layout_width="match_parent"
android:layout_height="@dimen/search_widget_hotseat_height"
android:layout_gravity="top|center_horizontal">
<fragment
android:id="@+id/search_wrapper_view"
android:name="ch.deletescape.nexuslauncher.HotseatQsb$HotseatQsbFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="qsb_view"
tools:ignore="FragmentTagUsage" />
<com.android.launcher3.ExtendedEditText
android:id="@+id/fallback_search_view"
android:layout_width="match_parent"
android:layout_height="@dimen/all_apps_search_bar_field_height"
android:layout_gravity="center"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="@drawable/bg_all_apps_searchbox"
android:elevation="1dp"
android:focusableInTouchMode="true"
android:gravity="center"
android:hint="@string/all_apps_search_bar_hint"
android:imeOptions="actionSearch|flagNoExtractUi"
android:inputType="text|textNoSuggestions|textCapWords"
android:maxLines="1"
android:padding="8dp"
android:saveEnabled="false"
android:scrollHorizontally="true"
android:singleLine="true"
android:textColor="?android:attr/textColorSecondary"
android:textColorHint="@drawable/all_apps_search_hint"
android:textSize="16sp"
android:visibility="invisible" />
</ch.deletescape.nexuslauncher.HotseatQsb>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<ch.deletescape.nexuslauncher.SmartspaceQsb
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@id/search_container_workspace"
android:layout_width="match_parent"
android:layout_height="0dp">
<fragment
android:name="ch.deletescape.nexuslauncher.SmartspaceQsb$SmartSpaceFragment"
android:layout_width="match_parent"
android:tag="smart_space_view"
android:layout_height="match_parent"
tools:ignore="FragmentTagUsage" />
</ch.deletescape.nexuslauncher.SmartspaceQsb>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/smartspace_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:animateLayoutChanges="true"
android:background="@android:color/transparent">
<TextClock
android:id="@+id/date_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="sans-serif"
android:format12Hour="EEEE, MMM d"
android:format24Hour="EEEE, MMM d"
android:includeFontPadding="false"
android:maxLines="1"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:shadowColor="#DD000000"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="9"
android:textColor="#FFFFFF"
android:textSize="24sp" />
</FrameLayout>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dynamic_grid_hotseat_size">70dp</dimen>
<dimen name="dynamic_grid_hotseat_top_padding">0dp</dimen>
<dimen name="dynamic_grid_hotseat_bottom_padding">72dp</dimen>
<dimen name="search_widget_top_shift">24dp</dimen>
<dimen name="search_widget_hotseat_height">56dp</dimen>
</resources>
+2
View File
@@ -36,4 +36,6 @@
<string name="colored_generated_icon_backgrounds_label">Make Colored Backgrounds</string>
<string name="colored_generated_icon_backgrounds_description">For generated Adaptive icons.</string>
<string name="notification_dots">Notification Dots</string>
<string name="smartspace_preferences">Preferences</string>
</resources>
@@ -0,0 +1,162 @@
package ch.deletescape.nexuslauncher
import android.content.Context
import android.graphics.Rect
import android.text.Selection
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.method.TextKeyListener
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.View
import android.view.animation.Interpolator
import android.widget.EditText
import com.android.launcher3.*
import com.android.launcher3.allapps.AllAppsContainerView
import com.android.launcher3.allapps.AllAppsStore
import com.android.launcher3.allapps.AlphabeticalAppsList
import com.android.launcher3.allapps.SearchUiManager
import com.android.launcher3.allapps.search.AllAppsSearchBarController
import com.android.launcher3.allapps.search.DefaultAppSearchAlgorithm
import com.android.launcher3.anim.Interpolators
import com.android.launcher3.anim.PropertySetter
import com.android.launcher3.graphics.TintedDrawableSpan
import com.android.launcher3.qsb.QsbContainerView
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.views.ActivityContext
import java.util.*
class HotseatQsb @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
QsbContainerView(context, attrs, defStyleAttr), Insettable, SearchUiManager, AllAppsSearchBarController.Callbacks, AllAppsStore.OnUpdateListener {
private val mActivity: ActivityContext = ActivityContext.lookupContext(context)
private val mFixedTranslationY: Int = resources.getDimensionPixelSize(R.dimen.search_widget_hotseat_height) / 2
private val mMarginTopAdjusting: Int = resources.getDimensionPixelSize(R.dimen.search_widget_top_shift)
private val mSearchBarController: AllAppsSearchBarController = AllAppsSearchBarController()
private val mSearchQueryBuilder: SpannableStringBuilder = SpannableStringBuilder()
private var mApps: AlphabeticalAppsList? = null
private var mAppsView: AllAppsContainerView? = null
private var mSearchWrapperView: View? = null
private var mFallbackSearchView: ExtendedEditText? = null
override fun onFinishInflate() {
super.onFinishInflate()
mFallbackSearchView = findViewById(R.id.fallback_search_view)
mSearchWrapperView = findViewById(R.id.search_wrapper_view)
val spanned = SpannableString(" " + mFallbackSearchView?.hint)
spanned.setSpan(TintedDrawableSpan(context, R.drawable.ic_allapps_search),
0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
mFallbackSearchView?.hint = spanned
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
mAppsView?.appsStore?.addUpdateListener(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
mAppsView?.appsStore?.removeUpdateListener(this)
}
override fun setInsets(insets: Rect) {
visibility = if (mActivity.deviceProfile.isVerticalBarLayout) View.GONE else View.VISIBLE
val mlp: MarginLayoutParams = layoutParams as MarginLayoutParams
mlp.topMargin = (-mFixedTranslationY).coerceAtLeast(insets.top - mMarginTopAdjusting)
val padding: Rect = mActivity.deviceProfile.hotseatLayoutPadding
setPaddingUnchecked(padding.left, 0, padding.right, 0)
requestLayout()
}
override fun initialize(appsView: AllAppsContainerView) {
mApps = appsView.apps
mAppsView = appsView
mSearchBarController.initialize(DefaultAppSearchAlgorithm(mApps?.apps),
mFallbackSearchView, Launcher.cast(mActivity), this)
}
override fun onAppsUpdated() {
mSearchBarController.refreshSearchResult()
}
override fun resetSearch() {
mSearchBarController.reset()
}
override fun preDispatchKeyEvent(event: KeyEvent) {
if (!mSearchBarController.isSearchFieldFocused &&
event.action == KeyEvent.ACTION_DOWN) {
val unicodeChar = event.unicodeChar
val isKeyNotWhitespace = unicodeChar > 0 &&
!Character.isWhitespace(unicodeChar) && !Character.isSpaceChar(unicodeChar)
if (isKeyNotWhitespace) {
val gotKey: Boolean = TextKeyListener.getInstance().onKeyDown(this, mSearchQueryBuilder,
event.keyCode, event)
if (gotKey && mSearchQueryBuilder.isNotEmpty()) {
mSearchBarController.focusSearchField()
}
}
}
}
override fun onSearchResult(query: String, apps: ArrayList<ComponentKey>) {
mApps?.setOrderedFilter(apps)
notifyResultChanged()
mAppsView?.setLastSearchQuery(query)
}
override fun clearSearchResult() {
if (mApps?.setOrderedFilter(null) == true) {
notifyResultChanged()
}
mSearchQueryBuilder.clear()
mSearchQueryBuilder.clearSpans()
Selection.setSelection(mSearchQueryBuilder, 0)
mAppsView?.onClearSearchResult()
}
private fun notifyResultChanged() {
mAppsView?.onSearchResultsChanged()
}
override fun getScrollRangeDelta(insets: Rect): Float {
return if (mActivity.deviceProfile.isVerticalBarLayout) {
0.0f
} else {
val dp: DeviceProfile = mActivity.deviceProfile
val percentageOfAvailSpaceFromBottom = 0.45f
val center = ((dp.hotseatBarSizePx - dp.hotseatCellHeightPx
- layoutParams.height - insets.bottom) * percentageOfAvailSpaceFromBottom).toInt()
val bottomMargin = insets.bottom + center
val topMargin = (-mFixedTranslationY).coerceAtLeast(insets.top - mMarginTopAdjusting)
val myBot: Int = layoutParams.height + topMargin + mFixedTranslationY
(bottomMargin + myBot).toFloat()
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
offsetTopAndBottom(mFixedTranslationY)
}
override fun setContentVisibility(visibleElements: Int, setter: PropertySetter,
interpolator: Interpolator) {
val showAllAppsMode = visibleElements and LauncherState.ALL_APPS_CONTENT != 0
setter.setViewAlpha(mSearchWrapperView, if (showAllAppsMode) 0f else 1f, Interpolators.LINEAR)
setter.setViewAlpha(mFallbackSearchView, if (showAllAppsMode) 1f else 0f, Interpolators.LINEAR)
}
override fun setTextSearchEnabled(isEnabled: Boolean): EditText? {
return mFallbackSearchView
}
class HotseatQsbFragment : QsbFragment() {
override fun isQsbEnabled(): Boolean {
return true
}
}
init {
Selection.setSelection(mSearchQueryBuilder, 0)
}
}
@@ -0,0 +1,119 @@
package ch.deletescape.nexuslauncher
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.graphics.RectF
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import android.view.View.OnLongClickListener
import android.view.ViewGroup
import com.android.launcher3.CheckLongPressHelper
import com.android.launcher3.Launcher
import com.android.launcher3.R
import com.android.launcher3.logging.StatsLogManager.EventEnum
import com.android.launcher3.qsb.QsbWidgetHostView
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener
import com.android.launcher3.views.OptionsPopupView
import com.android.launcher3.views.OptionsPopupView.OptionItem
class SmartSpaceHostView(context: Context?) : QsbWidgetHostView(context), OnLongClickListener, TouchCompleteListener {
private val mLauncher: Launcher = Launcher.getLauncher(context)
private val mLongPressHelper: CheckLongPressHelper = CheckLongPressHelper(this, this)
override fun getErrorView(): View {
return SmartspaceQsb.getDateView(this)
}
override fun onLongClick(view: View): Boolean {
if (!hasSettings(view.context)) {
return false
}
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
val pos = Rect()
mLauncher.dragLayer.getDescendantRectRelativeToSelf(this, pos)
val centerPos = RectF()
centerPos.right = pos.exactCenterX()
centerPos.left = centerPos.right
centerPos.top = 0f
centerPos.bottom = pos.bottom.toFloat()
centerPos.bottom = findBottomRecur(this, pos.top, pos).toFloat().coerceAtMost(centerPos.bottom)
val item = OptionItem(R.string.smartspace_preferences,
R.drawable.ic_smartspace_preferences, NexusLauncherEnum.SMARTSPACE_TAP_OR_LONGPRESS, { v: View -> openSettings(v) })
OptionsPopupView.show(mLauncher, centerPos, listOf(item))
return true
}
private fun findBottomRecur(view: View, max: Int, tempRect: Rect): Int {
var ret = max
if (view.visibility != VISIBLE) {
return ret
}
if (view is ViewGroup) {
for (i in view.childCount - 1 downTo 0) {
ret = findBottomRecur(view.getChildAt(i), ret, tempRect).coerceAtLeast(ret)
}
}
if (!view.willNotDraw()) {
mLauncher.dragLayer.getDescendantRectRelativeToSelf(view, tempRect)
return ret.coerceAtLeast(tempRect.bottom)
}
return ret
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
mLongPressHelper.onTouchEvent(ev)
return mLongPressHelper.hasPerformedLongPress()
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent): Boolean {
mLongPressHelper.onTouchEvent(ev)
return true
}
override fun cancelLongPress() {
super.cancelLongPress()
mLongPressHelper.cancelLongPress()
}
override fun onTouchComplete() {
if (!mLongPressHelper.hasPerformedLongPress()) {
mLongPressHelper.cancelLongPress()
}
}
private fun openSettings(v: View): Boolean {
v.context.startActivity(createSettingsIntent())
return true
}
companion object {
private const val SETTINGS_INTENT_ACTION = "com.google.android.apps.gsa.smartspace.SETTINGS"
fun hasSettings(context: Context): Boolean {
val info = context.packageManager
.resolveActivity(createSettingsIntent(), 0)
return info != null
}
fun createSettingsIntent(): Intent {
return Intent(SETTINGS_INTENT_ACTION)
.setPackage(SmartspaceQsb.WIDGET_PACKAGE_NAME)
.setFlags(Intent.FLAG_RECEIVER_FOREGROUND
or Intent.FLAG_ACTIVITY_NO_HISTORY
or Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
}
}
}
enum class NexusLauncherEnum(private val mId: Int) : EventEnum {
SMARTSPACE_TAP_OR_LONGPRESS(520);
override fun getId(): Int {
return mId
}
}
@@ -0,0 +1,62 @@
package ch.deletescape.nexuslauncher
import android.annotation.SuppressLint
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.os.Bundle
import android.os.Process
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.android.launcher3.R
import com.android.launcher3.qsb.QsbContainerView
class SmartspaceQsb @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : QsbContainerView(context, attrs, defStyleAttr) {
class SmartSpaceFragment : QsbFragment() {
override fun createHost(): QsbWidgetHost {
return QsbWidgetHost(context, SMART_SPACE_WIDGET_HOST_ID
) { c: Context? -> SmartSpaceHostView(c) }
}
@SuppressLint("NewApi")
override fun getSearchWidgetProvider(): AppWidgetProviderInfo? {
for (info in AppWidgetManager.getInstance(context)
.getInstalledProvidersForPackage(WIDGET_PACKAGE_NAME, Process.myUserHandle())) {
if (WIDGET_CLASS_NAME == info.provider.className) {
return info
}
}
return null
}
override fun getDefaultView(container: ViewGroup, showSetupIcon: Boolean): View {
return getDateView(container)
}
override fun createBindOptions(): Bundle {
val opts = super.createBindOptions()
opts.putString("attached-launcher-identifier", context.packageName)
opts.putBoolean("com.google.android.apps.gsa.widget.PREINSTALLED", true)
return opts
}
companion object {
private const val SMART_SPACE_WIDGET_HOST_ID = 1027
}
init {
mKeyWidgetId = "smart_space_widget_id"
}
}
companion object {
const val WIDGET_PACKAGE_NAME = "com.google.android.googlequicksearchbox"
private const val WIDGET_CLASS_NAME = "com.google.android.apps.gsa.staticplugins.smartspace.widget.SmartspaceWidgetProvider"
fun getDateView(parent: ViewGroup): View {
return LayoutInflater.from(parent.context)
.inflate(R.layout.smart_space_date_view, parent, false)
}
}
}