Skip to content

Commit

Permalink
Merge pull request #1937 from samunohito/feature/support_badge_roles
Browse files Browse the repository at this point in the history
feat: ユーザのプロフィールとノートの名前欄横にロールバッジを出す機能を追加
  • Loading branch information
pantasystem authored Nov 1, 2023
2 parents f3c5b93 + 3165703 commit 1771ead
Show file tree
Hide file tree
Showing 21 changed files with 467 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ data class MastodonAccountDTO(
description = note,
),
related = related,
badgeRoles = emptyList(),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ data class UserDTO(

@SerialName("notify")
val notifyState: String? = null,

@SerialName("badgeRoles")
val badgeRoles: List<BadgeRoleDTO>? = null
) : Serializable {

@kotlinx.serialization.Serializable
Expand Down Expand Up @@ -161,4 +164,15 @@ data class UserDTO(
@SerialName("value")
val value: String)

@kotlinx.serialization.Serializable
data class BadgeRoleDTO(
@SerialName("name")
val name: String,

@SerialName("iconUrl")
val iconUrl: String?,

@SerialName("displayOrder")
val displayOrder: Int,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ class UserDTOEntityConverter @Inject constructor(
it.name to it.host to it.url to it.uri
}

val badgeRoles = if (!userDTO.badgeRoles.isNullOrEmpty()) {
userDTO.badgeRoles!!.map { role ->
val iconUrl = role.iconUrl?.let {
imageCacheRepository.save(it)
}

User.BadgeRole(
name = role.name,
iconUri = iconUrl?.cachePath,
displayOrder = role.displayOrder
)
}
} else {
emptyList()
}

if (isDetail) {
return User.Detail(
id = User.Id(account.accountId, userDTO.id),
Expand Down Expand Up @@ -105,7 +121,8 @@ class UserDTOEntityConverter @Inject constructor(
isNotify = userDTO.notifyState?.let {
userDTO.notifyState == "normal"
}
)
),
badgeRoles = badgeRoles
)
} else {
return User.Simple(
Expand All @@ -120,7 +137,8 @@ class UserDTOEntityConverter @Inject constructor(
nickname = null,
isSameHost = userDTO.host == null,
instance = instanceInfo,
avatarBlurhash = userDTO.avatarBlurhash
avatarBlurhash = userDTO.avatarBlurhash,
badgeRoles = badgeRoles
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ object DbModule {
.addMigrations(MIGRATION_8_10)
.addMigrations(MIGRATION_10_11)
.addMigrations(MIGRATION_51_52)
.addMigrations(MIGRATION_56_57)
.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import net.pantasystem.milktea.data.infrastructure.user.renote.mute.db.RenoteMut
UserListRecord::class,
UserListMemberIdRecord::class,
InstanceInfoRecord::class,
BadgeRoleRecord::class,

SearchHistoryRecord::class,
UserInfoStateRecord::class,
Expand All @@ -116,7 +117,7 @@ import net.pantasystem.milktea.data.infrastructure.user.renote.mute.db.RenoteMut
CustomEmojiRecord::class,
CustomEmojiAliasRecord::class,
],
version = 56,
version = 57,
exportSchema = true,
autoMigrations = [
AutoMigration(from = 11, to = 12),
Expand Down Expand Up @@ -163,6 +164,7 @@ import net.pantasystem.milktea.data.infrastructure.user.renote.mute.db.RenoteMut
AutoMigration(from = 53, to = 54),
AutoMigration(from = 54, to = 55),
AutoMigration(from = 55, to = 56),
AutoMigration(from = 56, to = 57),
],
views = [UserView::class, GroupMemberView::class, UserListMemberView::class]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,11 @@ val MIGRATION_51_52 = object : Migration(51, 52) {
database.execSQL("DROP TABLE IF EXISTS 'emoji_alias_table'")
database.execSQL("DROP TABLE IF EXISTS 'emoji_table'")
}
}

val MIGRATION_56_57 = object : Migration(56, 57) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS 'user_badge_role' ('name' TEXT NOT NULL, 'iconUrl' TEXT NULL, 'displayOrder' INTEGER NOT NULL, 'userId' INTEGER NOT NULL, 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)")
database.execSQL("CREATE INDEX IF NOT EXISTS 'index_user_badge_role' ON 'user_badge_role' ('userId')")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import net.pantasystem.milktea.common.Logger
import net.pantasystem.milktea.common.collection.LRUCache
import net.pantasystem.milktea.common.runCancellableCatching
import net.pantasystem.milktea.common_android.hilt.IODispatcher
import net.pantasystem.milktea.data.infrastructure.user.db.BadgeRoleRecord
import net.pantasystem.milktea.data.infrastructure.user.db.PinnedNoteIdRecord
import net.pantasystem.milktea.data.infrastructure.user.db.UserDao
import net.pantasystem.milktea.data.infrastructure.user.db.UserEmojiRecord
Expand All @@ -25,6 +26,7 @@ import net.pantasystem.milktea.data.infrastructure.user.db.UserProfileFieldRecor
import net.pantasystem.milktea.data.infrastructure.user.db.UserRecord
import net.pantasystem.milktea.data.infrastructure.user.db.UserRelated
import net.pantasystem.milktea.data.infrastructure.user.db.UserRelatedStateRecord
import net.pantasystem.milktea.data.infrastructure.user.db.isEqualToBadgeRoleModels
import net.pantasystem.milktea.data.infrastructure.user.db.isEqualToModels
import net.pantasystem.milktea.model.AddResult
import net.pantasystem.milktea.model.user.Acct
Expand Down Expand Up @@ -139,6 +141,22 @@ class MediatorUserDataSource @Inject constructor(
// NOTE: 比較した上で同一でなければキャッシュの更新処理を行う
replaceEmojisIfNeed(dbId, user, record)

if (!record?.badgeRoles.isEqualToBadgeRoleModels(user.badgeRoles)) {
if (record != null) {
userDao.detachAllUserBadgeRoles(dbId)
}
userDao.insertUserBadgeRoles(
user.badgeRoles.map {
BadgeRoleRecord(
userId = dbId,
name = it.name,
iconUrl = it.iconUri,
displayOrder = it.displayOrder,
)
}
)
}

if (user is User.Detail) {
userDao.insert(
UserInfoStateRecord.from(dbId, user.info)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ abstract class UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertUserProfileFields(fields: List<UserProfileFieldRecord>): List<Long>

@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertUserBadgeRoles(fields: List<BadgeRoleRecord>): List<Long>

@Query("delete from pinned_note_id where userId = :userId")
abstract suspend fun detachAllPinnedNoteIds(userId: Long)

Expand All @@ -44,6 +47,9 @@ abstract class UserDao {
@Query("delete from user_profile_field where userId = :userId")
abstract suspend fun detachUserFields(userId: Long)

@Query("delete from user_badge_role where userId = :userId")
abstract suspend fun detachAllUserBadgeRoles(userId: Long)

@Update
abstract suspend fun update(user: UserRecord)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,18 @@ fun List<UserEmojiRecord>?.isEqualToModels(models: List<CustomEmoji>): Boolean {
}
}

fun List<BadgeRoleRecord>?.isEqualToBadgeRoleModels(models: List<User.BadgeRole>): Boolean {
if (this == null && models.isEmpty()) return true
if (this == null) return false
if (size != models.size) return false
val records = this.toSet()
return models.all { model ->
records.any { record ->
record.isEqualToModel(model)
}
}
}

@Entity(
tableName = "user_instance_info",
foreignKeys = [
Expand Down Expand Up @@ -533,6 +545,44 @@ data class UserView(
val avatarBlurhash: String?
)

@Entity(
tableName = "user_badge_role",
foreignKeys = [
ForeignKey(
parentColumns = ["id"],
childColumns = ["userId"],
entity = UserRecord::class,
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE,
)
],
indices = [
Index("userId")
]
)
data class BadgeRoleRecord(
@ColumnInfo("name")
val name: String,

@ColumnInfo("iconUrl")
val iconUrl: String?,

@ColumnInfo("displayOrder")
val displayOrder: Int,

@ColumnInfo("userId")
val userId: Long,

@ColumnInfo(name = "id")
@PrimaryKey(autoGenerate = true) val id: Long = 0,
) {
fun isEqualToModel(model: User.BadgeRole): Boolean {
return name == model.name &&
iconUrl == model.iconUri &&
displayOrder == model.displayOrder
}
}

interface HasUserModel {
fun toModel(): User
}
Expand All @@ -549,6 +599,11 @@ data class UserSimpleRelated(
entityColumn = "userId"
)
val instance: UserInstanceInfoRecord?,
@Relation(
parentColumn = "id",
entityColumn = "userId"
)
val badgeRoles: List<BadgeRoleRecord>,
) : HasUserModel {
override fun toModel(): User.Simple {
val instanceInfo = instance?.let {
Expand Down Expand Up @@ -584,6 +639,13 @@ data class UserSimpleRelated(
},
instance = instanceInfo,
avatarBlurhash = user.avatarBlurhash,
badgeRoles = badgeRoles.map {
User.BadgeRole(
name = it.name,
iconUri = it.iconUrl,
displayOrder = it.displayOrder,
)
}
)
}
}
Expand Down Expand Up @@ -625,8 +687,13 @@ data class UserRelated(
parentColumn = "id",
entityColumn = "userId",
)
val fields: List<UserProfileFieldRecord>?
val fields: List<UserProfileFieldRecord>?,

@Relation(
parentColumn = "id",
entityColumn = "userId"
)
val badgeRoles: List<BadgeRoleRecord>,
) : HasUserModel {
override fun toModel(): User {
val instanceInfo = instance?.let {
Expand Down Expand Up @@ -663,6 +730,13 @@ data class UserRelated(
},
instance = instanceInfo,
avatarBlurhash = user.avatarBlurhash,
badgeRoles = badgeRoles.map {
User.BadgeRole(
name = it.name,
iconUri = it.iconUrl,
displayOrder = it.displayOrder,
)
}
)
} else {
return User.Detail(
Expand Down Expand Up @@ -720,6 +794,13 @@ data class UserRelated(
hasPendingFollowRequestToYou = related.hasPendingFollowRequestToYou,
isNotify = related.isNotify ?: false,
)
},
badgeRoles = badgeRoles.map {
User.BadgeRole(
name = it.name,
iconUri = it.iconUrl,
displayOrder = it.displayOrder,
)
}
)
}
Expand Down
1 change: 1 addition & 0 deletions modules/features/note/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ dependencies {
implementation libs.accompanist.pager
implementation libs.accompanist.pager.indicators
implementation libs.coil.compose
implementation libs.coil.svg
implementation libs.compose.constraintlayout

// hilt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader
import com.bumptech.glide.util.FixedPreloadSizeProvider
import com.google.android.flexbox.FlexboxLayout
import com.google.android.material.composethemeadapter.MdcTheme
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
Expand All @@ -34,6 +35,8 @@ import net.pantasystem.milktea.note.databinding.ItemTimelineEmptyBinding
import net.pantasystem.milktea.note.databinding.ItemTimelineErrorBinding
import net.pantasystem.milktea.note.reaction.ReactionViewData
import net.pantasystem.milktea.note.timeline.viewmodel.TimelineListItem
import net.pantasystem.milktea.note.view.NoteBadgeRoleData
import net.pantasystem.milktea.note.view.NoteBadgeRoles
import net.pantasystem.milktea.note.view.NoteCardAction
import net.pantasystem.milktea.note.view.NoteCardActionListenerAdapter
import net.pantasystem.milktea.note.viewmodel.HasReplyToNoteViewData
Expand Down Expand Up @@ -186,7 +189,21 @@ class TimelineListAdapter(
override fun onBind(note: PlaneNoteViewData) {
binding.note = note
binding.noteCardActionListener = noteCardActionListenerAdapter

binding.simpleNote.badgeRoles.apply {
setContent {
MdcTheme {
NoteBadgeRoles(
badgeRoles = note.note.user.badgeRoles.map {
NoteBadgeRoleData(
name = it.name,
iconUri = it.iconUri,
displayOrder = it.displayOrder
)
}
)
}
}
}
}


Expand All @@ -209,9 +226,22 @@ class TimelineListAdapter(
override fun onBind(note: PlaneNoteViewData) {
if (note is HasReplyToNoteViewData) {
binding.hasReplyToNote = note

binding.noteCardActionListener = noteCardActionListenerAdapter

binding.simpleNote.badgeRoles.apply {
setContent {
MdcTheme {
NoteBadgeRoles(
badgeRoles = note.note.user.badgeRoles.map {
NoteBadgeRoleData(
name = it.name,
iconUri = it.iconUri,
displayOrder = it.displayOrder
)
}
)
}
}
}
}
}

Expand Down
Loading

0 comments on commit 1771ead

Please sign in to comment.