diff --git a/gradle.properties b/gradle.properties
index c6b9841..3e04b6f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,7 +15,6 @@
#Fri Jul 07 00:04:56 CST 2017
org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#org.gradle.jvmargs=-Xmx1536m
-android.enableD8=true
android.useAndroidX=true
android.enableJetifier=true
android.debug.obsoleteApi=true
diff --git a/xkcd/build.gradle b/xkcd/build.gradle
index 571c440..ca6ed35 100644
--- a/xkcd/build.gradle
+++ b/xkcd/build.gradle
@@ -69,7 +69,7 @@ android {
defaultConfig {
applicationId "xyz.jienan.xkcd"
minSdkVersion 16
- targetSdkVersion 32
+ targetSdkVersion 33
versionCode 76
versionName "2.8.2"
vectorDrawables.useSupportLibrary = true
@@ -218,7 +218,7 @@ dependencies {
implementation 'androidx.work:work-runtime-ktx:2.8.0-beta02'
implementation 'androidx.work:work-rxjava2:2.8.0-beta02'
- proprietaryImplementation 'com.google.firebase:firebase-analytics:21.2.0'
+ proprietaryImplementation 'com.google.firebase:firebase-analytics:19.0.2'
//noinspection GradleDependency
proprietaryImplementation('com.google.firebase:firebase-messaging:22.0.0') {
// 23.0.0 will require API level 19, Android 4.4 higher.
diff --git a/xkcd/src/main/AndroidManifest.xml b/xkcd/src/main/AndroidManifest.xml
index bc443aa..2fb504c 100644
--- a/xkcd/src/main/AndroidManifest.xml
+++ b/xkcd/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
+ val archive: Single
@GET("{article_id}/")
- fun getArticle(@Path("article_id") id: Long): Observable
+ fun getArticle(@Path("article_id") id: Long): Single
@FormUrlEncoded
@POST
- fun thumbsUpWhatIf(@Url url: String, @Field("what_if_id") whatIfId: Int): Observable
+ fun thumbsUpWhatIf(@Url url: String, @Field("what_if_id") whatIfId: Int): Single
@Headers("$HEADER_CACHEABLE: 60")
@GET
- fun getTopWhatIfs(@Url url: String, @Query("sortby") sortby: String): Observable>
+ fun getTopWhatIfs(@Url url: String, @Query("sortby") sortby: String): Single>
}
diff --git a/xkcd/src/main/java/xyz/jienan/xkcd/comics/fragment/SingleComicFragment.kt b/xkcd/src/main/java/xyz/jienan/xkcd/comics/fragment/SingleComicFragment.kt
index 11844cd..6b48c58 100644
--- a/xkcd/src/main/java/xyz/jienan/xkcd/comics/fragment/SingleComicFragment.kt
+++ b/xkcd/src/main/java/xyz/jienan/xkcd/comics/fragment/SingleComicFragment.kt
@@ -201,7 +201,7 @@ class SingleComicFragment : BaseFragment(), SingleComicContract.View {
if (currentPic == null) {
false
} else {
- showInfoDialog(showAltText = comicOnly)
+ showInfoDialog(showAltText = comicOnly && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT)
it.performHapticFeedback(LONG_PRESS)
true
}
diff --git a/xkcd/src/main/java/xyz/jienan/xkcd/list/presenter/WhatIfListPresenter.kt b/xkcd/src/main/java/xyz/jienan/xkcd/list/presenter/WhatIfListPresenter.kt
index cce4dc2..6712486 100644
--- a/xkcd/src/main/java/xyz/jienan/xkcd/list/presenter/WhatIfListPresenter.kt
+++ b/xkcd/src/main/java/xyz/jienan/xkcd/list/presenter/WhatIfListPresenter.kt
@@ -1,7 +1,9 @@
package xyz.jienan.xkcd.list.presenter
import android.view.View
+import io.reactivex.Flowable
import io.reactivex.Observable
+import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
@@ -39,14 +41,14 @@ class WhatIfListPresenter(private val view: WhatIfListContract.View) : ListPrese
WhatIfModel.thumbUpList
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { view.setLoading(true) }
- .flatMapSingle { whatIfArticles ->
- Observable.fromIterable(whatIfArticles)
+ .flatMap { whatIfArticles ->
+ Flowable.fromIterable(whatIfArticles)
.map { it.num }
.filter { num -> num <= latest }
.map { WhatIfModel.loadArticleFromDB(it)!! }
.toList()
}
- .doOnNext { view.setLoading(false) }
+ .doOnSuccess { view.setLoading(false) }
.subscribe({ view.updateData(it) },
{ e -> Timber.e(e, "get top what if error") })
.also { compositeDisposable.add(it) }
diff --git a/xkcd/src/main/java/xyz/jienan/xkcd/model/WhatIfModel.kt b/xkcd/src/main/java/xyz/jienan/xkcd/model/WhatIfModel.kt
index ddcde91..d5db678 100644
--- a/xkcd/src/main/java/xyz/jienan/xkcd/model/WhatIfModel.kt
+++ b/xkcd/src/main/java/xyz/jienan/xkcd/model/WhatIfModel.kt
@@ -2,6 +2,8 @@ package xyz.jienan.xkcd.model
import android.text.TextUtils
import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Maybe
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -26,7 +28,7 @@ object WhatIfModel {
val favWhatIf: List
get() = BoxManager.favWhatIf
- val thumbUpList: Observable>
+ val thumbUpList: Single>
get() = whatIfAPI.getTopWhatIfs(WHAT_IF_TOP, XKCD_TOP_SORT_BY_THUMB_UP)
.subscribeOn(Schedulers.io())
@@ -52,7 +54,6 @@ object WhatIfModel {
fun loadAllWhatIf(): Single> {
return whatIfAPI.archive
.subscribeOn(Schedulers.io())
- .singleOrError()
.map { WhatIfArticleUtil.getArticlesFromArchive(it) }
.map { BoxManager.updateAndSaveWhatIf(it.toMutableList()) }
}
@@ -65,7 +66,6 @@ object WhatIfModel {
fun loadArticle(id: Long): Single {
return loadArticleContentFromDB(id)
.switchIfEmpty(loadArticleFromAPI(id))
- .singleOrError()
.observeOn(AndroidSchedulers.mainThread())
}
@@ -90,7 +90,7 @@ object WhatIfModel {
* @param index
* @return thumb up count
*/
- fun thumbsUp(index: Long): Observable {
+ fun thumbsUp(index: Long): Single {
return whatIfAPI.thumbsUpWhatIf(WHAT_IF_THUMBS_UP, index.toInt())
.subscribeOn(Schedulers.io())
.doOnSubscribe { BoxManager.likeWhatIf(index) }
@@ -98,25 +98,29 @@ object WhatIfModel {
}
fun fastLoadWhatIfs(index: Long): Completable =
- Observable.just(index)
- .flatMap {
+ Flowable.just(index)
+ .flatMap {
if (index == 0L) {
loadLatest().map { it.num }
- .flatMapObservable { Observable.rangeLong(1, it) }
+ .toFlowable()
+ .flatMap { Flowable.rangeLong(1, it) }
} else {
- Observable.rangeLong(1, index)
+ Flowable.rangeLong(1, index)
}
- }.flatMap {
+ }.flatMapSingle {
if (loadArticleFromDB(it) == null || loadArticleFromDB(it)!!.content.isNullOrBlank()) {
loadArticleFromAPI(it)
+ .map { index }
+ .onErrorReturnItem(index)
} else {
- Observable.just(it)
+ Single.just(it)
}
}
.toList()
.ignoreElement()
+ .onErrorComplete()
- private fun loadArticleFromAPI(id: Long): Observable {
+ private fun loadArticleFromAPI(id: Long): Single {
return whatIfAPI.getArticle(id)
.subscribeOn(Schedulers.io())
.map { WhatIfArticleUtil.getArticleFromHtml(it) }
@@ -124,8 +128,8 @@ object WhatIfModel {
.map { BoxManager.updateAndSaveWhatIf(id, it) }
}
- private fun loadArticleContentFromDB(id: Long): Observable {
+ private fun loadArticleContentFromDB(id: Long): Maybe {
val article = BoxManager.getWhatIf(id)
- return if (article == null || TextUtils.isEmpty(article.content)) Observable.empty() else Observable.just(article)
+ return if (article == null || TextUtils.isEmpty(article.content)) Maybe.empty() else Maybe.just(article)
}
}
diff --git a/xkcd/src/main/java/xyz/jienan/xkcd/settings/SettingsFragment.kt b/xkcd/src/main/java/xyz/jienan/xkcd/settings/SettingsFragment.kt
index e6f376a..4f81a12 100644
--- a/xkcd/src/main/java/xyz/jienan/xkcd/settings/SettingsFragment.kt
+++ b/xkcd/src/main/java/xyz/jienan/xkcd/settings/SettingsFragment.kt
@@ -3,15 +3,36 @@ package xyz.jienan.xkcd.settings
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
+import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
+import android.provider.Settings
import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatDelegate
-import androidx.preference.*
-import androidx.work.*
+import androidx.core.app.NotificationManagerCompat
+import androidx.preference.ListPreference
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreferenceCompat
+import androidx.work.Constraints
+import androidx.work.ExistingWorkPolicy
+import androidx.work.NetworkType
+import androidx.work.OneTimeWorkRequest
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
import timber.log.Timber
-import xyz.jienan.xkcd.Const.*
+import xyz.jienan.xkcd.Const.PREF_ARROW
+import xyz.jienan.xkcd.Const.PREF_DARK_THEME
+import xyz.jienan.xkcd.Const.PREF_FONT
+import xyz.jienan.xkcd.Const.PREF_NOTIFICATION
+import xyz.jienan.xkcd.Const.PREF_XKCD_STORAGE
+import xyz.jienan.xkcd.Const.PREF_XKCD_TRANSLATION
+import xyz.jienan.xkcd.Const.PREF_ZOOM
import xyz.jienan.xkcd.R
import xyz.jienan.xkcd.model.WhatIfModel
import xyz.jienan.xkcd.model.XkcdModel
@@ -33,10 +54,20 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan
private val storagePref by lazy { findPreference(PREF_XKCD_STORAGE) }
+ private val notificationPref by lazy { findPreference(PREF_NOTIFICATION) }
+
+ private val notificationManager by lazy { NotificationManagerCompat.from(requireContext()) }
+
+ private lateinit var permissionLauncher: ActivityResultLauncher
+
+ // android.Manifest.permission.POST_NOTIFICATIONS
+ private val notificationPermission = "android.permission.POST_NOTIFICATIONS"
+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
Timber.d("onCreatePreferences")
setPreferencesFromResource(R.xml.prefs, rootKey)
+
findPreference(PREF_FONT)?.onPreferenceChangeListener = this
arrowPref?.summary = resources.getQuantityString(R.plurals.pref_arrow_summary,
@@ -46,6 +77,7 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan
darkPref?.onPreferenceChangeListener = this
storagePref?.onPreferenceChangeListener = this
+ notificationPref?.onPreferenceChangeListener = this
if (XkcdModel.localizedUrl.isBlank()) {
findPreference("pref_key_xkcd")?.removePreference(findPreference(PREF_XKCD_TRANSLATION)!!)
@@ -64,6 +96,13 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan
startActivity(Intent(context, ManageSpaceActivity::class.java))
true
}
+ permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
+ notificationPref?.isChecked = isGranted
+ Timber.d("isGranted $isGranted")
+ if (!crashFreeShouldShowRequestPermissionRationale(notificationPermission) && !isGranted) {
+ notificationPref?.setSummaryOff(R.string.pref_notification_summary_enable_from_settings)
+ }
+ }
}
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
@@ -94,10 +133,52 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan
ToastUtils.showToast(requireContext(), getString(R.string.pref_xkcd_storage_toast), duration = Toast.LENGTH_LONG)
return true
}
+ PREF_NOTIFICATION -> {
+ Timber.d("$PREF_NOTIFICATION, set to $newValue")
+
+ val notificationEnabled = notificationManager.areNotificationsEnabled()
+
+ if (notificationEnabled) {
+ (preference as SwitchPreferenceCompat).isChecked = (newValue == true)
+ } else {
+ if (newValue == true) {
+ if (crashFreeShouldShowRequestPermissionRationale(notificationPermission)) {
+ permissionLauncher.launch(notificationPermission)
+ } else {
+ showNotificationSettings(requireContext())
+ }
+ } else {
+ (preference as SwitchPreferenceCompat).isChecked = false
+ }
+ }
+ }
}
return false
}
+ override fun onResume() {
+ super.onResume()
+ if (notificationManager.areNotificationsEnabled() && notificationPref?.isChecked == false) {
+ notificationPref?.setSummaryOff(R.string.pref_notification_summary_disabled)
+ } else if (!notificationManager.areNotificationsEnabled()) {
+ notificationPref?.isChecked = false
+ if (crashFreeShouldShowRequestPermissionRationale(notificationPermission)) {
+ notificationPref?.setSummaryOff(R.string.pref_notification_summary_disabled)
+ } else {
+ notificationPref?.setSummaryOff(R.string.pref_notification_summary_enable_from_settings)
+ }
+ }
+ }
+
+ @Suppress("SameParameterValue")
+ private fun crashFreeShouldShowRequestPermissionRationale(permission: String): Boolean {
+ return try {
+ shouldShowRequestPermissionRationale(permission)
+ } catch (e: Exception) {
+ false
+ }
+ }
+
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@@ -131,6 +212,36 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan
}
}
+ private fun showNotificationSettings(context: Context, channelId: String? = null) {
+ val notificationSettingsIntent = when {
+ // TODO test 26 and above
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /*26*/ -> Intent().apply {
+ action = when (channelId) {
+ null -> Settings.ACTION_APP_NOTIFICATION_SETTINGS
+ else -> Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS
+ }
+ channelId?.let { putExtra(Settings.EXTRA_CHANNEL_ID, it) }
+ putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P /*28*/) {
+ flags += Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ }
+ // TODO test 21 - 25
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP /*21*/ -> Intent().apply {
+ action = "android.settings.APP_NOTIFICATION_SETTINGS"
+ putExtra("app_package", context.packageName)
+ putExtra("app_uid", context.applicationInfo.uid)
+ }
+ // TODO test 20 and below
+ else -> Intent().apply {
+ action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+ addCategory(Intent.CATEGORY_DEFAULT)
+ data = Uri.parse("package:" + context.packageName)
+ }
+ }
+ startActivity(notificationSettingsIntent)
+ }
+
companion object {
private const val RES_DARK = 101
}
diff --git a/xkcd/src/main/java/xyz/jienan/xkcd/ui/NotificationUtils.kt b/xkcd/src/main/java/xyz/jienan/xkcd/ui/NotificationUtils.kt
index 46b1181..650f248 100644
--- a/xkcd/src/main/java/xyz/jienan/xkcd/ui/NotificationUtils.kt
+++ b/xkcd/src/main/java/xyz/jienan/xkcd/ui/NotificationUtils.kt
@@ -12,7 +12,9 @@ import android.media.RingtoneManager
import android.os.Build
import android.text.TextUtils
import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
import androidx.core.graphics.createBitmap
+import androidx.preference.PreferenceManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import io.reactivex.Maybe
@@ -20,6 +22,7 @@ import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
+import xyz.jienan.xkcd.Const
import xyz.jienan.xkcd.Const.*
import xyz.jienan.xkcd.R
import xyz.jienan.xkcd.home.MainActivity
@@ -58,6 +61,16 @@ object NotificationUtils {
@SuppressLint("CheckResult")
fun showNotification(context: Context, article: WhatIfArticle) {
+
+ val allowNotification = PreferenceManager
+ .getDefaultSharedPreferences(context)
+ .getBoolean(PREF_NOTIFICATION, true)
+ && NotificationManagerCompat.from(context).areNotificationsEnabled()
+
+ if (!allowNotification) {
+ return
+ }
+
val width = 400
val height = 400
val num = article.num.toInt()
diff --git a/xkcd/src/main/java/xyz/jienan/xkcd/whatif/presenter/WhatIfMainPresenter.kt b/xkcd/src/main/java/xyz/jienan/xkcd/whatif/presenter/WhatIfMainPresenter.kt
index e7d3780..882f558 100644
--- a/xkcd/src/main/java/xyz/jienan/xkcd/whatif/presenter/WhatIfMainPresenter.kt
+++ b/xkcd/src/main/java/xyz/jienan/xkcd/whatif/presenter/WhatIfMainPresenter.kt
@@ -36,7 +36,6 @@ class WhatIfMainPresenter constructor(private val view: WhatIfMainContract.View)
}
WhatIfModel.thumbsUp(currentIndex)
- .debounce(100, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ view.showThumbUpCount(it) },
{ e -> Timber.e(e, "Thumbs up failed") })
diff --git a/xkcd/src/main/res/values-b+zh/strings.xml b/xkcd/src/main/res/values-b+zh/strings.xml
index e703882..f0cc6d7 100644
--- a/xkcd/src/main/res/values-b+zh/strings.xml
+++ b/xkcd/src/main/res/values-b+zh/strings.xml
@@ -127,4 +127,9 @@
数据可用: %d
xkcd extra
what if 文章
+
+ 通知
+ 点击前往设置开启应用通知
+ 每周一、三、五,4月1日,以及大概每四年收到更新通知
+ 开启后,在有更新时会收到通知
\ No newline at end of file
diff --git a/xkcd/src/main/res/values/strings.xml b/xkcd/src/main/res/values/strings.xml
index f813ea6..2747c4d 100644
--- a/xkcd/src/main/res/values/strings.xml
+++ b/xkcd/src/main/res/values/strings.xml
@@ -127,6 +127,13 @@
info ready: %d
xkcd extra
what if articles
+
Love & Archive
Archive comics and articles when added to favorites
+ Archive
+
+ Notification
+ Enable notification from system settings
+ Receive notifications on Monday, Wednesday, Friday, April 1, and every 4 years.
+ Turn ON to get notified of latest xkcd and what if
diff --git a/xkcd/src/main/res/xml/prefs.xml b/xkcd/src/main/res/xml/prefs.xml
index 24efca7..e089deb 100644
--- a/xkcd/src/main/res/xml/prefs.xml
+++ b/xkcd/src/main/res/xml/prefs.xml
@@ -59,6 +59,15 @@
+
+