From 5804f4e00175ef5d5504308fa861000b4069c836 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 24 Oct 2023 17:29:29 +0530 Subject: [PATCH] fix: SymbolInputView is invalidated multiple times on tab reselection --- .../adapters/SymbolInputAdapter.java | 70 ++++++++++++++----- .../com/itsaky/androidide/models/Symbol.kt | 53 ++++++++++++++ .../itsaky/androidide/ui/EditorBottomSheet.kt | 3 +- .../itsaky/androidide/ui/SymbolInputView.java | 61 ++++------------ .../com/itsaky/androidide/utils/Symbols.kt | 36 +++++----- 5 files changed, 138 insertions(+), 85 deletions(-) create mode 100644 app/src/main/java/com/itsaky/androidide/models/Symbol.kt diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java index e51739268..6ed8d90dd 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java @@ -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 { @@ -49,24 +49,54 @@ public class SymbolInputAdapter extends RecyclerView.Adapter'); } - private final IDEEditor editor; - private Symbol[] symbols; + private IDEEditor editor; + private List symbols; public SymbolInputAdapter(IDEEditor editor) { this(editor, null); } - public SymbolInputAdapter(IDEEditor editor, Symbol[] symbols) { + public SymbolInputAdapter(IDEEditor editor, List 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 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 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. + */ +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)" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt b/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt index 3c9bf5564..54e36c10a 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt +++ b/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt @@ -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() { diff --git a/app/src/main/java/com/itsaky/androidide/ui/SymbolInputView.java b/app/src/main/java/com/itsaky/androidide/ui/SymbolInputView.java index 127f9c7e0..8c7f946a2 100755 --- a/app/src/main/java/com/itsaky/androidide/ui/SymbolInputView.java +++ b/app/src/main/java/com/itsaky/androidide/ui/SymbolInputView.java @@ -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 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)); } } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/Symbols.kt b/app/src/main/java/com/itsaky/androidide/utils/Symbols.kt index 6b5b0ba75..e014fa1eb 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/Symbols.kt +++ b/app/src/main/java/com/itsaky/androidide/utils/Symbols.kt @@ -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 { + fun forFile(file: File?): List { 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 { - return arrayOf( + private val javaSymbols by lazy { + listOf( TabSymbol(), Symbol("{", "{}"), Symbol("}"), @@ -64,8 +65,8 @@ object Symbols { ) } - fun xmlSymbols(): Array { - return arrayOf( + private val xmlSymbols by lazy { + listOf( TabSymbol(), Symbol("<", "<>"), Symbol(">"), @@ -93,8 +94,8 @@ object Symbols { ) } - fun plainTextSymbols(): Array { - 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 } }