Commit 42f6a535 authored by Christophe Henry's avatar Christophe Henry

Add error report

parent fabd68c7
Pipeline #3460 failed with stage
......@@ -6,6 +6,7 @@
* Better handle images embedded in a link by showing the link seperatly ([!63](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/63))
* Display context options (copy/cut/etc.) on text selection in articles ([!63](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/63))
* Add a notification to report crashes ([!67](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/67))
## Bug fixes
......
......@@ -24,8 +24,8 @@ android {
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
......@@ -117,6 +117,7 @@ dependencies {
def android_support_version = "28.0.0"
def android_navigation = "1.0.0"
def jsoup_version = "1.12.1"
def acraVersion = "5.1.3"
// Linter
ktlint "com.github.shyiko:ktlint:0.31.0"
......@@ -183,6 +184,10 @@ dependencies {
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "org.jsoup:jsoup:$jsoup_version"
// Bug report
implementation "ch.acra:acra-http:$acraVersion"
implementation "ch.acra:acra-notification:$acraVersion"
// Tests
testImplementation 'junit:junit:4.13'
androidTestImplementation "androidx.test:runner:$test_runnner_version"
......
......@@ -18,8 +18,25 @@ import fr.chenry.android.freshrss.utils.Try
import fr.chenry.android.freshrss.utils.whenNotNull
import nl.komponents.kovenant.android.startKovenant
import nl.komponents.kovenant.android.stopKovenant
import org.acra.ACRA
import org.acra.annotation.*
import org.acra.data.StringFormat
import org.acra.sender.HttpSender
import java.util.Properties
@AcraCore(reportFormat = StringFormat.JSON)
@AcraHttpSender(
uri = "https://android-report.christophe-henry.dev/report",
basicAuthLogin = "MLvOfIAsj0MgjVwz",
basicAuthPassword = "VrAiuPLovj28CkyO",
httpMethod = HttpSender.Method.POST
)
@AcraNotification(
resChannelName = R.string.notification_channel_errors_reports_title,
resText = R.string.notification_crash_description,
resTitle = R.string.notification_crash_title,
resChannelImportance = NotificationManagerCompat.IMPORTANCE_MAX
)
class FreshRSSApplication: Application() {
private val _refresherService = MutableLiveData<RefresherService>()
private val serviceConnection = RefresherServiceConnection()
......@@ -34,6 +51,11 @@ class FreshRSSApplication: Application() {
.build()
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
ACRA.init(this)
}
override fun onCreate() {
super.onCreate()
startKovenant()
......@@ -62,16 +84,16 @@ class FreshRSSApplication: Application() {
companion object {
private lateinit var appInstance: FreshRSSApplication
val application: FreshRSSApplication get() = appInstance
val database get() = application.database
val context: Context get() = application.applicationContext
val app: FreshRSSApplication get() = appInstance
val db inline get() = app.database
val context: Context inline get() = app.applicationContext
val notificationManager: NotificationManagerCompat
get() = NotificationManagerCompat.from(application)
val preferences: FreshRSSPreferences get() = application.preferences
inline get() = NotificationManagerCompat.from(app)
val preferences: FreshRSSPreferences inline get() = app.preferences
fun getStringR(id: Int, vararg formatArgs: Any = arrayOf()): String = if(formatArgs.isEmpty())
application.resources.getString(id) else
application.resources.getString(id, *formatArgs)
app.resources.getString(id) else
app.resources.getString(id, *formatArgs)
}
inner class RefresherServiceConnection: ServiceConnection, SharedPreferences.OnSharedPreferenceChangeListener {
......@@ -80,15 +102,18 @@ class FreshRSSApplication: Application() {
private var refreshFrequency: Long = 30L
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
refreshFrequency = this@FreshRSSApplication.preferences.refreshFrequency
(service as RefresherBinder).service.let {
_refresherService.value = it
this@FreshRSSApplication.preferences.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
postDelayedRefesh()
if(service is RefresherBinder) {
refreshFrequency = this@FreshRSSApplication.preferences.refreshFrequency
service.service.let {
_refresherService.value = it
this@FreshRSSApplication.preferences.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
postDelayedRefesh()
}
}
}
override fun onServiceDisconnected(name: ComponentName?) {
_refresherService.value?.apply {cancelRefreshNotification()}
handler.removeCallbacksAndMessages(token)
this@FreshRSSApplication.preferences.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
_refresherService.value = null
......
......@@ -67,7 +67,7 @@ class RefresherService: Service() {
.bind {Store.getUnreadCount()}
.bind {
FreshRSSApplication
.database
.db
.getAllSubcriptionsIds()
.blockingFirst()
.map {Store.getStreamContents(it)}
......@@ -82,15 +82,18 @@ class RefresherService: Service() {
.notificationManager
.notify(NotificationHelper.FAIL_REFRESH_NOTIFICATION, failNotification)
}.alwaysUi {
FreshRSSApplication.notificationManager.cancel(NotificationHelper.ONGOING_REFRESH_NOTIFICATION)
cancelRefreshNotification()
}
return promise
}
fun cancelRefreshNotification() =
FreshRSSApplication.notificationManager.cancel(NotificationHelper.ONGOING_REFRESH_NOTIFICATION)
private fun createNotification(chan: NotificationChannels, title: Int, text: Int, color: Int? = null) =
NotificationCompat
.Builder(FreshRSSApplication.application, chan.channelId)
.Builder(FreshRSSApplication.app, chan.channelId)
.setContentTitle(FreshRSSApplication.getStringR(title))
.setContentText(FreshRSSApplication.getStringR(text))
.setSmallIcon(drawable.ic_rss_feed_black_24dp)
......
......@@ -40,7 +40,7 @@ class MainActivity: AppCompatActivity() {
restoreState()
setContentView(R.layout.activity_main)
setupActionBarWithNavController(navigation, appBarConfiguration)
FreshRSSApplication.application.refresherService.value.whenNotNull {it.refresh()}
FreshRSSApplication.app.refresherService.value.whenNotNull {it.refresh()}
drawerLayout.addDrawerListener(drawerToggle)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
......@@ -119,6 +119,6 @@ class MainActivity: AppCompatActivity() {
}
private fun saveState() {
FreshRSSApplication.application.preferences.subscriptionSection = Store.subscriptionsSection.value!!
FreshRSSApplication.app.preferences.subscriptionSection = Store.subscriptionsSection.value!!
}
}
......@@ -23,7 +23,7 @@ class StartActivity : AppCompatActivity() {
t.whenNotNull {
it.refresh()
.failUi { err -> this.e(err) }
.alwaysUi { FreshRSSApplication.application.refresherService.removeObserver(this) }
.alwaysUi { FreshRSSApplication.app.refresherService.removeObserver(this) }
}
}
}
......@@ -35,7 +35,7 @@ class StartActivity : AppCompatActivity() {
val now = LocalDateTime.now()
val userName = model.liveData.value.let {
it.whenNotNull {
FreshRSSApplication.application.refresherService.observeForever(observer)
FreshRSSApplication.app.refresherService.observeForever(observer)
}
when (it) {
......
......@@ -76,7 +76,7 @@ class SubscriptionsFragment : Fragment(), Observer<Subscriptions> {
it.isRefreshing = v != null
})
it.setOnRefreshListener {
FreshRSSApplication.application.refresherService.value.whenNotNull { rs -> rs.refresh() }
FreshRSSApplication.app.refresherService.value.whenNotNull {rs -> rs.refresh() }
}
}
......
......@@ -40,16 +40,16 @@ object Store {
private val onGoingPostRequests = ConcurrentHashMap<String, Promise<Unit, Exception>>()
init {
val flowable = FreshRSSApplication.database.getAccount()
val flowable = FreshRSSApplication.db.getAccount()
val account = flowable.blockingFirst().firstOrNull() ?: VoidAccount
api = Api(account)
accountLiveData = FreshRSSApplication.database.getAccount().toLiveData().apply {
accountLiveData = FreshRSSApplication.db.getAccount().toLiveData().apply {
observeForever {if(it.isNotEmpty()) api = Api(it.first())}
}
}
fun login(instance: String, user: String, password: String): Promise<Unit, Exception> =
Api.login(instance, user, password) then {FreshRSSApplication.database.insertAccount(it)}
Api.login(instance, user, password) then {FreshRSSApplication.db.insertAccount(it)}
fun ensureToken(): Promise<Unit, Exception> {
if(!api.account.isWriteTokenExpired) return Promise.ofSuccess(Unit)
......@@ -61,9 +61,9 @@ object Store {
fun getSubscriptions(): Promise<Unit, Exception> = api.getSubscriptions() bind {
val subscriptions = it.map {self -> Subscription.fromSubscriptionApiItem(self)}
val syncPromise = FreshRSSApplication.database.syncSubscriptions(subscriptions).always {
val syncPromise = FreshRSSApplication.db.syncSubscriptions(subscriptions).always {
task {
FreshRSSApplication.database.let {db ->
FreshRSSApplication.db.let {db ->
db.getAllSubcriptionsWithImageToUpdate()
.blockingFirst()
.forEach {sub -> db.insertSubscriptionImage(sub.id, sub.fetchImage())}
......@@ -72,7 +72,7 @@ object Store {
}
val categoriesPromise = task {
val subscriptionCategories = it.flatMap {self -> self.categories}.map(::fromApiItem)
FreshRSSApplication.database.insertAllSubscriptionCategories(subscriptionCategories)
FreshRSSApplication.db.insertAllSubscriptionCategories(subscriptionCategories)
}
all(syncPromise, categoriesPromise, cancelOthersOnError = false).toSuccessVoid()
}
......@@ -82,7 +82,7 @@ object Store {
totalUnreadCount.postValue(it.max)
it.unreadcounts
.forEach {self ->
FreshRSSApplication.database.updateSubscriptionCount(
FreshRSSApplication.db.updateSubscriptionCount(
self.id,
self.count
)
......@@ -93,8 +93,8 @@ object Store {
api.getStreamContents(id) bind {
val insertPromises = it.items.map {item ->
task {
FreshRSSApplication.database.insertArticle(Article.fromContentItem(item))
FreshRSSApplication.database.updateSubscriptionNewestArticleDate(
FreshRSSApplication.db.insertArticle(Article.fromContentItem(item))
FreshRSSApplication.db.updateSubscriptionNewestArticleDate(
id,
item.crawled
)
......@@ -109,7 +109,7 @@ object Store {
api.getUnreadItems(lastFetchTimestamp) bind {
val promises = it.items.map {item ->
task {
FreshRSSApplication.database.upsertArticle(
FreshRSSApplication.db.upsertArticle(
Article.fromContentItem(item).copy(
readStatus = UNREAD
)
......@@ -129,7 +129,7 @@ object Store {
article.requestOnGoing = false
onGoingPostRequests.remove(article.id)
}.success {
val db = FreshRSSApplication.database
val db = FreshRSSApplication.db
db.upsertArticle(article.copy(readStatus = readStatus))
when(readStatus) {
READ -> {
......
......@@ -13,7 +13,7 @@ class AccountVM : ViewModel() {
private val source: LiveData<List<Account>>
init {
val flowable = FreshRSSApplication.database.getAccount()
val flowable = FreshRSSApplication.db.getAccount()
source = flowable.toLiveData()
liveData = MutableLiveData<Account>().apply {
value = flowable.blockingFirst().firstOrNull()
......
......@@ -17,7 +17,7 @@ class SubscriptionArticleVM(articleId: ItemId) : ViewModel() {
private val source: LiveData<Articles>
init {
val flowable = FreshRSSApplication.database.getArticleById(articleId)
val flowable = FreshRSSApplication.db.getArticleById(articleId)
val article = flowable.blockingFirst().first()
source = flowable.toLiveData()
......@@ -26,7 +26,7 @@ class SubscriptionArticleVM(articleId: ItemId) : ViewModel() {
source.observeForever { value = it.first() }
}
subscription = FreshRSSApplication.database.getSubcriptionsById(article.streamId).blockingFirst().first()
subscription = FreshRSSApplication.db.getSubcriptionsById(article.streamId).blockingFirst().first()
}
}
......
......@@ -28,14 +28,14 @@ class SubscriptionArticlesVM(streamId: String, readStatus: ReadStatus) : ViewMod
o2.published.compareTo(o1.published)
}
val flowable = FreshRSSApplication.database.getSubcriptionsById(streamId)
val flowable = FreshRSSApplication.db.getSubcriptionsById(streamId)
subscription = flowable.blockingFirst().first()
subscriptionLiveData = flowable.toLiveData()
subscriptionLiveData.observeForever { if (it.isNotEmpty()) subscription = it.first() }
source = when (readStatus) {
ReadStatus.READ -> FreshRSSApplication.database.getArticlesByStreamId(streamId)
ReadStatus.UNREAD -> FreshRSSApplication.database.getArticleByStreamIdAndUnread(streamId)
ReadStatus.READ -> FreshRSSApplication.db.getArticlesByStreamId(streamId)
ReadStatus.UNREAD -> FreshRSSApplication.db.getArticleByStreamIdAndUnread(streamId)
}
liveData = MutableLiveData<List<SubscriptionArticleViewItem>>().apply {
......
package fr.chenry.android.freshrss.store.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.toLiveData
import androidx.lifecycle.*
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection.ALL
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection.FAVORITES
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection.UNREAD
import fr.chenry.android.freshrss.store.database.models.Subscription
import fr.chenry.android.freshrss.store.database.models.SubscriptionCategories
import fr.chenry.android.freshrss.store.database.models.SubscriptionCategory
import fr.chenry.android.freshrss.store.database.models.Subscriptions
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection.*
import fr.chenry.android.freshrss.store.database.models.*
import org.joda.time.LocalDateTime
sealed class SubscriptionsVM : ViewModel() {
......@@ -67,13 +57,13 @@ sealed class SubscriptionsVM : ViewModel() {
class AllSubscriptionsVM : SubscriptionsVM() {
override val comparator: Comparator<Subscription> get() = titleComparator
override val grouper: (Subscription) -> String get() = titleGrouper
override val subscriptionsOrigin = FreshRSSApplication.database.getAllSubcriptions().toLiveData()
override val subscriptionsOrigin = FreshRSSApplication.db.getAllSubcriptions().toLiveData()
override val subscriptionCategories: LiveData<SubscriptionCategories>
private val _subscriptionCategoriesLiveData: LiveData<SubscriptionCategories>
init {
val flowable = FreshRSSApplication.database.getAllCategories()
val flowable = FreshRSSApplication.db.getAllCategories()
_subscriptionCategoriesLiveData = flowable.toLiveData()
subscriptionCategories = MutableLiveData<SubscriptionCategories>().apply {
value = flowable.blockingFirst().orEmpty().sortedWith(labelComparator)
......@@ -87,14 +77,14 @@ class AllSubscriptionsVM : SubscriptionsVM() {
class UnreadSubscriptionsVM : SubscriptionsVM() {
override val comparator: Comparator<Subscription> get() = dateComparator
override val grouper: (Subscription) -> String get() = dateGrouper
override val subscriptionsOrigin = FreshRSSApplication.database.getAllUnreadSubcriptions().toLiveData()
override val subscriptionsOrigin = FreshRSSApplication.db.getAllUnreadSubcriptions().toLiveData()
override val subscriptionCategories by lazy {
MutableLiveData<SubscriptionCategories>().apply {
val observer = Observer<Subscriptions> {
val categories = (it ?: listOf()).flatMap { self -> self.subscriptionCategories }
postValue(
FreshRSSApplication.database
FreshRSSApplication.db
.getCategoriesById(categories)
.blockingFirst()
.sortedWith(labelComparator)
......@@ -126,15 +116,15 @@ class SubscriptionsForCategoryVM(
override val subscriptionsOrigin: LiveData<Subscriptions> by lazy {
when (subscriptionSection) {
FAVORITES ->
FreshRSSApplication.database
FreshRSSApplication.db
.getSubcriptionsBySubscriptionCategory(subscriptionCategory)
.toLiveData()
ALL ->
FreshRSSApplication.database
FreshRSSApplication.db
.getSubcriptionsBySubscriptionCategory(subscriptionCategory)
.toLiveData()
UNREAD ->
FreshRSSApplication.database
FreshRSSApplication.db
.getSubcriptionsBySubscriptionCategoryAndUnreadCount(subscriptionCategory)
.toLiveData()
}
......
......@@ -28,7 +28,7 @@ enum class NotificationChannels(nameResourceId: Int, descriptionResourceId: Int,
init {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) Try {
FreshRSSApplication.application.getSystemService(Context.NOTIFICATION_SERVICE).apply {
FreshRSSApplication.app.getSystemService(Context.NOTIFICATION_SERVICE).apply {
this as NotificationManager
val message = FreshRSSApplication.getStringR(nameResourceId)
NotificationChannel(channelId, message, importance.asAndroid()).apply {
......
......@@ -54,7 +54,10 @@
<string name="notification_channel_refresh_title">Refresh events</string>
<string name="notification_channel_refresh_description">FreshRSS is fetching content from your FreshRSS server</string>
<string name="notification_channel_errors_title">Errors</string>
<string name="notification_channel_errors_reports_title">Errors reports</string>
<string name="notification_channel_errors_description">FreshRSS has met an error you should be notified about</string>
<string name="notification_crash_title">FreshRSS has crashed</string>
<string name="notification_crash_description">FreshRSS has encountered an error. You may have a chance to help us improve FreshRSS. Would you like to send a report?</string>
<!-- Strings related to subscriptions -->
<string name="subscription_title_label">Title</string>
<string name="rss_icon">RSS icon</string>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment