Commit 8bf0b006 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Initial thread support

Updates also some libraries.
parent 4e5b2997
......@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 11,
"identityHash": "fb9f1c815809b0217d326452aeb34e92",
"identityHash": "78b2e16a2c5056ee7cc8145aae8a6cf3",
"entities": [
{
"tableName": "users",
......@@ -59,7 +59,7 @@
},
{
"tableName": "chatrooms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `topic` TEXT, `announcement` TEXT, `description` TEXT, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, `muted` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` TEXT NOT NULL, `parentId` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT NOT NULL, `fullname` TEXT, `userId` TEXT, `ownerId` TEXT, `readonly` INTEGER, `isDefault` INTEGER, `favorite` INTEGER, `topic` TEXT, `announcement` TEXT, `description` TEXT, `open` INTEGER NOT NULL, `alert` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `userMentions` INTEGER, `groupMentions` INTEGER, `updatedAt` INTEGER, `timestamp` INTEGER, `lastSeen` INTEGER, `lastMessageText` TEXT, `lastMessageUserId` TEXT, `lastMessageTimestamp` INTEGER, `broadcast` INTEGER, `muted` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`ownerId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`lastMessageUserId`) REFERENCES `users`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
"fields": [
{
"fieldPath": "id",
......@@ -73,6 +73,12 @@
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "parentId",
"columnName": "parentId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
......@@ -1099,7 +1105,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"fb9f1c815809b0217d326452aeb34e92\")"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"78b2e16a2c5056ee7cc8145aae8a6cf3\")"
]
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
package chat.rocket.android.chatroom.di
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.module.AppModule
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class MessageServiceProvider {
@Module
abstract class MessageServiceProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideMessageService(): MessageService
}
\ No newline at end of file
......@@ -931,6 +931,7 @@ class ChatRoomPresenter @Inject constructor(
ChatRoom(
id = id,
subscriptionId = subscriptionId,
parentId = parentId,
type = roomTypeOf(type),
unread = unread,
broadcast = broadcast ?: false,
......@@ -974,6 +975,7 @@ class ChatRoomPresenter @Inject constructor(
ChatRoom(
id = id,
subscriptionId = subscriptionId,
parentId = parentId,
type = roomTypeOf(type),
unread = unread,
broadcast = broadcast ?: false,
......
......@@ -17,12 +17,9 @@ import timber.log.Timber
import javax.inject.Inject
class MessageService : JobService() {
@Inject
lateinit var factory: ConnectionManagerFactory
@Inject
lateinit var dbFactory: DatabaseManagerFactory
@Inject
lateinit var getAccountsInteractor: GetAccountsInteractor
@Inject lateinit var factory: ConnectionManagerFactory
@Inject lateinit var dbFactory: DatabaseManagerFactory
@Inject lateinit var getAccountsInteractor: GetAccountsInteractor
override fun onCreate() {
super.onCreate()
......
......@@ -173,6 +173,7 @@ class UiModelMapper @Inject constructor(
ChatRoom(
id = id,
subscriptionId = subscriptionId,
parentId = parentId,
type = roomTypeOf(type),
unread = unread,
broadcast = broadcast ?: false,
......
......@@ -13,6 +13,7 @@ import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.domain.useSpecialCharsOnRoom
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.date
import chat.rocket.android.util.extensions.isNotNullNorEmpty
import chat.rocket.android.util.extensions.localDateTime
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.User
......@@ -40,14 +41,16 @@ class RoomUiModelMapper(
var lastType: String? = null
if (grouped) {
val favRooms = rooms.filter { it.chatRoom.favorite == true }
val unfavRooms = rooms.filterNot { it.chatRoom.favorite == true }
val unfavRooms = rooms.filter { it.chatRoom.favorite != true }
if (favRooms.isNotEmpty()) {
list.add(HeaderItemHolder(context.resources.getString(R.string.header_favorite)))
}
favRooms.forEach { room ->
list.add(RoomItemHolder(map(room, showLastMessage)))
}
unfavRooms.forEach { room ->
if (lastType != room.chatRoom.type) {
list.add(HeaderItemHolder(roomType(room.chatRoom.type)))
......@@ -67,9 +70,11 @@ class RoomUiModelMapper(
fun map(spotlight: SpotlightResult, showLastMessage: Boolean = true): List<ItemHolder<*>> {
val list = ArrayList<ItemHolder<*>>(spotlight.users.size + spotlight.rooms.size)
spotlight.users.filterNot { it.username.isNullOrEmpty() }.forEach { user ->
list.add(RoomItemHolder(mapUser(user)))
}
spotlight.rooms.filterNot { it.name.isNullOrEmpty() }.forEach { room ->
list.add(RoomItemHolder(mapRoom(room, showLastMessage)))
}
......@@ -143,6 +148,7 @@ class RoomUiModelMapper(
RoomUiModel(
id = id,
isDiscussion = parentId.isNotNullNorEmpty(),
name = roomName,
type = type,
avatar = avatar,
......
......@@ -19,6 +19,7 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit
private val resources: Resources = itemView.resources
private val channelIcon: Drawable = resources.getDrawable(R.drawable.ic_hashtag_12dp, null)
private val groupIcon: Drawable = resources.getDrawable(R.drawable.ic_lock_12_dp, null)
private val discussionIcon: Drawable = resources.getDrawable(R.drawable.ic_discussion_20dp, null)
private val onlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_online_12dp, null)
private val awayIcon: Drawable = resources.getDrawable(R.drawable.ic_status_away_12dp, null)
private val busyIcon: Drawable = resources.getDrawable(R.drawable.ic_status_busy_12dp, null)
......@@ -30,7 +31,9 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit
image_avatar.setImageURI(room.avatar)
text_chat_name.text = room.name
if (room.status != null && room.type is RoomType.DirectMessage) {
if (room.isDiscussion) {
image_chat_icon.setImageDrawable(discussionIcon)
} else if (room.status != null && room.type is RoomType.DirectMessage) {
image_chat_icon.setImageDrawable(getStatusDrawable(room.status))
} else {
image_chat_icon.setImageDrawable(getRoomDrawable(room.type))
......
......@@ -5,6 +5,7 @@ import chat.rocket.common.model.UserStatus
data class RoomUiModel(
val id: String,
val isDiscussion: Boolean = false,
val type: RoomType,
val name: CharSequence,
val avatar: String,
......
......@@ -10,7 +10,7 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao) {
// TODO - check how to use retryDB here - suspend
fun getChatRooms(order: Order): LiveData<List<ChatRoom>> {
return when(order) {
return when (order) {
Order.ACTIVITY -> dao.getAll()
Order.GROUPED_ACTIVITY -> dao.getAllGrouped()
Order.NAME -> dao.getAllAlphabetically()
......@@ -31,5 +31,5 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao) {
}
}
fun ChatRoomsRepository.Order.isGrouped(): Boolean = this == ChatRoomsRepository.Order.GROUPED_ACTIVITY
|| this == ChatRoomsRepository.Order.GROUPED_NAME
\ No newline at end of file
fun ChatRoomsRepository.Order.isGrouped(): Boolean =
this == ChatRoomsRepository.Order.GROUPED_ACTIVITY || this == ChatRoomsRepository.Order.GROUPED_NAME
\ No newline at end of file
......@@ -91,6 +91,7 @@ class ChatRoomsPresenter @Inject constructor(
val entity = ChatRoomEntity(
id = id,
subscriptionId = "",
parentId = "",
type = type.toString(),
name = username ?: name.toString(),
fullname = name.toString(),
......
......@@ -14,8 +14,12 @@ import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
@Singleton
@Component(modules = [AndroidSupportInjectionModule::class,
AppModule::class, ActivityBuilder::class, ServiceBuilder::class, ReceiverBuilder::class,
@Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ServiceBuilder::class,
ReceiverBuilder::class,
AndroidWorkerInjectionModule::class])
interface AppComponent {
......
......@@ -11,7 +11,6 @@ object AndroidWorkerInjection {
}
val workerInjector = (application as HasWorkerInjector).workerInjector()
checkNotNull(workerInjector) { "${application.javaClass}.workerInjector() return null" }
workerInjector.inject(worker)
}
}
\ No newline at end of file
......@@ -4,5 +4,6 @@ import androidx.work.Worker
import dagger.android.AndroidInjector
interface HasWorkerInjector {
fun workerInjector(): AndroidInjector<Worker>
}
\ No newline at end of file
......@@ -409,6 +409,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
val chatRoom = current.chatRoom
chatRoom.copy(
id = roomId,
parentId = parentId,
subscriptionId = id,
type = type.toString(),
name = name
......@@ -474,6 +475,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
return ChatRoomEntity(
id = room.id,
parentId = subscription.parentId,
subscriptionId = subscription.id,
type = room.type.toString(),
name = room.name ?: subscription.name
......@@ -516,6 +518,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
return ChatRoomEntity(
id = id,
subscriptionId = subscriptionId,
parentId = parentId,
type = type.toString(),
name = name,
fullname = fullName,
......
......@@ -19,13 +19,20 @@ import chat.rocket.android.emoji.internal.db.StringListConverter
@Database(
entities = [
UserEntity::class, ChatRoomEntity::class, MessageEntity::class,
MessageFavoritesRelation::class, MessageMentionsRelation::class,
MessageChannels::class, AttachmentEntity::class,
AttachmentFieldEntity::class, AttachmentActionEntity::class, UrlEntity::class,
ReactionEntity::class, MessagesSync::class
UserEntity::class,
ChatRoomEntity::class,
MessageEntity::class,
MessageFavoritesRelation::class,
MessageMentionsRelation::class,
MessageChannels::class,
AttachmentEntity::class,
AttachmentFieldEntity::class,
AttachmentActionEntity::class,
UrlEntity::class,
ReactionEntity::class,
MessagesSync::class
],
version = 11,
version = 13,
exportSchema = true
)
@TypeConverters(StringListConverter::class)
......
......@@ -26,6 +26,7 @@ import chat.rocket.android.emoji.internal.db.StringListConverter
data class ChatRoomEntity(
@PrimaryKey var id: String,
var subscriptionId: String,
var parentId: String? = null,
var type: String,
var name: String,
var fullname: String? = null,
......
package chat.rocket.android.push
import chat.rocket.android.dagger.module.AppModule
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class DirectReplyReceiverProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideDirectReplyReceiver(): DirectReplyReceiver
}
\ No newline at end of file
package chat.rocket.android.push.di
import chat.rocket.android.dagger.module.AppModule
import chat.rocket.android.push.DeleteReceiver
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class DeleteReceiverProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideDeleteReceiver(): DeleteReceiver
}
\ No newline at end of file
......@@ -88,6 +88,7 @@ class UserDetailsPresenter @Inject constructor(
val chatRoomEntity = ChatRoomEntity(
id = directMessage.id,
parentId = "",
name = userEntity.username ?: userEntity.name.orEmpty(),
description = null,
type = RoomType.DIRECT_MESSAGE,
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M17.0178,9.0088C17.4002,8.2308 17.479,7.9789 17.479,7.6459C17.479,4.8323 15.0667,2.5397 12.1027,2.5397C9.1391,2.5397 6.7273,4.832 6.7273,7.6459C6.7273,10.6045 9.2399,12.7514 12.8986,12.7514L13.0419,12.7522L13.2199,12.7522C15.7971,12.7522 17.3253,12.49 17.9955,12.099C17.4393,11.8181 17.312,11.7402 17.0644,11.4817C16.7269,11.1295 16.5447,10.6642 16.5903,10.1635C16.6148,9.8935 16.6686,9.7447 16.7751,9.5219C16.8362,9.3843 16.8928,9.2651 17.0178,9.0088ZM18.3011,10.1607C18.1916,10.3871 18.2091,10.4113 18.8615,10.7397C19.8228,11.2213 20.24,11.6696 19.8612,12.4352C19.1566,13.8599 16.8659,14.3395 13.2199,14.3395L13.0419,14.3395L12.8892,14.3387C8.3595,14.3387 5.0607,11.5237 5.0607,7.6459C5.0607,3.9545 8.2195,0.9524 12.1027,0.9524C15.9862,0.9524 19.1457,3.9547 19.1457,7.6459C19.1457,8.2881 18.979,8.7628 18.5273,9.6817C18.4105,9.9212 18.3589,10.0296 18.3011,10.1607ZM13.5847,16.3438C13.4018,16.575 13.2033,16.7931 12.9903,16.9977C12.7079,16.9977 12.4278,16.9894 12.1501,16.973L12.251,15.4273C12.2578,15.4187 12.2646,15.4102 12.2714,15.4016L13.5847,16.3438ZM12.2535,15.3888L12.2714,15.4016C12.2744,15.3979 12.2774,15.3941 12.2804,15.3903C12.5148,15.4037 12.7523,15.4104 12.9928,15.4104L13.1738,15.4114L13.3848,15.4114C13.6721,15.4114 13.9523,15.4089 14.2253,15.4036C13.9139,15.9928 13.5075,16.528 13.0195,16.9979L12.9928,16.9977C12.9919,16.9977 12.9911,16.9977 12.9903,16.9977C11.5566,18.3752 9.469,19.1391 7.111,19.1391L6.9581,19.1399L6.7801,19.1399C4.1004,19.1399 2.2612,18.8789 1.204,18.2903L0.9763,18.1635C0.5978,17.9163 0.3174,17.6159 0.1388,17.2549C-0.1741,16.6227 0.056,16.2068 0.6871,15.8098L1.263,15.5183C1.6568,15.319 1.6328,15.3349 1.7194,15.2446C1.7599,15.2023 1.7736,15.1649 1.7694,15.1185C1.7642,15.0606 1.772,15.0866 1.7081,14.9517C1.6576,14.8377 1.6095,14.736 1.491,14.4933C1.0219,13.5389 0.8746,13.0988 0.8746,12.4656C0.8746,11.0845 1.3214,9.7648 2.1303,8.6606C2.129,8.6512 2.1278,8.6417 2.1265,8.6322C2.5652,8.0375 3.1029,7.5134 3.7168,7.0817C3.713,7.1803 3.7112,7.2792 3.7112,7.3786C3.7112,7.962 3.7726,8.5253 3.8907,9.0644C3.8873,9.068 3.8839,9.0717 3.8805,9.0753L3.896,9.0884L3.8618,9.0953C3.0102,10.0132 2.521,11.2015 2.521,12.4656C2.521,12.7985 2.5998,13.0505 2.9842,13.8326C3.1146,14.0999 3.1702,14.2175 3.2126,14.3161C3.3274,14.5534 3.3842,14.703 3.4097,14.9832C3.4553,15.4839 3.2731,15.9492 2.9356,16.3014C2.6884,16.5596 2.561,16.6376 2.0044,16.9188C2.6743,17.3097 4.2025,17.5719 6.7801,17.5719L6.9487,17.5719L7.1108,17.5711C9.3389,17.5711 11.1786,16.7606 12.251,15.4273L12.2535,15.3888ZM1.9145,16.9641C1.6344,17.1044 1.5191,17.1878 1.5466,17.1462C1.6888,16.9311 1.6902,16.6667 1.6467,16.5786C1.7065,16.6996 1.8245,16.8138 2.0044,16.9188C1.9756,16.9333 1.9457,16.9484 1.9145,16.9641ZM2.2655,9.4174C2.2101,9.1681 2.165,8.9157 2.1303,8.6606C2.2904,8.4422 2.4646,8.2321 2.6523,8.0317L3.8805,9.0753C3.8743,9.0819 3.868,9.0886 3.8618,9.0953L2.2655,9.4174ZM2.8863,15.5497L2.0452,16.92L2.8273,18.3216C3.5014,17.9804 3.7692,17.8068 4.1368,17.4308C4.8511,17.5089 5.7278,17.5526 6.7801,17.5526L6.9581,17.5526L7.1013,17.5518C9.3708,17.5518 11.1972,16.724 12.2535,15.3888C12.4966,15.4031 12.7431,15.4104 12.9928,15.4104L13.1738,15.4114L13.3848,15.4114C13.6721,15.4114 13.9523,15.4089 14.2253,15.4036C13.0193,17.6858 10.387,19.1584 7.1108,19.1584L6.9581,19.1592L6.7801,19.1592C3.1341,19.1592 0.8434,18.6796 0.1388,17.2549C-0.24,16.4893 0.1772,16.041 1.1385,15.5594C1.7909,15.231 1.8084,15.2068 1.6989,14.9803C1.6411,14.8493 1.5895,14.7409 1.4727,14.5014C1.021,13.5824 0.8543,13.1078 0.8543,12.4656C0.8543,10.2609 1.9815,8.302 3.7168,7.0817C3.713,7.1803 3.7112,7.2792 3.7112,7.3786C3.7112,7.9706 3.7745,8.542 3.896,9.0884C3.0537,9.9876 2.5412,11.1713 2.5412,12.4656C2.5412,12.7951 2.6193,13.0447 3.0005,13.8203C3.1256,14.0767 3.1822,14.1961 3.2434,14.3338C3.3508,14.5585 3.405,14.7087 3.4299,14.9815C3.4528,15.2336 3.4186,15.4768 3.3365,15.7014C3.1322,15.6538 2.9811,15.6024 2.8863,15.5497Z"
android:strokeWidth="1"
android:fillColor="#9EA2A8"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>
package chat.rocket.android.push
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
......
package chat.rocket.android.push.di
import chat.rocket.android.dagger.module.AppModule
import chat.rocket.android.push.FirebaseMessagingService
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class FirebaseMessagingServiceProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
@Module
abstract class FirebaseMessagingServiceProvider {
abstract fun provideFirebaseMessagingService(): FirebaseMessagingService
}
\ No newline at end of file
package chat.rocket.android.push.worker
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import chat.rocket.android.dagger.injector.AndroidWorkerInjection
import chat.rocket.android.extensions.await
import chat.rocket.android.infrastructure.LocalRepository
......@@ -13,14 +15,13 @@ import kotlinx.coroutines.runBlocking
import timber.log.Timber
import javax.inject.Inject
class TokenRegistrationWorker : Worker() {
@Inject
lateinit var factory: RocketChatClientFactory
@Inject
lateinit var getAccountsInteractor: GetAccountsInteractor
@Inject
lateinit var localRepository: LocalRepository
class TokenRegistrationWorker @Inject constructor(
appContext: Context,
workerParams: WorkerParameters
) : Worker(appContext, workerParams) {
@Inject lateinit var factory: RocketChatClientFactory
@Inject lateinit var getAccountsInteractor: GetAccountsInteractor
@Inject lateinit var localRepository: LocalRepository
override fun doWork(): Result {
AndroidWorkerInjection.inject(this)
......@@ -36,7 +37,7 @@ class TokenRegistrationWorker : Worker() {
}
}
return Result.SUCCESS
return Result.success()
}
private fun refreshToken(): String? {
......
......@@ -9,7 +9,7 @@ ext {
dokka : '0.9.16',
// For app
kotlin : '1.3.21',
kotlin : '1.3.30',
coroutine : '1.1.1',
appCompat : '1.0.2',
......@@ -18,9 +18,9 @@ ext {
cardview : '1.0.0',
browser : '1.0.0',
androidKtx : '1.0.0',
workmanager : '1.0.0-alpha09',
workmanager : '2.0.1',
dagger : '2.16',
dagger : '2.22.1',
firebaseCloudMessage : '17.3.4',
firebaseAnalytics : '16.0.6',
playServicesAuth : '16.0.1',
......@@ -76,8 +76,7 @@ ext {
browser : "androidx.browser:browser:${versions.browser}",
androidKtx : "androidx.core:core-ktx:${versions.androidKtx}",
fragmentsKtx : "androidx.fragment:fragment-ktx:${versions.androidKtx}",
workmanager : "android.arch.work:work-runtime-ktx:${versions.workmanager}",
workmanagerFirebase : "android.arch.work:work-firebase:${versions.workmanager}",
workmanager : "androidx.work:work-runtime-ktx:${versions.workmanager}",
dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment