Merge "[flexiglass] SceneContainer in System UI."
This commit is contained in:
@@ -24,6 +24,9 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
|
||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||
import com.android.systemui.scene.shared.model.Scene
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
|
||||
/** The Compose facade, when Compose is *not* available. */
|
||||
@@ -58,6 +61,14 @@ object ComposeFacade : BaseComposeFacade {
|
||||
throwComposeUnavailableError()
|
||||
}
|
||||
|
||||
override fun createSceneContainerView(
|
||||
context: Context,
|
||||
viewModel: SceneContainerViewModel,
|
||||
sceneByKey: Map<SceneKey, Scene>,
|
||||
): View {
|
||||
throwComposeUnavailableError()
|
||||
}
|
||||
|
||||
private fun throwComposeUnavailableError(): Nothing {
|
||||
error(
|
||||
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
|
||||
|
||||
@@ -29,6 +29,11 @@ import com.android.systemui.people.ui.compose.PeopleScreen
|
||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||
import com.android.systemui.qs.footer.ui.compose.FooterActions
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||
import com.android.systemui.scene.shared.model.Scene
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.ui.composable.ComposableScene
|
||||
import com.android.systemui.scene.ui.composable.SceneContainer
|
||||
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
|
||||
/** The Compose facade, when Compose is available. */
|
||||
@@ -71,4 +76,22 @@ object ComposeFacade : BaseComposeFacade {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createSceneContainerView(
|
||||
context: Context,
|
||||
viewModel: SceneContainerViewModel,
|
||||
sceneByKey: Map<SceneKey, Scene>,
|
||||
): View {
|
||||
return ComposeView(context).apply {
|
||||
setContent {
|
||||
PlatformTheme {
|
||||
SceneContainer(
|
||||
viewModel = viewModel,
|
||||
sceneByKey =
|
||||
sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
|
||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||
import com.android.systemui.scene.shared.model.Scene
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
|
||||
/**
|
||||
@@ -66,4 +69,11 @@ interface BaseComposeFacade {
|
||||
viewModel: MultiShadeViewModel,
|
||||
clock: SystemClock,
|
||||
): View
|
||||
|
||||
/** Create a [View] to represent [viewModel] on screen. */
|
||||
fun createSceneContainerView(
|
||||
context: Context,
|
||||
viewModel: SceneContainerViewModel,
|
||||
sceneByKey: Map<SceneKey, Scene>,
|
||||
): View
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.systemui.dagger;
|
||||
|
||||
import com.android.systemui.globalactions.ShutdownUiModule;
|
||||
import com.android.systemui.keyguard.CustomizationProvider;
|
||||
import com.android.systemui.scene.startable.SceneContainerStartableModule;
|
||||
import com.android.systemui.statusbar.NotificationInsetsModule;
|
||||
import com.android.systemui.statusbar.QsFrameTranslateModule;
|
||||
|
||||
@@ -33,6 +34,7 @@ import dagger.Subcomponent;
|
||||
NotificationInsetsModule.class,
|
||||
QsFrameTranslateModule.class,
|
||||
ShutdownUiModule.class,
|
||||
SceneContainerStartableModule.class,
|
||||
SystemUIBinder.class,
|
||||
SystemUIModule.class,
|
||||
SystemUICoreStartableModule.class,
|
||||
|
||||
@@ -40,7 +40,6 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl;
|
||||
import com.android.systemui.recents.Recents;
|
||||
import com.android.systemui.recents.RecentsImplementation;
|
||||
import com.android.systemui.rotationlock.RotationLockModule;
|
||||
import com.android.systemui.scene.SceneContainerFrameworkModule;
|
||||
import com.android.systemui.screenshot.ReferenceScreenshotModule;
|
||||
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
|
||||
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
|
||||
@@ -104,7 +103,6 @@ import javax.inject.Named;
|
||||
QSModule.class,
|
||||
ReferenceScreenshotModule.class,
|
||||
RotationLockModule.class,
|
||||
SceneContainerFrameworkModule.class,
|
||||
StatusBarEventsModule.class,
|
||||
StartCentralSurfacesModule.class,
|
||||
VolumeModule.class,
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
|
||||
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
|
||||
import com.android.systemui.biometrics.dagger.BiometricsModule;
|
||||
import com.android.systemui.biometrics.dagger.UdfpsModule;
|
||||
import com.android.systemui.bouncer.ui.BouncerViewModule;
|
||||
import com.android.systemui.classifier.FalsingModule;
|
||||
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
|
||||
import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
|
||||
@@ -51,7 +52,6 @@ import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.FlagsModule;
|
||||
import com.android.systemui.keyboard.KeyboardModule;
|
||||
import com.android.systemui.bouncer.ui.BouncerViewModule;
|
||||
import com.android.systemui.log.dagger.LogModule;
|
||||
import com.android.systemui.log.dagger.MonitorLog;
|
||||
import com.android.systemui.log.table.TableLogBuffer;
|
||||
@@ -72,6 +72,7 @@ import com.android.systemui.qs.QSFragmentStartableModule;
|
||||
import com.android.systemui.qs.footer.dagger.FooterActionsModule;
|
||||
import com.android.systemui.recents.Recents;
|
||||
import com.android.systemui.retail.dagger.RetailModeModule;
|
||||
import com.android.systemui.scene.SceneContainerFrameworkModule;
|
||||
import com.android.systemui.screenrecord.ScreenRecordModule;
|
||||
import com.android.systemui.screenshot.dagger.ScreenshotModule;
|
||||
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
|
||||
@@ -183,6 +184,7 @@ import javax.inject.Named;
|
||||
QRCodeScannerModule.class,
|
||||
QSFragmentStartableModule.class,
|
||||
RetailModeModule.class,
|
||||
SceneContainerFrameworkModule.class,
|
||||
ScreenshotModule.class,
|
||||
SensorModule.class,
|
||||
SecurityRepositoryModule.class,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.android.systemui.scene
|
||||
|
||||
import com.android.systemui.scene.data.model.SceneContainerConfigModule
|
||||
import com.android.systemui.scene.shared.model.SceneContainerConfigModule
|
||||
import com.android.systemui.scene.ui.composable.SceneModule
|
||||
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule
|
||||
import dagger.Module
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.android.systemui.scene.data.repository
|
||||
|
||||
import com.android.systemui.scene.data.model.SceneContainerConfig
|
||||
import com.android.systemui.scene.shared.model.SceneContainerConfig
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import javax.inject.Inject
|
||||
@@ -28,11 +28,9 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
class SceneContainerRepository
|
||||
@Inject
|
||||
constructor(
|
||||
containerConfigurations: Set<SceneContainerConfig>,
|
||||
private val containerConfigByName: Map<String, SceneContainerConfig>,
|
||||
) {
|
||||
|
||||
private val containerConfigByName: Map<String, SceneContainerConfig> =
|
||||
containerConfigurations.associateBy { config -> config.name }
|
||||
private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> =
|
||||
containerConfigByName
|
||||
.map { (containerName, _) -> containerName to MutableStateFlow(true) }
|
||||
@@ -48,21 +46,6 @@ constructor(
|
||||
.map { (containerName, _) -> containerName to MutableStateFlow(1f) }
|
||||
.toMap()
|
||||
|
||||
init {
|
||||
val repeatedContainerNames =
|
||||
containerConfigurations
|
||||
.groupingBy { config -> config.name }
|
||||
.eachCount()
|
||||
.filter { (_, count) -> count > 1 }
|
||||
check(repeatedContainerNames.isEmpty()) {
|
||||
"Container names must be unique. The following container names appear more than once: ${
|
||||
repeatedContainerNames
|
||||
.map { (name, count) -> "\"$name\" appears $count times" }
|
||||
.joinToString(", ")
|
||||
}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keys to all scenes in the container with the given name.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,9 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.scene.data.model
|
||||
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
package com.android.systemui.scene.shared.model
|
||||
|
||||
/** Models the configuration of a single scene container. */
|
||||
data class SceneContainerConfig(
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,11 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.scene.data.model
|
||||
package com.android.systemui.scene.shared.model
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.scene.shared.model.SceneContainerNames
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Named
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* 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 com.android.systemui.scene.startable
|
||||
|
||||
import com.android.systemui.CoreStartable
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.scene.shared.model.Scene
|
||||
import com.android.systemui.scene.shared.model.SceneContainerConfig
|
||||
import com.android.systemui.scene.shared.model.SceneContainerNames
|
||||
import com.android.systemui.scene.ui.view.SceneWindowRootView
|
||||
import com.android.systemui.scene.ui.view.WindowRootView
|
||||
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
@SysUISingleton
|
||||
class SceneContainerStartable
|
||||
@Inject
|
||||
constructor(
|
||||
private val view: WindowRootView,
|
||||
@Named(SceneContainerNames.SYSTEM_UI_DEFAULT) private val viewModel: SceneContainerViewModel,
|
||||
@Named(SceneContainerNames.SYSTEM_UI_DEFAULT) private val containerConfig: SceneContainerConfig,
|
||||
@Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
|
||||
private val scenes: Set<@JvmSuppressWildcards Scene>,
|
||||
) : CoreStartable {
|
||||
|
||||
override fun start() {
|
||||
(view as? SceneWindowRootView)?.init(
|
||||
viewModel = viewModel,
|
||||
containerConfig = containerConfig,
|
||||
scenes = scenes,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
interface SceneContainerStartableModule {
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SceneContainerStartable::class)
|
||||
fun bind(impl: SceneContainerStartable): CoreStartable
|
||||
}
|
||||
@@ -2,6 +2,73 @@ package com.android.systemui.scene.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.activity.OnBackPressedDispatcherOwner
|
||||
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.compose.ComposeFacade
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.scene.shared.model.Scene
|
||||
import com.android.systemui.scene.shared.model.SceneContainerConfig
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** A root view of the main SysUI window that supports scenes. */
|
||||
class SceneWindowRootView(context: Context?, attrs: AttributeSet?) : WindowRootView(context, attrs)
|
||||
class SceneWindowRootView(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
) :
|
||||
WindowRootView(
|
||||
context,
|
||||
attrs,
|
||||
) {
|
||||
fun init(
|
||||
viewModel: SceneContainerViewModel,
|
||||
containerConfig: SceneContainerConfig,
|
||||
scenes: Set<Scene>,
|
||||
) {
|
||||
val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
|
||||
val sortedSceneByKey: Map<SceneKey, Scene> = buildMap {
|
||||
containerConfig.sceneKeys.forEach { sceneKey ->
|
||||
val scene =
|
||||
checkNotNull(unsortedSceneByKey[sceneKey]) {
|
||||
"Scene not found for key \"$sceneKey\"!"
|
||||
}
|
||||
|
||||
put(sceneKey, scene)
|
||||
}
|
||||
}
|
||||
|
||||
repeatWhenAttached {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
setViewTreeOnBackPressedDispatcherOwner(
|
||||
object : OnBackPressedDispatcherOwner {
|
||||
override val onBackPressedDispatcher =
|
||||
OnBackPressedDispatcher().apply {
|
||||
setOnBackInvokedDispatcher(viewRootImpl.onBackInvokedDispatcher)
|
||||
}
|
||||
|
||||
override val lifecycle: Lifecycle =
|
||||
this@repeatWhenAttached.lifecycle
|
||||
}
|
||||
)
|
||||
|
||||
addView(
|
||||
ComposeFacade.createSceneContainerView(
|
||||
context = context,
|
||||
viewModel = viewModel,
|
||||
sceneByKey = sortedSceneByKey,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Here when destroyed.
|
||||
removeAllViews()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,37 @@ package com.android.systemui.scene.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import com.android.systemui.compose.ComposeFacade
|
||||
|
||||
/** A view that can serve as the root of the main SysUI window. */
|
||||
open class WindowRootView(context: Context?, attrs: AttributeSet?) : FrameLayout(context, attrs)
|
||||
/** A view that can serve as the root of the main SysUI window. */
|
||||
open class WindowRootView(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
) :
|
||||
FrameLayout(
|
||||
context,
|
||||
attrs,
|
||||
) {
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
|
||||
if (ComposeFacade.isComposeAvailable() && isRoot()) {
|
||||
ComposeFacade.composeInitializer().onAttachedToWindow(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
|
||||
if (ComposeFacade.isComposeAvailable() && isRoot()) {
|
||||
ComposeFacade.composeInitializer().onDetachedFromWindow(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isRoot(): Boolean {
|
||||
return parent.let { it !is View || it.id == android.R.id.content }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ import android.widget.FrameLayout;
|
||||
import com.android.internal.view.FloatingActionMode;
|
||||
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.compose.ComposeFacade;
|
||||
import com.android.systemui.scene.ui.view.WindowRootView;
|
||||
|
||||
/**
|
||||
@@ -149,18 +148,6 @@ public class NotificationShadeWindowView extends WindowRootView {
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
setWillNotDraw(!DEBUG);
|
||||
|
||||
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||
ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||
ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.shade
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContentResolver
|
||||
import android.os.Handler
|
||||
import android.view.LayoutInflater
|
||||
@@ -28,6 +29,7 @@ import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
import com.android.systemui.biometrics.AuthRippleController
|
||||
import com.android.systemui.biometrics.AuthRippleView
|
||||
import com.android.systemui.compose.ComposeFacade
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
@@ -67,13 +69,16 @@ abstract class ShadeModule {
|
||||
companion object {
|
||||
const val SHADE_HEADER = "large_screen_shade_header"
|
||||
|
||||
@SuppressLint("InflateParams") // Root views don't have parents.
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
fun providesWindowRootView(
|
||||
layoutInflater: LayoutInflater,
|
||||
featureFlags: FeatureFlags,
|
||||
): WindowRootView {
|
||||
return if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
|
||||
return if (
|
||||
featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
|
||||
) {
|
||||
layoutInflater.inflate(R.layout.scene_window_root, null)
|
||||
} else {
|
||||
layoutInflater.inflate(R.layout.super_notification_shade, null)
|
||||
|
||||
@@ -24,9 +24,9 @@ import com.android.systemui.bouncer.data.repository.BouncerRepository
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
|
||||
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
|
||||
import com.android.systemui.scene.data.model.SceneContainerConfig
|
||||
import com.android.systemui.scene.data.repository.SceneContainerRepository
|
||||
import com.android.systemui.scene.domain.interactor.SceneInteractor
|
||||
import com.android.systemui.scene.shared.model.SceneContainerConfig
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -51,7 +51,7 @@ class SceneTestUtils(
|
||||
fakeSceneContainerConfig(CONTAINER_2),
|
||||
)
|
||||
): SceneContainerRepository {
|
||||
return SceneContainerRepository(containerConfigurations)
|
||||
return SceneContainerRepository(containerConfigurations.associateBy { it.name })
|
||||
}
|
||||
|
||||
fun fakeSceneKeys(): List<SceneKey> {
|
||||
|
||||
Reference in New Issue
Block a user