doesn't crash

This commit is contained in:
Matthieu 2021-08-20 21:26:11 +02:00
parent e6910c3ce0
commit dd6c1c9e03
16 changed files with 143 additions and 59 deletions

View File

@ -25,9 +25,11 @@ class NotificationWorkerTest {
@Test
fun testNotificationWorker() {
// Get the ListenableWorker
val worker =
TestListenableWorkerBuilder<NotificationsWorker>(context).build() // Run the worker synchronously
val result = worker.startWork().get()
MatcherAssert.assertThat(result, CoreMatchers.`is`(ListenableWorker.Result.success()))
while (true) {
val worker =
TestListenableWorkerBuilder<NotificationsWorker>(context).build() // Run the worker synchronously
val result = worker.startWork().get()
MatcherAssert.assertThat(result, CoreMatchers.`is`(ListenableWorker.Result.success()))
}
}
}

View File

@ -46,7 +46,9 @@ import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.hasInternet
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.INSTANCE_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.SHOW_NOTIFICATION_TAG
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.USER_NOTIFICATION_TAG
import retrofit2.HttpException
import java.io.IOException
@ -58,6 +60,7 @@ class MainActivity : BaseActivity() {
companion object {
const val ADD_ACCOUNT_IDENTIFIER: Long = -13
const val LOG_OUT_REQUESTED = "LOG_OUT_REQUESTED"
}
private lateinit var binding: ActivityMainBinding
@ -72,10 +75,12 @@ class MainActivity : BaseActivity() {
//get the currently active user
user = db.userDao().getActiveUser()
if (notificationFromOtherUser()) return
//Check if we have logged in and gotten an access token
if (user == null) {
launchActivity(LoginActivity(), firstTime = true)
finish()
launchActivity(LoginActivity(), firstTime = true)
} else {
sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this)
@ -109,6 +114,32 @@ class MainActivity : BaseActivity() {
}
}
//Checks if the activity was launched from a notification from another account than the
// current active one, and if so switches to that account
private fun notificationFromOtherUser(): Boolean {
val userOfNotification: String? = intent.extras?.getString(USER_NOTIFICATION_TAG)
val instanceOfNotification: String? = intent.extras?.getString(INSTANCE_NOTIFICATION_TAG)
if (userOfNotification != null && instanceOfNotification != null
&& (userOfNotification != user?.user_id
|| instanceOfNotification != user?.instance_uri)
) {
switchUser(userOfNotification)
val newIntent = Intent(this, MainActivity::class.java)
newIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
if (intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false)) {
newIntent.putExtra(SHOW_NOTIFICATION_TAG, true)
}
finish()
startActivity(newIntent)
return true
}
return false
}
private fun setupDrawer() {
binding.mainDrawerButton.setOnClickListener{
binding.drawerLayout.open()
@ -182,6 +213,7 @@ class MainActivity : BaseActivity() {
}
private fun logOut(){
finish()
db.runInTransaction {
db.userDao().deleteActiveUsers()
@ -234,16 +266,23 @@ class MainActivity : BaseActivity() {
return false
}
db.userDao().deActivateActiveUsers()
db.userDao().activateUser(profile.identifier.toString())
apiHolder.setToCurrentUser()
switchUser(profile.identifier.toString())
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
finish()
startActivity(intent)
return false
}
private fun switchUser(userId: String) {
db.userDao().deActivateActiveUsers()
db.userDao().activateUser(userId)
apiHolder.setToCurrentUser()
}
private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem {
return PrimaryDrawerItem()
.apply {

View File

@ -22,7 +22,6 @@ import java.net.URI
import java.net.URISyntaxException
import java.text.ParseException
import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.*
@ -130,11 +129,11 @@ fun parseHTMLText(
}
fun setTextViewFromISO8601(date: OffsetDateTime, textView: TextView, absoluteTime: Boolean, context: Context) {
val now = Date.from(OffsetDateTime.ofInstant(Instant.now(), ZoneOffset.UTC).toInstant()).time
fun setTextViewFromISO8601(date: Instant, textView: TextView, absoluteTime: Boolean, context: Context) {
val now = Date.from(Instant.now()).time
try {
val then = Date.from(date.toInstant()).time
val then = Date.from(date).time
val formattedDate: String = android.text.format.DateUtils
.getRelativeTimeSpanString(then, now,
android.text.format.DateUtils.SECOND_IN_MILLIS,

View File

@ -249,7 +249,7 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position)
uiModel.let {
uiModel?.let {
(holder as NotificationViewHolder).bind(
it,
apiHolder,

View File

@ -72,8 +72,8 @@ class SettingsActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceCha
putBoolean("restartMain", true)
}
intent.putExtras(savedInstanceState)
super.startActivity(intent)
finish()
super.startActivity(intent)
}
class SettingsFragment : PreferenceFragmentCompat() {

View File

@ -15,8 +15,9 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.*
import retrofit2.http.Field
import java.time.Instant
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
/*
@ -40,12 +41,16 @@ interface PixelfedAPI {
private var gSonInstance: Gson = GsonBuilder()
.registerTypeAdapter(
OffsetDateTime::class.java,
Instant::class.java,
JsonDeserializer { json: JsonElement, _, _ ->
OffsetDateTime.parse(
json.asString
DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(
json.asString, Instant::from
)
} as JsonDeserializer<OffsetDateTime>)
} as JsonDeserializer<Instant>).registerTypeAdapter(
Instant::class.java,
JsonSerializer { src: Instant, _,_ ->
JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(src));
})
.create()
private val intermediate: Retrofit.Builder = Retrofit.Builder()

View File

@ -9,6 +9,7 @@ import org.pixeldroid.app.utils.api.PixelfedAPI
import retrofit2.HttpException
import java.io.IOException
import java.io.Serializable
import java.time.Instant
/*
Represents a user and their associated profile.
@ -32,7 +33,7 @@ data class Account(
val emojis: List<Emoji>? = null,
val discoverable: Boolean? = true,
//Statistical attributes
val created_at: String? = "", //ISO 8601 Datetime (maybe can use a date type)
val created_at: Instant? = null, //ISO 8601 Datetime
val statuses_count: Int? = 0,
val followers_count: Int? = 0,
val following_count: Int? = 0,

View File

@ -5,7 +5,7 @@ import androidx.room.ForeignKey
import androidx.room.Index
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import java.io.Serializable
import java.time.OffsetDateTime
import java.time.Instant
/*
Represents a notification of an event relevant to the user.
@ -27,7 +27,7 @@ data class Notification(
//Required attributes
override val id: String,
val type: NotificationType?,
val created_at: OffsetDateTime?, //ISO 8601 Datetime
val created_at: Instant? = null, //ISO 8601 Datetime
val account: Account?,
//Optional attributes
val status: Status? = null,

View File

@ -1,5 +1,22 @@
package org.pixeldroid.app.utils.api.objects
import java.io.Serializable
import java.time.Instant
class Poll : Serializable
data class Poll (
val id: String?,
val expires_at: Instant? = null, //ISO 8601 Datetime, or null if poll does not end
val expired: Boolean?,
val multiple: Boolean, //Does the poll allow multiple-choice answers?
val votes_count: Int?,
val voters_count: Int?,
val voted: Boolean?, //null if gotten without user token
val own_votes: List<Int?>?,
val options: List<Option?>?,
val emojis: List<Emoji?>?
): Serializable {
data class Option(
val title: String?,
val votes_count: Int? //null if result not published yet
)
}

View File

@ -13,8 +13,7 @@ import org.pixeldroid.app.R
import org.pixeldroid.app.posts.getDomain
import java.io.File
import java.io.Serializable
import java.time.OffsetDateTime
import java.util.*
import java.time.Instant
/**
Represents a status posted by an account.
@ -25,7 +24,7 @@ open class Status(
//Base attributes
override val id: String,
val uri: String? = "",
val created_at: OffsetDateTime?, //ISO 8601 Datetime
val created_at: Instant? = null, //ISO 8601 Datetime
val account: Account?,
val content: String? = "", //HTML
val visibility: Visibility? = Visibility.public,

View File

@ -1,10 +1,10 @@
package org.pixeldroid.app.utils.db
import android.os.Build
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.pixeldroid.app.utils.api.objects.*
import java.time.Instant
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.util.*
@ -13,10 +13,22 @@ class Converters {
private val gson = Gson()
private val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
private val instantFormatter = DateTimeFormatter.ISO_INSTANT
@TypeConverter
fun toInstant(timestamp: String?): Instant? =
timestamp?.let {
instantFormatter.parse(it, Instant::from)
}
@TypeConverter
fun fromInstant(time: Instant?): String? =
time?.let { instantFormatter.format(it) }
@TypeConverter
fun toOffsetDateTime(value: String?): OffsetDateTime? {
return value?.let {
return formatter.parse(value, OffsetDateTime::from)
return formatter.parse(it, OffsetDateTime::from)
}
}

View File

@ -33,12 +33,14 @@ interface UserDao {
@Query("UPDATE users SET isActive=0")
fun deActivateActiveUsers()
//TODO also check instance_uri
@Query("UPDATE users SET isActive=1 WHERE user_id=:id")
fun activateUser(id: String)
@Query("DELETE FROM users WHERE isActive=1")
fun deleteActiveUsers()
//TODO also check instance_uri
@Query("SELECT * FROM users WHERE user_id=:id LIMIT 1")
fun getUserWithId(id: String): UserDatabaseEntity
}

View File

@ -4,8 +4,7 @@ import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import org.pixeldroid.app.utils.api.objects.*
import java.time.OffsetDateTime
import java.util.*
import java.time.Instant
@Entity(
tableName = "homePosts",
@ -57,7 +56,7 @@ class HomeStatusDatabaseEntity(
//Constructor to make Room happy. This sucks, and I know it.
constructor(id: String,
uri: String? = "",
created_at: OffsetDateTime?,
created_at: Instant?,
account: Account?,
content: String? = "",
visibility: Visibility? = Visibility.public,

View File

@ -4,8 +4,7 @@ import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import org.pixeldroid.app.utils.api.objects.*
import java.time.OffsetDateTime
import java.util.*
import java.time.Instant
@Entity(
tableName = "publicPosts",
@ -57,7 +56,7 @@ class PublicFeedStatusDatabaseEntity(
//Constructor to make Room happy. This sucks, and I know it.
constructor(id: String,
uri: String? = "",
created_at: OffsetDateTime?,
created_at: Instant?,
account: Account?,
content: String? = "",
visibility: Visibility? = Visibility.public,

View File

@ -3,6 +3,7 @@ package org.pixeldroid.app.utils.db.entities
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import java.io.Serializable
@Entity(
tableName = "users",
@ -27,4 +28,4 @@ data class UserDatabaseEntity(
val refreshToken: String?,
val clientId: String,
val clientSecret: String
)
): Serializable

View File

@ -24,10 +24,13 @@ import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import retrofit2.HttpException
import java.io.IOException
import java.time.OffsetDateTime
import java.time.Instant
import java.util.*
import javax.inject.Inject
class NotificationsWorker(
context: Context,
params: WorkerParameters
@ -64,12 +67,12 @@ class NotificationsWorker(
)
while (!newNotifications.isNullOrEmpty()
&& newNotifications.map { it.created_at ?: OffsetDateTime.MIN }
.maxOrNull()!! > previouslyLatestNotification?.created_at ?: OffsetDateTime.MIN
&& newNotifications.map { it.created_at ?: Instant.MIN }
.maxOrNull()!! > previouslyLatestNotification?.created_at ?: Instant.MIN
) {
// Add to db
val filteredNewNotifications: List<Notification> = newNotifications.filter {
it.created_at ?: OffsetDateTime.MIN > previouslyLatestNotification?.created_at ?: OffsetDateTime.MIN
it.created_at ?: Instant.MIN > previouslyLatestNotification?.created_at ?: Instant.MIN
}.map {
it.copy(user_id = user.user_id, instance_uri = user.instance_uri)
}.sortedBy { it.created_at }
@ -82,7 +85,7 @@ class NotificationsWorker(
}
previouslyLatestNotification =
filteredNewNotifications.maxByOrNull { it.created_at ?: OffsetDateTime.MIN }
filteredNewNotifications.maxByOrNull { it.created_at ?: Instant.MIN }
// Request again
newNotifications = api.notifications(
@ -106,6 +109,25 @@ class NotificationsWorker(
) {
val channelId = channelIdPrefix + (notification.type ?: "other").toString()
val intent: Intent = when (notification.type) {
mention -> notification.status?.let {
Intent(applicationContext, PostActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(Status.POST_TAG, notification.status)
putExtra(Status.VIEW_COMMENTS_TAG, true)
}
} ?: Intent(applicationContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(SHOW_NOTIFICATION_TAG, true)
}
else -> Intent(applicationContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(SHOW_NOTIFICATION_TAG, true)
}
}.putExtra(USER_NOTIFICATION_TAG, user.user_id)
.putExtra(INSTANCE_NOTIFICATION_TAG, user.instance_uri)
val builder = NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(
when (notification.type) {
@ -136,28 +158,13 @@ class NotificationsWorker(
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
// Set the intent that will fire when the user taps the notification
.setContentIntent(
PendingIntent.getActivity(applicationContext, 0, when (notification.type) {
mention -> notification.status?.let {
Intent(applicationContext, PostActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(Status.POST_TAG, notification.status)
putExtra(Status.VIEW_COMMENTS_TAG, true)
}
} ?: Intent(applicationContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(SHOW_NOTIFICATION_TAG, true)
}
else -> Intent(applicationContext, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
putExtra(SHOW_NOTIFICATION_TAG, true)
}
}, PendingIntent.FLAG_IMMUTABLE))
PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
)
.setAutoCancel(true)
if (notification.type == mention || notification.type == comment){
if (notification.type == mention || notification.type == comment || notification.type == poll){
builder.setContentText(notification.status?.content)
}
//TODO poll -> TODO()
with(NotificationManagerCompat.from(applicationContext)) {
// notificationId is a unique int for each notification
@ -196,7 +203,9 @@ class NotificationsWorker(
}
companion object {
const val SHOW_NOTIFICATION_TAG = "SHOW_NOTIFICATION"
const val SHOW_NOTIFICATION_TAG = "org.pixeldroid.app.SHOW_NOTIFICATION"
const val INSTANCE_NOTIFICATION_TAG = "org.pixeldroid.app.USER_NOTIFICATION"
const val USER_NOTIFICATION_TAG = "org.pixeldroid.app.INSTANCE_NOTIFICATION"
}
}