mirror of
https://github.com/SimpleMobileTools/Simple-Clock.git
synced 2025-03-25 16:00:09 +01:00
Merge pull request #469 from rawlin/next-alarm-fix
Fix for showing accurate next enabled alarm
This commit is contained in:
commit
586ca470e9
@ -13,10 +13,7 @@ import android.widget.TextView
|
|||||||
import com.simplemobiletools.clock.BuildConfig
|
import com.simplemobiletools.clock.BuildConfig
|
||||||
import com.simplemobiletools.clock.R
|
import com.simplemobiletools.clock.R
|
||||||
import com.simplemobiletools.clock.adapters.ViewPagerAdapter
|
import com.simplemobiletools.clock.adapters.ViewPagerAdapter
|
||||||
import com.simplemobiletools.clock.extensions.config
|
import com.simplemobiletools.clock.extensions.*
|
||||||
import com.simplemobiletools.clock.extensions.getNextAlarm
|
|
||||||
import com.simplemobiletools.clock.extensions.rescheduleEnabledAlarms
|
|
||||||
import com.simplemobiletools.clock.extensions.updateWidgets
|
|
||||||
import com.simplemobiletools.clock.helpers.*
|
import com.simplemobiletools.clock.helpers.*
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.*
|
import com.simplemobiletools.commons.helpers.*
|
||||||
@ -44,9 +41,11 @@ class MainActivity : SimpleActivity() {
|
|||||||
setupTabs()
|
setupTabs()
|
||||||
updateWidgets()
|
updateWidgets()
|
||||||
|
|
||||||
if (getNextAlarm().isEmpty()) {
|
getEnabledAlarms { enabledAlarms ->
|
||||||
ensureBackgroundThread {
|
if (enabledAlarms.isNullOrEmpty()) {
|
||||||
rescheduleEnabledAlarms()
|
ensureBackgroundThread {
|
||||||
|
rescheduleEnabledAlarms()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ import android.media.AudioAttributes
|
|||||||
import android.media.AudioManager.STREAM_ALARM
|
import android.media.AudioManager.STREAM_ALARM
|
||||||
import android.media.RingtoneManager
|
import android.media.RingtoneManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
@ -31,9 +33,9 @@ import com.simplemobiletools.clock.receivers.*
|
|||||||
import com.simplemobiletools.clock.services.SnoozeService
|
import com.simplemobiletools.clock.services.SnoozeService
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.*
|
import com.simplemobiletools.commons.helpers.*
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
@ -205,7 +207,8 @@ fun Context.deleteNotificationChannel(channelId: String) {
|
|||||||
try {
|
try {
|
||||||
val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
manager.deleteNotificationChannel(channelId)
|
manager.deleteNotificationChannel(channelId)
|
||||||
} catch (_: Throwable) {}
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,20 +269,53 @@ fun Context.formatTo12HourFormat(showSeconds: Boolean, hours: Int, minutes: Int,
|
|||||||
return "${formatTime(showSeconds, false, newHours, minutes, seconds)} $appendable"
|
return "${formatTime(showSeconds, false, newHours, minutes, seconds)} $appendable"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getNextAlarm(): String {
|
fun Context.getClosestEnabledAlarmString(callback: (result: String) -> Unit) {
|
||||||
val milliseconds = (getSystemService(Context.ALARM_SERVICE) as AlarmManager).nextAlarmClock?.triggerTime ?: return ""
|
getEnabledAlarms { enabledAlarms ->
|
||||||
val calendar = Calendar.getInstance()
|
if (enabledAlarms.isNullOrEmpty()) {
|
||||||
val isDaylightSavingActive = TimeZone.getDefault().inDaylightTime(Date())
|
callback("")
|
||||||
var offset = calendar.timeZone.rawOffset
|
return@getEnabledAlarms
|
||||||
if (isDaylightSavingActive) {
|
}
|
||||||
offset += TimeZone.getDefault().dstSavings
|
|
||||||
}
|
|
||||||
|
|
||||||
calendar.timeInMillis = milliseconds
|
val nextAlarmList = enabledAlarms
|
||||||
val dayOfWeekIndex = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
|
.mapNotNull { getTimeUntilNextAlarm(it.timeInMinutes, it.days) }
|
||||||
val dayOfWeek = resources.getStringArray(R.array.week_days_short)[dayOfWeekIndex]
|
|
||||||
val formatted = getFormattedTime(((milliseconds + offset) / 1000L).toInt(), false, false)
|
if (nextAlarmList.isEmpty()) {
|
||||||
return "$dayOfWeek $formatted"
|
callback("")
|
||||||
|
}
|
||||||
|
|
||||||
|
var closestAlarmTime = Int.MAX_VALUE
|
||||||
|
nextAlarmList.forEach { time ->
|
||||||
|
if (time < closestAlarmTime) {
|
||||||
|
closestAlarmTime = time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestAlarmTime == Int.MAX_VALUE) {
|
||||||
|
callback("")
|
||||||
|
}
|
||||||
|
|
||||||
|
val calendar = Calendar.getInstance().apply { firstDayOfWeek = Calendar.MONDAY }
|
||||||
|
calendar.add(Calendar.MINUTE, closestAlarmTime)
|
||||||
|
val dayOfWeekIndex = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
|
||||||
|
val dayOfWeek = resources.getStringArray(R.array.week_days_short)[dayOfWeekIndex]
|
||||||
|
val pattern = if (DateFormat.is24HourFormat(this)) {
|
||||||
|
"HH:mm"
|
||||||
|
} else {
|
||||||
|
"h:mm a"
|
||||||
|
}
|
||||||
|
|
||||||
|
val formattedTime = SimpleDateFormat(pattern, Locale.getDefault()).format(calendar.time)
|
||||||
|
callback("$dayOfWeek $formattedTime")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getEnabledAlarms(callback: (result: List<Alarm>?) -> Unit) {
|
||||||
|
ensureBackgroundThread {
|
||||||
|
val alarms = dbHelper.getEnabledAlarms()
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
callback(alarms)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.rescheduleEnabledAlarms() {
|
fun Context.rescheduleEnabledAlarms() {
|
||||||
|
@ -4,3 +4,5 @@ import java.util.concurrent.TimeUnit
|
|||||||
|
|
||||||
val Int.secondsToMillis get() = TimeUnit.SECONDS.toMillis(this.toLong())
|
val Int.secondsToMillis get() = TimeUnit.SECONDS.toMillis(this.toLong())
|
||||||
val Int.millisToSeconds get() = TimeUnit.MILLISECONDS.toSeconds(this.toLong())
|
val Int.millisToSeconds get() = TimeUnit.MILLISECONDS.toSeconds(this.toLong())
|
||||||
|
|
||||||
|
fun Int.isBitSet(bit: Int) = this and bit == bit
|
||||||
|
@ -89,13 +89,14 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface {
|
|||||||
it.timeInMinutes
|
it.timeInMinutes
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
context?.getEnabledAlarms { enabledAlarms ->
|
||||||
if (context?.getNextAlarm()?.isEmpty() == true) {
|
if (enabledAlarms.isNullOrEmpty()) {
|
||||||
alarms.forEach {
|
alarms.forEach {
|
||||||
if (it.days == TODAY_BIT && it.isEnabled && it.timeInMinutes <= getCurrentDayMinutes()) {
|
if (it.days == TODAY_BIT && it.isEnabled && it.timeInMinutes <= getCurrentDayMinutes()) {
|
||||||
it.isEnabled = false
|
it.isEnabled = false
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
context?.dbHelper?.updateAlarmEnabledState(it.id, false)
|
context?.dbHelper?.updateAlarmEnabledState(it.id, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import com.simplemobiletools.clock.models.MyTimeZone
|
|||||||
import com.simplemobiletools.commons.extensions.beVisibleIf
|
import com.simplemobiletools.commons.extensions.beVisibleIf
|
||||||
import com.simplemobiletools.commons.extensions.getProperTextColor
|
import com.simplemobiletools.commons.extensions.getProperTextColor
|
||||||
import com.simplemobiletools.commons.extensions.updateTextColors
|
import com.simplemobiletools.commons.extensions.updateTextColors
|
||||||
import kotlinx.android.synthetic.main.fragment_clock.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_clock.view.*
|
import kotlinx.android.synthetic.main.fragment_clock.view.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -113,10 +112,11 @@ class ClockFragment : Fragment() {
|
|||||||
|
|
||||||
fun updateAlarm() {
|
fun updateAlarm() {
|
||||||
view.apply {
|
view.apply {
|
||||||
val nextAlarm = requireContext().getNextAlarm()
|
requireContext().getClosestEnabledAlarmString { nextAlarm ->
|
||||||
clock_alarm.beVisibleIf(nextAlarm.isNotEmpty())
|
clock_alarm.beVisibleIf(nextAlarm.isNotEmpty())
|
||||||
clock_alarm.text = nextAlarm
|
clock_alarm.text = nextAlarm
|
||||||
clock_alarm.colorCompoundDrawable(requireContext().getProperTextColor())
|
clock_alarm.colorCompoundDrawable(requireContext().getProperTextColor())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.simplemobiletools.clock.helpers
|
package com.simplemobiletools.clock.helpers
|
||||||
|
|
||||||
|
import com.simplemobiletools.clock.extensions.isBitSet
|
||||||
import com.simplemobiletools.clock.models.MyTimeZone
|
import com.simplemobiletools.clock.models.MyTimeZone
|
||||||
|
import com.simplemobiletools.commons.extensions.addBit
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
@ -197,3 +199,51 @@ fun getAllTimeZones() = arrayListOf(
|
|||||||
MyTimeZone(88, "GMT+13:00 Auckland", "Pacific/Auckland"),
|
MyTimeZone(88, "GMT+13:00 Auckland", "Pacific/Auckland"),
|
||||||
MyTimeZone(89, "GMT+13:00 Tongatapu", "Pacific/Tongatapu")
|
MyTimeZone(89, "GMT+13:00 Tongatapu", "Pacific/Tongatapu")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun getTimeUntilNextAlarm(alarmTimeInMinutes: Int, days: Int): Int? {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.firstDayOfWeek = Calendar.MONDAY
|
||||||
|
val currentTimeInMinutes = calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)
|
||||||
|
val currentDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - Calendar.MONDAY
|
||||||
|
|
||||||
|
var minTimeDifferenceInMinutes = Int.MAX_VALUE
|
||||||
|
|
||||||
|
for (i in 0..6) {
|
||||||
|
val alarmDayOfWeek = (currentDayOfWeek + i) % 7
|
||||||
|
if (isAlarmEnabledForDay(alarmDayOfWeek, days)) {
|
||||||
|
val timeDifferenceInMinutes = getTimeDifferenceInMinutes(currentTimeInMinutes, alarmTimeInMinutes, i)
|
||||||
|
if (timeDifferenceInMinutes < minTimeDifferenceInMinutes) {
|
||||||
|
minTimeDifferenceInMinutes = timeDifferenceInMinutes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (minTimeDifferenceInMinutes != Int.MAX_VALUE) {
|
||||||
|
minTimeDifferenceInMinutes
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAlarmEnabledForDay(day: Int, alarmDays: Int): Boolean {
|
||||||
|
val bit = createBit(day)
|
||||||
|
return alarmDays.isBitSet(bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createBit(day: Int): Int {
|
||||||
|
var bit = 1
|
||||||
|
repeat(day) {
|
||||||
|
bit = bit.addBit(bit)
|
||||||
|
}
|
||||||
|
return bit
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTimeDifferenceInMinutes(currentTimeInMinutes: Int, alarmTimeInMinutes: Int, daysUntilAlarm: Int): Int {
|
||||||
|
val minutesInADay = 24 * 60
|
||||||
|
val minutesUntilAlarm = daysUntilAlarm * minutesInADay + alarmTimeInMinutes
|
||||||
|
return if (minutesUntilAlarm > currentTimeInMinutes) {
|
||||||
|
minutesUntilAlarm - currentTimeInMinutes
|
||||||
|
} else {
|
||||||
|
minutesInADay - (currentTimeInMinutes - minutesUntilAlarm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,11 +12,16 @@ import android.widget.RemoteViews
|
|||||||
import com.simplemobiletools.clock.R
|
import com.simplemobiletools.clock.R
|
||||||
import com.simplemobiletools.clock.activities.SplashActivity
|
import com.simplemobiletools.clock.activities.SplashActivity
|
||||||
import com.simplemobiletools.clock.extensions.config
|
import com.simplemobiletools.clock.extensions.config
|
||||||
import com.simplemobiletools.clock.extensions.getNextAlarm
|
import com.simplemobiletools.clock.extensions.getClosestEnabledAlarmString
|
||||||
import com.simplemobiletools.commons.extensions.applyColorFilter
|
import com.simplemobiletools.commons.extensions.applyColorFilter
|
||||||
import com.simplemobiletools.commons.extensions.getLaunchIntent
|
import com.simplemobiletools.commons.extensions.getLaunchIntent
|
||||||
import com.simplemobiletools.commons.extensions.setText
|
import com.simplemobiletools.commons.extensions.setText
|
||||||
import com.simplemobiletools.commons.extensions.setVisibleIf
|
import com.simplemobiletools.commons.extensions.setVisibleIf
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class MyDigitalTimeWidgetProvider : AppWidgetProvider() {
|
class MyDigitalTimeWidgetProvider : AppWidgetProvider() {
|
||||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||||
@ -37,10 +42,11 @@ class MyDigitalTimeWidgetProvider : AppWidgetProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTexts(context: Context, views: RemoteViews) {
|
private fun updateTexts(context: Context, views: RemoteViews) {
|
||||||
val nextAlarm = context.getNextAlarm()
|
context.getClosestEnabledAlarmString { nextAlarm ->
|
||||||
views.apply {
|
views.apply {
|
||||||
setText(R.id.widget_next_alarm, nextAlarm)
|
setText(R.id.widget_next_alarm, nextAlarm)
|
||||||
setVisibleIf(R.id.widget_alarm_holder, nextAlarm.isNotEmpty())
|
setVisibleIf(R.id.widget_alarm_holder, nextAlarm.isNotEmpty())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,20 +6,19 @@ import android.app.NotificationManager
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.text.format.DateFormat
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.simplemobiletools.clock.R
|
import com.simplemobiletools.clock.R
|
||||||
|
import com.simplemobiletools.clock.extensions.getClosestEnabledAlarmString
|
||||||
import com.simplemobiletools.clock.extensions.getDismissAlarmPendingIntent
|
import com.simplemobiletools.clock.extensions.getDismissAlarmPendingIntent
|
||||||
import com.simplemobiletools.clock.extensions.getNextAlarm
|
|
||||||
import com.simplemobiletools.clock.extensions.getOpenAlarmTabIntent
|
import com.simplemobiletools.clock.extensions.getOpenAlarmTabIntent
|
||||||
import com.simplemobiletools.clock.helpers.*
|
import com.simplemobiletools.clock.helpers.ALARM_ID
|
||||||
|
import com.simplemobiletools.clock.helpers.EARLY_ALARM_DISMISSAL_CHANNEL_ID
|
||||||
|
import com.simplemobiletools.clock.helpers.EARLY_ALARM_NOTIF_ID
|
||||||
import com.simplemobiletools.commons.helpers.isOreoPlus
|
import com.simplemobiletools.commons.helpers.isOreoPlus
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class EarlyAlarmDismissalReceiver : BroadcastReceiver() {
|
class EarlyAlarmDismissalReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val alarmId = intent.getIntExtra(ALARM_ID, -1)
|
val alarmId = intent.getIntExtra(ALARM_ID, -1)
|
||||||
if (alarmId == -1) {
|
if (alarmId == -1) {
|
||||||
return
|
return
|
||||||
@ -29,30 +28,35 @@ class EarlyAlarmDismissalReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun triggerEarlyDismissalNotification(context: Context, alarmId: Int) {
|
private fun triggerEarlyDismissalNotification(context: Context, alarmId: Int) {
|
||||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
context.getClosestEnabledAlarmString { alarmString ->
|
||||||
if (isOreoPlus()) {
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
NotificationChannel(EARLY_ALARM_DISMISSAL_CHANNEL_ID, context.getString(R.string.early_alarm_dismissal), NotificationManager.IMPORTANCE_DEFAULT).apply {
|
if (isOreoPlus()) {
|
||||||
setBypassDnd(true)
|
NotificationChannel(
|
||||||
setSound(null, null)
|
EARLY_ALARM_DISMISSAL_CHANNEL_ID,
|
||||||
notificationManager.createNotificationChannel(this)
|
context.getString(R.string.early_alarm_dismissal),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
setBypassDnd(true)
|
||||||
|
setSound(null, null)
|
||||||
|
notificationManager.createNotificationChannel(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
val dismissIntent = context.getDismissAlarmPendingIntent(alarmId, EARLY_ALARM_NOTIF_ID)
|
||||||
|
val contentIntent = context.getOpenAlarmTabIntent()
|
||||||
|
val notification = NotificationCompat.Builder(context)
|
||||||
|
.setContentTitle(context.getString(R.string.upcoming_alarm))
|
||||||
|
.setContentText(alarmString)
|
||||||
|
.setSmallIcon(R.drawable.ic_alarm_vector)
|
||||||
|
.setPriority(Notification.PRIORITY_LOW)
|
||||||
|
.addAction(0, context.getString(R.string.dismiss), dismissIntent)
|
||||||
|
.setContentIntent(contentIntent)
|
||||||
|
.setSound(null)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setChannelId(EARLY_ALARM_DISMISSAL_CHANNEL_ID)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(EARLY_ALARM_NOTIF_ID, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dismissIntent = context.getDismissAlarmPendingIntent(alarmId, EARLY_ALARM_NOTIF_ID)
|
|
||||||
val contentIntent = context.getOpenAlarmTabIntent()
|
|
||||||
val notification = NotificationCompat.Builder(context)
|
|
||||||
.setContentTitle(context.getString(R.string.upcoming_alarm))
|
|
||||||
.setContentText(context.getNextAlarm())
|
|
||||||
.setSmallIcon(R.drawable.ic_alarm_vector)
|
|
||||||
.setPriority(Notification.PRIORITY_LOW)
|
|
||||||
.addAction(0, context.getString(R.string.dismiss), dismissIntent)
|
|
||||||
.setContentIntent(contentIntent)
|
|
||||||
.setSound(null)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setChannelId(EARLY_ALARM_DISMISSAL_CHANNEL_ID)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
notificationManager.notify(EARLY_ALARM_NOTIF_ID, notification)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user