Skip to content

Commit

Permalink
Merge pull request #557 from ubergeek42/volume-btn-roles-new
Browse files Browse the repository at this point in the history
Volume buttons: implement Weechat-like input history rather than sent history
  • Loading branch information
mhoran authored Feb 5, 2023
2 parents 071af84 + 24a6c21 commit 1501f87
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ class BufferFragment : Fragment(), BufferEye {
savedInstanceState?.let {
restoreRecyclerViewState(it)
restoreSearchState(it)
restoreHistoryState(it)
}
}

Expand Down Expand Up @@ -281,7 +280,6 @@ class BufferFragment : Fragment(), BufferEye {
super.onSaveInstanceState(outState)
saveRecyclerViewState(outState)
saveSearchState(outState)
saveHistoryState(outState)
}

////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -518,15 +516,15 @@ class BufferFragment : Fragment(), BufferEye {
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP -> {
val up = keyCode == KeyEvent.KEYCODE_VOLUME_UP
when (P.volumeRole) {
P.VolumeRole.TEXT_SIZE -> {
P.VolumeRole.ChangeTextSize -> {
val change = if (up) 1f else -1f
val textSize = (P.textSize + change).coerceIn(5f, 30f)
P.setTextSizeColorAndLetterWidth(textSize)
return@OnKeyListener true
}
P.VolumeRole.SEND_HISTORY -> {
ulet(ui?.chatInput) {
history.navigateOffset(it, if (up) 1 else -1)
P.VolumeRole.NavigateInputHistory -> {
ui?.chatInput?.let {
P.history.navigate(it, if (up) History.Direction.Older else History.Direction.Newer)
}
return@OnKeyListener true
}
Expand All @@ -550,6 +548,7 @@ class BufferFragment : Fragment(), BufferEye {
assertThat(buffer).isEqualTo(linesAdapter?.buffer)
if (connectedToRelayAndSynced) {
SendMessageEvent.fireInput(buffer, input.text.toString())
P.history.reset(input)
input.setText("") // this will reset tab completion
} else {
Toaster.ErrorToast.show(R.string.error__etc__not_connected)
Expand Down Expand Up @@ -956,20 +955,6 @@ class BufferFragment : Fragment(), BufferEye {
}
}

////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// history
////////////////////////////////////////////////////////////////////////////////////////////////

private val history = History()

private fun saveHistoryState(outState: Bundle) {
outState.putBundle(KEY_HISTORY, history.save())
}

private fun restoreHistoryState(savedInstanceState: Bundle) {
savedInstanceState.getBundle(KEY_HISTORY)?.let { history.restore(it) }
}

////////////////////////////////////////////////////////////////////////////////////////////////

private fun setPendingInputForParallelFragments() = ulet(buffer, ui) { buffer, ui ->
Expand Down
36 changes: 31 additions & 5 deletions app/src/main/java/com/ubergeek42/WeechatAndroid/service/P.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.ubergeek42.WeechatAndroid.relay.BufferList;
import com.ubergeek42.WeechatAndroid.upload.UploadConfigKt;
import com.ubergeek42.WeechatAndroid.utils.Constants;
import com.ubergeek42.WeechatAndroid.utils.History;
import com.ubergeek42.WeechatAndroid.utils.MigratePreferences;
import com.ubergeek42.WeechatAndroid.utils.ThemeFix;
import com.ubergeek42.WeechatAndroid.utils.Utils;
Expand Down Expand Up @@ -125,7 +126,25 @@ public static void storeThemeOrColorSchemeColors(Context context) {

public static boolean showSend, showTab, showPaperclip, hotlistSync;

public enum VolumeRole {NONE, TEXT_SIZE, SEND_HISTORY};
public enum VolumeRole {
DoNothing("none"),
ChangeTextSize("change_text_size"),
NavigateInputHistory("navigate_input_history");

public final String value;

VolumeRole(String value) {
this.value = value;
}

static VolumeRole fromString(String value) {
for (VolumeRole role : VolumeRole.values()) {
if (role.value.equals(value)) return role;
}
// Should never be reached.
return VolumeRole.ChangeTextSize;
}
}
public static VolumeRole volumeRole;

public static boolean showBufferFilter;
Expand Down Expand Up @@ -170,7 +189,7 @@ public enum VolumeRole {NONE, TEXT_SIZE, SEND_HISTORY};
showTab = p.getBoolean(PREF_SHOW_TAB, PREF_SHOW_TAB_D);
showPaperclip = p.getBoolean(PREF_SHOW_PAPERCLIP, PREF_SHOW_PAPERCLIP_D);
hotlistSync = p.getBoolean(PREF_HOTLIST_SYNC, PREF_HOTLIST_SYNC_D);
volumeRole = VolumeRole.values()[Integer.parseInt(p.getString(PREF_VOLUME_ROLE, PREF_VOLUME_ROLE_D))];
volumeRole = VolumeRole.fromString(p.getString(PREF_VOLUME_ROLE, PREF_VOLUME_ROLE_D));

// buffer list filter
showBufferFilter = p.getBoolean(PREF_SHOW_BUFFER_FILTER, PREF_SHOW_BUFFER_FILTER_D);
Expand Down Expand Up @@ -368,7 +387,7 @@ public static void loadServerKeyVerifier() {
case PREF_SHOW_TAB: showTab = p.getBoolean(key, PREF_SHOW_TAB_D); break;
case PREF_SHOW_PAPERCLIP: showPaperclip = p.getBoolean(key, PREF_SHOW_PAPERCLIP_D); break;
case PREF_HOTLIST_SYNC: hotlistSync = p.getBoolean(key, PREF_HOTLIST_SYNC_D); break;
case PREF_VOLUME_ROLE: volumeRole = VolumeRole.values()[Integer.parseInt(p.getString(PREF_VOLUME_ROLE, PREF_VOLUME_ROLE_D))]; break;
case PREF_VOLUME_ROLE: volumeRole = VolumeRole.fromString(p.getString(PREF_VOLUME_ROLE, PREF_VOLUME_ROLE_D)); break;

// buffer list fragment
case PREF_SHOW_BUFFER_FILTER: showBufferFilter = p.getBoolean(key, PREF_SHOW_BUFFER_FILTER_D); break;
Expand Down Expand Up @@ -439,11 +458,11 @@ static void setServiceAlive(boolean alive) {

// protocol must be changed each time anything that uses the following function changes
// needed to make sure nothing crashes if we cannot restore the data
private static final int PROTOCOL_ID = 18;
private static final int PROTOCOL_ID = 19;

@AnyThread @Cat public static void saveStuff() {
for (Buffer buffer : BufferList.buffers) saveLastReadLine(buffer);
String data = Utils.serialize(new Object[]{openBuffers, bufferToLastReadLine, sentMessages});
String data = Utils.serialize(new Object[]{openBuffers, bufferToLastReadLine, sentMessages, history});
p.edit().putString(PREF_DATA, data).putInt(PREF_PROTOCOL_ID, PROTOCOL_ID).apply();
}

Expand All @@ -456,6 +475,7 @@ static void setServiceAlive(boolean alive) {
if (array[0] instanceof LinkedHashSet) openBuffers = (LinkedHashSet<Long>) array[0];
if (array[1] instanceof LinkedHashMap) bufferToLastReadLine = (LinkedHashMap<Long, BufferHotData>) array[1];
if (array[2] instanceof LinkedList) sentMessages = (LinkedList<String>) array[2];
if (array[3] instanceof History) history = (History) array[3];
}

////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -527,6 +547,12 @@ static void addSentMessage(String line) {
sentMessages.pop();
}

////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// input history
////////////////////////////////////////////////////////////////////////////////////////////////

public static History history = new History();

////////////////////////////////////////////////////////////////////////////////////////////////

private static String getString(String key, String defValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public class Constants {
public final static String PREF_SHOW_TAB = "tabbtn_show";
final public static boolean PREF_SHOW_TAB_D = true;
public final static String PREF_VOLUME_ROLE = "buttons__volume";
final public static String PREF_VOLUME_ROLE_D = String.valueOf(P.VolumeRole.TEXT_SIZE.ordinal());
final public static String PREF_VOLUME_ROLE_D = P.VolumeRole.ChangeTextSize.value;

final public static String PREF_SHOW_PAPERCLIP = "buttons__show_paperclip";
final public static boolean PREF_SHOW_PAPERCLIP_D = true;
Expand Down
145 changes: 101 additions & 44 deletions app/src/main/java/com/ubergeek42/WeechatAndroid/utils/History.kt
Original file line number Diff line number Diff line change
@@ -1,54 +1,111 @@
package com.ubergeek42.WeechatAndroid.utils

import android.os.Bundle
import android.text.Editable
import android.os.Parcel
import android.widget.EditText
import androidx.core.os.bundleOf
import com.ubergeek42.WeechatAndroid.service.P

class History {
private var index = -1
private var userInput: Editable? = null
private var selectionStart: Int? = null
private var selectionEnd: Int? = null

fun navigateOffset(editText: EditText, offset: Int) {
if (index == -1) {
userInput = editText.text
selectionStart = editText.selectionStart
selectionEnd = editText.selectionEnd
}
val newIndex = index + offset
messageAt(newIndex)?.let {
editText.text = it
editText.post {
if (newIndex == -1) {
// Restore user selection.
editText.setSelection(selectionStart ?: 0, selectionEnd ?: editText.length())
} else {
// Go to end for sent messages.
editText.setSelection(editText.length())
}
import androidx.core.text.getSpans
import com.ubergeek42.WeechatAndroid.R
import com.ubergeek42.WeechatAndroid.upload.ShareSpan

class History : java.io.Serializable {
companion object {
// Sentinel index representing the most recent end of the message history.
private const val END = -1

private const val MAX_MESSAGE_LENGTH = 2000
private const val MAX_HISTORY_LENGTH = 40
}

class Message : java.io.Serializable {
lateinit var content: String
var selectionStart: Int = 0
var selectionEnd: Int = 0

constructor(editText: EditText) {
updateFromEdit(editText)
}

constructor(source: Parcel) {
content = source.readString() ?: ""
selectionStart = source.readInt()
selectionStart = source.readInt()
}


fun updateFromEdit(editText: EditText) {
content = Utils.cut(editText.text.toString(), MAX_MESSAGE_LENGTH)
// Input length might be shorter because of the text ellipsis from above.
selectionStart = minOf(content.length, editText.selectionStart)
selectionEnd = minOf(content.length, editText.selectionEnd)
}

fun applyToEdit(editText: EditText) {
editText.setText(content)
editText.setSelection(selectionStart, selectionEnd)
}
}

private var messages: MutableList<Message> = mutableListOf()
private var index = END

enum class Direction { Older, Newer }

// Save message iff it is not empty and not equal to the most recent message.
// Will remove the oldest message if the message size goes above the maximum history size.
private fun maybeSaveMessage(editText: EditText): Boolean {
val message = Message(editText)
val content = message.content
if (content.trim().isNotEmpty()) {
val last = messages.getOrNull(0)?.content
if (last?.equalsIgnoringUselessSpans(content) != true) {
messages.add(0, message)
if (messages.size > MAX_HISTORY_LENGTH) messages.removeLast()
return true
}
index = newIndex
}
return false
}

fun save(): Bundle = bundleOf(
Pair("index", index),
Pair("userInput", userInput),
Pair("selStart", selectionStart),
Pair("selEnd", selectionEnd),
)

fun restore(bundle: Bundle) {
index = bundle.getInt("index", -1)
userInput = bundle.get("userInput") as Editable?
selectionStart = bundle.getInt("selStart", -1).let { if (it == -1) null else it }
selectionEnd = bundle.getInt("selEnd", -1).let { if (it == -1) null else it }
private fun shareSpanCount(editText: EditText): Int = editText.text.getSpans<ShareSpan>().size

fun navigate(editText: EditText, direction: Direction) {
val spc = shareSpanCount(editText)
if (spc > 0) {
// The editText contains non-uploaded ShareSpans that would be lost by navigating away.
// Bail out early to prevent data loss.
Toaster.ErrorToast.show(
editText.context.resources.getQuantityText(
R.plurals.error__history_with_sharespans,
spc
).toString()
)
return
}
var newIndex = when (direction) {
Direction.Older -> index + 1
Direction.Newer -> index - 1
}
if (index == END) {
// Not in history yet. Push current edit to history.
val wasInserted = maybeSaveMessage(editText)
// Special case when saving from end and going older: since we've just inserted, we need
// to skip over the message that was just saved.
if (wasInserted && direction == Direction.Older) newIndex += 1
} else {
// In history already. Save changes before navigating away.
messages[index].updateFromEdit(editText)
}
if (newIndex < END) newIndex = END
if (newIndex >= messages.size) newIndex = messages.size - 1
if (newIndex == END) {
if (editText.text.isNotEmpty()) editText.setText("")
} else if (newIndex != index) {
messages[newIndex].applyToEdit(editText)
}
index = newIndex
}

private fun messageAt(index: Int): Editable? =
if (index == -1) userInput else P.sentMessages.getOrNull(P.sentMessages.size - 1 - index)
?.let { Editable.Factory.getInstance().newEditable(it) }
fun reset(editText: EditText) {
maybeSaveMessage(editText)
index = END
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ class MigratePreferences(val context: Context) {
val volumeChangesSize = preferences.getBoolean(Constants.Deprecated.PREF_VOLUME_BTN_SIZE, Constants.Deprecated.PREF_VOLUME_BTN_SIZE_D)
preferences.edit {
this.remove(Constants.Deprecated.PREF_VOLUME_BTN_SIZE)
this.putString(Constants.PREF_VOLUME_ROLE, (if (volumeChangesSize) VolumeRole.TEXT_SIZE else VolumeRole.NONE).ordinal.toString(10))
this.putString(
Constants.PREF_VOLUME_ROLE,
(if (volumeChangesSize) VolumeRole.ChangeTextSize else VolumeRole.DoNothing).value
)
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -703,9 +703,10 @@
wenn der Büroklammer-Taste versteckt wurde,
um mehr Platz für das Eingabefeld zu schaffen.</string>

<string name="pref_buttons_volume__actions__none">Nichts tun</string>
<string name="pref_buttons_volume__actions__text_size">Lautstärketasten ändern die Textgröße</string>
<string name="pref_buttons_volume__actions__history">Navigieren im Sendeverlauf</string>
<string name="pref__buttons__volume__title">Lautstärketasten</string>
<string name="pref__buttons__volume__actions__none">Nichts tun</string>
<string name="pref__buttons__volume__actions__change_text_size">Lautstärketasten ändern die Textgröße</string>
<string name="pref__buttons__volume__actions__navigate_input_history">Navigieren Sie durch den Eingabeverlauf</string>

<!-- ####################################################################################### -->
<!-- #################################### notifications #################################### -->
Expand Down
14 changes: 11 additions & 3 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@
<string name="error__etc__cannot_share_to_empty_buffer_list">Aucun tampon disponible pour partager</string>
<string name="error__etc__activity_not_found_for_url">Activité introuvable pour “l’intent” %s</string>

<plurals name="error__history_with_sharespans">
<item quantity="one" tools:ignore="ImpliedQuantity">Supprimez ou uploadez l’image pour naviguer</item>
<item quantity="other">Supprimez ou uploadez les images pour naviguer</item>
<item quantity="many">Supprimez ou uploadez les images pour naviguer</item>
</plurals>

<!-- ############################## main activity ui elements ############################## -->

<string name="ui__button_paperclip">Téléverser un fichier</string>
Expand Down Expand Up @@ -691,9 +697,11 @@
Quand le bouton «&#8239;trombone&#8239;» devient invisible pour laisser de la place au champ de saisie,
il reste possible de joindre des fichiers via le menu déroulant.</string>

<string name="pref_buttons_volume__actions__none">Ne rien faire</string>
<string name="pref_buttons_volume__actions__text_size">Changer la taille du texte</string>
<string name="pref_buttons_volume__actions__history">Naviguer dans les messages envoyés</string>
<string name="pref__buttons__volume__title">Les boutons de volume</string>
<string name="pref__buttons__volume__actions__none">Ne font rien de spécial</string>
<string name="pref__buttons__volume__actions__change_text_size">Changent la taille du texte</string>
<string name="pref__buttons__volume__actions__navigate_input_history">Naviguent dans l’historique de saisie</string>

<!-- ####################################################################################### -->
<!-- #################################### notifications #################################### -->
<!-- ####################################################################################### -->
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -699,9 +699,10 @@
Когда кнопка «Скрепка» скрыта, чтобы оставить больше места для поля ввода,
выбирать файлы можно через меню.</string>

<string name="pref_buttons_volume__actions__none">Ничего не делать</string>
<string name="pref_buttons_volume__actions__text_size">Кнопки громкости меняют размер текста</string>
<string name="pref_buttons_volume__actions__history">Навигация по истории отправлений</string>
<string name="pref__buttons__volume__title">Кнопки громкости</string>
<string name="pref__buttons__volume__actions__none">Ничего не делать</string>
<string name="pref__buttons__volume__actions__change_text_size">Кнопки громкости меняют размер текста</string>
<string name="pref__buttons__volume__actions__navigate_input_history">Навигация по истории ввода</string>

<!-- ####################################################################################### -->
<!-- #################################### notifications #################################### -->
Expand Down
Loading

0 comments on commit 1501f87

Please sign in to comment.