Merge branch hotfix/1.3.15
This commit is contained in:
commit
194a584e4c
11
CHANGES.md
11
CHANGES.md
|
@ -1,3 +1,14 @@
|
|||
Changes in Element v1.3.15 (2022-01-18)
|
||||
=======================================
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Fix crash when viewing source which contains an emoji ([#4796](https://github.com/vector-im/element-android/issues/4796))
|
||||
- Prevent crash in Timeline and add more logs. ([#4959](https://github.com/vector-im/element-android/issues/4959))
|
||||
- Fix crash on API <24 and make sure this error will not occur again. ([#4962](https://github.com/vector-im/element-android/issues/4962))
|
||||
- Fixes sign in/up crash when selecting ems and other server types which use SSO ([#4969](https://github.com/vector-im/element-android/issues/4969))
|
||||
|
||||
|
||||
Changes in Element v1.3.14 (2022-01-12)
|
||||
=======================================
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ ext.libs = [
|
|||
'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso"
|
||||
],
|
||||
google : [
|
||||
// TODO There is 1.6.0?
|
||||
'material' : "com.google.android.material:material:1.4.0"
|
||||
],
|
||||
dagger : [
|
||||
|
|
|
@ -4,7 +4,6 @@ ext.groups = [
|
|||
],
|
||||
group: [
|
||||
'com.github.Armen101',
|
||||
'com.github.BillCarsonFr',
|
||||
'com.github.chrisbanes',
|
||||
'com.github.hyuwah',
|
||||
'com.github.jetradarmobile',
|
||||
|
@ -154,6 +153,7 @@ ext.groups = [
|
|||
'org.jetbrains.intellij.deps',
|
||||
'org.jetbrains.kotlin',
|
||||
'org.jetbrains.kotlinx',
|
||||
'org.json',
|
||||
'org.jsoup',
|
||||
'org.junit',
|
||||
'org.junit.jupiter',
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: First change in onboarding screens, including Analytics opt-in. Support for Events with Math added in the labs.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.15
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,64 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'com.jakewharton.butterknife'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk versions.minSdk
|
||||
targetSdk versions.targetSdk
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility versions.sourceCompat
|
||||
targetCompatibility versions.targetCompat
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation libs.androidx.appCompat
|
||||
implementation libs.androidx.core
|
||||
|
||||
implementation libs.airbnb.epoxy
|
||||
kapt libs.airbnb.epoxyProcessor
|
||||
|
||||
implementation libs.airbnb.mavericks
|
||||
// Span utils
|
||||
implementation 'me.gujun.android:span:1.7'
|
||||
|
||||
implementation libs.google.material
|
||||
|
||||
implementation libs.jetbrains.coroutinesCore
|
||||
implementation libs.jetbrains.coroutinesAndroid
|
||||
|
||||
testImplementation 'org.json:json:20190722'
|
||||
testImplementation libs.tests.junit
|
||||
androidTestImplementation libs.androidx.junit
|
||||
androidTestImplementation libs.androidx.espressoCore
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<manifest package="org.billcarsonfr.jsonviewer" />
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
|
||||
class JSonViewerDialog : DialogFragment() {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_dialog_jv, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val args: JSonViewerFragmentArgs = arguments?.getParcelable(Mavericks.KEY_ARG) ?: return
|
||||
if (savedInstanceState == null) {
|
||||
childFragmentManager.beginTransaction()
|
||||
.replace(
|
||||
R.id.fragmentContainer, JSonViewerFragment.newInstance(
|
||||
args.jsonString,
|
||||
args.defaultOpenDepth,
|
||||
true,
|
||||
args.styleProvider
|
||||
)
|
||||
)
|
||||
.commitNow()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// Get existing layout params for the window
|
||||
val params = dialog?.window?.attributes
|
||||
// Assign window properties to fill the parent
|
||||
params?.width = WindowManager.LayoutParams.MATCH_PARENT
|
||||
params?.height = WindowManager.LayoutParams.MATCH_PARENT
|
||||
dialog?.window?.attributes = params
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(
|
||||
jsonString: String,
|
||||
initialOpenDepth: Int = -1,
|
||||
styleProvider: JSonViewerStyleProvider? = null
|
||||
): JSonViewerDialog {
|
||||
val args = Bundle()
|
||||
val parcelableArgs =
|
||||
JSonViewerFragmentArgs(jsonString, initialOpenDepth, false, styleProvider)
|
||||
args.putParcelable(Mavericks.KEY_ARG, parcelableArgs)
|
||||
return JSonViewerDialog().apply { arguments = args }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Success
|
||||
import me.gujun.android.span.Span
|
||||
import me.gujun.android.span.span
|
||||
|
||||
internal class JSonViewerEpoxyController(private val context: Context) :
|
||||
TypedEpoxyController<JSonViewerState>() {
|
||||
|
||||
private var styleProvider: JSonViewerStyleProvider = JSonViewerStyleProvider.default(context)
|
||||
|
||||
fun setStyle(styleProvider: JSonViewerStyleProvider?) {
|
||||
this.styleProvider = styleProvider ?: JSonViewerStyleProvider.default(context)
|
||||
}
|
||||
|
||||
override fun buildModels(data: JSonViewerState?) {
|
||||
val async = data?.root ?: return
|
||||
|
||||
when (async) {
|
||||
is Fail -> {
|
||||
valueItem {
|
||||
id("fail")
|
||||
text(async.error.localizedMessage?.toSafeCharSequence())
|
||||
}
|
||||
}
|
||||
is Success -> {
|
||||
val model = data.root.invoke()
|
||||
|
||||
model?.let {
|
||||
buildRec(it, 0, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRec(
|
||||
model: JSonViewerModel,
|
||||
depth: Int,
|
||||
idBase: String
|
||||
) {
|
||||
val host = this
|
||||
val id = "$idBase/${model.key ?: model.index}_${model.isExpanded}}"
|
||||
when (model) {
|
||||
is JSonViewerObject -> {
|
||||
if (model.isExpanded) {
|
||||
open(id, model.key, model.index, depth, true, model)
|
||||
model.keys.forEach {
|
||||
buildRec(it.value, depth + 1, id)
|
||||
}
|
||||
close(id, depth, true)
|
||||
} else {
|
||||
valueItem {
|
||||
id(id + "_sum")
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (model.key != null) {
|
||||
span("\"${model.key}\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
if (model.index != null) {
|
||||
span("${model.index}") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span {
|
||||
+"{+${model.keys.size}}"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toSafeCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(model) })
|
||||
}
|
||||
}
|
||||
}
|
||||
is JSonViewerArray -> {
|
||||
if (model.isExpanded) {
|
||||
open(id, model.key, model.index, depth, false, model)
|
||||
model.items.forEach {
|
||||
buildRec(it, depth + 1, id)
|
||||
}
|
||||
close(id, depth, false)
|
||||
} else {
|
||||
valueItem {
|
||||
id(id + "_sum")
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (model.key != null) {
|
||||
span("\"${model.key}\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
if (model.index != null) {
|
||||
span("${model.index}") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span {
|
||||
+"[+${model.items.size}]"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toSafeCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(model) })
|
||||
}
|
||||
}
|
||||
}
|
||||
is JSonViewerLeaf -> {
|
||||
valueItem {
|
||||
id(id)
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (model.key != null) {
|
||||
span("\"${model.key}\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
|
||||
if (model.index != null) {
|
||||
span("${model.index}") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
append(host.valueToSpan(model))
|
||||
}.toSafeCharSequence()
|
||||
)
|
||||
copyValue(model.stringRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun valueToSpan(leaf: JSonViewerLeaf): Span {
|
||||
val host = this
|
||||
return when (leaf.type) {
|
||||
JSONType.STRING -> {
|
||||
span("\"${leaf.stringRes}\"") {
|
||||
textColor = host.styleProvider.stringColor
|
||||
}
|
||||
}
|
||||
JSONType.NUMBER -> {
|
||||
span(leaf.stringRes) {
|
||||
textColor = host.styleProvider.numberColor
|
||||
}
|
||||
}
|
||||
JSONType.BOOLEAN -> {
|
||||
span(leaf.stringRes) {
|
||||
textColor = host.styleProvider.booleanColor
|
||||
}
|
||||
}
|
||||
JSONType.NULL -> {
|
||||
span("null") {
|
||||
textColor = host.styleProvider.booleanColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun open(
|
||||
id: String,
|
||||
key: String?,
|
||||
index: Int?,
|
||||
depth: Int,
|
||||
isObject: Boolean = true,
|
||||
composed: JSonViewerModel
|
||||
) {
|
||||
val host = this
|
||||
valueItem {
|
||||
id("${id}_Open")
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
if (key != null) {
|
||||
span("\"$key\"") {
|
||||
textColor = host.styleProvider.keyColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
if (index != null) {
|
||||
span("$index") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span(" : ") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}
|
||||
span("- ") {
|
||||
textColor = host.styleProvider.secondaryColor
|
||||
}
|
||||
span("{".takeIf { isObject } ?: "[") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toSafeCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(composed) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun itemClicked(model: JSonViewerModel) {
|
||||
model.isExpanded = !model.isExpanded
|
||||
setData(currentData)
|
||||
}
|
||||
|
||||
private fun close(id: String, depth: Int, isObject: Boolean = true) {
|
||||
val host = this
|
||||
valueItem {
|
||||
id("${id}_Close")
|
||||
depth(depth)
|
||||
text(
|
||||
span {
|
||||
text = "}".takeIf { isObject } ?: "]"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}.toSafeCharSequence()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.airbnb.epoxy.EpoxyRecyclerView
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
internal data class JSonViewerFragmentArgs(
|
||||
val jsonString: String,
|
||||
val defaultOpenDepth: Int,
|
||||
val wrap: Boolean,
|
||||
val styleProvider: JSonViewerStyleProvider?
|
||||
) : Parcelable
|
||||
|
||||
class JSonViewerFragment : Fragment(), MavericksView {
|
||||
|
||||
private val viewModel: JSonViewerViewModel by fragmentViewModel()
|
||||
|
||||
private val epoxyController by lazy {
|
||||
JSonViewerEpoxyController(requireContext())
|
||||
}
|
||||
|
||||
private lateinit var recyclerView: EpoxyRecyclerView
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val args: JSonViewerFragmentArgs? = arguments?.getParcelable(Mavericks.KEY_ARG)
|
||||
val inflate =
|
||||
if (args?.wrap == true) {
|
||||
inflater.inflate(R.layout.fragment_jv_recycler_view_wrap, container, false)
|
||||
} else {
|
||||
inflater.inflate(R.layout.fragment_jv_recycler_view, container, false)
|
||||
}
|
||||
recyclerView = inflate.findViewById(R.id.jvRecyclerView)
|
||||
recyclerView.layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
recyclerView.setController(epoxyController)
|
||||
epoxyController.setStyle(args?.styleProvider)
|
||||
registerForContextMenu(recyclerView)
|
||||
return inflate
|
||||
}
|
||||
|
||||
fun showJson(jsonString: String, initialOpenDepth: Int) {
|
||||
viewModel.setJsonSource(jsonString, initialOpenDepth)
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
epoxyController.setData(state)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(
|
||||
jsonString: String,
|
||||
initialOpenDepth: Int = -1,
|
||||
wrap: Boolean = false,
|
||||
styleProvider: JSonViewerStyleProvider? = null
|
||||
): JSonViewerFragment {
|
||||
return JSonViewerFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(
|
||||
Mavericks.KEY_ARG,
|
||||
JSonViewerFragmentArgs(
|
||||
jsonString,
|
||||
initialOpenDepth,
|
||||
wrap,
|
||||
styleProvider
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
internal open class JSonViewerModel(var key: String?, var index: Int?, val jObject: Any) {
|
||||
var depth = 0
|
||||
var isExpanded = false
|
||||
}
|
||||
|
||||
internal interface Composed {
|
||||
fun addChild(model: JSonViewerModel)
|
||||
}
|
||||
|
||||
internal class JSonViewerObject(key: String?, index: Int?, jObject: JSONObject) :
|
||||
JSonViewerModel(key, index, jObject),
|
||||
Composed {
|
||||
|
||||
var keys = LinkedHashMap<String, JSonViewerModel>()
|
||||
|
||||
override fun addChild(model: JSonViewerModel) {
|
||||
keys[model.key!!] = model
|
||||
}
|
||||
}
|
||||
|
||||
internal class JSonViewerArray(key: String?, index: Int?, jObject: JSONArray) :
|
||||
JSonViewerModel(key, index, jObject), Composed {
|
||||
var items = ArrayList<JSonViewerModel>()
|
||||
|
||||
override fun addChild(model: JSonViewerModel) {
|
||||
items.add(model)
|
||||
}
|
||||
}
|
||||
|
||||
internal class JSonViewerLeaf(key: String?, index: Int?, val stringRes: String, val type: JSONType) :
|
||||
JSonViewerModel(key, index, stringRes)
|
||||
|
||||
internal enum class JSONType {
|
||||
STRING,
|
||||
NUMBER,
|
||||
BOOLEAN,
|
||||
NULL
|
||||
}
|
||||
|
||||
internal object ModelParser {
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun fromJsonString(jsonString: String, initialOpenDepth: Int = -1): JSonViewerObject {
|
||||
val jobj = JSONObject(jsonString.trim())
|
||||
val root = JSonViewerObject(null, null, jobj).apply { isExpanded = true }
|
||||
jobj.keys().forEach {
|
||||
eval(root, it, null, jobj.get(it), 1, initialOpenDepth)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
private fun eval(parent: Composed, key: String?, index: Int?, obj: Any, depth: Int, initialOpenDepth: Int) {
|
||||
when (obj) {
|
||||
is JSONObject -> {
|
||||
val objectComposed = JSonViewerObject(key, index, obj)
|
||||
.apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth }
|
||||
objectComposed.depth = depth
|
||||
obj.keys().forEach {
|
||||
eval(objectComposed, it, null, obj.get(it), depth + 1, initialOpenDepth)
|
||||
}
|
||||
parent.addChild(objectComposed)
|
||||
}
|
||||
is JSONArray -> {
|
||||
val objectComposed = JSonViewerArray(key, index, obj)
|
||||
.apply { isExpanded = initialOpenDepth == -1 || depth <= initialOpenDepth }
|
||||
objectComposed.depth = depth
|
||||
for (i in 0 until obj.length()) {
|
||||
eval(objectComposed, null, i, obj[i], depth + 1, initialOpenDepth)
|
||||
}
|
||||
parent.addChild(objectComposed)
|
||||
}
|
||||
is String -> {
|
||||
JSonViewerLeaf(key, index, obj, JSONType.STRING).let {
|
||||
it.depth = depth
|
||||
parent.addChild(it)
|
||||
}
|
||||
}
|
||||
is Number -> {
|
||||
JSonViewerLeaf(key, index, obj.toString(), JSONType.NUMBER).let {
|
||||
it.depth = depth
|
||||
parent.addChild(it)
|
||||
}
|
||||
}
|
||||
is Boolean -> {
|
||||
JSonViewerLeaf(key, index, obj.toString(), JSONType.BOOLEAN).let {
|
||||
it.depth = depth
|
||||
parent.addChild(it)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (obj == JSONObject.NULL) {
|
||||
JSonViewerLeaf(key, index, "null", JSONType.NULL).let {
|
||||
it.depth = depth
|
||||
parent.addChild(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class JSonViewerStyleProvider(
|
||||
@ColorInt val keyColor: Int,
|
||||
@ColorInt val stringColor: Int,
|
||||
@ColorInt val booleanColor: Int,
|
||||
@ColorInt val numberColor: Int,
|
||||
@ColorInt val baseColor: Int,
|
||||
@ColorInt val secondaryColor: Int
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
fun default(context: Context) = JSonViewerStyleProvider(
|
||||
keyColor = ContextCompat.getColor(context, R.color.key_color),
|
||||
stringColor = ContextCompat.getColor(context, R.color.string_color),
|
||||
booleanColor = ContextCompat.getColor(context, R.color.bool_color),
|
||||
numberColor = ContextCompat.getColor(context, R.color.number_color),
|
||||
baseColor = ContextCompat.getColor(context, R.color.base_color),
|
||||
secondaryColor = ContextCompat.getColor(context, R.color.secondary_color)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.MavericksViewModel
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal data class JSonViewerState(
|
||||
val root: Async<JSonViewerObject> = Uninitialized
|
||||
) : MavericksState
|
||||
|
||||
internal class JSonViewerViewModel(initialState: JSonViewerState) :
|
||||
MavericksViewModel<JSonViewerState>(initialState) {
|
||||
|
||||
fun setJsonSource(json: String, initialOpenDepth: Int) {
|
||||
setState {
|
||||
copy(root = Loading())
|
||||
}
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
ModelParser.fromJsonString(json, initialOpenDepth).let {
|
||||
setState {
|
||||
copy(
|
||||
root = Success(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (error: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
root = Fail(error)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<JSonViewerViewModel, JSonViewerState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun initialState(viewModelContext: ViewModelContext): JSonViewerState? {
|
||||
val arg: JSonViewerFragmentArgs = viewModelContext.args()
|
||||
return try {
|
||||
JSonViewerState(
|
||||
Success(ModelParser.fromJsonString(arg.jsonString, arg.defaultOpenDepth))
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
JSonViewerState(Fail(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
/**
|
||||
* Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering
|
||||
* TODO Mutualize
|
||||
*/
|
||||
internal class SafeCharSequence(val charSequence: CharSequence) {
|
||||
private val hash = charSequence.toString().hashCode()
|
||||
|
||||
override fun hashCode() = hash
|
||||
override fun equals(other: Any?) = other is SafeCharSequence && other.hash == hash
|
||||
}
|
||||
|
||||
internal fun CharSequence.toSafeCharSequence() = SafeCharSequence(this)
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
|
||||
/**
|
||||
* TODO Mutualize
|
||||
*/
|
||||
internal object Utils {
|
||||
fun dpToPx(dp: Int, context: Context): Int {
|
||||
return TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dp.toFloat(),
|
||||
context.resources.displayMetrics
|
||||
).toInt()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.view.ContextMenu
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyHolder
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
|
||||
@EpoxyModelClass(layout = R2.layout.item_jv_base_value)
|
||||
internal abstract class ValueItem : EpoxyModelWithHolder<ValueItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var text: SafeCharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var depth: Int = 0
|
||||
|
||||
@EpoxyAttribute
|
||||
var copyValue: String? = null
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var itemClickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.textView.text = text?.charSequence
|
||||
holder.baseView.setPadding(Utils.dpToPx(16 * depth, holder.baseView.context), 0, 0, 0)
|
||||
itemClickListener?.let { holder.baseView.setOnClickListener(it) }
|
||||
holder.copyValue = copyValue
|
||||
}
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
super.unbind(holder)
|
||||
holder.baseView.setOnClickListener(null)
|
||||
holder.copyValue = null
|
||||
}
|
||||
|
||||
class Holder : EpoxyHolder(), View.OnCreateContextMenuListener {
|
||||
|
||||
lateinit var textView: TextView
|
||||
lateinit var baseView: LinearLayout
|
||||
var copyValue: String? = null
|
||||
|
||||
override fun bindView(itemView: View) {
|
||||
baseView = itemView.findViewById(R.id.jvBaseLayout)
|
||||
textView = itemView.findViewById(R.id.jvValueText)
|
||||
itemView.setOnCreateContextMenuListener(this)
|
||||
}
|
||||
|
||||
override fun onCreateContextMenu(
|
||||
menu: ContextMenu?,
|
||||
v: View?,
|
||||
menuInfo: ContextMenu.ContextMenuInfo?
|
||||
) {
|
||||
if (copyValue != null) {
|
||||
val menuItem = menu?.add(
|
||||
Menu.NONE, R.id.copy_value,
|
||||
Menu.NONE, R.string.copy_value
|
||||
)
|
||||
val clipService =
|
||||
v?.context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
|
||||
menuItem?.setOnMenuItemClickListener {
|
||||
clipService?.setPrimaryClip(ClipData.newPlainText("", copyValue))
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragmentContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||
android:id="@+id/jvRecyclerView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical"
|
||||
tools:itemCount="5"
|
||||
tools:listitem="@layout/item_jv_base_value" />
|
||||
|
||||
</HorizontalScrollView>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.airbnb.epoxy.EpoxyRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/jvRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical"
|
||||
tools:itemCount="5"
|
||||
tools:listitem="@layout/item_jv_base_value" />
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/jvBaseLayout"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
tools:paddingLeft="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/jvValueText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text=""Title": "example glossary"" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/copy_value"
|
||||
android:title="@string/copy_value" />
|
||||
|
||||
</menu>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<color name="key_color">#FF006700</color>
|
||||
<color name="string_color">#FF040091</color>
|
||||
<color name="bool_color">#FF980000</color>
|
||||
<color name="number_color">#FF1700FF</color>
|
||||
<color name="base_color">#FF000000</color>
|
||||
<color name="secondary_color">#FFAAAAAA</color>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="copy_value">Copy Value</string>
|
||||
</resources>
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.billcarsonfr.jsonviewer
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ModelParseTest {
|
||||
@Test
|
||||
fun parsing_isCorrect() {
|
||||
val string = """
|
||||
{
|
||||
"glossary": {
|
||||
"title": "example glossary",
|
||||
"GlossDiv": {
|
||||
"title": "S",
|
||||
"GlossList": {
|
||||
"GlossEntry": {
|
||||
"ID": "SGML",
|
||||
"SortAs": "SGML",
|
||||
"GlossTerm": "Standard Generalized Markup Language",
|
||||
"Acronym": "SGML",
|
||||
"Abbrev": "ISO 8879:1986",
|
||||
"GlossDef": {
|
||||
"para": "A meta-markup language, used to create markup languages such as DocBook.",
|
||||
"GlossSeeAlso": ["GML", "XML"]
|
||||
},
|
||||
"GlossSee": "markup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trim()
|
||||
|
||||
val model = ModelParser.fromJsonString(string)
|
||||
|
||||
Assert.assertEquals(0, model.depth)
|
||||
Assert.assertEquals(1, model.keys.size)
|
||||
Assert.assertTrue(model.keys.containsKey("glossary"))
|
||||
Assert.assertTrue(model.keys["glossary"] is JSonViewerObject)
|
||||
|
||||
val glossary = model.keys["glossary"] as JSonViewerObject
|
||||
Assert.assertEquals(2, glossary.keys.size)
|
||||
Assert.assertTrue(glossary.keys.containsKey("title"))
|
||||
Assert.assertTrue(glossary.keys.containsKey("GlossDiv"))
|
||||
|
||||
Assert.assertTrue(glossary.keys["title"] is JSonViewerLeaf)
|
||||
(glossary.keys["title"] as JSonViewerLeaf).let {
|
||||
Assert.assertEquals(JSONType.STRING, it.type)
|
||||
}
|
||||
|
||||
Assert.assertTrue(glossary.keys["GlossDiv"] is JSonViewerObject)
|
||||
val glossDiv = glossary.keys["GlossDiv"] as JSonViewerObject
|
||||
|
||||
Assert.assertTrue(glossDiv.keys["GlossList"] is JSonViewerObject)
|
||||
val glossList = glossDiv.keys["GlossList"] as JSonViewerObject
|
||||
|
||||
Assert.assertTrue(glossList.keys["GlossEntry"] is JSonViewerObject)
|
||||
val glossEntry = glossList.keys["GlossEntry"] as JSonViewerObject
|
||||
|
||||
Assert.assertTrue(glossEntry.keys["GlossDef"] is JSonViewerObject)
|
||||
val glossDef = glossEntry.keys["GlossDef"] as JSonViewerObject
|
||||
|
||||
Assert.assertTrue(glossDef.keys["GlossSeeAlso"] is JSonViewerArray)
|
||||
val glossSeeAlso = glossDef.keys["GlossSeeAlso"] as JSonViewerArray
|
||||
|
||||
Assert.assertEquals(2, glossSeeAlso.items.size)
|
||||
Assert.assertEquals(0, glossSeeAlso.items.first().index)
|
||||
Assert.assertNull(glossSeeAlso.items.first().key)
|
||||
Assert.assertEquals("GML", (glossSeeAlso.items.first() as JSonViewerLeaf).stringRes)
|
||||
}
|
||||
}
|
|
@ -5,4 +5,5 @@ include ':attachment-viewer'
|
|||
include ':multipicker'
|
||||
include ':library:core-utils'
|
||||
include ':library:ui-styles'
|
||||
include ':library:jsonviewer'
|
||||
include ':matrix-sdk-android-flow'
|
||||
|
|
|
@ -332,6 +332,7 @@ dependencies {
|
|||
implementation project(":diff-match-patch")
|
||||
implementation project(":multipicker")
|
||||
implementation project(":attachment-viewer")
|
||||
implementation project(":library:jsonviewer")
|
||||
implementation project(":library:ui-styles")
|
||||
implementation project(":library:core-utils")
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
|
@ -465,7 +466,6 @@ dependencies {
|
|||
gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||
|
||||
implementation "androidx.emoji2:emoji2:1.0.1"
|
||||
implementation('com.github.BillCarsonFr:JsonViewer:0.7')
|
||||
|
||||
// WebRTC
|
||||
// org.webrtc:google-webrtc is for development purposes only
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
<issue id="RtlSymmetry" severity="error" />
|
||||
|
||||
<!-- Code -->
|
||||
<issue id="NewApi" severity="error" />
|
||||
<issue id="SetTextI18n" severity="error" />
|
||||
<issue id="ViewConstructor" severity="error" />
|
||||
<issue id="UseValueOf" severity="error" />
|
||||
|
@ -82,10 +83,6 @@
|
|||
<ignore path="**/generated/resolved/**/resolved.xml" />
|
||||
</issue>
|
||||
|
||||
<!-- Bug in lint agp 4.1 incorrectly thinks kotlin forEach is using java 8 API's. -->
|
||||
<!-- FIXME this workaround should be removed in a near future -->
|
||||
<issue id="NewApi" severity="warning" />
|
||||
|
||||
<!-- DI -->
|
||||
<issue id="JvmStaticProvidesInObjectDetector" severity="error" />
|
||||
</lint>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.utils.compat
|
||||
|
||||
import android.os.Build
|
||||
|
||||
fun <E> MutableCollection<E>.removeIfCompat(predicate: (E) -> Boolean) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
removeIf(predicate)
|
||||
} else {
|
||||
removeAll(filter(predicate).toSet())
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.analytics
|
||||
|
||||
import im.vector.app.core.time.Clock
|
||||
import im.vector.app.core.utils.compat.removeIfCompat
|
||||
import im.vector.app.features.analytics.plan.Error
|
||||
import im.vector.lib.core.utils.flow.tickerFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
@ -89,7 +90,7 @@ class DecryptionFailureTracker @Inject constructor(
|
|||
fun onTimeLineDisposed(roomId: String) {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
synchronized(failures) {
|
||||
failures.removeIf { it.roomId == roomId }
|
||||
failures.removeIfCompat { it.roomId == roomId }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +106,7 @@ class DecryptionFailureTracker @Inject constructor(
|
|||
|
||||
private fun removeFailureForEventId(eventId: String) {
|
||||
synchronized(failures) {
|
||||
failures.removeIf { it.failedEventId == eventId }
|
||||
failures.removeIfCompat { it.failedEventId == eventId }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
|||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.min
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||
|
@ -185,6 +186,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
synchronized(modelCache) {
|
||||
assertUpdateCallbacksAllowed()
|
||||
Timber.v("listUpdateCallback.onChanged(position: $position, count: $count). " +
|
||||
"\ncurrentSnapshot has size of ${currentSnapshot.size} items")
|
||||
(position until position + count).forEach {
|
||||
// Invalidate cache
|
||||
modelCache[it] = null
|
||||
|
@ -192,10 +195,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
// Also invalidate the first previous displayable event if
|
||||
// it's sent by the same user so we are sure we have up to date information.
|
||||
val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId
|
||||
val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast {
|
||||
// In some cases onChanged will be called before onRemoved and onInserted so position will be bigger than currentSnapshot.size.
|
||||
val prevList = currentSnapshot.subList(0, min(position, currentSnapshot.size))
|
||||
val prevDisplayableEventIndex = prevList.indexOfLast {
|
||||
timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
|
||||
}
|
||||
if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) {
|
||||
if (prevDisplayableEventIndex != -1 && currentSnapshot.getOrNull(prevDisplayableEventIndex)?.senderInfo?.userId == invalidatedSenderId) {
|
||||
modelCache[prevDisplayableEventIndex] = null
|
||||
}
|
||||
requestModelBuild()
|
||||
|
@ -205,6 +210,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||
synchronized(modelCache) {
|
||||
assertUpdateCallbacksAllowed()
|
||||
Timber.v("listUpdateCallback.onMoved(fromPosition: $fromPosition, toPosition: $toPosition). " +
|
||||
"\ncurrentSnapshot has size of ${currentSnapshot.size} items")
|
||||
val model = modelCache.removeAt(fromPosition)
|
||||
modelCache.add(toPosition, model)
|
||||
requestModelBuild()
|
||||
|
@ -214,6 +221,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
override fun onInserted(position: Int, count: Int) {
|
||||
synchronized(modelCache) {
|
||||
assertUpdateCallbacksAllowed()
|
||||
Timber.v("listUpdateCallback.onInserted(position: $position, count: $count). " +
|
||||
"\ncurrentSnapshot has size of ${currentSnapshot.size} items")
|
||||
repeat(count) {
|
||||
modelCache.add(position, null)
|
||||
}
|
||||
|
@ -224,6 +233,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
override fun onRemoved(position: Int, count: Int) {
|
||||
synchronized(modelCache) {
|
||||
assertUpdateCallbacksAllowed()
|
||||
Timber.v("listUpdateCallback.onRemoved(position: $position, count: $count). " +
|
||||
"\ncurrentSnapshot has size of ${currentSnapshot.size} items")
|
||||
repeat(count) {
|
||||
modelCache.removeAt(position)
|
||||
}
|
||||
|
@ -306,6 +317,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
inSubmitList = true
|
||||
val diffCallback = TimelineEventDiffUtilCallback(currentSnapshot, newSnapshot)
|
||||
currentSnapshot = newSnapshot
|
||||
Timber.v("Submit a new snapshot of ${currentSnapshot.size} items.")
|
||||
val diffResult = DiffUtil.calculateDiff(diffCallback)
|
||||
diffResult.dispatchUpdatesTo(listUpdateCallback)
|
||||
requestDelayedModelBuild(0)
|
||||
|
|
|
@ -852,13 +852,17 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
withState {
|
||||
when (it.onboardingFlow) {
|
||||
OnboardingFlow.SignIn -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.SignIn))
|
||||
OnboardingFlow.SignUp -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.SignUp))
|
||||
OnboardingFlow.SignInSignUp,
|
||||
null -> {
|
||||
_viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved)
|
||||
if (it.serverType == ServerType.MatrixOrg) {
|
||||
when (it.onboardingFlow) {
|
||||
OnboardingFlow.SignIn -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.SignIn))
|
||||
OnboardingFlow.SignUp -> handleUpdateSignMode(OnboardingAction.UpdateSignMode(SignMode.SignUp))
|
||||
OnboardingFlow.SignInSignUp,
|
||||
null -> {
|
||||
_viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_viewEvents.post(OnboardingViewEvents.OnLoginFlowRetrieved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.compat.removeIfCompat
|
||||
import im.vector.app.features.reactions.data.EmojiData
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -215,14 +216,7 @@ class EmojiRecyclerAdapter @Inject constructor() :
|
|||
override fun onViewRecycled(holder: ViewHolder) {
|
||||
if (holder is EmojiViewHolder) {
|
||||
holder.data = null
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
toUpdateWhenNotBusy.removeIf { it.second == holder }
|
||||
} else {
|
||||
val index = toUpdateWhenNotBusy.indexOfFirst { it.second == holder }
|
||||
if (index != -1) {
|
||||
toUpdateWhenNotBusy.removeAt(index)
|
||||
}
|
||||
}
|
||||
toUpdateWhenNotBusy.removeIfCompat { it.second == holder }
|
||||
}
|
||||
super.onViewRecycled(holder)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue