Lawnchair: Port and rewrite HotseatQsb and Smartspace from R
This commit is contained in:
committed by
Patryk Michalik
parent
c6f8d713bd
commit
2f91a8f956
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user