diff --git a/app/src/main/inkscape/ic_bufferlist_arrow_down.svg b/app/src/main/inkscape/ic_bufferlist_arrow_down.svg
new file mode 100644
index 00000000..6e5c8d15
--- /dev/null
+++ b/app/src/main/inkscape/ic_bufferlist_arrow_down.svg
@@ -0,0 +1,131 @@
+
+
diff --git a/app/src/main/inkscape/ic_bufferlist_arrow_up.svg b/app/src/main/inkscape/ic_bufferlist_arrow_up.svg
new file mode 100644
index 00000000..0d075846
--- /dev/null
+++ b/app/src/main/inkscape/ic_bufferlist_arrow_up.svg
@@ -0,0 +1,133 @@
+
+
diff --git a/app/src/main/inkscape/optimized/ic_bufferlist_arrow_down.svg b/app/src/main/inkscape/optimized/ic_bufferlist_arrow_down.svg
new file mode 100644
index 00000000..0823ab00
--- /dev/null
+++ b/app/src/main/inkscape/optimized/ic_bufferlist_arrow_down.svg
@@ -0,0 +1,5 @@
+
diff --git a/app/src/main/inkscape/optimized/ic_bufferlist_arrow_up.svg b/app/src/main/inkscape/optimized/ic_bufferlist_arrow_up.svg
new file mode 100644
index 00000000..0cf3dce8
--- /dev/null
+++ b/app/src/main/inkscape/optimized/ic_bufferlist_arrow_up.svg
@@ -0,0 +1,5 @@
+
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/WeechatActivity.kt b/app/src/main/java/com/ubergeek42/WeechatAndroid/WeechatActivity.kt
index ee3a15b7..7ac07c14 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/WeechatActivity.kt
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/WeechatActivity.kt
@@ -269,7 +269,6 @@ class WeechatActivity : AppCompatActivity(), CutePageChangeListener,
Network.get().register(this, null) // no callback, simply make sure that network info is correct while we are showing
EventBus.getDefault().register(this)
connectionState = EventBus.getDefault().getStickyEvent(StateChangedEvent::class.java).state
- updateHotCount(BufferList.totalHotMessageCount)
started = true
P.storeThemeOrColorSchemeColors(this)
applyColorSchemeToViews()
@@ -500,6 +499,7 @@ class WeechatActivity : AppCompatActivity(), CutePageChangeListener,
uiMenu = menu
updateMenuItems()
makeMenuReflectConnectionStatus()
+ // `onCreateOptionsMenu` is called *after* onStart, when `updateHotCount` was already called
updateHotCount(hotNumber)
return super.onCreateOptionsMenu(menu)
}
@@ -669,6 +669,8 @@ class WeechatActivity : AppCompatActivity(), CutePageChangeListener,
}
}
+ fun isBufferListVisible() = !slidy || isPagerNoticeablyObscured
+
// set the kitty image that appears when no pages are open
private var kittyImageResourceId = -1
@MainThread @Cat private fun setKittyImage(resourceId: Int) {
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/adapters/BufferListAdapter.kt b/app/src/main/java/com/ubergeek42/WeechatAndroid/adapters/BufferListAdapter.kt
index 15c4df66..1fb579ab 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/adapters/BufferListAdapter.kt
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/adapters/BufferListAdapter.kt
@@ -25,17 +25,20 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.ubergeek42.WeechatAndroid.R
-import com.ubergeek42.WeechatAndroid.adapters.BufferListAdapter.*
+import com.ubergeek42.WeechatAndroid.adapters.BufferListAdapter.VisualBuffer
import com.ubergeek42.WeechatAndroid.databinding.BufferlistItemBinding
import com.ubergeek42.WeechatAndroid.relay.Buffer
import com.ubergeek42.WeechatAndroid.relay.BufferList
import com.ubergeek42.WeechatAndroid.relay.BufferSpec
import com.ubergeek42.WeechatAndroid.service.P
import com.ubergeek42.WeechatAndroid.upload.main
+import com.ubergeek42.WeechatAndroid.utils.Constants.PREF_SORT_BUFFER_LIST_BY_HOT_MESSAGES_THEN_BY_NUMBER
+import com.ubergeek42.WeechatAndroid.utils.Constants.PREF_SORT_BUFFER_LIST_BY_HOT_MESSAGES_THEN_BY_OTHER_MESSAGES_THEN_BY_NUMBER
+import com.ubergeek42.WeechatAndroid.utils.Constants.PREF_SORT_BUFFER_LIST_BY_NUMBER
import com.ubergeek42.WeechatAndroid.utils.Utils
import com.ubergeek42.cats.Kitty
import com.ubergeek42.cats.Root
-import java.util.*
+import java.util.Collections
class BufferListAdapter(
@@ -135,12 +138,15 @@ class BufferListAdapter(
newBuffers.add(VisualBuffer.fromBuffer(buffer))
}
- if (P.sortBuffers) {
- Collections.sort(newBuffers, sortByHotAndMessageCountComparator)
- } else {
- Collections.sort(newBuffers, sortByHotCountAndNumberComparator)
+ val bufferComparator = when (P.sortBufferList) {
+ PREF_SORT_BUFFER_LIST_BY_NUMBER -> sortByNumberComparator
+ PREF_SORT_BUFFER_LIST_BY_HOT_MESSAGES_THEN_BY_NUMBER -> sortByHotMessagesThenByNumberComparator
+ PREF_SORT_BUFFER_LIST_BY_HOT_MESSAGES_THEN_BY_OTHER_MESSAGES_THEN_BY_NUMBER -> sortByHotMessagesThenByOtherMessagesThenByNumberComparator
+ else -> sortByNumberComparator
}
+ Collections.sort(newBuffers, bufferComparator)
+
val diffResult = DiffUtil.calculateDiff(DiffCallback(buffers, newBuffers), false)
main {
@@ -206,6 +212,23 @@ class BufferListAdapter(
}
}
+ //////////////////////////////////////////////////////////////////////////////////////////// Etc
+
+ @MainThread fun findNextHotBufferPositionOrNull(positionsToSearch: IntProgression) =
+ positionsToSearch.firstOrNull { position ->
+ val buffer = buffers.getOrElse(position) { return@firstOrNull false }
+ buffer.highlights > 0 || (buffer.type == BufferSpec.Type.Private && buffer.unreads != 0)
+ }
+
+ @MainThread fun findAllHotBufferIds() =
+ buffers.mapNotNull { buffer ->
+ val hot = buffer.highlights > 0 || (buffer.type == BufferSpec.Type.Private && buffer.unreads != 0)
+ if (hot) buffer.pointer else null
+ }
+
+ // Returns -1 if not found
+ @MainThread fun findPositionByBufferId(id: Long): Int = buffers.indexOfFirst { it.pointer == id }
+
companion object {
@Root private val kitty: Kitty = Kitty.make()
@@ -214,7 +237,11 @@ class BufferListAdapter(
}
-private val sortByHotCountAndNumberComparator = Comparator { left, right ->
+private val sortByNumberComparator = Comparator { left, right ->
+ left.number - right.number
+}
+
+private val sortByHotMessagesThenByNumberComparator = Comparator { left, right ->
val highlightDiff = right.highlights - left.highlights
if (highlightDiff != 0) return@Comparator highlightDiff
@@ -227,7 +254,7 @@ private val sortByHotCountAndNumberComparator = Comparator { left,
}
-private val sortByHotAndMessageCountComparator = Comparator { left, right ->
+private val sortByHotMessagesThenByOtherMessagesThenByNumberComparator = Comparator { left, right ->
val highlightDiff = right.highlights - left.highlights
if (highlightDiff != 0) return@Comparator highlightDiff
@@ -236,5 +263,8 @@ private val sortByHotAndMessageCountComparator = Comparator { left
val pmDiff = pmRight - pmLeft
if (pmDiff != 0) return@Comparator pmDiff
- right.unreads - left.unreads
+ val unreadsDiff = right.unreads - left.unreads
+ if (unreadsDiff != 0) return@Comparator unreadsDiff
+
+ left.number - right.number
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferListFragment.kt b/app/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferListFragment.kt
index aef670c9..8c931c92 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferListFragment.kt
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/fragments/BufferListFragment.kt
@@ -1,30 +1,30 @@
package com.ubergeek42.WeechatAndroid.fragments
import android.content.Context
-import com.ubergeek42.WeechatAndroid.relay.BufferListEye
-import com.ubergeek42.WeechatAndroid.WeechatActivity
-import com.ubergeek42.WeechatAndroid.adapters.BufferListAdapter
-import com.ubergeek42.cats.Cat
import android.os.Bundle
import android.view.LayoutInflater
-import android.view.ViewGroup
-import org.greenrobot.eventbus.EventBus
-import com.ubergeek42.WeechatAndroid.service.P
-import org.greenrobot.eventbus.Subscribe
-import com.ubergeek42.WeechatAndroid.service.Events.StateChangedEvent
-import com.ubergeek42.WeechatAndroid.service.RelayService
-import com.ubergeek42.WeechatAndroid.relay.BufferList
import android.view.View
+import android.view.ViewGroup
import androidx.annotation.AnyThread
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SuppressedLinearLayoutManager
+import com.ubergeek42.WeechatAndroid.WeechatActivity
+import com.ubergeek42.WeechatAndroid.adapters.BufferListAdapter
import com.ubergeek42.WeechatAndroid.databinding.BufferlistBinding
+import com.ubergeek42.WeechatAndroid.relay.BufferList
+import com.ubergeek42.WeechatAndroid.relay.BufferListEye
+import com.ubergeek42.WeechatAndroid.service.P
import com.ubergeek42.WeechatAndroid.upload.main
import com.ubergeek42.WeechatAndroid.utils.afterTextChanged
import com.ubergeek42.WeechatAndroid.views.BufferListFragmentFullScreenController
import com.ubergeek42.WeechatAndroid.views.FULL_SCREEN_DRAWER_ENABLED
import com.ubergeek42.WeechatAndroid.views.FullScreenDrawerLinearLayoutManager
+import com.ubergeek42.WeechatAndroid.views.jumpThenSmoothScrollCentering
+import com.ubergeek42.WeechatAndroid.views.scrollCenteringWithoutAnimation
+import com.ubergeek42.cats.Cat
import com.ubergeek42.cats.Kitty
import com.ubergeek42.cats.Root
@@ -34,6 +34,7 @@ class BufferListFragment : Fragment(), BufferListEye {
}
private lateinit var weechatActivity: WeechatActivity
+ private lateinit var layoutManager: LinearLayoutManager
private lateinit var adapter: BufferListAdapter
lateinit var ui: BufferlistBinding
@@ -58,7 +59,7 @@ class BufferListFragment : Fragment(), BufferListEye {
savedInstanceState: Bundle?): View {
ui = BufferlistBinding.inflate(inflater)
- val layoutManager = if (FULL_SCREEN_DRAWER_ENABLED) {
+ layoutManager = if (FULL_SCREEN_DRAWER_ENABLED) {
FullScreenDrawerLinearLayoutManager(requireContext(), ui.bufferList, adapter)
} else {
SuppressedLinearLayoutManager(requireContext())
@@ -66,11 +67,20 @@ class BufferListFragment : Fragment(), BufferListEye {
ui.bufferList.layoutManager = layoutManager
ui.bufferList.adapter = adapter
+ ui.bufferList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ showHideArrows()
+ }
+ })
+
+ ui.arrowUp.setOnClickListener { scrollUpToNextHotBuffer() }
+ ui.arrowDown.setOnClickListener { scrollDownToNextHotBuffer() }
ui.filterClear.setOnClickListener {
ui.filterInput.text = null
}
+ // The below callback will be called on view restoration
ui.filterInput.afterTextChanged {
applyFilter()
adapter.onBuffersChanged()
@@ -81,26 +91,19 @@ class BufferListFragment : Fragment(), BufferListEye {
@MainThread @Cat override fun onStart() {
super.onStart()
- EventBus.getDefault().register(this)
ui.filterInput.visibility = if (P.showBufferFilter) View.VISIBLE else View.GONE
applyColorSchemeToViews()
+ attachToBufferList()
}
@MainThread @Cat override fun onStop() {
super.onStop()
detachFromBufferList()
- EventBus.getDefault().unregister(this)
}
////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////// event
- ////////////////////////////////////////////////////////////////////////////////////////////////
-
- @Subscribe(sticky = true) @AnyThread @Cat fun onEvent(event: StateChangedEvent) {
- if (event.state.contains(RelayService.STATE.LISTED)) attachToBufferList()
- }
-
////////////////////////////////////////////////////////////////////////////////////// the juice
+ ////////////////////////////////////////////////////////////////////////////////////////////////
@AnyThread private fun attachToBufferList() {
BufferList.bufferListEye = this
@@ -116,23 +119,45 @@ class BufferListFragment : Fragment(), BufferListEye {
////////////////////////////////////////////////////////////////////////////////// BufferListEye
////////////////////////////////////////////////////////////////////////////////////////////////
- @Volatile private var hotCount = 0
-
- // if hot count has changed and > 0, scroll to top. note that if the scroll operation is not
- // posted, it cat try to scroll to the view that was at position 0 before the diff update
+ private var hotBufferIds: Collection = emptyList()
+
+ // If the drawer is hidden, and if we've got *new* hot buffers, pick one and center it.
+ // If the drawer is visible, do not scroll anywhere,
+ // instead relying on the arrows for hot buffer indication.
+ //
+ // To calculate the “diff”, we use buffer IDs, not their positions,
+ // otherwise a hot buffer that's been merely moved would appear as a new hot buffer.
+ //
+ // Note: the `main` block below is run on *every* change in buffers,
+ // but the adapter might skip some final updates (on main thread)
+ // if its `onBuffersChanged` is called again soon (usually, on another worker thread).
+ //
// todo don't update on every change?
// todo move hotlist updates to the activity
@AnyThread @Cat override fun onBuffersChanged() {
adapter.onBuffersChanged()
+ val hotBufferCount = BufferList.hotBufferCount
- val hotCount = BufferList.totalHotMessageCount
main {
- if (this.hotCount != hotCount) {
- this.hotCount = hotCount
- if (hotCount > 0) ui.bufferList.smoothScrollToPosition(0)
+ val newHotBufferIds = adapter.findAllHotBufferIds()
+
+ if (weechatActivity.isBufferListVisible()) {
+ showHideArrows()
+ } else {
+ val oneNewHotBufferId = newHotBufferIds.firstOrNull { it !in hotBufferIds }
+ if (oneNewHotBufferId != null) {
+ val position = adapter.findPositionByBufferId(oneNewHotBufferId)
+ if (position != -1) {
+ ui.bufferList.scrollCenteringWithoutAnimation(position)
+ }
+ } else {
+ showHideArrows()
+ }
}
- weechatActivity.updateHotCount(hotCount)
+
+ weechatActivity.updateHotCount(hotBufferCount)
weechatActivity.onBuffersChanged()
+ hotBufferIds = newHotBufferIds
}
}
@@ -140,6 +165,41 @@ class BufferListFragment : Fragment(), BufferListEye {
////////////////////////////////////////////////////////////////////////////////////////// other
////////////////////////////////////////////////////////////////////////////////////////////////
+ private fun findNextHotBufferPositionAboveVisibleBuffersOrNull(): Int? {
+ val firstVisibleBufferPosition = layoutManager.findFirstVisibleItemPosition()
+ val positionsToSearch = (firstVisibleBufferPosition - 1) downTo 0
+ return adapter.findNextHotBufferPositionOrNull(positionsToSearch)
+ }
+
+ private fun findNextHotBufferPositionBelowVisibleBuffersOrNull(): Int? {
+ val lastBufferPosition = adapter.itemCount - 1
+ val lastVisibleBufferPosition = layoutManager.findLastVisibleItemPosition()
+ val positionsToSearch = (lastVisibleBufferPosition + 1)..lastBufferPosition
+ return adapter.findNextHotBufferPositionOrNull(positionsToSearch)
+ }
+
+ private fun showHideArrows() {
+ val animate = weechatActivity.isBufferListVisible()
+ val showArrowUp = findNextHotBufferPositionAboveVisibleBuffersOrNull() != null
+ val showArrowDown = findNextHotBufferPositionBelowVisibleBuffersOrNull() != null
+ if (showArrowUp) ui.arrowUp.show(animate) else ui.arrowUp.hide(animate)
+ if (showArrowDown) ui.arrowDown.show(animate) else ui.arrowDown.hide(animate)
+ }
+
+ private fun scrollUpToNextHotBuffer() {
+ findNextHotBufferPositionAboveVisibleBuffersOrNull()?.let { position ->
+ ui.bufferList.jumpThenSmoothScrollCentering(position)
+ }
+ }
+
+ private fun scrollDownToNextHotBuffer() {
+ findNextHotBufferPositionBelowVisibleBuffersOrNull()?.let { position ->
+ ui.bufferList.jumpThenSmoothScrollCentering(position)
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+
@AnyThread private fun applyFilter() {
val text = ui.filterInput.text.toString()
adapter.setFilter(text, true)
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/relay/BufferList.kt b/app/src/main/java/com/ubergeek42/WeechatAndroid/relay/BufferList.kt
index 6ff2c0c5..0c5e7d67 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/relay/BufferList.kt
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/relay/BufferList.kt
@@ -61,8 +61,6 @@ object BufferList {
return buffers.firstOrNull { it.pointer == pointer }
}
- val totalHotMessageCount get() = buffers.sumOf { it.hotCount }
-
/////////////////////////////////////////////////////////////////////////////////////// handlers
private val handlers = ConcurrentHashMap()
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/service/P.java b/app/src/main/java/com/ubergeek42/WeechatAndroid/service/P.java
index d3d5d423..0acb9247 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/service/P.java
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/service/P.java
@@ -105,7 +105,7 @@ public static void storeThemeOrColorSchemeColors(Context context) {
final public static float _4dp = 4 * _1dp;
final public static float _200dp = 200 * _1dp;
- public static boolean sortBuffers;
+ public static String sortBufferList;
public static boolean filterBuffers;
public static boolean hideHiddenBuffers;
public static boolean optimizeTraffic;
@@ -158,7 +158,7 @@ static VolumeRole fromString(String value) {
@MainThread private static void loadUIPreferences() {
// buffer list preferences
- sortBuffers = p.getBoolean(PREF_SORT_BUFFERS, PREF_SORT_BUFFERS_D);
+ sortBufferList = p.getString(PREF_SORT_BUFFER_LIST, PREF_SORT_BUFFER_LIST_D);
filterBuffers = p.getBoolean(PREF_FILTER_NONHUMAN_BUFFERS, PREF_FILTER_NONHUMAN_BUFFERS_D);
hideHiddenBuffers = p.getBoolean(PREF_HIDE_HIDDEN_BUFFERS, PREF_HIDE_HIDDEN_BUFFERS_D);
optimizeTraffic = p.getBoolean(PREF_OPTIMIZE_TRAFFIC, PREF_OPTIMIZE_TRAFFIC_D); // okay this is out of sync with onChanged stuff—used for the bell icon
@@ -331,7 +331,7 @@ public static void loadServerKeyVerifier() {
@MainThread @Override @CatD public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
// buffer list preferences
- case PREF_SORT_BUFFERS: sortBuffers = p.getBoolean(key, PREF_SORT_BUFFERS_D); break;
+ case PREF_SORT_BUFFER_LIST: sortBufferList = p.getString(key, PREF_SORT_BUFFER_LIST_D); break;
case PREF_FILTER_NONHUMAN_BUFFERS: filterBuffers = p.getBoolean(key, PREF_FILTER_NONHUMAN_BUFFERS_D); break;
case PREF_HIDE_HIDDEN_BUFFERS: hideHiddenBuffers = p.getBoolean(key, PREF_HIDE_HIDDEN_BUFFERS_D); break;
case PREF_AUTO_HIDE_ACTIONBAR: autoHideActionbar = p.getBoolean(key, PREF_AUTO_HIDE_ACTIONBAR_D); break;
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/utils/Constants.java b/app/src/main/java/com/ubergeek42/WeechatAndroid/utils/Constants.java
index d46b7529..a6200559 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/utils/Constants.java
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/utils/Constants.java
@@ -85,8 +85,12 @@ public class Constants {
// buffer list
public static final String PREF_BUFFERLIST_GROUP = "bufferlist_group";
- public static final String PREF_SORT_BUFFERS = "sort_buffers";
- final public static boolean PREF_SORT_BUFFERS_D = false;
+ public static final String PREF_SORT_BUFFER_LIST = "sort_buffer_list";
+ public static final String PREF_SORT_BUFFER_LIST_BY_NUMBER = "by_number";
+ public static final String PREF_SORT_BUFFER_LIST_BY_HOT_MESSAGES_THEN_BY_NUMBER = "by_hot_messages_then_by_number";
+ public static final String PREF_SORT_BUFFER_LIST_BY_HOT_MESSAGES_THEN_BY_OTHER_MESSAGES_THEN_BY_NUMBER = "by_hot_messages_then_by_other_messages_then_by_number";
+ final public static String PREF_SORT_BUFFER_LIST_D = PREF_SORT_BUFFER_LIST_BY_NUMBER;
+
public static final String PREF_HIDE_HIDDEN_BUFFERS = "hide_hidden_buffers";
final public static boolean PREF_HIDE_HIDDEN_BUFFERS_D = true;
public static final String PREF_FILTER_NONHUMAN_BUFFERS = "filter_nonhuman_buffers";
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/views/CircularImageButton.kt b/app/src/main/java/com/ubergeek42/WeechatAndroid/views/FloatingImageButton.kt
similarity index 60%
rename from app/src/main/java/com/ubergeek42/WeechatAndroid/views/CircularImageButton.kt
rename to app/src/main/java/com/ubergeek42/WeechatAndroid/views/FloatingImageButton.kt
index d6e65bba..b980e5e4 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/views/CircularImageButton.kt
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/views/FloatingImageButton.kt
@@ -10,34 +10,53 @@ import android.view.ViewOutlineProvider
import android.view.animation.Animation
import android.view.animation.ScaleAnimation
import androidx.appcompat.widget.AppCompatImageButton
+import com.ubergeek42.WeechatAndroid.service.P
import com.ubergeek42.WeechatAndroid.upload.applicationContext
import com.ubergeek42.WeechatAndroid.upload.f
-class CircularImageButton @JvmOverloads constructor(
+open class FloatingImageButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : AppCompatImageButton(context, attrs) {
- init {
- background = null
- outlineProvider = pillOutlineProvider
- clipToOutline = true
- }
-
- fun show() {
+ fun show(animate: Boolean = true) {
if (visibility != VISIBLE) {
visibility = VISIBLE
- startAnimation(showAnimation)
+ if (animate) startAnimation(makeShowAnimation())
}
}
- fun hide() {
+ fun hide(animate: Boolean = true) {
if (visibility != INVISIBLE) {
visibility = INVISIBLE
- startAnimation(hideAnimation)
+ if (animate) startAnimation(makeHideAnimation())
}
}
+ private fun makeShowAnimation() = ScaleAnimation(
+ 0f, 1f, 0f, 1f,
+ Animation.RELATIVE_TO_SELF, 0.5f,
+ Animation.RELATIVE_TO_SELF, 0.5f)
+ .apply { duration = shortAnimTime }
+
+ private fun makeHideAnimation() = ScaleAnimation(
+ 1f, .5f, 1f, 0.5f,
+ Animation.RELATIVE_TO_SELF, 0.5f,
+ Animation.RELATIVE_TO_SELF, 0.5f)
+ .apply { duration = shortAnimTime / 2 }
+}
+
+
+class CircularImageButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+) : FloatingImageButton(context, attrs) {
+ init {
+ background = null
+ outlineProvider = pillOutlineProvider
+ clipToOutline = true
+ }
+
override fun setBackgroundColor(color: Int) {
if (backgroundPaint.color != color) {
backgroundPaint.color = color
@@ -51,18 +70,18 @@ class CircularImageButton @JvmOverloads constructor(
canvas.drawPaint(backgroundPaint)
super.onDraw(canvas)
}
+}
- private val showAnimation = ScaleAnimation(
- 0f, 1f, 0f, 1f,
- Animation.RELATIVE_TO_SELF, 0.5f,
- Animation.RELATIVE_TO_SELF, 0.5f)
- .apply { duration = animationDuration }
- private val hideAnimation = ScaleAnimation(
- 1f, .5f, 1f, 0.5f,
- Animation.RELATIVE_TO_SELF, 0.5f,
- Animation.RELATIVE_TO_SELF, 0.5f)
- .apply { duration = animationDuration }
+class RectangularImageButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+) : FloatingImageButton(context, attrs) {
+ init {
+ background = null
+ outlineProvider = RoundedRectangleOutlineProvider(10 * P._1dp)
+ clipToOutline = true
+ }
}
@@ -73,5 +92,12 @@ private val pillOutlineProvider = object : ViewOutlineProvider() {
}
-private val animationDuration = applicationContext
+class RoundedRectangleOutlineProvider(private val cornerRadius: Float) : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(0, 0, view.width, view.height, cornerRadius)
+ }
+}
+
+
+private val shortAnimTime = applicationContext
.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/views/FullScreenActivityController.kt b/app/src/main/java/com/ubergeek42/WeechatAndroid/views/FullScreenActivityController.kt
index 297ac034..f74f2480 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/views/FullScreenActivityController.kt
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/views/FullScreenActivityController.kt
@@ -148,6 +148,8 @@ class BufferListFragmentFullScreenController(val fragment: BufferListFragment) :
if (!FULL_SCREEN_DRAWER_ENABLED) {
fragment.ui.bufferList.updateMargins(
bottom = if (P.showBufferFilter) filterBarHeight else 0)
+ fragment.ui.arrowDown.updateMargins(
+ bottom = if (P.showBufferFilter) filterBarHeight else 0)
} else {
insetListeners.add(insetListener)
insetListener.onInsetsChanged()
@@ -163,8 +165,11 @@ class BufferListFragmentFullScreenController(val fragment: BufferListFragment) :
val ui = fragment.ui
val layoutManager = ui.bufferList.layoutManager as? FullScreenDrawerLinearLayoutManager
+ ui.arrowUp.updateMargins(top = windowInsets.top)
+
if (P.showBufferFilter) {
ui.bufferList.updateMargins(bottom = filterBarHeight + windowInsets.bottom)
+ ui.arrowDown.updateMargins(bottom = filterBarHeight + windowInsets.bottom)
ui.filterInput.updateMargins(bottom = windowInsets.bottom)
ui.filterInput.updatePadding(left = windowInsets.left)
@@ -176,6 +181,7 @@ class BufferListFragmentFullScreenController(val fragment: BufferListFragment) :
windowInsets.left)
} else {
ui.bufferList.updateMargins(bottom = 0)
+ ui.arrowDown.updateMargins(bottom = windowInsets.bottom)
layoutManager?.setInsets(windowInsets.top,
windowInsets.bottom,
diff --git a/app/src/main/java/com/ubergeek42/WeechatAndroid/views/ViewUtils.kt b/app/src/main/java/com/ubergeek42/WeechatAndroid/views/ViewUtils.kt
index d2483a23..2e137037 100644
--- a/app/src/main/java/com/ubergeek42/WeechatAndroid/views/ViewUtils.kt
+++ b/app/src/main/java/com/ubergeek42/WeechatAndroid/views/ViewUtils.kt
@@ -83,6 +83,17 @@ fun RecyclerView.jumpThenSmoothScrollCentering(position: Int) {
}
+// A bit hacky, but should be safe. Assumes that height of all children is constant.
+fun RecyclerView.scrollCenteringWithoutAnimation(position: Int) {
+ val layoutManager = layoutManager as? LinearLayoutManager ?: return
+ val childHeight = getChildAt(0)?.height ?: 0
+ val originalItemAnimator = itemAnimator
+ itemAnimator = null
+ layoutManager.scrollToPositionWithOffset(position, height / 2 - childHeight / 2 )
+ post { itemAnimator = originalItemAnimator }
+}
+
+
private class CenteringSmoothScroller(context: Context) : LinearSmoothScroller(context) {
override fun calculateDtToFit(viewStart: Int, viewEnd: Int,
boxStart: Int, boxEnd: Int,
diff --git a/app/src/main/res/drawable/bg_bufferlist_item_hot.xml b/app/src/main/res/drawable/bg_bufferlist_item_hot.xml
index d4ca78c4..7fe6e678 100644
--- a/app/src/main/res/drawable/bg_bufferlist_item_hot.xml
+++ b/app/src/main/res/drawable/bg_bufferlist_item_hot.xml
@@ -1,14 +1,14 @@
- -
+
-
-
+
-
-
+
diff --git a/app/src/main/res/drawable/bg_bufferlist_item_warm.xml b/app/src/main/res/drawable/bg_bufferlist_item_warm.xml
index e9667fdd..8f6caf13 100644
--- a/app/src/main/res/drawable/bg_bufferlist_item_warm.xml
+++ b/app/src/main/res/drawable/bg_bufferlist_item_warm.xml
@@ -1,14 +1,14 @@
- -
+
-
-
+
-
-
+
diff --git a/app/src/main/res/drawable/ic_bufferlist_arrow_down.xml b/app/src/main/res/drawable/ic_bufferlist_arrow_down.xml
new file mode 100644
index 00000000..9afba19d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bufferlist_arrow_down.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_bufferlist_arrow_up.xml b/app/src/main/res/drawable/ic_bufferlist_arrow_up.xml
new file mode 100644
index 00000000..3e774557
--- /dev/null
+++ b/app/src/main/res/drawable/ic_bufferlist_arrow_up.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/bufferlist.xml b/app/src/main/res/layout/bufferlist.xml
index dccc79db..3b921ded 100644
--- a/app/src/main/res/layout/bufferlist.xml
+++ b/app/src/main/res/layout/bufferlist.xml
@@ -15,6 +15,26 @@
android:fadingEdge="none"
android:scrollbars="vertical" />
+
+
+
+
Buffer-Liste
-
- Liste der Buffer sortieren
-
- Sortierung nach Anzahl der Highlights/privaten Nachrichten/ungelesenen Nachrichten
-
Ausblenden von Buffern in denen man nicht schreibt
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 1ba2625d..4f5f0748 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -569,11 +569,6 @@
Liste des tampons
-
- Trier la liste des tampons
-
- Trier par le nombre de messages notables/messages privés/messages non lus
-
Cacher les tampons hors salons
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 41c52788..6c0baf74 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -575,11 +575,6 @@
Список буферов
-
- Сортировать список буферов
-
- Сортировать по количество упоминаний и новых сообщений в приватных буферах
-
Скрывать буферы без людей
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index aea8920f..185b0308 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -577,10 +577,13 @@
Buffer list
-
+
Sort buffer list
-
- Sort by number of highlights/private messages/unread messages
+
+ - By number
+ - By hot messages, then by number
+ - By hot messages, then by other messages, then by number
+
Hide non-conversation buffers
diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml
index 832b95ec..23016d78 100644
--- a/app/src/main/res/values/values.xml
+++ b/app/src/main/res/values/values.xml
@@ -3,6 +3,12 @@
+
+ - by_number
+ - by_hot_messages_then_by_number
+ - by_hot_messages_then_by_other_messages_then_by_number
+
+
- left
- right
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index e598ed70..c93c0df0 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -201,11 +201,13 @@
-
+