fix: SymbolInputView is invalidated multiple times on tab reselection

This commit is contained in:
Akash Yadav
2023-10-24 17:29:29 +05:30
parent 31285710f5
commit 5804f4e001
5 changed files with 138 additions and 85 deletions
@@ -23,17 +23,17 @@ import static com.itsaky.androidide.utils.ResourceUtilsKt.resolveAttr;
import android.annotation.SuppressLint;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.itsaky.androidide.R;
import com.itsaky.androidide.databinding.LayoutSymbolItemBinding;
import com.itsaky.androidide.ui.SymbolInputView.Symbol;
import com.itsaky.androidide.editor.ui.IDEEditor;
import com.itsaky.androidide.models.Symbol;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.VH> {
@@ -49,24 +49,54 @@ public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.
pairs.add('>');
}
private final IDEEditor editor;
private Symbol[] symbols;
private IDEEditor editor;
private List<Symbol> symbols;
public SymbolInputAdapter(IDEEditor editor) {
this(editor, null);
}
public SymbolInputAdapter(IDEEditor editor, Symbol[] symbols) {
public SymbolInputAdapter(IDEEditor editor, List<Symbol> symbols) {
this.editor = editor;
this.symbols = symbols == null ? new Symbol[0] : symbols;
this.symbols = symbols == null ? Collections.emptyList() : symbols;
setHasStableIds(true);
}
public void refresh(IDEEditor editor, List<Symbol> newSymbols) {
this.editor = Objects.requireNonNull(editor);
@SuppressLint("NotifyDataSetChanged")
public void setSymbols(boolean notify, Symbol... symbols) {
this.symbols = symbols;
if (notify) {
notifyDataSetChanged();
if (this.symbols.equals(newSymbols)) {
// no need to update symbols
return;
}
Objects.requireNonNull(newSymbols, "Symbol list cannot be null");
final var callback = new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return symbols.size();
}
@Override
public int getNewListSize() {
return newSymbols.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return Objects.equals(symbols.get(oldItemPosition), newSymbols.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return areItemsTheSame(oldItemPosition, newItemPosition);
}
};
final var result = DiffUtil.calculateDiff(callback);
this.symbols = newSymbols;
result.dispatchUpdatesTo(this);
}
@NonNull
@@ -78,8 +108,10 @@ public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
if (symbols == null || symbols[position] == null) return;
final Symbol symbol = symbols[position];
if (symbols == null || symbols.get(position) == null) {
return;
}
final Symbol symbol = symbols.get(position);
holder.binding.symbol.setText(symbol.getLabel());
holder.binding.symbol.setTextColor(
resolveAttr(holder.binding.symbol.getContext(), R.attr.colorOnSurface));
@@ -87,9 +119,14 @@ public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.
__ -> insertSymbol(symbol.getCommit(), symbol.getOffset()));
}
@Override
public long getItemId(int position) {
return symbols.get(position).hashCode();
}
@Override
public int getItemCount() {
return symbols == null ? 0 : symbols.length;
return symbols == null ? 0 : symbols.size();
}
void insertSymbol(String text, int selectionOffset) {
@@ -131,6 +168,7 @@ public class SymbolInputAdapter extends RecyclerView.Adapter<SymbolInputAdapter.
}
public static class VH extends RecyclerView.ViewHolder {
LayoutSymbolItemBinding binding;
public VH(LayoutSymbolItemBinding binding) {
@@ -0,0 +1,53 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.models
/**
* A symbol that is shown in the [SymbolInputView][com.itsaky.androidide.ui.SymbolInputView].
*
* @author Akash Yadav
*/
open class Symbol @JvmOverloads constructor(
open val label: String,
open val commit: String,
open val offset: Int = 1
) {
@JvmOverloads
constructor(both: String, offset: Int = 1) : this(both, both, offset)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Symbol) return false
if (label != other.label) return false
if (commit != other.commit) return false
if (offset != other.offset) return false
return true
}
override fun hashCode(): Int {
var result = label.hashCode()
result = 31 * result + commit.hashCode()
result = 31 * result + offset
return result
}
override fun toString(): String {
return "Symbol(label='$label', commit='$commit', offset=$offset)"
}
}
@@ -244,8 +244,7 @@ constructor(
}
fun refreshSymbolInput(editor: CodeEditorView) {
binding.symbolInput.bindEditor(editor.editor)
binding.symbolInput.setSymbols(*forFile(editor.file))
binding.symbolInput.refresh(editor.editor, forFile(editor.file))
}
fun onSoftInputChanged() {
@@ -23,12 +23,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.itsaky.androidide.adapters.SymbolInputAdapter;
import com.itsaky.androidide.editor.ui.IDEEditor;
import com.itsaky.androidide.models.Symbol;
import com.itsaky.androidide.utils.Symbols;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SymbolInputView extends RecyclerView {
private SymbolInputAdapter adapter;
public SymbolInputView(Context context) {
this(context, null);
}
@@ -42,55 +44,16 @@ public class SymbolInputView extends RecyclerView {
setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
}
public void bindEditor(IDEEditor editor) {
adapter = new SymbolInputAdapter(editor);
setAdapter(adapter);
}
public void setSymbols(Symbol... symbols) {
if (adapter != null) {
if (symbols.length == 0) {
symbols = Symbols.INSTANCE.plainTextSymbols();
}
adapter.setSymbols(true, symbols);
}
}
public static class Symbol {
private final String label;
private final String commit;
private final int offset;
public Symbol(String both) {
this(both, 1);
public void refresh(IDEEditor editor, List<Symbol> symbols) {
if (symbols == null || symbols.isEmpty()) {
symbols = Symbols.INSTANCE.getPlainTextSymbols();
}
public Symbol(String both, int offset) {
this(both, both, offset);
}
public Symbol(String label, String commit, int offset) {
this.label = label;
this.commit = commit;
this.offset = offset;
}
public Symbol(String label, String commit) {
this(label, commit, 1);
}
public String getLabel() {
return label;
}
public String getCommit() {
return commit;
}
public int getOffset() {
return offset;
final var adapter = getAdapter();
if (adapter instanceof SymbolInputAdapter) {
((SymbolInputAdapter) adapter).refresh(editor, symbols);
} else {
setAdapter(new SymbolInputAdapter(editor));
}
}
}
@@ -17,28 +17,29 @@
*/
package com.itsaky.androidide.utils
import com.itsaky.androidide.ui.SymbolInputView.Symbol
import com.itsaky.androidide.models.Symbol
import java.io.File
object Symbols {
@JvmStatic
fun forFile(file: File?): Array<Symbol?> {
fun forFile(file: File?): List<Symbol> {
if (file == null || !file.isFile) {
return emptyArray()
return emptyList()
}
return when (file.extension) {
"java",
"gradle",
"kt" -> javaSymbols()
"xml" -> xmlSymbols()
else -> plainTextSymbols()
"kt" -> javaSymbols
"xml" -> xmlSymbols
else -> plainTextSymbols
}
}
fun javaSymbols(): Array<Symbol?> {
return arrayOf(
private val javaSymbols by lazy {
listOf(
TabSymbol(),
Symbol("{", "{}"),
Symbol("}"),
@@ -64,8 +65,8 @@ object Symbols {
)
}
fun xmlSymbols(): Array<Symbol?> {
return arrayOf(
private val xmlSymbols by lazy {
listOf(
TabSymbol(),
Symbol("<", "<>"),
Symbol(">"),
@@ -93,8 +94,8 @@ object Symbols {
)
}
fun plainTextSymbols(): Array<Symbol?> {
return arrayOf(
val plainTextSymbols by lazy {
listOf(
TabSymbol(),
Symbol("{", "{}"),
Symbol("}"),
@@ -121,12 +122,11 @@ object Symbols {
}
private class TabSymbol : Symbol("↹") {
override fun getCommit(): String {
return "\t"
}
override fun getOffset(): Int {
return 1
}
override val commit: String
get() = "\t"
override val offset: Int
get() = 1
}
}