Unbreak link previews in timelines (#2506)
This commit is contained in:
parent
db81ede04a
commit
b4eda5ea65
6 changed files with 50 additions and 10 deletions
|
@ -21,6 +21,7 @@ import com.keylesspalace.tusky.db.TimelineAccountEntity
|
||||||
import com.keylesspalace.tusky.db.TimelineStatusEntity
|
import com.keylesspalace.tusky.db.TimelineStatusEntity
|
||||||
import com.keylesspalace.tusky.db.TimelineStatusWithAccount
|
import com.keylesspalace.tusky.db.TimelineStatusWithAccount
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
|
import com.keylesspalace.tusky.entity.Card
|
||||||
import com.keylesspalace.tusky.entity.Emoji
|
import com.keylesspalace.tusky.entity.Emoji
|
||||||
import com.keylesspalace.tusky.entity.HashTag
|
import com.keylesspalace.tusky.entity.HashTag
|
||||||
import com.keylesspalace.tusky.entity.Poll
|
import com.keylesspalace.tusky.entity.Poll
|
||||||
|
@ -96,7 +97,8 @@ fun Placeholder.toEntity(timelineUserId: Long): TimelineStatusEntity {
|
||||||
expanded = loading,
|
expanded = loading,
|
||||||
contentCollapsed = false,
|
contentCollapsed = false,
|
||||||
contentShowing = false,
|
contentShowing = false,
|
||||||
pinned = false
|
pinned = false,
|
||||||
|
card = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +138,8 @@ fun Status.toEntity(
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
contentShowing = contentShowing,
|
contentShowing = contentShowing,
|
||||||
contentCollapsed = contentCollapsed,
|
contentCollapsed = contentCollapsed,
|
||||||
pinned = actionableStatus.pinned == true
|
pinned = actionableStatus.pinned == true,
|
||||||
|
card = actionableStatus.card?.let(gson::toJson),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +154,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
||||||
val application = gson.fromJson(status.application, Status.Application::class.java)
|
val application = gson.fromJson(status.application, Status.Application::class.java)
|
||||||
val emojis: List<Emoji> = gson.fromJson(status.emojis, emojisListType) ?: emptyList()
|
val emojis: List<Emoji> = gson.fromJson(status.emojis, emojisListType) ?: emptyList()
|
||||||
val poll: Poll? = gson.fromJson(status.poll, Poll::class.java)
|
val poll: Poll? = gson.fromJson(status.poll, Poll::class.java)
|
||||||
|
val card: Card? = gson.fromJson(status.card, Card::class.java)
|
||||||
|
|
||||||
val reblog = status.reblogServerId?.let { id ->
|
val reblog = status.reblogServerId?.let { id ->
|
||||||
Status(
|
Status(
|
||||||
|
@ -178,7 +182,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
||||||
pinned = false,
|
pinned = false,
|
||||||
muted = status.muted,
|
muted = status.muted,
|
||||||
poll = poll,
|
poll = poll,
|
||||||
card = null
|
card = card,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val status = if (reblog != null) {
|
val status = if (reblog != null) {
|
||||||
|
@ -235,7 +239,7 @@ fun TimelineStatusWithAccount.toViewData(gson: Gson): StatusViewData {
|
||||||
pinned = status.pinned,
|
pinned = status.pinned,
|
||||||
muted = status.muted,
|
muted = status.muted,
|
||||||
poll = poll,
|
poll = poll,
|
||||||
card = null
|
card = card,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return StatusViewData.Concrete(
|
return StatusViewData.Concrete(
|
||||||
|
|
|
@ -31,7 +31,7 @@ import java.io.File;
|
||||||
*/
|
*/
|
||||||
@Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
|
@Database(entities = { DraftEntity.class, AccountEntity.class, InstanceEntity.class, TimelineStatusEntity.class,
|
||||||
TimelineAccountEntity.class, ConversationEntity.class
|
TimelineAccountEntity.class, ConversationEntity.class
|
||||||
}, version = 34)
|
}, version = 35)
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
|
|
||||||
public abstract AccountDao accountDao();
|
public abstract AccountDao accountDao();
|
||||||
|
@ -534,4 +534,11 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||||
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsUpdates` INTEGER NOT NULL DEFAULT 1");
|
database.execSQL("ALTER TABLE `AccountEntity` ADD COLUMN `notificationsUpdates` INTEGER NOT NULL DEFAULT 1");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static final Migration MIGRATION_34_35 = new Migration(34, 35) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("ALTER TABLE `TimelineStatusEntity` ADD COLUMN `card` TEXT");
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ SELECT s.serverId, s.url, s.timelineUserId,
|
||||||
s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt,
|
s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt,
|
||||||
s.emojis, s.reblogsCount, s.favouritesCount, s.reblogged, s.favourited, s.bookmarked, s.sensitive,
|
s.emojis, s.reblogsCount, s.favouritesCount, s.reblogged, s.favourited, s.bookmarked, s.sensitive,
|
||||||
s.spoilerText, s.visibility, s.mentions, s.tags, s.application, s.reblogServerId,s.reblogAccountId,
|
s.spoilerText, s.visibility, s.mentions, s.tags, s.application, s.reblogServerId,s.reblogAccountId,
|
||||||
s.content, s.attachments, s.poll, s.muted, s.expanded, s.contentShowing, s.contentCollapsed, s.pinned,
|
s.content, s.attachments, s.poll, s.card, s.muted, s.expanded, s.contentShowing, s.contentCollapsed, s.pinned,
|
||||||
a.serverId as 'a_serverId', a.timelineUserId as 'a_timelineUserId',
|
a.serverId as 'a_serverId', a.timelineUserId as 'a_timelineUserId',
|
||||||
a.localUsername as 'a_localUsername', a.username as 'a_username',
|
a.localUsername as 'a_localUsername', a.username as 'a_username',
|
||||||
a.displayName as 'a_displayName', a.url as 'a_url', a.avatar as 'a_avatar',
|
a.displayName as 'a_displayName', a.url as 'a_url', a.avatar as 'a_avatar',
|
||||||
|
|
|
@ -78,7 +78,8 @@ data class TimelineStatusEntity(
|
||||||
val expanded: Boolean, // used as the "loading" attribute when this TimelineStatusEntity is a placeholder
|
val expanded: Boolean, // used as the "loading" attribute when this TimelineStatusEntity is a placeholder
|
||||||
val contentCollapsed: Boolean,
|
val contentCollapsed: Boolean,
|
||||||
val contentShowing: Boolean,
|
val contentShowing: Boolean,
|
||||||
val pinned: Boolean
|
val pinned: Boolean,
|
||||||
|
val card: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
|
|
|
@ -63,7 +63,7 @@ class AppModule {
|
||||||
AppDatabase.Migration25_26(appContext.getExternalFilesDir("Tusky")),
|
AppDatabase.Migration25_26(appContext.getExternalFilesDir("Tusky")),
|
||||||
AppDatabase.MIGRATION_26_27, AppDatabase.MIGRATION_27_28, AppDatabase.MIGRATION_28_29,
|
AppDatabase.MIGRATION_26_27, AppDatabase.MIGRATION_27_28, AppDatabase.MIGRATION_28_29,
|
||||||
AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
|
AppDatabase.MIGRATION_29_30, AppDatabase.MIGRATION_30_31, AppDatabase.MIGRATION_31_32,
|
||||||
AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34
|
AppDatabase.MIGRATION_32_33, AppDatabase.MIGRATION_33_34, AppDatabase.MIGRATION_34_35,
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -369,13 +369,36 @@ class TimelineDaoTest {
|
||||||
assertEquals("99", timelineDao.getTopPlaceholderId(1))
|
assertEquals("99", timelineDao.getTopPlaceholderId(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `preview card survives roundtrip`() = runBlocking {
|
||||||
|
val setOne = makeStatus(statusId = 3, cardUrl = "https://foo.bar")
|
||||||
|
|
||||||
|
for ((status, author, reblogger) in listOf(setOne)) {
|
||||||
|
timelineDao.insertAccount(author)
|
||||||
|
reblogger?.let {
|
||||||
|
timelineDao.insertAccount(it)
|
||||||
|
}
|
||||||
|
timelineDao.insertStatus(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pagingSource = timelineDao.getStatuses(setOne.first.timelineUserId)
|
||||||
|
|
||||||
|
val loadResult = pagingSource.load(PagingSource.LoadParams.Refresh(null, 2, false))
|
||||||
|
|
||||||
|
val loadedStatuses = (loadResult as PagingSource.LoadResult.Page).data
|
||||||
|
|
||||||
|
assertEquals(1, loadedStatuses.size)
|
||||||
|
assertStatuses(listOf(setOne), loadedStatuses)
|
||||||
|
}
|
||||||
|
|
||||||
private fun makeStatus(
|
private fun makeStatus(
|
||||||
accountId: Long = 1,
|
accountId: Long = 1,
|
||||||
statusId: Long = 10,
|
statusId: Long = 10,
|
||||||
reblog: Boolean = false,
|
reblog: Boolean = false,
|
||||||
createdAt: Long = statusId,
|
createdAt: Long = statusId,
|
||||||
authorServerId: String = "20",
|
authorServerId: String = "20",
|
||||||
domain: String = "mastodon.example"
|
domain: String = "mastodon.example",
|
||||||
|
cardUrl: String? = null,
|
||||||
): Triple<TimelineStatusEntity, TimelineAccountEntity, TimelineAccountEntity?> {
|
): Triple<TimelineStatusEntity, TimelineAccountEntity, TimelineAccountEntity?> {
|
||||||
val author = TimelineAccountEntity(
|
val author = TimelineAccountEntity(
|
||||||
serverId = authorServerId,
|
serverId = authorServerId,
|
||||||
|
@ -403,6 +426,10 @@ class TimelineDaoTest {
|
||||||
)
|
)
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
|
val card = when (cardUrl) {
|
||||||
|
null -> null
|
||||||
|
else -> "{ url: \"$cardUrl\" }"
|
||||||
|
}
|
||||||
val even = accountId % 2 == 0L
|
val even = accountId % 2 == 0L
|
||||||
val status = TimelineStatusEntity(
|
val status = TimelineStatusEntity(
|
||||||
serverId = statusId.toString(),
|
serverId = statusId.toString(),
|
||||||
|
@ -433,7 +460,8 @@ class TimelineDaoTest {
|
||||||
expanded = false,
|
expanded = false,
|
||||||
contentCollapsed = false,
|
contentCollapsed = false,
|
||||||
contentShowing = true,
|
contentShowing = true,
|
||||||
pinned = false
|
pinned = false,
|
||||||
|
card = card,
|
||||||
)
|
)
|
||||||
return Triple(status, author, reblogAuthor)
|
return Triple(status, author, reblogAuthor)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue