add fill_unstable and use rust fmt
This commit is contained in:
parent
ae215ce252
commit
4dd5587b49
|
@ -33,7 +33,6 @@ async fn main() -> bluer::Result<()> {
|
||||||
let spectacle = Spectacle::from(config);
|
let spectacle = Spectacle::from(config);
|
||||||
let spectacle = Arc::new(Mutex::new(spectacle));
|
let spectacle = Arc::new(Mutex::new(spectacle));
|
||||||
|
|
||||||
|
|
||||||
let adapter = bluetooth::create_session().await?;
|
let adapter = bluetooth::create_session().await?;
|
||||||
let (tx_scan, rx_scan) = mpsc::channel(3);
|
let (tx_scan, rx_scan) = mpsc::channel(3);
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
mod scanner;
|
mod blink;
|
||||||
mod fade;
|
|
||||||
mod color_wipe;
|
mod color_wipe;
|
||||||
mod rainbow;
|
mod fade;
|
||||||
|
mod fade_random;
|
||||||
|
mod fade_unstable;
|
||||||
mod fill_random;
|
mod fill_random;
|
||||||
mod fill_unstable;
|
mod fill_unstable;
|
||||||
mod blink;
|
|
||||||
mod rain;
|
mod rain;
|
||||||
mod fade_random;
|
mod rainbow;
|
||||||
|
mod scanner;
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use std::fmt;
|
|
||||||
use std::ops::{AddAssign, Div, Index, IndexMut, SubAssign};
|
|
||||||
use rand::Rng;
|
|
||||||
use rand::rngs::ThreadRng;
|
|
||||||
use crate::patterns::blink::Blink;
|
use crate::patterns::blink::Blink;
|
||||||
use crate::patterns::color_wipe::ColorWipe;
|
use crate::patterns::color_wipe::ColorWipe;
|
||||||
use crate::patterns::fade::Fade;
|
use crate::patterns::fade::Fade;
|
||||||
use crate::patterns::fade_random::FadeRandom;
|
use crate::patterns::fade_random::FadeRandom;
|
||||||
|
use crate::patterns::fade_unstable::FadeUnstable;
|
||||||
use crate::patterns::fill_random::FillRandom;
|
use crate::patterns::fill_random::FillRandom;
|
||||||
use crate::patterns::fill_unstable::FillUnstable;
|
use crate::patterns::fill_unstable::FillUnstable;
|
||||||
use crate::patterns::rain::Rain;
|
use crate::patterns::rain::Rain;
|
||||||
use crate::patterns::rainbow::Rainbow;
|
use crate::patterns::rainbow::Rainbow;
|
||||||
use crate::patterns::scanner::Scanner;
|
use crate::patterns::scanner::Scanner;
|
||||||
|
use rand::rngs::ThreadRng;
|
||||||
|
use rand::Rng;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
|
use std::ops::{AddAssign, Div, Index, IndexMut, SubAssign};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
|
@ -34,7 +36,8 @@ pub(crate) enum Patterns<const N: usize> {
|
||||||
FillUnstable(FillUnstable<N>),
|
FillUnstable(FillUnstable<N>),
|
||||||
Blink(Blink<N>),
|
Blink(Blink<N>),
|
||||||
Rain(Rain<N>),
|
Rain(Rain<N>),
|
||||||
FadeRandom(FadeRandom<N>)
|
FadeRandom(FadeRandom<N>),
|
||||||
|
FadeUnstable(FadeUnstable<N>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Pattern: Iterator {
|
pub trait Pattern: Iterator {
|
||||||
|
@ -58,6 +61,7 @@ impl<const N: usize> Iterator for Patterns<N> {
|
||||||
Patterns::Blink(p) => p.next(),
|
Patterns::Blink(p) => p.next(),
|
||||||
Patterns::Rain(p) => p.next(),
|
Patterns::Rain(p) => p.next(),
|
||||||
Patterns::FadeRandom(p) => p.next(),
|
Patterns::FadeRandom(p) => p.next(),
|
||||||
|
Patterns::FadeUnstable(p) => p.next(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +80,7 @@ impl<const N: usize> Pattern for Patterns<N> {
|
||||||
Patterns::Blink(p) => p.init(),
|
Patterns::Blink(p) => p.init(),
|
||||||
Patterns::Rain(p) => p.init(),
|
Patterns::Rain(p) => p.init(),
|
||||||
Patterns::FadeRandom(p) => p.init(),
|
Patterns::FadeRandom(p) => p.init(),
|
||||||
|
Patterns::FadeUnstable(p) => p.init(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +95,7 @@ impl<const N: usize> Pattern for Patterns<N> {
|
||||||
Patterns::Blink(p) => p.is_last_iteration(),
|
Patterns::Blink(p) => p.is_last_iteration(),
|
||||||
Patterns::Rain(p) => p.is_last_iteration(),
|
Patterns::Rain(p) => p.is_last_iteration(),
|
||||||
Patterns::FadeRandom(p) => p.is_last_iteration(),
|
Patterns::FadeRandom(p) => p.is_last_iteration(),
|
||||||
|
Patterns::FadeUnstable(p) => p.is_last_iteration(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,11 +170,7 @@ impl PixelColor {
|
||||||
green = self.green.saturating_sub(green_delta);
|
green = self.green.saturating_sub(green_delta);
|
||||||
blue = self.blue.saturating_sub(blue_delta);
|
blue = self.blue.saturating_sub(blue_delta);
|
||||||
}
|
}
|
||||||
PixelColor {
|
PixelColor { red, green, blue }
|
||||||
red,
|
|
||||||
green,
|
|
||||||
blue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +212,9 @@ impl<const N: usize> Strip<N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(color: PixelColor) -> Strip<N> {
|
pub fn new(color: PixelColor) -> Strip<N> {
|
||||||
Strip { 0: [Some(color); N] }
|
Strip {
|
||||||
|
0: [Some(color); N],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill(&mut self, color: PixelColor) {
|
pub fn fill(&mut self, color: PixelColor) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::patterns::fade::Fade;
|
use crate::patterns::fade::Fade;
|
||||||
use crate::Strip;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use crate::patterns::Pattern;
|
use crate::patterns::Pattern;
|
||||||
|
use crate::Strip;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # Blink
|
/// # Blink
|
||||||
///
|
///
|
||||||
|
@ -23,7 +23,7 @@ impl<const N: usize> Pattern for Blink<N> {
|
||||||
|
|
||||||
fn is_last_iteration(&self) -> bool {
|
fn is_last_iteration(&self) -> bool {
|
||||||
match self.max_iteration {
|
match self.max_iteration {
|
||||||
None => { false }
|
None => false,
|
||||||
Some(max_iter) => {
|
Some(max_iter) => {
|
||||||
if self.current_iteration >= max_iter {
|
if self.current_iteration >= max_iter {
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::patterns::{Pattern, PixelColor};
|
use crate::patterns::{Pattern, PixelColor};
|
||||||
use crate::Strip;
|
use crate::Strip;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # Colorwipe pattern
|
/// # Colorwipe pattern
|
||||||
/// every pattern implement the iterator trait
|
/// every pattern implement the iterator trait
|
||||||
|
@ -33,7 +33,7 @@ impl<const N: usize> Pattern for ColorWipe<N> {
|
||||||
|
|
||||||
fn is_last_iteration(&self) -> bool {
|
fn is_last_iteration(&self) -> bool {
|
||||||
match self.max_iteration {
|
match self.max_iteration {
|
||||||
None => { false }
|
None => false,
|
||||||
Some(max_iter) => {
|
Some(max_iter) => {
|
||||||
if self.current_iteration >= max_iter {
|
if self.current_iteration >= max_iter {
|
||||||
true
|
true
|
||||||
|
@ -59,8 +59,6 @@ impl<const N: usize> Iterator for ColorWipe<N> {
|
||||||
}
|
}
|
||||||
let iteration = self.current_iteration % N;
|
let iteration = self.current_iteration % N;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for i in 0..iteration {
|
for i in 0..iteration {
|
||||||
strip[i] = Some(self.color);
|
strip[i] = Some(self.color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::patterns::{Pattern, PixelColor};
|
use crate::patterns::{Pattern, PixelColor};
|
||||||
use crate::Strip;
|
use crate::Strip;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # fade pattern
|
/// # fade pattern
|
||||||
/// fade from one color to an other
|
/// fade from one color to an other
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::patterns::fade::Fade;
|
use crate::patterns::fade::Fade;
|
||||||
use crate::Strip;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use crate::patterns::Pattern;
|
use crate::patterns::Pattern;
|
||||||
|
use crate::Strip;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # Blink
|
/// # Blink
|
||||||
///
|
///
|
||||||
|
@ -24,7 +24,7 @@ impl<const N: usize> Pattern for FadeRandom<N> {
|
||||||
|
|
||||||
fn is_last_iteration(&self) -> bool {
|
fn is_last_iteration(&self) -> bool {
|
||||||
match self.max_iteration {
|
match self.max_iteration {
|
||||||
None => { false }
|
None => false,
|
||||||
Some(max_iter) => {
|
Some(max_iter) => {
|
||||||
if self.current_iteration >= max_iter {
|
if self.current_iteration >= max_iter {
|
||||||
true
|
true
|
||||||
|
@ -42,7 +42,7 @@ impl<const N: usize> Iterator for FadeRandom<N> {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let strip = self.fade.next();
|
let strip = self.fade.next();
|
||||||
match strip {
|
match strip {
|
||||||
None => { None }
|
None => None,
|
||||||
Some(mut fade) => {
|
Some(mut fade) => {
|
||||||
let rng = rand::thread_rng();
|
let rng = rand::thread_rng();
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
|
@ -52,7 +52,6 @@ impl<const N: usize> Iterator for FadeRandom<N> {
|
||||||
fade[i] = Some(pixel.random_delta(self.stability, &mut rng.clone()))
|
fade[i] = Some(pixel.random_delta(self.stability, &mut rng.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Some(fade)
|
Some(fade)
|
||||||
}
|
}
|
||||||
|
|
54
src/patterns/fade_unstable.rs
Normal file
54
src/patterns/fade_unstable.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::patterns::fade::Fade;
|
||||||
|
use crate::patterns::Pattern;
|
||||||
|
use crate::Strip;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// # Blink
|
||||||
|
///
|
||||||
|
/// fade from one color to an other then go back several times
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
|
||||||
|
pub struct FadeUnstable<const N: usize> {
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) current_iteration: usize,
|
||||||
|
pub(crate) max_iteration: Option<usize>,
|
||||||
|
pub(crate) stability: usize,
|
||||||
|
fade: Fade<N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Pattern for FadeUnstable<N> {
|
||||||
|
type Strip = Strip<N>;
|
||||||
|
|
||||||
|
fn init(&mut self) -> Option<Self::Strip> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_last_iteration(&self) -> bool {
|
||||||
|
match self.max_iteration {
|
||||||
|
None => false,
|
||||||
|
Some(max_iter) => {
|
||||||
|
if self.current_iteration >= max_iter {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Iterator for FadeUnstable<N> {
|
||||||
|
type Item = Strip<N>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let strip = self.fade.next();
|
||||||
|
match strip {
|
||||||
|
None => None,
|
||||||
|
Some(fade) => {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let pixel = fade[0].unwrap().random_delta(self.stability, &mut rng);
|
||||||
|
|
||||||
|
Some(Strip::new(pixel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::patterns::{Pattern, PixelColor};
|
use crate::patterns::{Pattern, PixelColor};
|
||||||
use crate::Strip;
|
use crate::Strip;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # FillRandom
|
/// # FillRandom
|
||||||
///
|
///
|
||||||
|
@ -23,7 +23,7 @@ impl<const N: usize> Pattern for FillRandom<N> {
|
||||||
|
|
||||||
fn is_last_iteration(&self) -> bool {
|
fn is_last_iteration(&self) -> bool {
|
||||||
match self.max_iteration {
|
match self.max_iteration {
|
||||||
None => { false }
|
None => false,
|
||||||
Some(max_iter) => {
|
Some(max_iter) => {
|
||||||
if self.current_iteration >= max_iter {
|
if self.current_iteration >= max_iter {
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::patterns::{Pattern, PixelColor};
|
use crate::patterns::{Pattern, PixelColor};
|
||||||
use crate::Strip;
|
use crate::Strip;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # FillUnstable
|
/// # FillUnstable
|
||||||
///
|
///
|
||||||
|
@ -23,7 +23,7 @@ impl<const N: usize> Pattern for FillUnstable<N> {
|
||||||
|
|
||||||
fn is_last_iteration(&self) -> bool {
|
fn is_last_iteration(&self) -> bool {
|
||||||
match self.max_iteration {
|
match self.max_iteration {
|
||||||
None => { false }
|
None => false,
|
||||||
Some(max_iter) => {
|
Some(max_iter) => {
|
||||||
if self.current_iteration >= max_iter {
|
if self.current_iteration >= max_iter {
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use rand::Rng;
|
|
||||||
use crate::patterns::{Pattern, PixelColor};
|
use crate::patterns::{Pattern, PixelColor};
|
||||||
use crate::Strip;
|
use crate::Strip;
|
||||||
use serde::{Serialize, Deserialize};
|
use rand::Rng;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # rain pattern
|
/// # rain pattern
|
||||||
/// randomly fall colors from top or bottom.
|
/// randomly fall colors from top or bottom.
|
||||||
|
@ -40,7 +40,7 @@ impl<const N: usize> Pattern for Rain<N> {
|
||||||
|
|
||||||
fn is_last_iteration(&self) -> bool {
|
fn is_last_iteration(&self) -> bool {
|
||||||
match self.max_iteration {
|
match self.max_iteration {
|
||||||
None => { false }
|
None => false,
|
||||||
Some(max_iter) => {
|
Some(max_iter) => {
|
||||||
if self.current_iteration >= max_iter {
|
if self.current_iteration >= max_iter {
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::patterns::{Pattern, PixelColor};
|
use crate::patterns::{Pattern, PixelColor};
|
||||||
use crate::Strip;
|
use crate::Strip;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # Rainbow pattern
|
/// # Rainbow pattern
|
||||||
///
|
///
|
||||||
|
@ -29,7 +29,7 @@ impl<const N: usize> Pattern for Rainbow<N> {
|
||||||
|
|
||||||
fn is_last_iteration(&self) -> bool {
|
fn is_last_iteration(&self) -> bool {
|
||||||
match self.max_iteration {
|
match self.max_iteration {
|
||||||
None => { false }
|
None => false,
|
||||||
Some(max_iter) => {
|
Some(max_iter) => {
|
||||||
if self.current_iteration >= max_iter {
|
if self.current_iteration >= max_iter {
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::patterns::{Pattern, PixelColor};
|
use crate::patterns::{Pattern, PixelColor};
|
||||||
use crate::Strip;
|
use crate::Strip;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// # scanner pattern
|
/// # scanner pattern
|
||||||
/// color one pixel with a color and leave a train of this color fading to black
|
/// color one pixel with a color and leave a train of this color fading to black
|
||||||
|
@ -31,7 +31,7 @@ impl<const N:usize> Pattern for Scanner<N> {
|
||||||
|
|
||||||
fn is_last_iteration(&self) -> bool {
|
fn is_last_iteration(&self) -> bool {
|
||||||
match self.max_iteration {
|
match self.max_iteration {
|
||||||
None => { false }
|
None => false,
|
||||||
Some(max_iter) => {
|
Some(max_iter) => {
|
||||||
if self.current_iteration >= max_iter {
|
if self.current_iteration >= max_iter {
|
||||||
true
|
true
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::patterns::{Patterns, PixelColor, Pattern};
|
use crate::patterns::{Pattern, Patterns, PixelColor};
|
||||||
use crate::Strip;
|
use crate::Strip;
|
||||||
use bluer::Address;
|
use bluer::Address;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -10,9 +10,8 @@ use std::time::Duration;
|
||||||
use tokio::sync::watch::{Receiver, Sender};
|
use tokio::sync::watch::{Receiver, Sender};
|
||||||
|
|
||||||
pub(crate) mod period {
|
pub(crate) mod period {
|
||||||
use std::time::Duration;
|
|
||||||
use serde::{de::Error as _, Deserialize, Deserializer};
|
use serde::{de::Error as _, Deserialize, Deserializer};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
/// Custom deserializer for period from millisecond value
|
/// Custom deserializer for period from millisecond value
|
||||||
///
|
///
|
||||||
|
@ -27,7 +26,8 @@ pub(crate) mod period {
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
String::deserialize(deserializer)?
|
String::deserialize(deserializer)?
|
||||||
.parse::<u64>().and_then(|d| Ok(Duration::from_millis(d)))
|
.parse::<u64>()
|
||||||
|
.and_then(|d| Ok(Duration::from_millis(d)))
|
||||||
.map_err(|e| D::Error::custom(format!("{}", e)))
|
.map_err(|e| D::Error::custom(format!("{}", e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,6 @@ pub struct Context<const N: usize> {
|
||||||
|
|
||||||
pub(crate) type DeviceSequence<const N: usize> = Vec<Context<N>>;
|
pub(crate) type DeviceSequence<const N: usize> = Vec<Context<N>>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub async fn runner_loop<const N: usize>(runner: DeviceSequence<N>, tx: Sender<Strip<N>>) {
|
pub async fn runner_loop<const N: usize>(runner: DeviceSequence<N>, tx: Sender<Strip<N>>) {
|
||||||
let mut background = Strip::<N>::default();
|
let mut background = Strip::<N>::default();
|
||||||
for mut context in runner {
|
for mut context in runner {
|
||||||
|
@ -151,7 +149,6 @@ impl<const N: usize> Spectacle<N> {
|
||||||
}
|
}
|
||||||
joinhandles
|
joinhandles
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> From<Config<N>> for Spectacle<N> {
|
impl<const N: usize> From<Config<N>> for Spectacle<N> {
|
||||||
|
@ -178,7 +175,6 @@ impl<const N: usize> From<Config<N>> for Spectacle<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -188,13 +184,13 @@ mod tests {
|
||||||
let pixel_entry = PixelColor {
|
let pixel_entry = PixelColor {
|
||||||
red: 10,
|
red: 10,
|
||||||
green: 10,
|
green: 10,
|
||||||
blue: 10
|
blue: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
let pixel_mask = PixelColor {
|
let pixel_mask = PixelColor {
|
||||||
red: 20,
|
red: 20,
|
||||||
green: 20,
|
green: 20,
|
||||||
blue: 20
|
blue: 20,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut entry = Strip::<5>::new(pixel_entry);
|
let mut entry = Strip::<5>::new(pixel_entry);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user