Library updates are now in groups, tapping on them take you directly to the chapter to read
Fixes to lollipop snackbar and webview
This commit is contained in:
parent
1b545c9e4d
commit
1291adf821
@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
|||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.util.chop
|
import eu.kanade.tachiyomi.util.chop
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
|
||||||
import eu.kanade.tachiyomi.util.notificationManager
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
@ -170,8 +169,8 @@ internal class DownloadNotifier(private val context: Context) {
|
|||||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
clearActions()
|
clearActions()
|
||||||
setContentIntent(NotificationReceiver.openChapterPendingBroadcast(context, download
|
setContentIntent(NotificationReceiver.openChapterPendingActivity(context, download
|
||||||
.manga, download.chapter, Notifications.ID_DOWNLOAD_CHAPTER))
|
.manga, download.chapter))
|
||||||
setProgress(0, 0, false)
|
setProgress(0, 0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import android.os.Build
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
@ -30,8 +31,6 @@ import eu.kanade.tachiyomi.source.SourceManager
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
|
||||||
import eu.kanade.tachiyomi.ui.recent_updates.RecentChapterItem
|
|
||||||
import eu.kanade.tachiyomi.util.*
|
import eu.kanade.tachiyomi.util.*
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
@ -274,7 +273,7 @@ class LibraryUpdateService(
|
|||||||
// Initialize the variables holding the progress of the updates.
|
// Initialize the variables holding the progress of the updates.
|
||||||
val count = AtomicInteger(0)
|
val count = AtomicInteger(0)
|
||||||
// List containing new updates
|
// List containing new updates
|
||||||
val newUpdates = ArrayList<Manga>()
|
val newUpdates = ArrayList<Pair<Manga, Chapter>>()
|
||||||
// list containing failed updates
|
// list containing failed updates
|
||||||
val failedUpdates = ArrayList<Manga>()
|
val failedUpdates = ArrayList<Manga>()
|
||||||
// List containing categories that get included in downloads.
|
// List containing categories that get included in downloads.
|
||||||
@ -307,7 +306,7 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Convert to the manga that contains new chapters.
|
// Convert to the manga that contains new chapters.
|
||||||
.map { manga }
|
.map { Pair(manga, (it.first.minBy { ch -> ch.chapter_number }!!)) }
|
||||||
}
|
}
|
||||||
// Add manga with new chapters to the list.
|
// Add manga with new chapters to the list.
|
||||||
.doOnNext { manga ->
|
.doOnNext { manga ->
|
||||||
@ -329,6 +328,7 @@ class LibraryUpdateService(
|
|||||||
|
|
||||||
cancelProgressNotification()
|
cancelProgressNotification()
|
||||||
}
|
}
|
||||||
|
.map { manga -> manga.first }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
||||||
@ -442,53 +442,50 @@ class LibraryUpdateService(
|
|||||||
*
|
*
|
||||||
* @param updates a list of manga with new updates.
|
* @param updates a list of manga with new updates.
|
||||||
*/
|
*/
|
||||||
private fun showResultNotification(updates: List<Manga>) {
|
private fun showResultNotification(updates: List<Pair<Manga, Chapter>>) {
|
||||||
val newUpdates = updates.map { it.title.chop(45) }.toMutableSet()
|
val notifications = ArrayList<Pair<Notification, Int>>()
|
||||||
|
updates.forEach {
|
||||||
// Append new chapters from a previous, existing notification
|
val manga = it.first
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val chapter = it.second
|
||||||
val previousNotification = notificationManager.activeNotifications
|
notifications.add(Pair(notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
.find { it.id == Notifications.ID_NEW_CHAPTERS }
|
setSmallIcon(R.drawable.ic_book_white_24dp)
|
||||||
|
setLargeIcon(notificationBitmap)
|
||||||
if (previousNotification != null) {
|
setContentTitle(manga.title.chop(45))
|
||||||
val oldUpdates = previousNotification.notification.extras
|
color = ContextCompat.getColor(applicationContext, R.color.colorAccentLight)
|
||||||
.getString(Notification.EXTRA_BIG_TEXT)
|
setContentText(chapter.name)
|
||||||
|
priority = NotificationCompat.PRIORITY_HIGH
|
||||||
if (!oldUpdates.isNullOrEmpty()) {
|
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||||
newUpdates += oldUpdates.split("\n")
|
setContentIntent(
|
||||||
}
|
NotificationReceiver.openChapterPendingActivity(
|
||||||
}
|
this@LibraryUpdateService, manga, chapter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setAutoCancel(true)
|
||||||
|
}, manga.id.hashCode()))
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationManager.notify(Notifications.ID_NEW_CHAPTERS, notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
NotificationManagerCompat.from(this).apply {
|
||||||
|
notifications.forEach {
|
||||||
|
notify(it.second, it.first)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || notificationManager
|
||||||
|
.activeNotifications.find { it.groupKey == Notifications.GROUP_NEW_CHAPTERS } == null) {
|
||||||
|
notify(Notifications.ID_NEW_CHAPTERS, notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
setSmallIcon(R.drawable.ic_book_white_24dp)
|
setSmallIcon(R.drawable.ic_book_white_24dp)
|
||||||
setLargeIcon(notificationBitmap)
|
setLargeIcon(notificationBitmap)
|
||||||
setContentTitle(getString(R.string.notification_new_chapters))
|
setContentTitle(getString(R.string.notification_new_chapters))
|
||||||
color = ContextCompat.getColor(applicationContext, R.color.colorAccentLight)
|
color = ContextCompat.getColor(applicationContext, R.color.colorAccentLight)
|
||||||
if (newUpdates.size > 1) {
|
setContentText(getString(R.string.notification_new_chapters_text, updates.size))
|
||||||
setContentText(getString(R.string.notification_new_chapters_text, newUpdates.size))
|
|
||||||
setStyle(NotificationCompat.BigTextStyle().bigText(newUpdates.joinToString("\n")))
|
|
||||||
setNumber(newUpdates.size)
|
|
||||||
} else {
|
|
||||||
val onlyManga = updates.first()
|
|
||||||
val id = onlyManga.id ?: 0
|
|
||||||
setContentText(newUpdates.first())
|
|
||||||
|
|
||||||
val context = applicationContext
|
|
||||||
val db = DatabaseHelper(context)
|
|
||||||
val chapters = db.getChapters(onlyManga).executeAsBlocking()
|
|
||||||
val chapter = chapters.sortedByDescending { it.source_order }.find { !it.read }
|
|
||||||
if (chapter != null) {
|
|
||||||
addAction(R.drawable.ic_in_library_24dp, getString(R.string.action_start_reading),
|
|
||||||
NotificationReceiver.openChapterPendingBroadcast(context, onlyManga,
|
|
||||||
chapter, Notifications.ID_NEW_CHAPTERS))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
priority = NotificationCompat.PRIORITY_HIGH
|
priority = NotificationCompat.PRIORITY_HIGH
|
||||||
|
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||||
|
setGroupSummary(true)
|
||||||
setContentIntent(getNotificationIntent())
|
setContentIntent(getNotificationIntent())
|
||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels the progress notification.
|
* Cancels the progress notification.
|
||||||
|
@ -300,10 +300,11 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
* @param manga manga of chapter
|
* @param manga manga of chapter
|
||||||
* @param chapter chapter that needs to be opened
|
* @param chapter chapter that needs to be opened
|
||||||
*/
|
*/
|
||||||
internal fun openChapterPendingBroadcast(context: Context, manga: Manga, chapter:
|
internal fun openChapterPendingActivity(context: Context, manga: Manga, chapter:
|
||||||
Chapter, notificationId: Int): PendingIntent {
|
Chapter): PendingIntent {
|
||||||
val newIntent = ReaderActivity.newIntent(context, manga, chapter, notificationId)
|
val newIntent = ReaderActivity.newIntent(context, manga, chapter)
|
||||||
return PendingIntent.getActivity(context, 0, newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent
|
||||||
|
.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,7 +36,8 @@ object Notifications {
|
|||||||
* Notification channel and ids used by the library updater.
|
* Notification channel and ids used by the library updater.
|
||||||
*/
|
*/
|
||||||
const val CHANNEL_NEW_CHAPTERS = "new_chapters_channel"
|
const val CHANNEL_NEW_CHAPTERS = "new_chapters_channel"
|
||||||
const val ID_NEW_CHAPTERS = 301
|
const val ID_NEW_CHAPTERS = -301
|
||||||
|
const val GROUP_NEW_CHAPTERS = "eu.kanade.tachiyomi.NEW_CHAPTERS"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the notification channels introduced in Android Oreo.
|
* Creates the notification channels introduced in Android Oreo.
|
||||||
|
@ -508,15 +508,14 @@ open class BrowseCatalogueController(bundle: Bundle) :
|
|||||||
presenter.changeMangaFavorite(manga)
|
presenter.changeMangaFavorite(manga)
|
||||||
adapter?.notifyItemChanged(position)
|
adapter?.notifyItemChanged(position)
|
||||||
snack =
|
snack =
|
||||||
catalogue_view?.snack(activity.getString(R.string.manga_removed_library), 5000) {
|
catalouge_layout?.snack(R.string.manga_removed_library, 5000) {
|
||||||
setAction(R.string.action_undo) {
|
setAction(R.string.action_undo) {
|
||||||
if (!manga.favorite) addManga(manga, position)
|
if (!manga.favorite) addManga(manga, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addManga(manga, position)
|
addManga(manga, position)
|
||||||
snack =
|
snack = catalouge_layout?.snack(R.string.manga_added_library)
|
||||||
catalogue_view?.snack(activity.getString(R.string.manga_added_library))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import android.util.AttributeSet
|
|||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -98,7 +99,8 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
swipe_refresh.setOnRefreshListener {
|
swipe_refresh.setOnRefreshListener {
|
||||||
if (!LibraryUpdateService.isRunning(context)) {
|
if (!LibraryUpdateService.isRunning(context)) {
|
||||||
LibraryUpdateService.start(context, category)
|
LibraryUpdateService.start(context, category)
|
||||||
swipe_refresh.snack(R.string.updating_category)
|
controller.snack?.dismiss()
|
||||||
|
controller.snack = swipe_refresh.snack(R.string.updating_category)
|
||||||
}
|
}
|
||||||
// It can be a very long operation, so we disable swipe refresh and show a toast.
|
// It can be a very long operation, so we disable swipe refresh and show a toast.
|
||||||
swipe_refresh.isRefreshing = false
|
swipe_refresh.isRefreshing = false
|
||||||
|
@ -18,6 +18,7 @@ import androidx.core.view.GravityCompat
|
|||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
import com.bluelinelabs.conductor.ControllerChangeType
|
||||||
import com.f2prateek.rx.preferences.Preference
|
import com.f2prateek.rx.preferences.Preference
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.jakewharton.rxbinding.support.v4.view.pageSelections
|
import com.jakewharton.rxbinding.support.v4.view.pageSelections
|
||||||
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges
|
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges
|
||||||
import com.jakewharton.rxrelay.BehaviorRelay
|
import com.jakewharton.rxrelay.BehaviorRelay
|
||||||
@ -121,6 +122,8 @@ class LibraryController(
|
|||||||
|
|
||||||
private var searchViewSubscription: Subscription? = null
|
private var searchViewSubscription: Subscription? = null
|
||||||
|
|
||||||
|
var snack: Snackbar? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
retainViewMode = RetainViewMode.RETAIN_DETACH
|
retainViewMode = RetainViewMode.RETAIN_DETACH
|
||||||
@ -176,6 +179,12 @@ class LibraryController(
|
|||||||
super.onDestroyView(view)
|
super.onDestroyView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDetach(view: View) {
|
||||||
|
snack?.dismiss()
|
||||||
|
snack = null
|
||||||
|
super.onDetach(view)
|
||||||
|
}
|
||||||
|
|
||||||
override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup {
|
override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup {
|
||||||
val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
|
val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
|
||||||
navView = view
|
navView = view
|
||||||
@ -476,7 +485,8 @@ class LibraryController(
|
|||||||
val mangas = selectedMangas.toList()
|
val mangas = selectedMangas.toList()
|
||||||
presenter.removeMangaFromLibrary(mangas, true)
|
presenter.removeMangaFromLibrary(mangas, true)
|
||||||
destroyActionModeIfNeeded()
|
destroyActionModeIfNeeded()
|
||||||
view?.snack(activity?.getString(R.string.manga_removed_library) ?: "", 5000) {
|
snack?.dismiss()
|
||||||
|
snack = view?.snack(activity?.getString(R.string.manga_removed_library) ?: "", 5000) {
|
||||||
setAction(R.string.action_undo) {
|
setAction(R.string.action_undo) {
|
||||||
presenter.addMangas(mangas)
|
presenter.addMangas(mangas)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ class MangaWebViewController(bundle: Bundle? = null) : BaseController(bundle) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||||
return inflater.inflate(R.layout.manga_info_web_controller, container, false)
|
return WebView(applicationContext)// inflater.inflate(R.layout.manga_info_web_controller, container,
|
||||||
|
// false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
|
@ -19,6 +19,7 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
@ -120,13 +121,13 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>(),
|
|||||||
const val VERTICAL = 3
|
const val VERTICAL = 3
|
||||||
const val WEBTOON = 4
|
const val WEBTOON = 4
|
||||||
|
|
||||||
fun newIntent(context: Context, manga: Manga, chapter: Chapter, notificationId:Int? =
|
fun newIntent(context: Context, manga: Manga, chapter: Chapter):
|
||||||
null):
|
|
||||||
Intent {
|
Intent {
|
||||||
val intent = Intent(context, ReaderActivity::class.java)
|
val intent = Intent(context, ReaderActivity::class.java)
|
||||||
intent.putExtra("manga", manga.id)
|
intent.putExtra("manga", manga.id)
|
||||||
intent.putExtra("chapter", chapter.id)
|
intent.putExtra("chapter", chapter.id)
|
||||||
intent.putExtra("notificationId", notificationId)
|
intent.putExtra("chapterUrl", chapter.url)
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,17 +147,14 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>(),
|
|||||||
if (presenter.needsInit()) {
|
if (presenter.needsInit()) {
|
||||||
val manga = intent.extras!!.getLong("manga", -1)
|
val manga = intent.extras!!.getLong("manga", -1)
|
||||||
val chapter = intent.extras!!.getLong("chapter", -1)
|
val chapter = intent.extras!!.getLong("chapter", -1)
|
||||||
val notificationId = intent.extras!!.getInt("notificationId", -1)
|
val chapterUrl = intent.extras!!.getString("chapterUrl", "")
|
||||||
if (notificationId > 0) {
|
if (manga == -1L || chapterUrl == "" && chapter == -1L) {
|
||||||
applicationContext.notificationManager.cancel(notificationId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (manga == -1L || chapter == -1L) {
|
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
presenter.init(manga, chapter)
|
if (chapterUrl.isEmpty()) presenter.init(manga, chapter)
|
||||||
|
else presenter.init(manga, chapterUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
|
@ -185,6 +185,15 @@ class ReaderPresenter(
|
|||||||
}, ReaderActivity::setInitialChapterError)
|
}, ReaderActivity::setInitialChapterError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun init(mangaId: Long, chapterUrl: String) {
|
||||||
|
if (!needsInit()) return
|
||||||
|
val context = Injekt.get<Application>()
|
||||||
|
val db = DatabaseHelper(context)
|
||||||
|
val chapterId = db.getChapter(chapterUrl).executeAsBlocking()?.id
|
||||||
|
if (chapterId != null)
|
||||||
|
init(mangaId, chapterId)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes this presenter with the given [manga] and [initialChapterId]. This method will
|
* Initializes this presenter with the given [manga] and [initialChapterId]. This method will
|
||||||
* set the chapter loader, view subscriptions and trigger an initial load.
|
* set the chapter loader, view subscriptions and trigger an initial load.
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:id="@+id/catalouge_layout"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
Loading…
Reference in New Issue
Block a user