Fix chat layout issues on some android 15 devices

This commit is contained in:
Phillip Thelen 2025-03-14 13:46:04 +01:00
parent 6125f90577
commit 33bb40370b
11 changed files with 129 additions and 54 deletions

View file

@ -32,7 +32,6 @@
<activity
android:name=".ui.activities.MainActivity"
android:theme="@style/LaunchAppTheme"
android:windowSoftInputMode="stateHidden|adjustResize"
android:configChanges="screenSize | smallestScreenSize | screenLayout"
android:exported="true">
<nav-graph android:value="@navigation/navigation" />

View file

@ -1,14 +1,16 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.habitrpg.android.habitica.ui.helpers.RecyclerViewEmptySupport
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:windowInsetBottom="false" />
<com.habitrpg.android.habitica.ui.views.social.ChatBarView
android:id="@+id/chatBarView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:id="@+id/chatBarView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View file

@ -1,14 +1,16 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.habitrpg.android.habitica.ui.helpers.RecyclerViewEmptySupport
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.habitrpg.android.habitica.ui.helpers.RecyclerViewEmptySupport
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
android:layout_weight="1"
app:windowInsetBottom="false" />
<com.habitrpg.android.habitica.ui.views.social.ChatBarView
android:id="@+id/chatBarView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
android:id="@+id/chatBarView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View file

@ -150,4 +150,10 @@
<declare-styleable name="HabiticaScrollView">
<attr name="skipDirectchild" format="boolean" />
</declare-styleable>
<declare-styleable name="RecyclerViewEmptySupport">
<attr name="windowInsetTop" format="boolean" />
<attr name="windowInsetStart" format="boolean" />
<attr name="windowInsetEnd" format="boolean" />
<attr name="windowInsetBottom" format="boolean" />
</declare-styleable>
</resources>

View file

@ -42,6 +42,7 @@ import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.getThemeColor
@ -145,8 +146,11 @@ abstract class BaseActivity : AppCompatActivity() {
ViewCompat.setOnApplyWindowInsetsListener(appbar) { v, windowInsets ->
val insets = windowInsets.getInsets(
WindowInsetsCompat.Type.systemBars()
+ WindowInsetsCompat.Type.displayCutout()
+ WindowInsetsCompat.Type.displayCutout()
)
val isImeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom
KeyboardUtil.updatePaddingForIme(isImeVisible, imeHeight, insets)
v.updatePadding(top = insets.top + paddingTop)
(v as AppBarLayout).children.forEach {
if (it !is TabLayout) {

View file

@ -145,13 +145,6 @@ class InboxMessageListFragment : BaseMainFragment<FragmentInboxMessageListBindin
super.onResume()
}
override fun onAttach(context: Context) {
view?.invalidate()
view?.forceLayout()
super.onAttach(context)
}
override fun onDestroy() {
socialRepository.close()
super.onDestroy()

View file

@ -65,14 +65,12 @@ class PartyFragment : BaseMainFragment<FragmentViewpagerBinding>() {
updateGroupUI(it)
}
binding?.viewPager?.currentItem = 0
setViewPagerAdapter()
arguments?.let {
val args = PartyFragmentArgs.fromBundle(it)
binding?.viewPager?.post {
binding?.viewPager?.currentItem = args.tabToOpen
binding?.viewPager?.currentItem = savedInstanceState?.getInt("tab") ?: args.tabToOpen
}
if (args.partyID?.isNotBlank() == true) {
viewModel.setGroupID(args.partyID ?: "")
@ -84,6 +82,11 @@ class PartyFragment : BaseMainFragment<FragmentViewpagerBinding>() {
viewModel.retrieveGroup {}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("tab", binding?.viewPager?.currentItem ?: 0)
}
override fun onResume() {
if (viewModel.isLeader) {
this.tutorialStepIdentifier = "party"

View file

@ -17,10 +17,18 @@ package com.habitrpg.android.habitica.ui.helpers
*/
import android.app.Activity
import android.util.Log
import android.view.inputmethod.InputMethodManager
import androidx.core.graphics.Insets
interface OnImeVisibilityChangedListener {
fun onImeVisibilityChanged(visible: Boolean, height: Int, safeInsets: Insets)
}
class KeyboardUtil {
companion object {
private val imeListeners = mutableListOf<OnImeVisibilityChangedListener>()
fun dismissKeyboard(act: Activity?) {
if (act != null && act.currentFocus != null) {
val inputMethodManager =
@ -28,6 +36,31 @@ class KeyboardUtil {
inputMethodManager?.hideSoftInputFromWindow(act.currentFocus?.windowToken, 0)
}
}
private var lastVisible = false
private var lastHeight = 0
fun updatePaddingForIme(isVisible: Boolean, height: Int, safeInsets: Insets) {
if (lastVisible == isVisible && lastHeight == height) {
return
}
lastVisible = isVisible
lastHeight = height
Log.e("KeyboardUtil", "updatePaddingForIme: isVisible = $isVisible, height = $height")
for (listener in imeListeners) {
Log.e("KeyboardUtil", "updatePaddingForIme: listener = $listener")
listener.onImeVisibilityChanged(isVisible, height, safeInsets)
}
}
fun addImeVisibilityListener(listener: OnImeVisibilityChangedListener) {
Log.e("KeyboardUtil", "addImeVisibilityListener: listener = $listener")
imeListeners.add(listener)
}
fun removeImeVisibilityListener(listener: OnImeVisibilityChangedListener) {
Log.e("KeyboardUtil", "removeImeVisibilityListener: listener = $listener")
imeListeners.remove(listener)
}
}
}

View file

@ -6,6 +6,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.consumeWindowInsetsAbove30
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.RecyclerViewState
@ -50,22 +51,35 @@ constructor(
private var actualAdapter: Adapter<*>? = null
private val emptyAdapter = RecyclerViewStateAdapter()
private var windowInsetTop = false
private var windowInsetBottom = true
private var windowInsetStart = true
private var windowInsetEnd = true
init {
context.theme?.obtainStyledAttributes(attrs, R.styleable.RecyclerViewEmptySupport, 0, 0)?.let {
windowInsetTop = it.getBoolean(R.styleable.RecyclerViewEmptySupport_windowInsetTop, false)
windowInsetBottom = it.getBoolean(R.styleable.RecyclerViewEmptySupport_windowInsetBottom, true)
windowInsetStart = it.getBoolean(R.styleable.RecyclerViewEmptySupport_windowInsetStart, true)
windowInsetEnd = it.getBoolean(R.styleable.RecyclerViewEmptySupport_windowInsetEnd, true)
}
clipToPadding = false
val topPadding = paddingTop
val bottomPadding = paddingBottom
val leftPadding = paddingLeft
val rightPadding = paddingRight
ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
val bars = insets.getInsets(
WindowInsetsCompat.Type.systemBars()
or WindowInsetsCompat.Type.displayCutout()
)
v.updatePadding(
left = bars.left + leftPadding,
top = topPadding,
right = bars.right + rightPadding,
bottom = bars.bottom + bottomPadding,
left = (if (windowInsetStart) bars.left else 0) + leftPadding,
top = (if (windowInsetTop) bars.top else 0) + topPadding,
right = (if (windowInsetEnd) bars.right else 0) + rightPadding,
bottom = (if (windowInsetBottom) bars.bottom else 0) + bottomPadding,
)
consumeWindowInsetsAbove30(insets)
}

View file

@ -3,12 +3,14 @@ package com.habitrpg.android.habitica.ui.views.social
import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
@ -19,11 +21,13 @@ import com.habitrpg.android.habitica.extensions.consumeWindowInsetsAbove30
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.ui.helpers.AutocompleteAdapter
import com.habitrpg.android.habitica.ui.helpers.AutocompleteTokenizer
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
import com.habitrpg.android.habitica.ui.helpers.OnImeVisibilityChangedListener
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.helpers.MainNavigationController
class ChatBarView : LinearLayout {
class ChatBarView : LinearLayout, OnImeVisibilityChangedListener {
var hasAcceptedGuidelines: Boolean = false
set(value) {
field = value
@ -62,6 +66,9 @@ class ChatBarView : LinearLayout {
get() = binding.chatEditText.text.toString()
set(value) = binding.chatEditText.setText(value, TextView.BufferType.EDITABLE)
private var safeInsets: Insets = Insets.NONE
private var imeHeight: Int = 0
constructor(context: Context) : super(context) {
setupView()
}
@ -108,19 +115,13 @@ class ChatBarView : LinearLayout {
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
override fun onWindowVisibilityChanged(visibility: Int) {
super.onWindowVisibilityChanged(visibility)
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
val safePadding = insets.getInsets(WindowInsetsCompat.Type.systemBars()
or WindowInsetsCompat.Type.displayCutout())
updatePadding(
left = safePadding.left,
right = safePadding.right,
bottom = if (imeVisible) imeHeight else safePadding.bottom)
consumeWindowInsetsAbove30(insets)
if (visibility == VISIBLE) {
KeyboardUtil.addImeVisibilityListener(this)
} else {
KeyboardUtil.removeImeVisibilityListener(this)
}
}
@ -160,4 +161,22 @@ class ChatBarView : LinearLayout {
sendAction?.invoke(chatText)
}
}
override fun onImeVisibilityChanged(visible: Boolean, height: Int, safeInsets: Insets) {
this.safeInsets = safeInsets
imeHeight = if (visible) {
height
} else {
0
}
applyAllPadding()
}
private fun applyAllPadding() {
Log.e("ChatBarView", "applyAllPadding: safeInsets = $safeInsets, imeHeight = $imeHeight")
updatePadding(
left = safeInsets.left,
right = safeInsets.right,
bottom = if (imeHeight > 0) imeHeight else safeInsets.bottom)
}
}

View file

@ -1,6 +1,6 @@
New in 4.6.0
- Improvements to reminder scheduling to help them appear on time
- Reminders should no longer appear if a task isn't due that day
- Your avatar now shows at the top of the screen when choosing equipment, letting you see how you look as you try gear on!
- We made it easier to choose what to do with tasks from broken Challenges so they don't get stuck on your task list
- You'll now be able to leave your Party if you're participating in an active Quest
New in 4.7.2
- Upgraded to the latest Google Sign In authentication standards
- Implemented full edge-to-edge display functionality on Android 11+ devices
- Fixed some issues where the text box in chat wasn't adjusting properly
- More support for landscape mode
- Various other bug fixes and improvements