mirror of
https://github.com/Matte23/circolapp.git
synced 2025-12-06 07:29:10 +00:00
Automatically resolve circular's direct URL
This commit is contained in:
@@ -80,6 +80,7 @@ dependencies {
|
|||||||
// Misc
|
// Misc
|
||||||
implementation(Dependencies.Misc.appIntro)
|
implementation(Dependencies.Misc.appIntro)
|
||||||
implementation(Dependencies.Misc.materialSpinner)
|
implementation(Dependencies.Misc.materialSpinner)
|
||||||
|
implementation(Dependencies.Misc.materialProgressBar)
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
testImplementation(Dependencies.Testing.junit)
|
testImplementation(Dependencies.Testing.junit)
|
||||||
|
|||||||
@@ -23,19 +23,27 @@ import android.view.LayoutInflater
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import net.underdesk.circolapp.MainActivity
|
import net.underdesk.circolapp.MainActivity
|
||||||
|
import net.underdesk.circolapp.data.AndroidCircularRepository
|
||||||
import net.underdesk.circolapp.databinding.ItemAttachmentBinding
|
import net.underdesk.circolapp.databinding.ItemAttachmentBinding
|
||||||
|
import net.underdesk.circolapp.shared.data.Circular
|
||||||
|
import net.underdesk.circolapp.shared.data.CircularRepository
|
||||||
import net.underdesk.circolapp.utils.DownloadableFile
|
import net.underdesk.circolapp.utils.DownloadableFile
|
||||||
import net.underdesk.circolapp.utils.FileUtils
|
import net.underdesk.circolapp.utils.FileUtils
|
||||||
|
|
||||||
class AttachmentAdapter(
|
class AttachmentAdapter(
|
||||||
private val attachmentsNames: List<String>,
|
private val circular: Circular,
|
||||||
private val attachmentsUrls: List<String>,
|
|
||||||
private val mainActivity: MainActivity,
|
private val mainActivity: MainActivity,
|
||||||
|
private val adapterScope: CoroutineScope,
|
||||||
|
private val circularHolder: CircularLetterAdapter.CircularLetterViewHolder
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<AttachmentAdapter.AttachmentViewHolder>() {
|
RecyclerView.Adapter<AttachmentAdapter.AttachmentViewHolder>() {
|
||||||
private val adapterCallback: CircularLetterAdapter.AdapterCallback = mainActivity
|
private val adapterCallback: CircularLetterAdapter.AdapterCallback = mainActivity
|
||||||
|
private lateinit var circularRepository: CircularRepository
|
||||||
private lateinit var context: Context
|
private lateinit var context: Context
|
||||||
|
|
||||||
inner class AttachmentViewHolder(binding: ItemAttachmentBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class AttachmentViewHolder(binding: ItemAttachmentBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
@@ -48,26 +56,74 @@ class AttachmentAdapter(
|
|||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AttachmentViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AttachmentViewHolder {
|
||||||
val binding = ItemAttachmentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = ItemAttachmentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
context = parent.context
|
context = parent.context
|
||||||
|
circularRepository = AndroidCircularRepository.getInstance(context)
|
||||||
|
|
||||||
return AttachmentViewHolder(binding)
|
return AttachmentViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: AttachmentViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: AttachmentViewHolder, position: Int) {
|
||||||
holder.title.text = attachmentsNames[position]
|
holder.title.text = circular.attachmentsNames[position]
|
||||||
|
|
||||||
|
val observer = Observer<Boolean> {
|
||||||
|
if (it) {
|
||||||
|
holder.viewButton.isEnabled = false
|
||||||
|
holder.downloadButton.isEnabled = false
|
||||||
|
holder.shareButton.isEnabled = false
|
||||||
|
} else {
|
||||||
|
holder.viewButton.isEnabled = true
|
||||||
|
holder.downloadButton.isEnabled = true
|
||||||
|
holder.shareButton.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
circularHolder.loading.observe(mainActivity, observer)
|
||||||
|
circularHolder.observer.add(observer)
|
||||||
|
|
||||||
holder.viewButton.setOnClickListener {
|
holder.viewButton.setOnClickListener {
|
||||||
FileUtils.viewFile(attachmentsUrls[position], context, mainActivity.customTabsSession)
|
runWhenUrlIsAvailable(position) { url ->
|
||||||
|
FileUtils.viewFile(url, context, mainActivity.customTabsSession)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.shareButton.setOnClickListener {
|
holder.shareButton.setOnClickListener {
|
||||||
FileUtils.shareFile(attachmentsUrls[position], context)
|
runWhenUrlIsAvailable(position) { url -> FileUtils.shareFile(url, context) }
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.downloadButton.setOnClickListener {
|
holder.downloadButton.setOnClickListener {
|
||||||
val file = DownloadableFile(attachmentsNames[position], attachmentsUrls[position])
|
runWhenUrlIsAvailable(position) { url ->
|
||||||
|
val file = DownloadableFile(circular.attachmentsNames[position], url)
|
||||||
FileUtils.downloadFile(file, adapterCallback, context)
|
FileUtils.downloadFile(file, adapterCallback, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount() = attachmentsNames.size
|
private fun runWhenUrlIsAvailable(position: Int, code: (url: String) -> Unit) {
|
||||||
|
if (circular.realAttachmentsUrls.size != circular.attachmentsUrls.size || circular.realAttachmentsUrls[position] == "") {
|
||||||
|
circularHolder.loading.postValue(true)
|
||||||
|
|
||||||
|
adapterScope.launch {
|
||||||
|
val realUrls = circularRepository.getRealUrlForAttachment(
|
||||||
|
position,
|
||||||
|
circular.attachmentsUrls,
|
||||||
|
circular.realAttachmentsUrls,
|
||||||
|
circular.id,
|
||||||
|
circular.school
|
||||||
|
)
|
||||||
|
circularHolder.loading.postValue(false)
|
||||||
|
|
||||||
|
if (circular.realAttachmentsUrls.size != circular.attachmentsUrls.size) {
|
||||||
|
circular.realAttachmentsUrls.clear()
|
||||||
|
repeat(circular.attachmentsUrls.size) { circular.realAttachmentsUrls.add("") }
|
||||||
|
}
|
||||||
|
|
||||||
|
circular.realAttachmentsUrls[position] = realUrls[position]
|
||||||
|
code(realUrls[position])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code(circular.realAttachmentsUrls[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = circular.attachmentsNames.size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,13 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.content.ContextCompat.getDrawable
|
import androidx.core.content.ContextCompat.getDrawable
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -37,10 +40,12 @@ import kotlinx.coroutines.launch
|
|||||||
import net.underdesk.circolapp.AlarmBroadcastReceiver
|
import net.underdesk.circolapp.AlarmBroadcastReceiver
|
||||||
import net.underdesk.circolapp.MainActivity
|
import net.underdesk.circolapp.MainActivity
|
||||||
import net.underdesk.circolapp.R
|
import net.underdesk.circolapp.R
|
||||||
|
import net.underdesk.circolapp.data.AndroidCircularRepository
|
||||||
import net.underdesk.circolapp.data.AndroidDatabase
|
import net.underdesk.circolapp.data.AndroidDatabase
|
||||||
import net.underdesk.circolapp.databinding.ItemCircularBinding
|
import net.underdesk.circolapp.databinding.ItemCircularBinding
|
||||||
import net.underdesk.circolapp.fragments.NewReminderFragment
|
import net.underdesk.circolapp.fragments.NewReminderFragment
|
||||||
import net.underdesk.circolapp.shared.data.Circular
|
import net.underdesk.circolapp.shared.data.Circular
|
||||||
|
import net.underdesk.circolapp.shared.data.CircularRepository
|
||||||
import net.underdesk.circolapp.utils.DownloadableFile
|
import net.underdesk.circolapp.utils.DownloadableFile
|
||||||
import net.underdesk.circolapp.utils.FileUtils
|
import net.underdesk.circolapp.utils.FileUtils
|
||||||
|
|
||||||
@@ -51,6 +56,7 @@ class CircularLetterAdapter(
|
|||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<CircularLetterAdapter.CircularLetterViewHolder>() {
|
RecyclerView.Adapter<CircularLetterAdapter.CircularLetterViewHolder>() {
|
||||||
private lateinit var context: Context
|
private lateinit var context: Context
|
||||||
|
private lateinit var circularRepository: CircularRepository
|
||||||
private val adapterCallback: AdapterCallback = mainActivity
|
private val adapterCallback: AdapterCallback = mainActivity
|
||||||
private var collapsedItems = -1
|
private var collapsedItems = -1
|
||||||
|
|
||||||
@@ -60,6 +66,7 @@ class CircularLetterAdapter(
|
|||||||
|
|
||||||
inner class CircularLetterViewHolder(binding: ItemCircularBinding) : RecyclerView.ViewHolder(binding.root) {
|
inner class CircularLetterViewHolder(binding: ItemCircularBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
var card: CardView = binding.circularCard
|
var card: CardView = binding.circularCard
|
||||||
|
var progressBar: ProgressBar = binding.circularProgressBar
|
||||||
var title: TextView = binding.circularTitleTextview
|
var title: TextView = binding.circularTitleTextview
|
||||||
var number: TextView = binding.circularNumberTextview
|
var number: TextView = binding.circularNumberTextview
|
||||||
var date: TextView = binding.circularDateTextview
|
var date: TextView = binding.circularDateTextview
|
||||||
@@ -71,6 +78,9 @@ class CircularLetterAdapter(
|
|||||||
var reminderButton: ImageButton = binding.circularReminderButton
|
var reminderButton: ImageButton = binding.circularReminderButton
|
||||||
var attachmentsList: RecyclerView = binding.circularsAttachmentsList
|
var attachmentsList: RecyclerView = binding.circularsAttachmentsList
|
||||||
|
|
||||||
|
val loading = MutableLiveData(false)
|
||||||
|
var observer: MutableList<Observer<in Boolean>> = mutableListOf()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
attachmentsList.layoutManager = LinearLayoutManager(context)
|
attachmentsList.layoutManager = LinearLayoutManager(context)
|
||||||
}
|
}
|
||||||
@@ -79,6 +89,7 @@ class CircularLetterAdapter(
|
|||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CircularLetterViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CircularLetterViewHolder {
|
||||||
val binding = ItemCircularBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = ItemCircularBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
context = parent.context
|
context = parent.context
|
||||||
|
circularRepository = AndroidCircularRepository.getInstance(context)
|
||||||
|
|
||||||
return CircularLetterViewHolder(binding)
|
return CircularLetterViewHolder(binding)
|
||||||
}
|
}
|
||||||
@@ -88,6 +99,23 @@ class CircularLetterAdapter(
|
|||||||
holder.title.text = circulars[position].name
|
holder.title.text = circulars[position].name
|
||||||
holder.date.text = circulars[position].date
|
holder.date.text = circulars[position].date
|
||||||
|
|
||||||
|
val observer = Observer<Boolean> {
|
||||||
|
if (it) {
|
||||||
|
holder.progressBar.visibility = View.VISIBLE
|
||||||
|
holder.viewButton.isEnabled = false
|
||||||
|
holder.downloadButton.isEnabled = false
|
||||||
|
holder.shareButton.isEnabled = false
|
||||||
|
} else {
|
||||||
|
holder.progressBar.visibility = View.GONE
|
||||||
|
holder.viewButton.isEnabled = true
|
||||||
|
holder.downloadButton.isEnabled = true
|
||||||
|
holder.shareButton.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.loading.observe(mainActivity, observer)
|
||||||
|
holder.observer.add(observer)
|
||||||
|
|
||||||
if (circulars[position].read) {
|
if (circulars[position].read) {
|
||||||
holder.number.typeface = Typeface.DEFAULT
|
holder.number.typeface = Typeface.DEFAULT
|
||||||
holder.date.typeface = Typeface.DEFAULT
|
holder.date.typeface = Typeface.DEFAULT
|
||||||
@@ -139,7 +167,7 @@ class CircularLetterAdapter(
|
|||||||
holder.attachmentsList.adapter = null
|
holder.attachmentsList.adapter = null
|
||||||
} else {
|
} else {
|
||||||
FileUtils.preloadFiles(
|
FileUtils.preloadFiles(
|
||||||
circulars[position].url,
|
circulars[position].realUrl,
|
||||||
circulars[position].attachmentsUrls,
|
circulars[position].attachmentsUrls,
|
||||||
mainActivity.customTabsSession
|
mainActivity.customTabsSession
|
||||||
)
|
)
|
||||||
@@ -161,9 +189,10 @@ class CircularLetterAdapter(
|
|||||||
if (circulars[position].attachmentsNames.isNotEmpty()) {
|
if (circulars[position].attachmentsNames.isNotEmpty()) {
|
||||||
holder.attachmentsList.visibility = View.VISIBLE
|
holder.attachmentsList.visibility = View.VISIBLE
|
||||||
holder.attachmentsList.adapter = AttachmentAdapter(
|
holder.attachmentsList.adapter = AttachmentAdapter(
|
||||||
circulars[position].attachmentsNames,
|
circulars[position],
|
||||||
circulars[position].attachmentsUrls,
|
mainActivity,
|
||||||
mainActivity
|
adapterScope,
|
||||||
|
holder
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
holder.attachmentsList.adapter = null
|
holder.attachmentsList.adapter = null
|
||||||
@@ -181,17 +210,30 @@ class CircularLetterAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileUtils.viewFile(circulars[position].url, context, mainActivity.customTabsSession)
|
runWhenUrlIsAvailable(holder, circulars[position]) { url ->
|
||||||
|
FileUtils.viewFile(
|
||||||
|
url,
|
||||||
|
context,
|
||||||
|
mainActivity.customTabsSession
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.shareButton.setOnClickListener {
|
holder.shareButton.setOnClickListener {
|
||||||
FileUtils.shareFile(circulars[position].url, context)
|
runWhenUrlIsAvailable(holder, circulars[position]) { url ->
|
||||||
|
FileUtils.shareFile(
|
||||||
|
url,
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.downloadButton.setOnClickListener {
|
holder.downloadButton.setOnClickListener {
|
||||||
val file = DownloadableFile(circulars[position].name, circulars[position].url)
|
runWhenUrlIsAvailable(holder, circulars[position]) { url ->
|
||||||
|
val file = DownloadableFile(circulars[position].name, url)
|
||||||
FileUtils.downloadFile(file, adapterCallback, context)
|
FileUtils.downloadFile(file, adapterCallback, context)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
holder.favouriteButton.setOnClickListener {
|
holder.favouriteButton.setOnClickListener {
|
||||||
adapterScope.launch {
|
adapterScope.launch {
|
||||||
@@ -254,6 +296,32 @@ class CircularLetterAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: CircularLetterViewHolder) {
|
||||||
|
holder.observer.forEach { holder.loading.removeObserver(it) }
|
||||||
|
holder.observer.clear()
|
||||||
|
super.onViewRecycled(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runWhenUrlIsAvailable(
|
||||||
|
holder: CircularLetterViewHolder,
|
||||||
|
circular: Circular,
|
||||||
|
code: (url: String) -> Unit
|
||||||
|
) {
|
||||||
|
if (circular.realUrl == null) {
|
||||||
|
holder.loading.postValue(true)
|
||||||
|
|
||||||
|
adapterScope.launch {
|
||||||
|
val realUrl =
|
||||||
|
circularRepository.getRealUrl(circular.url, circular.id, circular.school)
|
||||||
|
holder.loading.postValue(false)
|
||||||
|
code(realUrl)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code(circular.realUrl!!)
|
||||||
|
}
|
||||||
|
|
||||||
fun changeDataSet(newCirculars: List<Circular>) {
|
fun changeDataSet(newCirculars: List<Circular>) {
|
||||||
if (circulars.size != newCirculars.size)
|
if (circulars.size != newCirculars.size)
|
||||||
collapsedItems = -1
|
collapsedItems = -1
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class CircolappFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
val url = remoteMessage.data["url"] ?: ""
|
val url = remoteMessage.data["url"] ?: ""
|
||||||
|
|
||||||
val circular = Circular(
|
val circular = Circular(
|
||||||
id, -1, name, url, "",
|
id, -1, name, url, null, "",
|
||||||
favourite = false,
|
favourite = false,
|
||||||
reminder = false,
|
reminder = false,
|
||||||
attachmentsNames = mutableListOf(),
|
attachmentsNames = mutableListOf(),
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ object FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun preloadFiles(url: String, otherUrls: List<String>, session: CustomTabsSession?) {
|
fun preloadFiles(url: String?, otherUrls: List<String>, session: CustomTabsSession?) {
|
||||||
|
if (url == null)
|
||||||
|
return
|
||||||
|
|
||||||
if (!url.endsWith(".pdf")) {
|
if (!url.endsWith(".pdf")) {
|
||||||
val bundles = arrayListOf<Bundle>()
|
val bundles = arrayListOf<Bundle>()
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,19 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
|
||||||
|
android:id="@+id/circular_progress_bar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:mpb_progressStyle="horizontal"
|
||||||
|
app:mpb_useIntrinsicPadding="false"
|
||||||
|
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/circular_number_textview"
|
android:id="@+id/circular_number_textview"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ object Dependencies {
|
|||||||
const val jsoup = "org.jsoup:jsoup:1.13.1"
|
const val jsoup = "org.jsoup:jsoup:1.13.1"
|
||||||
const val appIntro = "com.github.AppIntro:AppIntro:6.0.0"
|
const val appIntro = "com.github.AppIntro:AppIntro:6.0.0"
|
||||||
const val materialSpinner = "com.github.tiper:MaterialSpinner:1.4.2"
|
const val materialSpinner = "com.github.tiper:MaterialSpinner:1.4.2"
|
||||||
|
const val materialProgressBar = "me.zhanghai.android.materialprogressbar:library:1.6.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Testing {
|
object Testing {
|
||||||
|
|||||||
@@ -25,5 +25,18 @@ import com.squareup.sqldelight.db.SqlDriver
|
|||||||
actual class DatabaseDriverFactory(private val context: Context) {
|
actual class DatabaseDriverFactory(private val context: Context) {
|
||||||
actual fun createDriver(): SqlDriver {
|
actual fun createDriver(): SqlDriver {
|
||||||
return AndroidSqliteDriver(AppDatabase.Schema, context, "circolapp.db")
|
return AndroidSqliteDriver(AppDatabase.Schema, context, "circolapp.db")
|
||||||
|
.also {
|
||||||
|
var currentVer = DatabaseFactory.getVersion(it)
|
||||||
|
val schemaVer: Int = AppDatabase.Schema.version
|
||||||
|
|
||||||
|
if (currentVer == 0) {
|
||||||
|
currentVer = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaVer > currentVer) {
|
||||||
|
AppDatabase.Schema.migrate(it, currentVer, schemaVer)
|
||||||
|
DatabaseFactory.setVersion(it, schemaVer)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,26 @@ actual class SpecificCurieServer actual constructor(private val curieServer: Cur
|
|||||||
val list = ArrayList<Circular>()
|
val list = ArrayList<Circular>()
|
||||||
|
|
||||||
htmlList.forEach { element ->
|
htmlList.forEach { element ->
|
||||||
|
val url = element.attr("href")
|
||||||
if (element.parents().size == 6) {
|
if (element.parents().size == 6) {
|
||||||
list.last().attachmentsNames.add(element.text())
|
list.last().attachmentsNames.add(element.text())
|
||||||
list.last().attachmentsUrls.add(element.attr("href"))
|
list.last().attachmentsUrls.add(url)
|
||||||
|
|
||||||
|
if (url.endsWith(".pdf")) {
|
||||||
|
list.last().realAttachmentsUrls.add(url)
|
||||||
|
} else {
|
||||||
|
list.last().realAttachmentsUrls.add("")
|
||||||
|
}
|
||||||
} else if (element.parents().size == 4) {
|
} else if (element.parents().size == 4) {
|
||||||
list.add(curieServer.generateFromString(element.text(), element.attr("href")))
|
list.add(curieServer.generateFromString(element.text(), url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun parseFileUrl(string: String): String {
|
||||||
|
val document = Jsoup.parseBodyFragment(string)
|
||||||
|
return document.getElementsByClass("mtli_attachment")[0].attr("href")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ data class Circular(
|
|||||||
val school: Int,
|
val school: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
val url: String,
|
val url: String,
|
||||||
|
val realUrl: String?,
|
||||||
val date: String,
|
val date: String,
|
||||||
var favourite: Boolean = false,
|
var favourite: Boolean = false,
|
||||||
var reminder: Boolean = false,
|
var reminder: Boolean = false,
|
||||||
var read: Boolean = false,
|
var read: Boolean = false,
|
||||||
val attachmentsNames: MutableList<String> = mutableListOf(),
|
val attachmentsNames: MutableList<String> = mutableListOf(),
|
||||||
val attachmentsUrls: MutableList<String> = mutableListOf()
|
val attachmentsUrls: MutableList<String> = mutableListOf(),
|
||||||
|
val realAttachmentsUrls: MutableList<String> = mutableListOf()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -34,18 +34,20 @@ class CircularDao(
|
|||||||
private val appDatabaseQueries = database.appDatabaseQueries
|
private val appDatabaseQueries = database.appDatabaseQueries
|
||||||
|
|
||||||
private val circularMapper =
|
private val circularMapper =
|
||||||
{ id: Long, school: Long, name: String, url: String, date: String, favourite: Long, reminder: Long, read: Long, attachmentsNames: String, attachmentsUrls: String ->
|
{ id: Long, school: Long, name: String, url: String, date: String, favourite: Long, reminder: Long, read: Long, attachmentsNames: String, attachmentsUrls: String, realAttachmentsUrls: String, realUrl: String? ->
|
||||||
Circular(
|
Circular(
|
||||||
id,
|
id,
|
||||||
school.toInt(),
|
school.toInt(),
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
|
realUrl,
|
||||||
date,
|
date,
|
||||||
favourite.toBoolean(),
|
favourite.toBoolean(),
|
||||||
reminder.toBoolean(),
|
reminder.toBoolean(),
|
||||||
read.toBoolean(),
|
read.toBoolean(),
|
||||||
attachmentsNames.toList(),
|
attachmentsNames.toList(),
|
||||||
attachmentsUrls.toList()
|
attachmentsUrls.toList(),
|
||||||
|
realAttachmentsUrls.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +63,9 @@ class CircularDao(
|
|||||||
it.reminder.toLong(),
|
it.reminder.toLong(),
|
||||||
it.read.toLong(),
|
it.read.toLong(),
|
||||||
it.attachmentsNames.joinToString(),
|
it.attachmentsNames.joinToString(),
|
||||||
it.attachmentsUrls.joinToString()
|
it.attachmentsUrls.joinToString(),
|
||||||
|
it.realAttachmentsUrls.joinToString(),
|
||||||
|
it.realUrl
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,6 +80,24 @@ class CircularDao(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setRealUrl(id: Long, school: Int, url: String?) =
|
||||||
|
withContext(PlatformDispatcher.IO) {
|
||||||
|
appDatabaseQueries.setRealUrl(
|
||||||
|
url,
|
||||||
|
id,
|
||||||
|
school.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setRealAttachmentsUrls(id: Long, school: Int, urls: List<String>) =
|
||||||
|
withContext(PlatformDispatcher.IO) {
|
||||||
|
appDatabaseQueries.setRealAttachmentsUrls(
|
||||||
|
urls.joinToString(),
|
||||||
|
id,
|
||||||
|
school.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun markRead(id: Long, school: Int, read: Boolean) =
|
suspend fun markRead(id: Long, school: Int, read: Boolean) =
|
||||||
withContext(PlatformDispatcher.IO) {
|
withContext(PlatformDispatcher.IO) {
|
||||||
appDatabaseQueries.markCircularRead(
|
appDatabaseQueries.markCircularRead(
|
||||||
|
|||||||
@@ -57,4 +57,34 @@ class CircularRepository(
|
|||||||
}
|
}
|
||||||
return Pair(onlyNewCirculars, errorCode)
|
return Pair(onlyNewCirculars, errorCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getRealUrl(rawUrl: String, id: Long, school: Int): String {
|
||||||
|
val result = serverAPI.getRealUrl(rawUrl)
|
||||||
|
|
||||||
|
if (result.second != ServerAPI.Companion.Result.SUCCESS)
|
||||||
|
return rawUrl
|
||||||
|
|
||||||
|
circularDao.setRealUrl(id, school, result.first)
|
||||||
|
return result.first
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getRealUrlForAttachment(
|
||||||
|
index: Int,
|
||||||
|
rawUrls: List<String>,
|
||||||
|
realUrls: List<String>,
|
||||||
|
id: Long,
|
||||||
|
school: Int
|
||||||
|
): List<String> {
|
||||||
|
val result = serverAPI.getRealUrl(rawUrls[index])
|
||||||
|
|
||||||
|
if (result.second != ServerAPI.Companion.Result.SUCCESS)
|
||||||
|
return realUrls
|
||||||
|
|
||||||
|
val newList =
|
||||||
|
if (realUrls.size != rawUrls.size) MutableList(rawUrls.size) { "" } else realUrls.toMutableList()
|
||||||
|
newList[index] = result.first
|
||||||
|
|
||||||
|
circularDao.setRealAttachmentsUrls(id, school, newList)
|
||||||
|
return newList
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,4 +22,16 @@ import com.squareup.sqldelight.db.SqlDriver
|
|||||||
|
|
||||||
object DatabaseFactory {
|
object DatabaseFactory {
|
||||||
fun createDatabase(sqlDriver: SqlDriver) = AppDatabase(sqlDriver)
|
fun createDatabase(sqlDriver: SqlDriver) = AppDatabase(sqlDriver)
|
||||||
|
|
||||||
|
fun getVersion(driver: SqlDriver): Int {
|
||||||
|
val sqlCursor = driver.executeQuery(null, "PRAGMA user_version;", 0, null)
|
||||||
|
if (!sqlCursor.next())
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return sqlCursor.getLong(0)?.toInt() ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setVersion(driver: SqlDriver, version: Int) {
|
||||||
|
driver.execute(null, "PRAGMA user_version = $version;", 0, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ abstract class Server(
|
|||||||
) {
|
) {
|
||||||
abstract val serverID: Int
|
abstract val serverID: Int
|
||||||
abstract suspend fun getCircularsFromServer(): Pair<List<Circular>, ServerAPI.Companion.Result>
|
abstract suspend fun getCircularsFromServer(): Pair<List<Circular>, ServerAPI.Companion.Result>
|
||||||
|
abstract suspend fun getRealUrl(rawUrl: String): Pair<String, ServerAPI.Companion.Result>
|
||||||
abstract suspend fun newCircularsAvailable(): Pair<Boolean, ServerAPI.Companion.Result>
|
abstract suspend fun newCircularsAvailable(): Pair<Boolean, ServerAPI.Companion.Result>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ class ServerAPI(serverName: Servers) {
|
|||||||
server.getCircularsFromServer()
|
server.getCircularsFromServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getRealUrl(rawUrl: String): Pair<String, Result> =
|
||||||
|
withContext(PlatformDispatcher.IO) {
|
||||||
|
server.getRealUrl(rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
fun changeServer(serverName: Servers) {
|
fun changeServer(serverName: Servers) {
|
||||||
server = createServer(serverName, ktorClient)
|
server = createServer(serverName, ktorClient)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,23 @@ class CurieServer(ktorClient: HttpClient) : Server(ktorClient) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getRealUrl(rawUrl: String): Pair<String, ServerAPI.Companion.Result> {
|
||||||
|
if (rawUrl.endsWith(".pdf"))
|
||||||
|
return Pair(rawUrl, ServerAPI.Companion.Result.SUCCESS)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
val html: String = ktorClient.get(rawUrl)
|
||||||
|
val realUrl = SpecificCurieServer(this@CurieServer).parseFileUrl(html)
|
||||||
|
Pair(realUrl, ServerAPI.Companion.Result.SUCCESS)
|
||||||
|
}
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
Pair(rawUrl, ServerAPI.Companion.Result.NETWORK_ERROR)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Pair(rawUrl, ServerAPI.Companion.Result.GENERIC_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun newCircularsAvailable(): Pair<Boolean, ServerAPI.Companion.Result> {
|
override suspend fun newCircularsAvailable(): Pair<Boolean, ServerAPI.Companion.Result> {
|
||||||
return Pair(true, ServerAPI.Companion.Result.SUCCESS)
|
return Pair(true, ServerAPI.Companion.Result.SUCCESS)
|
||||||
}
|
}
|
||||||
@@ -61,6 +78,8 @@ class CurieServer(ktorClient: HttpClient) : Server(ktorClient) {
|
|||||||
"""(\d+)""".toRegex()
|
"""(\d+)""".toRegex()
|
||||||
val idMatcher = idRegex.find(string)
|
val idMatcher = idRegex.find(string)
|
||||||
|
|
||||||
|
val realUrl = if (url.endsWith(".pdf")) url else null
|
||||||
|
|
||||||
val id = idMatcher?.value?.toLong() ?: -1L
|
val id = idMatcher?.value?.toLong() ?: -1L
|
||||||
|
|
||||||
val dateRegex =
|
val dateRegex =
|
||||||
@@ -75,9 +94,9 @@ class CurieServer(ktorClient: HttpClient) : Server(ktorClient) {
|
|||||||
.removePrefix("_")
|
.removePrefix("_")
|
||||||
.removePrefix(" ")
|
.removePrefix(" ")
|
||||||
|
|
||||||
Circular(id, serverID, title, url, dateMatcher.value)
|
Circular(id, serverID, title, url, realUrl, dateMatcher.value)
|
||||||
} else {
|
} else {
|
||||||
Circular(id, serverID, title, url, "")
|
Circular(id, serverID, title, url, realUrl, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,4 +107,5 @@ class CurieServer(ktorClient: HttpClient) : Server(ktorClient) {
|
|||||||
|
|
||||||
expect class SpecificCurieServer(curieServer: CurieServer) {
|
expect class SpecificCurieServer(curieServer: CurieServer) {
|
||||||
fun parseHtml(string: String): List<Circular>
|
fun parseHtml(string: String): List<Circular>
|
||||||
|
fun parseFileUrl(string: String): String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,10 @@ class PorporatoServer(ktorClient: HttpClient) : Server(ktorClient) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getRealUrl(rawUrl: String): Pair<String, ServerAPI.Companion.Result> {
|
||||||
|
return Pair(rawUrl, ServerAPI.Companion.Result.SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun newCircularsAvailable(): Pair<Boolean, ServerAPI.Companion.Result> {
|
override suspend fun newCircularsAvailable(): Pair<Boolean, ServerAPI.Companion.Result> {
|
||||||
return Pair(true, ServerAPI.Companion.Result.SUCCESS)
|
return Pair(true, ServerAPI.Companion.Result.SUCCESS)
|
||||||
}
|
}
|
||||||
@@ -85,6 +89,7 @@ class PorporatoServer(ktorClient: HttpClient) : Server(ktorClient) {
|
|||||||
val parent = list.find { it.id == attachment.id && !it.name.startsWith("All") }
|
val parent = list.find { it.id == attachment.id && !it.name.startsWith("All") }
|
||||||
parent?.attachmentsNames?.add(attachment.name)
|
parent?.attachmentsNames?.add(attachment.name)
|
||||||
parent?.attachmentsUrls?.add(attachment.url)
|
parent?.attachmentsUrls?.add(attachment.url)
|
||||||
|
parent?.realAttachmentsUrls?.add(attachment.url)
|
||||||
|
|
||||||
return@removeAll true
|
return@removeAll true
|
||||||
}
|
}
|
||||||
@@ -101,6 +106,7 @@ class PorporatoServer(ktorClient: HttpClient) : Server(ktorClient) {
|
|||||||
val parent = list[lastIndex]
|
val parent = list[lastIndex]
|
||||||
parent.attachmentsNames.add(attachment.name)
|
parent.attachmentsNames.add(attachment.name)
|
||||||
parent.attachmentsUrls.add(attachment.url)
|
parent.attachmentsUrls.add(attachment.url)
|
||||||
|
parent.realAttachmentsUrls.add(attachment.url)
|
||||||
|
|
||||||
return@removeAll true
|
return@removeAll true
|
||||||
}
|
}
|
||||||
@@ -146,9 +152,9 @@ class PorporatoServer(ktorClient: HttpClient) : Server(ktorClient) {
|
|||||||
title = title.removeRange(dateMatcher.range)
|
title = title.removeRange(dateMatcher.range)
|
||||||
.removeSuffix(" (pubb.: )")
|
.removeSuffix(" (pubb.: )")
|
||||||
|
|
||||||
Circular(id, serverID, title, fullUrl, dateMatcher.value.replace("-", "/"))
|
Circular(id, serverID, title, fullUrl, fullUrl, dateMatcher.value.replace("-", "/"))
|
||||||
} else {
|
} else {
|
||||||
Circular(id, serverID, title, fullUrl, "")
|
Circular(id, serverID, title, fullUrl, fullUrl, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE Circulars ADD COLUMN realAttachmentsUrls TEXT NOT NULL DEFAULT "";
|
||||||
|
ALTER TABLE Circulars ADD COLUMN realUrl TEXT;
|
||||||
@@ -9,18 +9,30 @@ CREATE TABLE Circulars (
|
|||||||
read INTEGER NOT NULL DEFAULT 0,
|
read INTEGER NOT NULL DEFAULT 0,
|
||||||
attachmentsNames TEXT NOT NULL,
|
attachmentsNames TEXT NOT NULL,
|
||||||
attachmentsUrls TEXT NOT NULL,
|
attachmentsUrls TEXT NOT NULL,
|
||||||
|
realAttachmentsUrls TEXT NOT NULL,
|
||||||
|
realUrl TEXT,
|
||||||
PRIMARY KEY (id, school)
|
PRIMARY KEY (id, school)
|
||||||
);
|
);
|
||||||
|
|
||||||
insertCircular:
|
insertCircular:
|
||||||
INSERT OR IGNORE INTO Circulars(id, school, name, url, date, favourite, reminder, read, attachmentsNames, attachmentsUrls)
|
INSERT OR IGNORE INTO Circulars(id, school, name, url, date, favourite, reminder, read, attachmentsNames, attachmentsUrls, realAttachmentsUrls, realUrl)
|
||||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
|
|
||||||
updateCircular:
|
updateCircular:
|
||||||
UPDATE Circulars
|
UPDATE Circulars
|
||||||
SET favourite = ?, reminder = ?
|
SET favourite = ?, reminder = ?
|
||||||
WHERE id = ? AND school = ?;
|
WHERE id = ? AND school = ?;
|
||||||
|
|
||||||
|
setRealUrl:
|
||||||
|
UPDATE Circulars
|
||||||
|
SET realUrl = ?
|
||||||
|
WHERE id = ? AND school = ?;
|
||||||
|
|
||||||
|
setRealAttachmentsUrls:
|
||||||
|
UPDATE Circulars
|
||||||
|
SET realAttachmentsUrls = ?
|
||||||
|
WHERE id = ? AND school = ?;
|
||||||
|
|
||||||
markCircularRead:
|
markCircularRead:
|
||||||
UPDATE Circulars
|
UPDATE Circulars
|
||||||
SET read = ?
|
SET read = ?
|
||||||
|
|||||||
@@ -24,5 +24,18 @@ import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
|
|||||||
actual class DatabaseDriverFactory {
|
actual class DatabaseDriverFactory {
|
||||||
actual fun createDriver(): SqlDriver {
|
actual fun createDriver(): SqlDriver {
|
||||||
return NativeSqliteDriver(AppDatabase.Schema, "circolapp.db")
|
return NativeSqliteDriver(AppDatabase.Schema, "circolapp.db")
|
||||||
|
.also {
|
||||||
|
var currentVer = DatabaseFactory.getVersion(it)
|
||||||
|
val schemaVer: Int = AppDatabase.Schema.version
|
||||||
|
|
||||||
|
if (currentVer == 0) {
|
||||||
|
currentVer = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaVer > currentVer) {
|
||||||
|
AppDatabase.Schema.migrate(it, currentVer, schemaVer)
|
||||||
|
DatabaseFactory.setVersion(it, schemaVer)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,25 @@ actual class SpecificCurieServer actual constructor(private val curieServer: Cur
|
|||||||
val list = ArrayList<Circular>()
|
val list = ArrayList<Circular>()
|
||||||
|
|
||||||
htmlList?.forEach { element ->
|
htmlList?.forEach { element ->
|
||||||
|
val url = element.attributes.objectForKey("href").toString()
|
||||||
if (element.parentElement?.parentElement?.parentElement?.tagName == "li") {
|
if (element.parentElement?.parentElement?.parentElement?.tagName == "li") {
|
||||||
list.last().attachmentsNames.add(element.textContent)
|
list.last().attachmentsNames.add(element.textContent)
|
||||||
list.last().attachmentsUrls.add(element.attributes.objectForKey("href").toString())
|
list.last().attachmentsUrls.add(url)
|
||||||
|
|
||||||
|
if (url.endsWith(".pdf")) {
|
||||||
|
list.last().realAttachmentsUrls.add(url)
|
||||||
} else {
|
} else {
|
||||||
list.add(curieServer.generateFromString(element.textContent, element.attributes.objectForKey("href").toString()))
|
list.last().realAttachmentsUrls.add("")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.add(curieServer.generateFromString(element.textContent, url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actual fun parseFileUrl(string: String): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,27 +25,18 @@ actual class DatabaseDriverFactory(private val path: String) {
|
|||||||
actual fun createDriver(): SqlDriver {
|
actual fun createDriver(): SqlDriver {
|
||||||
return JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY + path)
|
return JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY + path)
|
||||||
.also {
|
.also {
|
||||||
val currentVer = getVersion(it)
|
val currentVer = DatabaseFactory.getVersion(it)
|
||||||
if (currentVer == 0) {
|
if (currentVer == 0) {
|
||||||
AppDatabase.Schema.create(it)
|
AppDatabase.Schema.create(it)
|
||||||
setVersion(it, 1)
|
DatabaseFactory.setVersion(it, 1)
|
||||||
} else {
|
} else {
|
||||||
val schemaVer: Int = AppDatabase.Schema.version
|
val schemaVer: Int = AppDatabase.Schema.version
|
||||||
if (schemaVer > currentVer) {
|
if (schemaVer > currentVer) {
|
||||||
AppDatabase.Schema.migrate(it, currentVer, schemaVer)
|
AppDatabase.Schema.migrate(it, currentVer, schemaVer)
|
||||||
setVersion(it, schemaVer)
|
DatabaseFactory.setVersion(it, schemaVer)
|
||||||
println("init: migrated from $currentVer to $schemaVer")
|
println("init: migrated from $currentVer to $schemaVer")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVersion(driver: SqlDriver): Int {
|
|
||||||
val sqlCursor = driver.executeQuery(null, "PRAGMA user_version;", 0, null)
|
|
||||||
return sqlCursor.getLong(0)!!.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setVersion(driver: SqlDriver, version: Int) {
|
|
||||||
driver.execute(null, String.format("PRAGMA user_version = %d;", version), 0, null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user