Merge branch 'db_fixes' into 'master'

Various fixes related to database

See merge request pixeldroid/PixelDroid!383
This commit is contained in:
Matthieu 2021-09-09 13:36:52 +00:00
commit 002b1548e1
21 changed files with 140 additions and 60 deletions

View File

@ -123,6 +123,7 @@ dependencies {
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation "androidx.activity:activity-ktx:1.3.1"
implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation "androidx.work:work-runtime-ktx:2.5.0"
// Use the most recent version of CameraX
def cameraX_version = '1.0.1'
@ -187,7 +188,7 @@ dependencies {
implementation "com.mikepenz:iconics-core:5.3.0"
implementation 'com.mikepenz:materialdrawer-iconics:8.4.2'
implementation "com.mikepenz:iconics-views:5.3.0"
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'
implementation 'com.mikepenz:google-material-typeface:4.0.0.1-kotlin@aar'
implementation 'com.karumi:dexter:6.2.3'

View File

@ -515,6 +515,47 @@
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.constraintlayout:constraintlayout-core:+
name: constraintlayout-core
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://tools.android.com
- artifact: androidx.databinding:databinding-ktx:+
name: databinding-ktx
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.lifecycle:lifecycle-extensions:+
name: lifecycle-extensions
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-process:+
name: lifecycle-process
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.lifecycle:lifecycle-service:+
name: lifecycle-service
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: androidx.work:work-runtime-ktx:+
name: work-runtime-ktx
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/work#2.5.0
- artifact: androidx.work:work-runtime:+
name: work-runtime
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx/releases/work#2.5.0
- artifact: androidx.paging:paging-common:+
name: paging-common
copyrightHolder: Google Inc.
@ -668,12 +709,6 @@
license: The Apache License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: androidx.constraintlayout:constraintlayout-solver:+
name: constraintlayout-solver
copyrightHolder: Google Inc.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://tools.android.com
- artifact: com.google.guava:listenablefuture:+
name: listenablefuture
copyrightHolder: Google Inc.

View File

@ -72,8 +72,8 @@ class MainActivity : BaseActivity() {
//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)
@ -172,6 +172,7 @@ class MainActivity : BaseActivity() {
}
private fun logOut(){
finish()
db.runInTransaction {
db.userDao().deleteActiveUsers()
@ -229,6 +230,8 @@ class MainActivity : BaseActivity() {
apiHolder.setToCurrentUser()
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

View File

@ -36,7 +36,7 @@ class ThumbnailAdapter (private val context: Context,
holder.thumbnail.setImageBitmap(tbItem.image)
holder.thumbnail.setOnClickListener {
listener.onFilterSelected(tbItem.filter)
selectedIndex = position
selectedIndex = holder.bindingAdapterPosition
notifyDataSetChanged()
}

View File

@ -38,8 +38,8 @@ class PostActivity : BaseActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
status = intent.getSerializableExtra(POST_TAG) as Status
val viewComments: Boolean = (intent.getSerializableExtra(VIEW_COMMENTS_TAG) ?: false) as Boolean
val postComment: Boolean = (intent.getSerializableExtra(POST_COMMENT_TAG) ?: false) as Boolean
val viewComments: Boolean = intent.getBooleanExtra(VIEW_COMMENTS_TAG, false)
val postComment: Boolean = intent.getBooleanExtra(POST_COMMENT_TAG, false)
val user = db.userDao().getActiveUser()

View File

@ -87,7 +87,8 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
val intent: Intent =
when (type) {
Notification.NotificationType.mention, Notification.NotificationType.favourite,
Notification.NotificationType.poll, Notification.NotificationType.reblog -> {
Notification.NotificationType.poll, Notification.NotificationType.reblog,
Notification.NotificationType.comment -> {
openPostFromNotification()
}
Notification.NotificationType.follow -> {
@ -113,39 +114,38 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
) {
val context = textView.context
val (format: String, drawable: Drawable?) = when (type) {
Notification.NotificationType.follow -> {
Notification.NotificationType.follow ->
getStringAndDrawable(
context,
R.string.followed_notification,
R.drawable.ic_follow
)
}
Notification.NotificationType.mention -> {
Notification.NotificationType.mention ->
getStringAndDrawable(
context,
R.string.mention_notification,
R.drawable.mention_at_24dp
)
}
Notification.NotificationType.reblog -> {
Notification.NotificationType.comment ->
getStringAndDrawable(
context,
R.string.comment_notification,
R.drawable.ic_comment_empty
)
Notification.NotificationType.reblog ->
getStringAndDrawable(
context,
R.string.shared_notification,
R.drawable.ic_reblog_blue
)
}
Notification.NotificationType.favourite -> {
Notification.NotificationType.favourite ->
getStringAndDrawable(
context,
R.string.liked_notification,
R.drawable.ic_like_full
)
}
Notification.NotificationType.poll -> {
Notification.NotificationType.poll ->
getStringAndDrawable(context, R.string.poll_notification, R.drawable.poll)
}
}
textView.text = format.format(username)
textView.setCompoundDrawablesWithIntrinsicBounds(
@ -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

@ -73,7 +73,7 @@ class NotificationsRemoteMediator @Inject constructor(
db.withTransaction {
// clear table in the database
if (loadType == LoadType.REFRESH) {
db.notificationDao().clearFeedContent()
db.notificationDao().clearFeedContent(user.user_id, user.instance_uri)
}
db.notificationDao().insertAll(apiResponse)
}

View File

@ -59,7 +59,7 @@ class HomeFeedRemoteMediator @Inject constructor(
db.withTransaction {
// clear table in the database
if (loadType == LoadType.REFRESH) {
db.homePostDao().clearFeedContent()
db.homePostDao().clearFeedContent(user.user_id, user.instance_uri)
}
db.homePostDao().insertAll(dbObjects)
}

View File

@ -74,7 +74,7 @@ class PublicFeedRemoteMediator @Inject constructor(
db.withTransaction {
// clear table in the database
if (loadType == LoadType.REFRESH) {
db.publicPostDao().clearFeedContent()
db.publicPostDao().clearFeedContent(user.user_id, user.instance_uri)
}
db.publicPostDao().insertAll(dbObjects)
}

View File

@ -3,6 +3,11 @@ package org.pixeldroid.app.utils.api
import org.pixeldroid.app.utils.api.objects.*
import io.reactivex.Observable
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.di.TokenAuthenticator
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
@ -28,6 +33,30 @@ interface PixelfedAPI {
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(PixelfedAPI::class.java)
}
private val intermediate: Retrofit.Builder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
fun apiForUser(
user: UserDatabaseEntity,
db: AppDatabase,
pixelfedAPIHolder: PixelfedAPIHolder
): PixelfedAPI =
intermediate
.baseUrl(user.instance_uri)
.client(
OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user, db, pixelfedAPIHolder))
.addInterceptor {
it.request().newBuilder().run {
header("Accept", "application/json")
header("Authorization", "Bearer ${user.accessToken}")
it.proceed(build())
}
}.build()
)
.build().create(PixelfedAPI::class.java)
}

View File

@ -38,6 +38,6 @@ data class Notification(
override var instance_uri: String,
): FeedContent, FeedContentDatabase {
enum class NotificationType: Serializable {
follow, mention, reblog, favourite, poll
follow, mention, reblog, favourite, poll, comment
}
}

View File

@ -3,6 +3,8 @@ package org.pixeldroid.app.utils.db
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import org.pixeldroid.app.utils.db.dao.*
import org.pixeldroid.app.utils.db.dao.feedContent.NotificationDao
import org.pixeldroid.app.utils.db.dao.feedContent.posts.HomePostDao
@ -29,4 +31,12 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun homePostDao(): HomePostDao
abstract fun publicPostDao(): PublicPostDao
abstract fun notificationDao(): NotificationDao
}
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DELETE FROM homePosts")
database.execSQL("DELETE FROM publicPosts")
database.execSQL("DELETE FROM notifications")
}
}

View File

@ -14,7 +14,7 @@ import java.lang.IllegalArgumentException
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
db.userDao().insertUser(
db.userDao().insertOrUpdate(
UserDatabaseEntity(
user_id = account.id!!,
instance_uri = normalizeDomain(instance_uri),

View File

@ -5,8 +5,21 @@ import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(user: UserDatabaseEntity)
/**
* Insert a user, if it already exists return -1
*/
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertUser(user: UserDatabaseEntity): Long
@Transaction
fun insertOrUpdate(user: UserDatabaseEntity) {
if (insertUser(user) == -1L) {
updateUser(user)
}
}
@Update
fun updateUser(user: UserDatabaseEntity)
@Query("UPDATE users SET accessToken = :accessToken, refreshToken = :refreshToken WHERE user_id = :id and instance_uri = :instance_uri")
fun updateAccessToken(accessToken: String, refreshToken: String, id: String, instance_uri: String)

View File

@ -9,7 +9,7 @@ interface FeedContentDao<T: FeedContentDatabase>{
fun feedContent(userId: String, instanceUri: String): PagingSource<Int, T>
suspend fun clearFeedContent()
suspend fun clearFeedContent(userId: String, instanceUri: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(feedContent: List<T>)

View File

@ -8,8 +8,8 @@ import org.pixeldroid.app.utils.api.objects.Notification
@Dao
interface NotificationDao: FeedContentDao<Notification> {
@Query("DELETE FROM notifications")
override suspend fun clearFeedContent()
@Query("DELETE FROM notifications WHERE user_id=:userId AND instance_uri=:instanceUri")
override suspend fun clearFeedContent(userId: String, instanceUri: String)
@Query("""SELECT * FROM notifications WHERE user_id=:userId AND instance_uri=:instanceUri
ORDER BY CAST(created_at AS FLOAT) DESC""")

View File

@ -12,8 +12,8 @@ interface HomePostDao: FeedContentDao<HomeStatusDatabaseEntity> {
ORDER BY CAST(created_at AS FLOAT)""")
override fun feedContent(userId: String, instanceUri: String): PagingSource<Int, HomeStatusDatabaseEntity>
@Query("DELETE FROM homePosts")
override suspend fun clearFeedContent()
@Query("DELETE FROM homePosts WHERE user_id=:userId AND instance_uri=:instanceUri")
override suspend fun clearFeedContent(userId: String, instanceUri: String)
@Query("DELETE FROM homePosts WHERE user_id=:userId AND instance_uri=:instanceUri AND id=:id")
override suspend fun delete(id: String, userId: String, instanceUri: String)

View File

@ -12,8 +12,8 @@ interface PublicPostDao: FeedContentDao<PublicFeedStatusDatabaseEntity> {
ORDER BY CAST(created_at AS FLOAT)""")
override fun feedContent(userId: String, instanceUri: String): PagingSource<Int, PublicFeedStatusDatabaseEntity>
@Query("DELETE FROM publicPosts")
override suspend fun clearFeedContent()
@Query("DELETE FROM publicPosts WHERE user_id=:userId AND instance_uri=:instanceUri")
override suspend fun clearFeedContent(userId: String, instanceUri: String)
@Query("DELETE FROM publicPosts WHERE user_id=:userId AND instance_uri=:instanceUri AND id=:id")
override suspend fun delete(id: String, userId: String, instanceUri: String)

View File

@ -8,9 +8,7 @@ import dagger.Module
import dagger.Provides
import kotlinx.coroutines.runBlocking
import okhttp3.*
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import org.pixeldroid.app.utils.api.PixelfedAPI.Companion.apiForUser
import javax.inject.Singleton
@Module
@ -78,9 +76,6 @@ class TokenAuthenticator(val user: UserDatabaseEntity, val db: AppDatabase, val
}
class PixelfedAPIHolder(private val db: AppDatabase){
private val intermediate: Retrofit.Builder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
var api: PixelfedAPI? =
db.userDao().getActiveUser()?.let {
@ -90,21 +85,8 @@ class PixelfedAPIHolder(private val db: AppDatabase){
fun setToCurrentUser(
user: UserDatabaseEntity = db.userDao().getActiveUser()!!
): PixelfedAPI {
val newAPI = intermediate
.baseUrl(user.instance_uri)
.client(
OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user, db, this))
.addInterceptor {
it.request().newBuilder().run {
header("Accept", "application/json")
header("Authorization", "Bearer ${user.accessToken}")
it.proceed(build())
}
}.build()
)
.build().create(PixelfedAPI::class.java)
val newAPI = apiForUser(user, db, this)
api = newAPI
return newAPI
}
}

View File

@ -5,6 +5,7 @@ import androidx.room.Room
import org.pixeldroid.app.utils.db.AppDatabase
import dagger.Module
import dagger.Provides
import org.pixeldroid.app.utils.db.MIGRATION_3_4
import javax.inject.Singleton
@Module
@ -16,6 +17,6 @@ class DatabaseModule(private val context: Context) {
return Room.databaseBuilder(
context,
AppDatabase::class.java, "pixeldroid"
).allowMainThreadQueries().build()
).addMigrations(MIGRATION_3_4).allowMainThreadQueries().build()
}
}

View File

@ -50,6 +50,12 @@
<!-- Notifications: end of poll notification -->
<string name="poll_notification">"%1$s's poll has ended"</string>
<!-- Notifications: comment notification -->
<string name="comment_notification">%1$s commented on your post</string>
<!-- Notifications: other notification -->
<string name="other_notification">"Notification from %1$s"</string>
<!-- Login page -->
<string name="whats_an_instance">"What's an instance?"</string>
<string name="whats_an_instance_explanation">"You might be confused by the text field asking for the domain of your 'instance'.