Dialog notifying user of failure when media upload fails (#3135)
* First attempt at user notifications of failure when media upload fails * Drafts alert displays alert * ktLint * Fix defaced 46.json, add 47.json * Mock draftsNeedUserAlert in MainActivityTest to prevent spurious failure * Friendlier posts-failed message * Create DraftsAlert object * DraftsAlert works * Not the cleanest, but DraftsAlert works with multiple accounts * Use plural strings * KtLint * Clean up debug prints * Simplify DraftsAlert per Conny suggestions * Text change suggested by Conny * ktLint again * Back out test changes * Fix MainActivityTest for new approach * Tweak debug log * Do not use GlobalScope for coroutines
This commit is contained in:
parent
47b1299ff2
commit
b2511d782d
13 changed files with 1138 additions and 2 deletions
|
|
@ -31,7 +31,7 @@ import java.io.File;
|
|||
*/
|
||||
@Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
|
||||
TimelineAccountEntity.class, ConversationEntity.class
|
||||
}, version = 46)
|
||||
}, version = 47)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
||||
public abstract AccountDao accountDao();
|
||||
|
|
@ -639,4 +639,11 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||
database.execSQL("ALTER TABLE `DraftEntity` ADD COLUMN `statusId` TEXT");
|
||||
}
|
||||
};
|
||||
|
||||
public static final Migration MIGRATION_46_47 = new Migration(46, 47) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
database.execSQL("ALTER TABLE `DraftEntity` ADD COLUMN `failedToSendNew` INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
package com.keylesspalace.tusky.db
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
|
|
@ -30,6 +31,12 @@ interface DraftDao {
|
|||
@Query("SELECT * FROM DraftEntity WHERE accountId = :accountId ORDER BY id ASC")
|
||||
fun draftsPagingSource(accountId: Long): PagingSource<Int, DraftEntity>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM DraftEntity where accountId = :accountId and failedToSendNew=true")
|
||||
fun draftsNeedUserAlert(accountId: Long): LiveData<Int>
|
||||
|
||||
@Query("UPDATE DraftEntity SET failedToSendNew=false where accountId = :accountId and failedToSendNew=true")
|
||||
suspend fun draftsClearNeedUserAlert(accountId: Long)
|
||||
|
||||
@Query("SELECT * FROM DraftEntity WHERE accountId = :accountId")
|
||||
suspend fun loadDrafts(accountId: Long): List<DraftEntity>
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ data class DraftEntity(
|
|||
val attachments: List<DraftAttachment>,
|
||||
val poll: NewPoll?,
|
||||
val failedToSend: Boolean,
|
||||
val failedToSendNew: Boolean,
|
||||
val scheduledAt: String?,
|
||||
val language: String?,
|
||||
val statusId: String?,
|
||||
|
|
|
|||
99
app/src/main/java/com/keylesspalace/tusky/db/DraftsAlert.kt
Normal file
99
app/src/main/java/com/keylesspalace/tusky/db/DraftsAlert.kt
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* Copyright 2023 Andi McClure
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.db
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.drafts.DraftsActivity
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* This class manages an alert popup when a post has failed and been saved to drafts.
|
||||
* It must be separately registered in each lifetime in which it is to appear,
|
||||
* and it only appears if the post failure belongs to the current user.
|
||||
*/
|
||||
|
||||
private const val TAG = "DraftsAlert"
|
||||
|
||||
@Singleton
|
||||
class DraftsAlert @Inject constructor(db: AppDatabase) {
|
||||
// For tracking when a media upload fails in the service
|
||||
private val draftDao: DraftDao = db.draftDao()
|
||||
|
||||
@Inject
|
||||
lateinit var accountManager: AccountManager
|
||||
|
||||
public fun <T> observeInContext(context: T, showAlert: Boolean) where T : Context, T : LifecycleOwner {
|
||||
accountManager.activeAccount?.let { activeAccount ->
|
||||
val coroutineScope = context.lifecycleScope
|
||||
|
||||
// Assume a single MainActivity, AccountActivity or DraftsActivity never sees more then one user id in its lifetime.
|
||||
val activeAccountId = activeAccount.id
|
||||
|
||||
// This LiveData will be automatically disposed when the activity is destroyed.
|
||||
val draftsNeedUserAlert = draftDao.draftsNeedUserAlert(activeAccountId)
|
||||
|
||||
// observe ensures that this gets called at the most appropriate moment wrt the context lifecycle—
|
||||
// at init, at next onResume, or immediately if the context is resumed already.
|
||||
if (showAlert) {
|
||||
draftsNeedUserAlert.observe(context) { count ->
|
||||
Log.d(TAG, "User id $activeAccountId changed: Notification-worthy draft count $count")
|
||||
if (count > 0) {
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.action_post_failed)
|
||||
.setMessage(
|
||||
context.getResources().getQuantityString(R.plurals.action_post_failed_detail, count)
|
||||
)
|
||||
.setPositiveButton(R.string.action_post_failed_show_drafts) { _: DialogInterface?, _: Int ->
|
||||
clearDraftsAlert(coroutineScope, activeAccountId) // User looked at drafts
|
||||
|
||||
val intent = DraftsActivity.newIntent(context)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
.setNegativeButton(R.string.action_post_failed_do_nothing) { _: DialogInterface?, _: Int ->
|
||||
clearDraftsAlert(coroutineScope, activeAccountId) // User doesn't care
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
draftsNeedUserAlert.observe(context) { _ ->
|
||||
Log.d(TAG, "User id $activeAccountId: Clean out notification-worthy drafts")
|
||||
clearDraftsAlert(coroutineScope, activeAccountId)
|
||||
}
|
||||
}
|
||||
} ?: run {
|
||||
Log.w(TAG, "Attempted to observe drafts, but there is no active account")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear drafts alert for specified user
|
||||
*/
|
||||
fun clearDraftsAlert(coroutineScope: LifecycleCoroutineScope, id: Long) {
|
||||
coroutineScope.launch {
|
||||
draftDao.draftsClearNeedUserAlert(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue