From d75ad7fb42aba81b334f9453c012f04c3d5f3e0a Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Mon, 18 Sep 2017 10:43:55 -0400 Subject: Fix/notification defaults (#308) * Update downloader * Disable deaults on creation * Use notifCount rather than index * Remove quiet * Add checks to ensure job service exists * Update changelog --- .../pitchedapps/frost/facebook/FbUrlFormatter.kt | 2 +- .../pitchedapps/frost/services/DownloadService.kt | 4 +- .../frost/services/FrostNotifications.kt | 151 +++++++++++------- .../frost/services/NotificationService.kt | 50 +----- .../com/pitchedapps/frost/utils/Downloader.kt | 172 +++------------------ .../com/pitchedapps/frost/web/FrostWebView.kt | 2 +- app/src/main/res/xml/frost_changelog.xml | 10 +- docs/Changelog.md | 4 + 8 files changed, 141 insertions(+), 254 deletions(-) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt index 3d016909..cd7d9002 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbUrlFormatter.kt @@ -30,7 +30,7 @@ class FbUrlFormatter(url: String) { cleanedUrl = cleanedUrl.substring(0, qm) } discardableQueries.forEach { queries.remove(it) } - if (cleanedUrl.startsWith("#!/")) cleanedUrl = cleanedUrl.substring(2) + if (cleanedUrl.startsWith("#!")) cleanedUrl = cleanedUrl.substring(2) if (cleanedUrl.startsWith("/")) cleanedUrl = FB_URL_BASE + cleanedUrl.substring(1) cleanedUrl = cleanedUrl.replaceFirst(".facebook.com//", ".facebook.com/") //sometimes we are given a bad url L.v(null, "Formatted url from $url to $cleanedUrl") diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt index ee0c2027..986467b8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/DownloadService.kt @@ -28,6 +28,8 @@ import java.io.File * * Background file downloader * All we are given is a link and a mime type + * + * With reference to the OkHttp3 sample */ class DownloadService : IntentService("FrostVideoDownloader") { @@ -64,7 +66,7 @@ class DownloadService : IntentService("FrostVideoDownloader") { .url(url) .build() - notifBuilder = frostNotification.quiet + notifBuilder = frostNotification notifId = Math.abs(url.hashCode() + System.currentTimeMillis().toInt()) val cancelIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt index 800fe492..b892af91 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -22,6 +22,7 @@ import com.pitchedapps.frost.R import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.enums.OverlayContext +import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.utils.* import org.jetbrains.anko.runOnUiThread @@ -34,16 +35,13 @@ import org.jetbrains.anko.runOnUiThread val Context.frostNotification: NotificationCompat.Builder - get() = frostNotification() + get() = NotificationCompat.Builder(this, BuildConfig.APPLICATION_ID).apply { + setSmallIcon(R.drawable.frost_f_24) + setAutoCancel(true) + color = color(R.color.frost_notification_accent) + } -/** - * Wrap the default builder with our icon and accent color - */ -fun Context.frostNotification(ringtone: String = Prefs.notificationRingtone): NotificationCompat.Builder - = NotificationCompat.Builder(this, BuildConfig.APPLICATION_ID).apply { - setSmallIcon(R.drawable.frost_f_24) - setAutoCancel(true) - color = color(R.color.frost_notification_accent) +fun NotificationCompat.Builder.withDefaults(ringtone: String = Prefs.notificationRingtone) = apply { var defaults = 0 if (Prefs.notificationVibrate) defaults = defaults or Notification.DEFAULT_VIBRATE if (Prefs.notificationSound) { @@ -54,12 +52,6 @@ fun Context.frostNotification(ringtone: String = Prefs.notificationRingtone): No setDefaults(defaults) } -val NotificationCompat.Builder.quiet - get() = apply { setDefaults(0) } - -val NotificationCompat.Builder.messageRingtone - get() = apply { } - val NotificationCompat.Builder.withBigText: NotificationCompat.BigTextStyle get() = NotificationCompat.BigTextStyle(this) @@ -81,8 +73,82 @@ class FrostNotificationTarget(val context: Context, } } -internal const val FROST_NOTIFICATION_GROUP = "frost" -internal const val FROST_MESSAGE_NOTIFICATION_GROUP = "frost_im" +/** + * Enum to handle notification creations + */ +enum class NotificationType( + private val groupPrefix: String, + private val overlayContext: OverlayContext, + private val contentRes: Int, + private val pendingUrl: String, + private val ringtone: () -> String) { + GENERAL("frost", OverlayContext.NOTIFICATION, R.string.notifications, FbItem.NOTIFICATIONS.url, { Prefs.notificationRingtone }), + MESSAGE("frost_im", OverlayContext.MESSAGE, R.string.messages, FbItem.MESSAGES.url, { Prefs.messageRingtone }); + + /** + * Create and submit a new notification with the given [content] + * If [withDefaults] is set, it will also add the appropriate sound, vibration, and light + * Note that when we have multiple notifications coming in at once, we don't want to have defaults for all of them + */ + fun createNotification(context: Context, content: NotificationContent, withDefaults: Boolean) { + with(content) { + val intent = Intent(context, FrostWebActivity::class.java) + intent.data = Uri.parse(href.formattedFbUrl) + intent.putExtra(ARG_USER_ID, data.id) + intent.putExtra(ARG_OVERLAY_CONTEXT, overlayContext) + val group = "${groupPrefix}_${data.id}" + val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) + val notifBuilder = context.frostNotification + .setContentTitle(title ?: context.string(R.string.frost_name)) + .setContentText(text) + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_SOCIAL) + .setSubText(data.name) + .setGroup(group) + + if (withDefaults) + notifBuilder.withDefaults(ringtone()) + + if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000) + L.v("Notif load", context.toString()) + NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.withBigText.build()) + + if (profileUrl.isNotBlank()) { + context.runOnUiThread { + //todo verify if context is valid? + Glide.with(context) + .asBitmap() + .load(profileUrl) + .withRoundIcon() + .into(FrostNotificationTarget(context, notifId, group, notifBuilder)) + } + } + } + } + + /** + * Create a summary notification to wrap the previous ones + * This will always produce sound, vibration, and lights based on preferences + * and will only show if we have at least 2 notifications + */ + fun summaryNotification(context: Context, userId: Long, count: Int) { + frostAnswersCustom("Notifications", "Type" to name, "Count" to count) + if (count <= 1) return + val intent = Intent(context, FrostWebActivity::class.java) + intent.data = Uri.parse(pendingUrl) + intent.putExtra(ARG_USER_ID, userId) + val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) + val notifBuilder = context.frostNotification.withDefaults(ringtone()) + .setContentTitle(context.string(R.string.frost_name)) + .setContentText("$count ${context.string(contentRes)}") + .setGroup("${groupPrefix}_$userId") + .setGroupSummary(true) + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_SOCIAL) + + NotificationManagerCompat.from(context).notify("${groupPrefix}_$userId", userId.toInt(), notifBuilder.build()) + } +} /** * Notification data holder @@ -93,44 +159,7 @@ data class NotificationContent(val data: CookieModel, val title: String? = null, val text: String, val timestamp: Long, - val profileUrl: String) { - - fun createNotification(context: Context) = createNotification(context, FROST_NOTIFICATION_GROUP) - - fun createMessageNotification(context: Context) = createNotification(context, FROST_MESSAGE_NOTIFICATION_GROUP) - - private fun createNotification(context: Context, groupPrefix: String) { - val intent = Intent(context, FrostWebActivity::class.java) - intent.data = Uri.parse(href.formattedFbUrl) - intent.putExtra(ARG_USER_ID, data.id) - val overlayContext = if (groupPrefix == FROST_MESSAGE_NOTIFICATION_GROUP) OverlayContext.MESSAGE else OverlayContext.NOTIFICATION - intent.putExtra(ARG_OVERLAY_CONTEXT, overlayContext) - val group = "${groupPrefix}_${data.id}" - val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) - val ringtone = if (groupPrefix == FROST_MESSAGE_NOTIFICATION_GROUP) Prefs.messageRingtone else Prefs.notificationRingtone - val notifBuilder = context.frostNotification(ringtone) - .setContentTitle(title ?: context.string(R.string.frost_name)) - .setContentText(text) - .setContentIntent(pendingIntent) - .setCategory(Notification.CATEGORY_SOCIAL) - .setSubText(data.name) - .setGroup(group) - - if (timestamp != -1L) notifBuilder.setWhen(timestamp * 1000) - L.v("Notif load", this.toString()) - NotificationManagerCompat.from(context).notify(group, notifId, notifBuilder.withBigText.build()) - - if (profileUrl.isNotBlank()) { - context.runOnUiThread { - Glide.with(context) - .asBitmap() - .load(profileUrl) - .withRoundIcon() - .into(FrostNotificationTarget(context, notifId, group, notifBuilder)) - } - } - } -} + val profileUrl: String) const val NOTIFICATION_PERIODIC_JOB = 7 @@ -139,7 +168,11 @@ const val NOTIFICATION_PERIODIC_JOB = 7 * returns false if an error occurs; true otherwise */ fun Context.scheduleNotifications(minutes: Long): Boolean { - val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler + val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler? + if (scheduler == null) { + L.e("JobScheduler not found; cannot schedule notifications") + return false + } scheduler.cancel(NOTIFICATION_PERIODIC_JOB) if (minutes < 0L) return true val serviceComponent = ComponentName(this, NotificationService::class.java) @@ -161,7 +194,11 @@ const val NOTIFICATION_JOB_NOW = 6 * Run notification job right now */ fun Context.fetchNotifications(): Boolean { - val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler + val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler? + if (scheduler == null) { + L.e("JobScheduler not found") + return false + } val serviceComponent = ComponentName(this, NotificationService::class.java) val builder = JobInfo.Builder(NOTIFICATION_JOB_NOW, serviceComponent) .setMinimumLatency(0L) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt index 9321c42f..d481e941 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -1,17 +1,12 @@ package com.pitchedapps.frost.services -import android.app.Notification -import android.app.PendingIntent import android.app.job.JobParameters import android.app.job.JobService import android.content.Context -import android.content.Intent -import android.net.Uri import android.support.v4.app.NotificationManagerCompat import ca.allanwang.kau.utils.string import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R -import com.pitchedapps.frost.activities.FrostWebActivity import com.pitchedapps.frost.dbflow.CookieModel import com.pitchedapps.frost.dbflow.lastNotificationTime import com.pitchedapps.frost.dbflow.loadFbCookie @@ -21,7 +16,6 @@ import com.pitchedapps.frost.facebook.FbItem import com.pitchedapps.frost.facebook.USER_AGENT_BASIC import com.pitchedapps.frost.facebook.formattedFbUrl import com.pitchedapps.frost.injectors.JsAssets -import com.pitchedapps.frost.utils.ARG_USER_ID import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostAnswersCustom @@ -130,20 +124,18 @@ class NotificationService : JobService() { val prevLatestEpoch = prevNotifTime.epoch L.v("Notif Prev Latest Epoch $prevLatestEpoch") var newLatestEpoch = prevLatestEpoch - unreadNotifications.forEach unread@ { - elem -> + unreadNotifications.forEach unread@ { elem -> val notif = parseNotification(data, elem) ?: return@unread L.v("Notif timestamp ${notif.timestamp}") if (notif.timestamp <= prevLatestEpoch) return@unread - notif.createNotification(this@NotificationService) + NotificationType.GENERAL.createNotification(this, notif, notifCount == 0) if (notif.timestamp > newLatestEpoch) newLatestEpoch = notif.timestamp notifCount++ } if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epoch = newLatestEpoch).save() L.d("Notif new latest epoch ${lastNotificationTime(data.id).epoch}") - frostAnswersCustom("Notifications", "Type" to "General", "Count" to notifCount) - summaryNotification(data.id, notifCount) + NotificationType.GENERAL.summaryNotification(this, data.id, notifCount) } fun parseNotification(data: CookieModel, element: Element): NotificationContent? { @@ -161,9 +153,6 @@ class NotificationService : JobService() { return NotificationContent(data, notifId.toInt(), a.attr("href"), null, text, epoch, pUrl) } - fun summaryNotification(userId: Long, count: Int) - = summaryNotification(userId, count, R.string.notifications, FbItem.NOTIFICATIONS.url, FROST_NOTIFICATION_GROUP) - /* * ---------------------------------------------------------------- * Instant message notification logic. @@ -174,8 +163,7 @@ class NotificationService : JobService() { inline fun fetchMessageNotifications(data: CookieModel, crossinline callback: (success: Boolean) -> Unit) { launchHeadlessHtmlExtractor(FbItem.MESSAGES.url, JsAssets.NOTIF_MSG) { - it.observeOn(Schedulers.newThread()).subscribe { - (html, errorRes) -> + it.observeOn(Schedulers.newThread()).subscribe { (html, errorRes) -> L.d("Notf IM html received") if (errorRes != -1) return@subscribe callback(false) fetchMessageNotifications(data, html) @@ -193,20 +181,18 @@ class NotificationService : JobService() { val prevLatestEpoch = prevNotifTime.epochIm L.v("Notif Prev Latest Im Epoch $prevLatestEpoch") var newLatestEpoch = prevLatestEpoch - unreadNotifications.forEach unread@ { - elem -> + unreadNotifications.forEach unread@ { elem -> val notif = parseMessageNotification(data, elem) ?: return@unread L.v("Notif im timestamp ${notif.timestamp}") if (notif.timestamp <= prevLatestEpoch) return@unread - notif.createMessageNotification(this@NotificationService) + NotificationType.MESSAGE.createNotification(this, notif, notifCount == 0) if (notif.timestamp > newLatestEpoch) newLatestEpoch = notif.timestamp notifCount++ } if (newLatestEpoch != prevLatestEpoch) prevNotifTime.copy(epochIm = newLatestEpoch).save() L.d("Notif new latest im epoch ${lastNotificationTime(data.id).epochIm}") - frostAnswersCustom("Notifications", "Type" to "Message", "Count" to notifCount) - summaryMessageNotification(data.id, notifCount) + NotificationType.MESSAGE.summaryNotification(this, data.id, notifCount) } fun parseMessageNotification(data: CookieModel, element: Element): NotificationContent? { @@ -225,32 +211,12 @@ class NotificationService : JobService() { return NotificationContent(data, notifId.toInt(), a.attr("href"), a.text(), text, epoch, pUrl) } - fun summaryMessageNotification(userId: Long, count: Int) - = summaryNotification(userId, count, R.string.messages, FbItem.MESSAGES.url, FROST_MESSAGE_NOTIFICATION_GROUP) - private fun Context.debugNotification(text: String) { if (!BuildConfig.DEBUG) return - val notifBuilder = frostNotification + val notifBuilder = frostNotification.withDefaults() .setContentTitle(string(R.string.frost_name)) .setContentText(text) NotificationManagerCompat.from(this).notify(999, notifBuilder.build()) } - private fun summaryNotification(userId: Long, count: Int, contentRes: Int, pendingUrl: String, groupPrefix: String) { - if (count <= 1) return - val intent = Intent(this, FrostWebActivity::class.java) - intent.data = Uri.parse(pendingUrl) - intent.putExtra(ARG_USER_ID, userId) - val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0) - val notifBuilder = frostNotification - .setContentTitle(string(R.string.frost_name)) - .setContentText("$count ${string(contentRes)}") - .setGroup("${groupPrefix}_$userId") - .setGroupSummary(true) - .setContentIntent(pendingIntent) - .setCategory(Notification.CATEGORY_SOCIAL) - - NotificationManagerCompat.from(this).notify("${groupPrefix}_$userId", userId.toInt(), notifBuilder.build()) - } - } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt index e161533a..64fb130f 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt @@ -1,162 +1,38 @@ package com.pitchedapps.frost.utils -import android.app.Notification -import android.app.PendingIntent +import android.app.DownloadManager import android.content.Context -import android.content.Intent -import android.support.v4.app.NotificationCompat -import android.support.v4.app.NotificationManagerCompat -import android.support.v4.content.FileProvider +import android.content.Context.DOWNLOAD_SERVICE +import android.net.Uri +import android.os.Environment +import android.webkit.URLUtil import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE import ca.allanwang.kau.permissions.kauRequestPermissions -import ca.allanwang.kau.utils.copyFromInputStream -import ca.allanwang.kau.utils.string -import com.pitchedapps.frost.BuildConfig -import com.pitchedapps.frost.R -import com.pitchedapps.frost.services.DownloadService -import com.pitchedapps.frost.services.frostNotification -import com.pitchedapps.frost.services.getNotificationPendingCancelIntent -import com.pitchedapps.frost.services.quiet -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.ResponseBody -import okio.* -import org.jetbrains.anko.AnkoAsyncContext -import org.jetbrains.anko.startService -import java.io.File -import java.io.IOException -import java.lang.ref.WeakReference +import com.pitchedapps.frost.dbflow.loadFbCookie + /** * Created by Allan Wang on 2017-08-04. * - * With reference to the OkHttp3 sample - * - * TODO delete this file; we've moved to our downloader service for now and will create our own player soon + * With reference to Stack Overflow */ -fun Context.frostDownload(url: String) { +fun Context.frostDownload(url: String, userAgent: String, contentDisposition: String, mimeType: String, contentLength: Long) { L.d("Received download request", "Download $url") kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { granted, _ -> - if (granted) -// doAsync { frostDownloadImpl(url, type) } - startService(DownloadService.EXTRA_URL to url) - } -} - -private const val MAX_PROGRESS = 1000 -//private val DOWNLOAD_GROUP: String? = null -private const val DOWNLOAD_GROUP = "frost_downloads" - -private enum class DownloadType(val downloadingRes: Int, val downloadedRes: Int) { - VIDEO(R.string.downloading_video, R.string.downloaded_video), - FILE(R.string.downloading_file, R.string.downloaded_file); - - fun getPendingIntent(context: Context, file: File): PendingIntent { - val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file) - val type = context.contentResolver.getType(uri) - L.d("DownloadType: retrieved pending intent - $uri $type") - val intent = Intent(Intent.ACTION_VIEW, uri) - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .setDataAndType(uri, type) - return PendingIntent.getActivity(context, 0, intent, 0) - } -} - -private fun AnkoAsyncContext.frostDownloadImpl(url: String, type: DownloadType) { - L.d("Starting download request") - val notifId = Math.abs(url.hashCode() + System.currentTimeMillis().toInt()) - var notifBuilderAttempt: NotificationCompat.Builder? = null - weakRef.get()?.apply { - notifBuilderAttempt = frostNotification.quiet - .setContentTitle(string(type.downloadingRes)) - .setCategory(Notification.CATEGORY_PROGRESS) - .setWhen(System.currentTimeMillis()) - .setProgress(MAX_PROGRESS, 0, false) - .setOngoing(true) - .addAction(R.drawable.ic_action_cancel, string(R.string.kau_cancel), getNotificationPendingCancelIntent(DOWNLOAD_GROUP, notifId)) - .setGroup(DOWNLOAD_GROUP) - } - val notifBuilder = notifBuilderAttempt ?: return - notifBuilder.show(weakRef, notifId) - - val request: Request = Request.Builder() - .url(url) - .tag(url) - .build() - - var client: OkHttpClient? = null - client = OkHttpClient.Builder() - .addNetworkInterceptor { chain -> - val original = chain.proceed(chain.request()) - return@addNetworkInterceptor original.newBuilder().body(ProgressResponseBody(original.body()!!) { bytesRead, contentLength, done -> - //cancel request if context reference is now invalid - if (weakRef.get() == null) { - client?.cancel(url) - } - val ctx = weakRef.get() ?: return@ProgressResponseBody client?.cancel(url) ?: Unit - val percentage = bytesRead.toFloat() / contentLength.toFloat() * MAX_PROGRESS - L.v("Download request progress: $percentage") - notifBuilder.setProgress(MAX_PROGRESS, percentage.toInt(), false) - if (done) { - notifBuilder.setFinished(ctx, type) - L.d("Download request finished") - } - notifBuilder.show(weakRef, notifId) - }).build() - } - .build() - client.newCall(request).execute().use { response -> - if (!response.isSuccessful) throw IOException("Unexpected code $response") - val stream = response.body()?.byteStream() - if (stream != null) { - val destination = createMediaFile(".mp4") - destination.copyFromInputStream(stream) - weakRef.get()?.apply { - notifBuilder.setContentIntent(type.getPendingIntent(this, destination)) - notifBuilder.show(weakRef, notifId) - } - } - } -} - -private fun NotificationCompat.Builder.setFinished(context: Context, type: DownloadType) - = setContentTitle(context.string(type.downloadedRes)) - .setProgress(0, 0, false).setOngoing(false).setAutoCancel(true) - .apply { mActions.clear() } - -private fun OkHttpClient.cancel(url: String) { - val call = dispatcher().runningCalls().firstOrNull { it.request().tag() == url } - if (call != null && !call.isCanceled) call.cancel() -} - -private fun NotificationCompat.Builder.show(weakRef: WeakReference, notifId: Int) { - val c = weakRef.get() ?: return - NotificationManagerCompat.from(c).notify(DOWNLOAD_GROUP, notifId, build()) -} - -private class ProgressResponseBody( - val responseBody: ResponseBody, - val listener: (bytesRead: Long, contentLength: Long, done: Boolean) -> Unit) : ResponseBody() { - - private val bufferedSource: BufferedSource by lazy { Okio.buffer(source(responseBody.source())) } - - override fun contentLength(): Long = responseBody.contentLength() - - override fun contentType(): MediaType? = responseBody.contentType() - - override fun source(): BufferedSource = bufferedSource - - private fun source(source: Source): Source = object : ForwardingSource(source) { - - private var totalBytesRead = 0L - - override fun read(sink: Buffer?, byteCount: Long): Long { - val bytesRead = super.read(sink, byteCount) - // read() returns the number of bytes read, or -1 if this source is exhausted. - totalBytesRead += if (bytesRead != -1L) bytesRead else 0 - listener(totalBytesRead, responseBody.contentLength(), bytesRead == -1L) - return bytesRead - } + if (!granted) return@kauRequestPermissions + + val request = DownloadManager.Request(Uri.parse(url)) + + request.setMimeType(mimeType) + val cookie = loadFbCookie(Prefs.userId) ?: return@kauRequestPermissions + request.addRequestHeader("cookie", cookie.cookie) + request.addRequestHeader("User-Agent", userAgent) + request.setDescription("Downloading file...") + request.setTitle(URLUtil.guessFileName(url, contentDisposition, mimeType)) + request.allowScanningByMediaScanner() + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "Frost/" + URLUtil.guessFileName(url, contentDisposition, mimeType)) + val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager + dm.enqueue(request) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt index 7fb7324e..f6d64ab7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebView.kt @@ -70,7 +70,7 @@ class FrostWebView @JvmOverloads constructor( webChromeClient = FrostChromeClient(this) addJavascriptInterface(FrostJSI(this), "Frost") setBackgroundColor(Color.TRANSPARENT) - setDownloadListener { downloadUrl, _, _, _, _ -> context.frostDownload(downloadUrl) } + setDownloadListener(context::frostDownload) } } diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml index 42046133..895c271f 100644 --- a/app/src/main/res/xml/frost_changelog.xml +++ b/app/src/main/res/xml/frost_changelog.xml @@ -11,6 +11,12 @@ + + + + + + @@ -19,10 +25,6 @@ - - - - diff --git a/docs/Changelog.md b/docs/Changelog.md index b9bf21ee..0518f37f 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,6 +1,9 @@ # Changelog ## Beta Updates +* Add default download manager to download all files + +## v1.5.1 * Release day is here! * Add full support for messaging in overlays. We will dynamically launch new overlays when required to. * Prevent bad messenger intent from launching @@ -8,6 +11,7 @@ * Add contextual menu items. Easily go to your full list of notifications or messages from the overlay. * Ensure that bottom bar layout does not hide the web content * Add option to share external links to Frost +* Trigger notification service on each app start ## v1.4.13 * Prevent image loading from trimming too many characters -- cgit v1.2.3