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 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ - +