That fuck shit the fascists are using
at master 273 lines 9.6 kB view raw
1package org.tm.archive.components 2 3import android.content.Context 4import android.os.Build 5import android.util.AttributeSet 6import android.util.DisplayMetrics 7import android.view.Surface 8import androidx.constraintlayout.widget.ConstraintLayout 9import androidx.constraintlayout.widget.Guideline 10import androidx.core.content.withStyledAttributes 11import androidx.core.graphics.Insets 12import androidx.core.view.ViewCompat 13import androidx.core.view.WindowInsetsAnimationCompat 14import androidx.core.view.WindowInsetsCompat 15import org.signal.core.util.logging.Log 16import org.tm.archive.R 17import org.tm.archive.keyvalue.SignalStore 18import org.tm.archive.util.ServiceUtil 19import org.tm.archive.util.ViewUtil 20 21/** 22 * A specialized [ConstraintLayout] that sets guidelines based on the window insets provided 23 * by the system. For improved backwards-compatibility we must use [ViewCompat] for configuring 24 * the inset change callbacks. 25 * 26 * In portrait mode these are how the guidelines will be configured: 27 * 28 * - [R.id.status_bar_guideline] is set to the bottom of the status bar 29 * - [R.id.navigation_bar_guideline] is set to the top of the navigation bar 30 * - [R.id.parent_start_guideline] is set to the start of the parent 31 * - [R.id.parent_end_guideline] is set to the end of the parent 32 * - [R.id.keyboard_guideline] will be set to the top of the keyboard and will 33 * change as the keyboard is shown or hidden 34 * 35 * In landscape, the spirit of the guidelines are maintained but their names may not 36 * correlated exactly to the inset they are providing. 37 * 38 * These guidelines will only be updated if present in your layout, you can use 39 * `<include layout="@layout/system_ui_guidelines" />` to quickly include them. 40 */ 41@Suppress("LeakingThis") 42open class InsetAwareConstraintLayout @JvmOverloads constructor( 43 context: Context, 44 attrs: AttributeSet? = null, 45 defStyleAttr: Int = 0 46) : ConstraintLayout(context, attrs, defStyleAttr) { 47 48 companion object { 49 private val TAG = Log.tag(InsetAwareConstraintLayout::class.java) 50 private val keyboardType = WindowInsetsCompat.Type.ime() 51 private val windowTypes = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() 52 } 53 54 protected val statusBarGuideline: Guideline? by lazy { findViewById(R.id.status_bar_guideline) } 55 private val navigationBarGuideline: Guideline? by lazy { findViewById(R.id.navigation_bar_guideline) } 56 private val parentStartGuideline: Guideline? by lazy { findViewById(R.id.parent_start_guideline) } 57 private val parentEndGuideline: Guideline? by lazy { findViewById(R.id.parent_end_guideline) } 58 private val keyboardGuideline: Guideline? by lazy { findViewById(R.id.keyboard_guideline) } 59 60 private val windowInsetsListeners: MutableSet<WindowInsetsListener> = mutableSetOf() 61 private val keyboardStateListeners: MutableSet<KeyboardStateListener> = mutableSetOf() 62 private val keyboardAnimator = KeyboardInsetAnimator() 63 private val displayMetrics = DisplayMetrics() 64 private var overridingKeyboard: Boolean = false 65 private var previousKeyboardHeight: Int = 0 66 67 val isKeyboardShowing: Boolean 68 get() = previousKeyboardHeight > 0 69 70 init { 71 ViewCompat.setOnApplyWindowInsetsListener(this) { _, windowInsetsCompat -> 72 applyInsets(windowInsets = windowInsetsCompat.getInsets(windowTypes), keyboardInsets = windowInsetsCompat.getInsets(keyboardType)) 73 windowInsetsCompat 74 } 75 76 if (attrs != null) { 77 context.withStyledAttributes(attrs, R.styleable.InsetAwareConstraintLayout) { 78 if (getBoolean(R.styleable.InsetAwareConstraintLayout_animateKeyboardChanges, false)) { 79 ViewCompat.setWindowInsetsAnimationCallback(this@InsetAwareConstraintLayout, keyboardAnimator) 80 } 81 } 82 } 83 } 84 85 fun addKeyboardStateListener(listener: KeyboardStateListener) { 86 keyboardStateListeners += listener 87 } 88 89 fun removeKeyboardStateListener(listener: KeyboardStateListener) { 90 keyboardStateListeners.remove(listener) 91 } 92 93 fun addWindowInsetsListener(listener: WindowInsetsListener) { 94 windowInsetsListeners += listener 95 } 96 97 fun removeWindowInsetsListener(listener: WindowInsetsListener) { 98 windowInsetsListeners.remove(listener) 99 } 100 101 private fun applyInsets(windowInsets: Insets, keyboardInsets: Insets) { 102 val isLtr = ViewUtil.isLtr(this) 103 104 val statusBar = windowInsets.top 105 val navigationBar = windowInsets.bottom 106 val parentStart = if (isLtr) windowInsets.left else windowInsets.right 107 val parentEnd = if (isLtr) windowInsets.right else windowInsets.left 108 109 statusBarGuideline?.setGuidelineBegin(statusBar) 110 navigationBarGuideline?.setGuidelineEnd(navigationBar) 111 parentStartGuideline?.setGuidelineBegin(parentStart) 112 parentEndGuideline?.setGuidelineEnd(parentEnd) 113 114 windowInsetsListeners.forEach { it.onApplyWindowInsets(statusBar, navigationBar, parentStart, parentEnd) } 115 116 if (keyboardInsets.bottom > 0) { 117 setKeyboardHeight(keyboardInsets.bottom) 118 if (!keyboardAnimator.animating) { 119 keyboardGuideline?.setGuidelineEnd(keyboardInsets.bottom) 120 } else { 121 keyboardAnimator.endingGuidelineEnd = keyboardInsets.bottom 122 } 123 } else if (!overridingKeyboard) { 124 if (!keyboardAnimator.animating) { 125 keyboardGuideline?.setGuidelineEnd(windowInsets.bottom) 126 } else { 127 keyboardAnimator.endingGuidelineEnd = windowInsets.bottom 128 } 129 } 130 131 if (previousKeyboardHeight != keyboardInsets.bottom) { 132 keyboardStateListeners.forEach { 133 if (previousKeyboardHeight <= 0 && keyboardInsets.bottom > 0) { 134 it.onKeyboardShown() 135 } else if (previousKeyboardHeight > 0 && keyboardInsets.bottom <= 0) { 136 it.onKeyboardHidden() 137 } 138 } 139 } 140 141 previousKeyboardHeight = keyboardInsets.bottom 142 } 143 144 protected fun overrideKeyboardGuidelineWithPreviousHeight() { 145 overridingKeyboard = true 146 keyboardGuideline?.setGuidelineEnd(getKeyboardHeight()) 147 } 148 149 protected fun clearKeyboardGuidelineOverride() { 150 overridingKeyboard = false 151 } 152 153 protected fun resetKeyboardGuideline() { 154 clearKeyboardGuidelineOverride() 155 keyboardGuideline?.setGuidelineEnd(navigationBarGuideline.guidelineEnd) 156 } 157 158 private fun getKeyboardHeight(): Int { 159 val height = if (isLandscape()) { 160 SignalStore.misc().keyboardLandscapeHeight 161 } else { 162 SignalStore.misc().keyboardPortraitHeight 163 } 164 165 val minHeight = resources.getDimensionPixelSize(R.dimen.default_custom_keyboard_size) 166 return if (height > minHeight) { 167 height 168 } else { 169 Log.w(TAG, "Saved keyboard height ($height) is too low, using default size ($minHeight)") 170 minHeight 171 } 172 } 173 174 private fun setKeyboardHeight(height: Int) { 175 if (isLandscape()) { 176 SignalStore.misc().keyboardLandscapeHeight = height 177 } else { 178 SignalStore.misc().keyboardPortraitHeight = height 179 } 180 } 181 182 private fun isLandscape(): Boolean { 183 val rotation = getDeviceRotation() 184 return rotation == Surface.ROTATION_90 185 } 186 187 @Suppress("DEPRECATION") 188 private fun getDeviceRotation(): Int { 189 if (isInEditMode) { 190 return Surface.ROTATION_0 191 } 192 193 if (Build.VERSION.SDK_INT >= 30) { 194 context.display?.getRealMetrics(displayMetrics) 195 } else { 196 ServiceUtil.getWindowManager(context).defaultDisplay.getRealMetrics(displayMetrics) 197 } 198 199 return if (displayMetrics.widthPixels > displayMetrics.heightPixels) Surface.ROTATION_90 else Surface.ROTATION_0 200 } 201 202 private val Guideline?.guidelineEnd: Int 203 get() = if (this == null) 0 else (layoutParams as LayoutParams).guideEnd 204 205 interface KeyboardStateListener { 206 fun onKeyboardShown() 207 fun onKeyboardHidden() 208 } 209 210 interface WindowInsetsListener { 211 fun onApplyWindowInsets(statusBar: Int, navigationBar: Int, parentStart: Int, parentEnd: Int) 212 } 213 214 /** 215 * Adjusts the [keyboardGuideline] to move with the IME keyboard opening or closing. 216 */ 217 private inner class KeyboardInsetAnimator : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) { 218 219 var animating = false 220 private set 221 222 private var startingGuidelineEnd: Int = 0 223 var endingGuidelineEnd: Int = 0 224 set(value) { 225 field = value 226 growing = value > startingGuidelineEnd 227 } 228 private var growing: Boolean = false 229 230 override fun onPrepare(animation: WindowInsetsAnimationCompat) { 231 if (overridingKeyboard) { 232 return 233 } 234 235 animating = true 236 startingGuidelineEnd = keyboardGuideline.guidelineEnd 237 } 238 239 override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat>): WindowInsetsCompat { 240 if (overridingKeyboard) { 241 return insets 242 } 243 244 val imeAnimation = runningAnimations.find { it.typeMask and WindowInsetsCompat.Type.ime() != 0 } 245 if (imeAnimation == null) { 246 return insets 247 } 248 249 val estimatedKeyboardHeight: Int = if (growing) { 250 endingGuidelineEnd * imeAnimation.interpolatedFraction 251 } else { 252 startingGuidelineEnd * (1f - imeAnimation.interpolatedFraction) 253 }.toInt() 254 255 if (growing) { 256 keyboardGuideline?.setGuidelineEnd(estimatedKeyboardHeight.coerceAtLeast(startingGuidelineEnd)) 257 } else { 258 keyboardGuideline?.setGuidelineEnd(estimatedKeyboardHeight.coerceAtLeast(endingGuidelineEnd)) 259 } 260 261 return insets 262 } 263 264 override fun onEnd(animation: WindowInsetsAnimationCompat) { 265 if (overridingKeyboard) { 266 return 267 } 268 269 keyboardGuideline?.setGuidelineEnd(endingGuidelineEnd) 270 animating = false 271 } 272 } 273}