mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-18 05:58:30 +00:00
Added primitive implementation of modules screen
This commit is contained in:
parent
efbb3ab25f
commit
70a3dbe2b0
@ -1,6 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
@ -9,7 +11,10 @@ import com.topjohnwu.magisk.extensions.get
|
|||||||
import com.topjohnwu.magisk.extensions.toggle
|
import com.topjohnwu.magisk.extensions.toggle
|
||||||
import com.topjohnwu.magisk.model.entity.module.Module
|
import com.topjohnwu.magisk.model.entity.module.Module
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
|
import com.topjohnwu.magisk.redesign.module.ModuleViewModel
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
|
import com.topjohnwu.magisk.utils.rotationTo
|
||||||
|
import com.topjohnwu.magisk.utils.setRevealed
|
||||||
|
|
||||||
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
||||||
|
|
||||||
@ -73,3 +78,34 @@ class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
|
|||||||
|
|
||||||
override fun itemSameAs(other: RepoRvItem): Boolean = item.id == other.item.id
|
override fun itemSameAs(other: RepoRvItem): Boolean = item.id == other.item.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ModuleItem(val item: Module) : ComparableRvItem<ModuleItem>() {
|
||||||
|
|
||||||
|
override val layoutRes = R.layout.item_module_md2
|
||||||
|
|
||||||
|
val isExpanded = KObservableField(false)
|
||||||
|
val isEnabled = KObservableField(item.enable)
|
||||||
|
|
||||||
|
val isModified get() = item.enable != isEnabled.value
|
||||||
|
|
||||||
|
fun toggle(viewModel: ModuleViewModel) {
|
||||||
|
isEnabled.toggle()
|
||||||
|
viewModel.moveToState(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggle(view: View) {
|
||||||
|
isExpanded.toggle()
|
||||||
|
view.rotationTo(if (isExpanded.value) 225 else 180)
|
||||||
|
(view.parent as ViewGroup)
|
||||||
|
.findViewById<View>(R.id.module_expand_container)
|
||||||
|
.setRevealed(isExpanded.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentSameAs(other: ModuleItem): Boolean = item.version == other.item.version
|
||||||
|
&& item.versionCode == other.item.versionCode
|
||||||
|
&& item.description == other.item.description
|
||||||
|
&& item.name == other.item.name
|
||||||
|
|
||||||
|
override fun itemSameAs(other: ModuleItem): Boolean = item.id == other.item.id
|
||||||
|
|
||||||
|
}
|
@ -1,16 +1,49 @@
|
|||||||
package com.topjohnwu.magisk.redesign.module
|
package com.topjohnwu.magisk.redesign.module
|
||||||
|
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
import com.topjohnwu.magisk.extensions.subscribeK
|
||||||
|
import com.topjohnwu.magisk.model.entity.module.Module
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.ModuleItem
|
||||||
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
|
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
|
||||||
import com.topjohnwu.magisk.redesign.home.itemBindingOf
|
import com.topjohnwu.magisk.redesign.home.itemBindingOf
|
||||||
import com.topjohnwu.magisk.redesign.superuser.diffListOf
|
import com.topjohnwu.magisk.redesign.superuser.diffListOf
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
class ModuleViewModel : CompatViewModel() {
|
class ModuleViewModel : CompatViewModel() {
|
||||||
|
|
||||||
val items = diffListOf<ModuleRvItem>()
|
val items = diffListOf<ModuleItem>()
|
||||||
val itemBinding = itemBindingOf<ModuleRvItem> {
|
val itemsPending = diffListOf<ModuleItem>()
|
||||||
|
val itemBinding = itemBindingOf<ModuleItem> {
|
||||||
it.bindExtra(BR.viewModel, this)
|
it.bindExtra(BR.viewModel, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun refresh() = Single.fromCallable { Module.loadModules() }
|
||||||
|
.map { it.map { ModuleItem(it) } }
|
||||||
|
.map { it to items.calculateDiff(it) }
|
||||||
|
.subscribeK {
|
||||||
|
items.update(it.first, it.second)
|
||||||
|
items.forEach { moveToState(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveToState(item: ModuleItem) {
|
||||||
|
val isActive = items.indexOfFirst { it.itemSameAs(item) } != -1
|
||||||
|
val isPending = itemsPending.indexOfFirst { it.itemSameAs(item) } != -1
|
||||||
|
|
||||||
|
when {
|
||||||
|
isActive && isPending -> if (item.isModified) {
|
||||||
|
items.remove(item)
|
||||||
|
} else {
|
||||||
|
itemsPending.remove(item)
|
||||||
|
}
|
||||||
|
isActive && item.isModified -> {
|
||||||
|
items.remove(item)
|
||||||
|
itemsPending.add(item)
|
||||||
|
}
|
||||||
|
isPending && !item.isModified -> {
|
||||||
|
itemsPending.remove(item)
|
||||||
|
items.add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -444,3 +444,10 @@ fun ProgressBar.setProgressAnimated(newProgress: Int) {
|
|||||||
tag = this
|
tag = this
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("android:rotation")
|
||||||
|
fun View.setRotationNotAnimated(rotation: Int) {
|
||||||
|
if (animation != null) {
|
||||||
|
this.rotation = rotation.toFloat()
|
||||||
|
}
|
||||||
|
}
|
12
app/src/main/res/drawable/ic_download_md2.xml
Normal file
12
app/src/main/res/drawable/ic_download_md2.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="?colorOnSurface"
|
||||||
|
android:pathData="M13,5V11H14.17L12,13.17L9.83,11H11V5H13M15,3H9V9H5L12,16L19,9H15V3M19,18H5V20H19V18Z"
|
||||||
|
tools:fillColor="#000" />
|
||||||
|
</vector>
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
|
<import type="com.topjohnwu.magisk.R" />
|
||||||
|
|
||||||
<import type="com.topjohnwu.magisk.Config" />
|
<import type="com.topjohnwu.magisk.Config" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
@ -15,16 +17,18 @@
|
|||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:clipToPadding="false"
|
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:fillViewport="true"
|
android:fillViewport="true"
|
||||||
android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}"
|
android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}"
|
||||||
android:paddingBottom="@{viewModel.insets.bottom + (int) @dimen/l2}"
|
android:paddingBottom="@{viewModel.insets.bottom + (int) @dimen/l1}"
|
||||||
|
tools:paddingBottom="64dp"
|
||||||
tools:paddingTop="24dp">
|
tools:paddingTop="24dp">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/module_notice"
|
android:id="@+id/module_notice"
|
||||||
@ -49,17 +53,106 @@
|
|||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
gone="@{viewModel.itemsPending.empty}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/l1"
|
||||||
|
android:paddingEnd="@dimen/l1">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/l1"
|
||||||
|
android:layout_marginBottom="@dimen/l1"
|
||||||
|
android:text="Applied on next boot"
|
||||||
|
android:textAppearance="?appearanceTextBodyNormal"
|
||||||
|
android:textColor="?colorPrimaryTransient"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/module_reboot_button"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/module_reboot_button"
|
||||||
|
style="?styleButtonText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/reboot"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_restart"
|
||||||
|
app:iconPadding="@dimen/l_50"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
dividerHorizontal="@{R.drawable.divider_l1}"
|
||||||
|
dividerVertical="@{R.drawable.divider_l1}"
|
||||||
|
gone="@{viewModel.itemsPending.empty}"
|
||||||
|
itemBinding="@{viewModel.itemBinding}"
|
||||||
|
items="@{viewModel.itemsPending}"
|
||||||
|
nestedScrollingEnabled="@{false}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="@dimen/l1"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
|
||||||
|
app:spanCount="2"
|
||||||
|
tools:itemCount="1"
|
||||||
|
tools:listitem="@layout/item_module_md2" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
gone="@{viewModel.itemsPending.empty || viewModel.items.empty}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/l1"
|
||||||
|
android:paddingEnd="@dimen/l1"
|
||||||
|
android:text="Active"
|
||||||
|
android:textAppearance="?appearanceTextBodyNormal"
|
||||||
|
android:textColor="?colorPrimaryTransient"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/module_reboot_button"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
dividerHorizontal="@{R.drawable.divider_l1}"
|
||||||
|
dividerVertical="@{R.drawable.divider_l1}"
|
||||||
|
gone="@{viewModel.items.empty}"
|
||||||
itemBinding="@{viewModel.itemBinding}"
|
itemBinding="@{viewModel.itemBinding}"
|
||||||
items="@{viewModel.items}"
|
items="@{viewModel.items}"
|
||||||
|
nestedScrollingEnabled="@{false}"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/l1"
|
android:layout_marginTop="@dimen/l1"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
android:paddingStart="@dimen/l1"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/module_notice" />
|
android:paddingEnd="0dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/module_notice"
|
||||||
|
app:spanCount="2"
|
||||||
|
tools:itemCount="3"
|
||||||
|
tools:listitem="@layout/item_module_md2" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<com.google.android.material.button.MaterialButton
|
||||||
|
style="?styleButtonText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="@dimen/l1"
|
||||||
|
android:text="Download more"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_download_md2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
106
app/src/main/res/layout/item_module_md2.xml
Normal file
106
app/src/main/res/layout/item_module_md2.xml
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<import type="com.topjohnwu.magisk.Config" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="item"
|
||||||
|
type="com.topjohnwu.magisk.model.entity.recycler.ModuleItem" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="com.topjohnwu.magisk.redesign.module.ModuleViewModel" />
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
style="?styleCardVariant"
|
||||||
|
isEnabled="@{!Config.coreOnly}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="@{item.isEnabled() && !Config.coreOnly ? 1f : .5f}"
|
||||||
|
android:onClick="@{() -> item.toggle(viewModel)}"
|
||||||
|
tools:layout_gravity="center"
|
||||||
|
tools:layout_marginBottom="@dimen/l1"
|
||||||
|
tools:layout_marginEnd="@dimen/l1">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="@dimen/l1">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/module_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/l1"
|
||||||
|
android:layout_marginTop="@dimen/l1"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:text="@{item.item.name}"
|
||||||
|
android:textAppearance="?appearanceTextBodyNormal"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@tools:sample/lorem" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/module_version_author"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/l1"
|
||||||
|
android:layout_marginEnd="@dimen/l1"
|
||||||
|
android:text="@{@string/module_version_author(item.item.version, item.item.author)}"
|
||||||
|
android:textAppearance="?appearanceTextCaptionVariant"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/module_title"
|
||||||
|
tools:text="v1 by topjohnwu" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
gone="@{item.item.description.empty}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/l1"
|
||||||
|
android:layout_marginTop="@dimen/l1"
|
||||||
|
android:layout_marginEnd="@dimen/l1"
|
||||||
|
android:text="@{item.item.description}"
|
||||||
|
android:textAppearance="?appearanceTextCaptionVariant"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/module_version_author"
|
||||||
|
tools:lines="4"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/module_expand_container"
|
||||||
|
revealFix="@{item.isExpanded}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?colorSurfaceVariant"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
style="?styleButtonError"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="Remove"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
app:icon="@drawable/ic_delete_md2" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
style="?styleIconNormal"
|
||||||
|
isSelected="@{item.isExpanded}"
|
||||||
|
android:layout_gravity="top|end"
|
||||||
|
android:onClick="@{(v) -> item.toggle(v)}"
|
||||||
|
android:rotation="@{item.isExpanded ? 225 : 180}"
|
||||||
|
app:srcCompat="@drawable/ic_more_collapse" />
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
|
||||||
|
</layout>
|
@ -151,7 +151,7 @@
|
|||||||
isSelected="@{item.isExpanded}"
|
isSelected="@{item.isExpanded}"
|
||||||
android:layout_gravity="top|end"
|
android:layout_gravity="top|end"
|
||||||
android:onClick="@{(view) -> item.toggle(view)}"
|
android:onClick="@{(view) -> item.toggle(view)}"
|
||||||
android:rotation="180"
|
android:rotation="@{item.isExpanded ? 225 : 180}"
|
||||||
app:srcCompat="@drawable/ic_more_collapse" />
|
app:srcCompat="@drawable/ic_more_collapse" />
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
<string name="install_start">Let\'s go</string>
|
<string name="install_start">Let\'s go</string>
|
||||||
|
|
||||||
<string name="module_safe_mode_message">You\'re in safe mode. None of user modules will work.\nThis message will disappear once safe mode is disabled.</string>
|
<string name="module_safe_mode_message">You\'re in safe mode. None of user modules will work.\nThis message will disappear once safe mode is disabled.</string>
|
||||||
|
<string name="module_version_author">%1$s by %2$s</string>
|
||||||
|
|
||||||
<string name="superuser_toggle_log">Toggles logging</string>
|
<string name="superuser_toggle_log">Toggles logging</string>
|
||||||
<string name="superuser_toggle_notification">Toggles “toast” notifications</string>
|
<string name="superuser_toggle_notification">Toggles “toast” notifications</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user