remade new marks into a FAB
This commit is contained in:
@@ -14,6 +14,8 @@ import androidx.compose.ui.unit.dp
|
||||
import com.odweta.solon.ui.theme.PastelYellow
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
|
||||
enum class Screen {
|
||||
Main,
|
||||
@@ -32,8 +34,7 @@ class Setting(auxId: SettingsId, auxName: String, auxChecked: Boolean) {
|
||||
enum class SettingsId {
|
||||
Svoboda,
|
||||
Amoled,
|
||||
RefreshButton,
|
||||
TagNewMarks
|
||||
RefreshButton
|
||||
}
|
||||
|
||||
class SettingsData {
|
||||
@@ -55,13 +56,7 @@ class SettingsData {
|
||||
true
|
||||
)
|
||||
|
||||
var tagNewMarks: Setting = Setting(
|
||||
SettingsId.TagNewMarks,
|
||||
"Označit nové známky",
|
||||
true
|
||||
)
|
||||
|
||||
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton\n$tagNewMarks"
|
||||
override fun toString() = "$useSvobodaMode\n$useAmoledMode\n$showRefreshButton"
|
||||
}
|
||||
|
||||
enum class LectureNoteType {
|
||||
@@ -158,6 +153,7 @@ class Mark {
|
||||
lateinit var subject: String // subjectId -> parse that
|
||||
lateinit var date: String // markDate
|
||||
lateinit var theme: String // theme
|
||||
var editDate: LocalDateTime = LocalDateTime.now()
|
||||
|
||||
override fun toString(): String {
|
||||
return "mark: ${this.grade} * ${this.weight}, subject: ${this.subject}, description: ${this.theme}, date: ${this.date}"
|
||||
@@ -187,6 +183,7 @@ class Global {
|
||||
lateinit var timetableJSON: JSONObject
|
||||
lateinit var marksJSON: JSONObject
|
||||
lateinit var marks: MutableMap<String, MutableList<Mark>>
|
||||
lateinit var newMarks: MutableMap<String, MutableList<Mark>>
|
||||
lateinit var settings: SettingsData
|
||||
lateinit var fileDir: File
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -73,6 +74,7 @@ fun launchContinue(
|
||||
global.timetable = parsedTimetable
|
||||
val parsedMarks = Parsing.parseMarks(marks)
|
||||
global.marks = parsedMarks
|
||||
global.newMarks = Parsing.parseNewMarks(marks)
|
||||
|
||||
// load the settings
|
||||
val settingsData = Util.fileToString(global.filePaths.settings).toSettingsData()
|
||||
|
||||
@@ -20,16 +20,29 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AutoAwesome
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shadow
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.compose.ui.zIndex
|
||||
import com.odweta.solon.ui.theme.PastelGreen
|
||||
import com.odweta.solon.ui.theme.PastelGreenTransparent
|
||||
import com.odweta.solon.ui.theme.PastelOrange
|
||||
@@ -43,6 +56,8 @@ import com.odweta.solon.ui.theme.PastelYellowTransparent
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
var showNewMarksDialog by mutableStateOf(false)
|
||||
|
||||
enum class MarksDepthLevel {
|
||||
All,
|
||||
Subject,
|
||||
@@ -56,6 +71,106 @@ fun MarksScreen() {
|
||||
MarksDepthLevel.Subject -> MarksSubject()
|
||||
MarksDepthLevel.Detail -> MarksDetail()
|
||||
}
|
||||
if (showNewMarksDialog) {
|
||||
val scrollStateNewMarks = rememberScrollState()
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = { showNewMarksDialog = false },
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = true
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
Modifier.verticalScroll(scrollStateNewMarks).padding(vertical = 10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
"Nové známky",
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 30.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.background(
|
||||
color = Color.Black,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
).border(
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellow,
|
||||
width = global.dimens.borderWidth
|
||||
).padding(10.dp)
|
||||
)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
for ((k, v) in global.newMarks) {
|
||||
if (v.size > 0) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.border(
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner),
|
||||
color = PastelYellow,
|
||||
width = global.dimens.borderWidth
|
||||
)
|
||||
.background(
|
||||
Color.Black,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
k,
|
||||
color = Color.White,
|
||||
modifier = Modifier.padding(10.dp),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 30.sp
|
||||
)
|
||||
for (i in v) {
|
||||
Row(
|
||||
Modifier.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
i.theme,
|
||||
color = Color.White,
|
||||
modifier = Modifier.padding(end = 5.dp, top = 5.dp)
|
||||
.weight(1f)
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Text(
|
||||
i.grade + " * " + i.weight,
|
||||
color = Color.White,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = getAvgColor(i.grade, true),
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
).padding(5.dp),
|
||||
style = TextStyle(
|
||||
shadow = Shadow(
|
||||
color = Color.Black,
|
||||
offset = Offset(4f, 4f),
|
||||
blurRadius = 8f
|
||||
)
|
||||
)
|
||||
)
|
||||
Text(
|
||||
i.editDate.format(
|
||||
DateTimeFormatter.ofPattern("dd. MM. yyyy")
|
||||
),
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
HorizontalLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAvgColor(avg: String, isBorder: Boolean = false): Color {
|
||||
@@ -112,7 +227,10 @@ fun MarksAll() {
|
||||
.fillMaxHeight()
|
||||
.border(
|
||||
width = global.dimens.borderWidth,
|
||||
color = getAvgColor(Util.avg(markList), true), // decide on the color (transparent)
|
||||
color = getAvgColor(
|
||||
Util.avg(markList),
|
||||
true
|
||||
), // decide on the color (transparent)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
} else {
|
||||
@@ -131,7 +249,10 @@ fun MarksAll() {
|
||||
.height(70.dp)
|
||||
.border(
|
||||
width = global.dimens.borderWidth,
|
||||
color = getAvgColor(Util.avg(markList), true), // decide on the color (transparent)
|
||||
color = getAvgColor(
|
||||
Util.avg(markList),
|
||||
true
|
||||
), // decide on the color (transparent)
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.clickable {
|
||||
@@ -176,16 +297,6 @@ fun MarksAll() {
|
||||
Box(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
|
||||
// only add if mark date is not older than one week
|
||||
var subjectContainsNewMark = false
|
||||
for (mark in markList) {
|
||||
if (LocalDate.parse(mark.date, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
.isAfter(LocalDate.now().minusWeeks(1))
|
||||
) {
|
||||
subjectContainsNewMark = true
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = amoledModifierBackground
|
||||
) {
|
||||
@@ -224,18 +335,11 @@ fun MarksAll() {
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().weight(1f),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.weight(1f),
|
||||
contentAlignment = Alignment.TopStart
|
||||
) {
|
||||
if (subjectContainsNewMark && settings.tagNewMarks.checked.value) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(start = 4.dp, top = 4.dp),
|
||||
imageVector = Icons.Default.AutoAwesome,
|
||||
contentDescription = "New marks in subject",
|
||||
tint = Color.White // You can customize the icon color
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -251,6 +355,27 @@ fun MarksAll() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(80.dp))
|
||||
}
|
||||
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
FloatingActionButton(
|
||||
onClick = { showNewMarksDialog = true },
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(10.dp)
|
||||
.background(Color.Black, shape = RoundedCornerShape(global.dimens.roundedCorner)),
|
||||
containerColor = Color.Black,
|
||||
contentColor = PastelYellow
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(start = 4.dp, top = 4.dp),
|
||||
imageVector = Icons.Default.AutoAwesome,
|
||||
contentDescription = "New marks in subject",
|
||||
tint = PastelYellow // You can customize the icon color
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +412,8 @@ fun MarksSubject() {
|
||||
),
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.background(color = if (settings.useAmoledMode.checked.value) {
|
||||
.background(
|
||||
color = if (settings.useAmoledMode.checked.value) {
|
||||
Color.Black
|
||||
} else {
|
||||
getAvgColor(
|
||||
@@ -327,7 +453,8 @@ fun MarksSubject() {
|
||||
color = PastelYellow,
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.background(color = if (settings.useAmoledMode.checked.value) {
|
||||
.background(
|
||||
color = if (settings.useAmoledMode.checked.value) {
|
||||
Color.Black
|
||||
} else {
|
||||
PastelYellowTransparent
|
||||
@@ -353,11 +480,12 @@ fun MarksSubject() {
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 5.dp)
|
||||
.background(color = if (settings.useAmoledMode.checked.value) {
|
||||
Color.Black
|
||||
} else {
|
||||
getAvgColor(mark.grade)
|
||||
},
|
||||
.background(
|
||||
color = if (settings.useAmoledMode.checked.value) {
|
||||
Color.Black
|
||||
} else {
|
||||
getAvgColor(mark.grade)
|
||||
},
|
||||
shape = RoundedCornerShape(global.dimens.roundedCorner)
|
||||
)
|
||||
.padding(vertical = 10.dp)
|
||||
@@ -392,20 +520,6 @@ fun MarksSubject() {
|
||||
) // theme
|
||||
}
|
||||
}
|
||||
|
||||
// only add if mark date is not older than one week
|
||||
if ((LocalDate
|
||||
.parse(mark.date, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
.isAfter(LocalDate.now().minusWeeks(1))
|
||||
) && settings.tagNewMarks.checked.value
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(start = 7.dp, top = 4.dp),
|
||||
imageVector = Icons.Default.AutoAwesome,
|
||||
contentDescription = "A new mark",
|
||||
tint = Color.White // You can customize the icon color
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.odweta.solon
|
||||
|
||||
import android.util.Log
|
||||
import org.json.JSONObject
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class Parsing {
|
||||
@@ -324,9 +326,8 @@ class Parsing {
|
||||
return timetable
|
||||
}
|
||||
|
||||
fun parseMarks(jsonString: JSONObject): MutableMap<String, MutableList<Mark>> {
|
||||
fun parseMarks(json: JSONObject): MutableMap<String, MutableList<Mark>> {
|
||||
// no marks
|
||||
val json = jsonString
|
||||
if (json.has("marks") && json.getJSONArray("marks").length() == 0) {
|
||||
val noMarks = mutableMapOf<String, MutableList<Mark>>()
|
||||
val list = mutableListOf<Mark>()
|
||||
@@ -340,9 +341,9 @@ class Parsing {
|
||||
|
||||
val marksList = mutableListOf<JSONObject>()
|
||||
|
||||
val marksJSONArray = jsonString.getJSONArray("marks")
|
||||
val marksJSONArray = json.getJSONArray("marks")
|
||||
|
||||
val subjectsJSONArray = jsonString.getJSONArray("subjects")
|
||||
val subjectsJSONArray = json.getJSONArray("subjects")
|
||||
val subjectMap = mutableMapOf<String, String>()
|
||||
|
||||
for (i in 0..<subjectsJSONArray.length()) {
|
||||
@@ -361,16 +362,6 @@ class Parsing {
|
||||
marks[subj] = mutableListOf()
|
||||
}
|
||||
|
||||
/*
|
||||
* class Mark {
|
||||
* lateinit var grade: String // markText
|
||||
* lateinit var weight: String // weight
|
||||
* lateinit var subject: String // subjectId -> parse that
|
||||
* lateinit var date: String // markDate
|
||||
* lateinit var theme: String // theme
|
||||
* }
|
||||
*/
|
||||
|
||||
for (markJSON in marksList) {
|
||||
val mark = Mark()
|
||||
mark.grade = markJSON.getString("markText")
|
||||
@@ -384,6 +375,57 @@ class Parsing {
|
||||
return marks
|
||||
}
|
||||
|
||||
fun parseNewMarks(json: JSONObject): MutableMap<String, MutableList<Mark>> {
|
||||
// no marks
|
||||
if (json.has("marks") && json.getJSONArray("marks").length() == 0) {
|
||||
val noMarks = mutableMapOf<String, MutableList<Mark>>()
|
||||
val list = mutableListOf<Mark>()
|
||||
list.add(Mark().none())
|
||||
noMarks["-"] = list
|
||||
|
||||
return noMarks
|
||||
}
|
||||
|
||||
// [subject] = (<editDate>, <mark>)
|
||||
val marks = mutableMapOf<String, MutableList<Mark>>()
|
||||
|
||||
val marksList = mutableListOf<JSONObject>()
|
||||
|
||||
val marksJSONArray = json.getJSONArray("marks")
|
||||
|
||||
val subjectsJSONArray = json.getJSONArray("subjects")
|
||||
val subjectMap = mutableMapOf<String, String>()
|
||||
|
||||
for (i in 0..<subjectsJSONArray.length()) {
|
||||
val subj = subjectsJSONArray.getJSONObject(i)
|
||||
subjectMap[subj.getString("id")] = subj.getString("abbrev")
|
||||
|
||||
// map names to abbrevs
|
||||
global.subjectNameMap[subj.getString("abbrev")] = subj.getString("name")
|
||||
}
|
||||
|
||||
for (i in 0..<marksJSONArray.length()) {
|
||||
marksList.add(marksJSONArray.getJSONObject(i))
|
||||
}
|
||||
|
||||
for (subj in subjectMap.values) {
|
||||
marks[subj] = mutableListOf()
|
||||
}
|
||||
|
||||
for (markJSON in marksList) {
|
||||
val mark = Mark()
|
||||
mark.grade = markJSON.getString("markText")
|
||||
mark.weight = markJSON.getString("weight")
|
||||
mark.subject = subjectMap[markJSON.getString("subjectId")].toString()
|
||||
mark.date = markJSON.getString("markDate")
|
||||
mark.theme = markJSON.getString("theme")
|
||||
mark.editDate = LocalDateTime.parse(markJSON.getString("editDate"), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
if (isTimeBetweenD(mark.editDate, LocalDateTime.now().minusWeeks(1), LocalDateTime.now())) marks[mark.subject]?.add(mark)
|
||||
}
|
||||
|
||||
return marks
|
||||
}
|
||||
|
||||
fun getStudentClass(): String {
|
||||
return if (!userIsParent()) {
|
||||
global.userData
|
||||
|
||||
@@ -2,8 +2,6 @@ package com.odweta.solon
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -11,16 +9,9 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchColors
|
||||
import androidx.compose.material3.Text
|
||||
@@ -58,10 +49,6 @@ fun SettingsScreen() {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
SettingsTableRow(settings.showRefreshButton)
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
SettingsTableRow(settings.tagNewMarks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +78,6 @@ fun SettingsTableRow(setting: Setting) {
|
||||
SettingsId.Svoboda -> settings.useSvobodaMode = setting
|
||||
SettingsId.Amoled -> settings.useAmoledMode = setting
|
||||
SettingsId.RefreshButton -> settings.showRefreshButton = setting
|
||||
SettingsId.TagNewMarks -> settings.tagNewMarks = setting
|
||||
else -> {}
|
||||
}
|
||||
Util.saveStringToFile(global.filePaths.settings, settings.toString())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.odweta.solon
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
@@ -66,6 +67,7 @@ import com.odweta.solon.ui.theme.PastelYellowTransparent
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@@ -255,8 +257,12 @@ private fun getSubjectColor(s: Subject, transparent: Boolean): Color {
|
||||
}
|
||||
}
|
||||
}
|
||||
fun isTimeBetweenT(givenTime: LocalTime, startTime: LocalTime, endTime: LocalTime): Boolean {
|
||||
// Check if the given time is between startTime and endTime
|
||||
return givenTime.isAfter(startTime) && givenTime.isBefore(endTime)
|
||||
}
|
||||
|
||||
fun isTimeBetween(givenTime: LocalTime, startTime: LocalTime, endTime: LocalTime): Boolean {
|
||||
fun isTimeBetweenD(givenTime: LocalDateTime, startTime: LocalDateTime, endTime: LocalDateTime): Boolean {
|
||||
// Check if the given time is between startTime and endTime
|
||||
return givenTime.isAfter(startTime) && givenTime.isBefore(endTime)
|
||||
}
|
||||
@@ -283,7 +289,7 @@ fun subjectModifier(d: Day, s: Subject, lectureNumber: Int): Modifier {
|
||||
val todayDate = LocalDate.now()
|
||||
val now = LocalTime.now()
|
||||
|
||||
if (dateParsed == todayDate && isTimeBetween(now, startTimeParsed, endTimeParsed)) {
|
||||
if (dateParsed == todayDate && isTimeBetweenT(now, startTimeParsed, endTimeParsed)) {
|
||||
global.currentLectureNumber = lectureNumber
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user