doesn't crash
This commit is contained in:
parent
e6910c3ce0
commit
dd6c1c9e03
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue