Commit ee16b416 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Merge branch 'develop' of github.com:RocketChat/Rocket.Chat.Android into new/licence-contactus

parents 1ba86e52 4c9ae11a
This diff is collapsed.
......@@ -61,7 +61,14 @@ interface Analytics {
fun logServerSwitch(serverUrl: String, serverCount: Int) {}
/**
* Logs the admin opening.
* Logs the admin opening event.
*/
fun logOpenAdmin() {}
/**
* Logs the reset password event.
*
* @param resetPasswordSucceeded True if successful reset password, false otherwise.
*/
fun logResetPassword(resetPasswordSucceeded: Boolean) {}
}
......@@ -70,4 +70,10 @@ class AnalyticsManager @Inject constructor(
analytics.forEach { it.logOpenAdmin() }
}
}
fun logResetPassword(resetPasswordSucceeded: Boolean) {
if (analyticsTrackingInteractor.get()) {
analytics.forEach { it.logResetPassword(resetPasswordSucceeded) }
}
}
}
......@@ -61,6 +61,10 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
reactionListener?.onReactionAdded(messageId, emoji)
}
}
override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List<String>) {
reactionListener?.onReactionLongClicked(shortname, isCustom,url, usernames)
}
}
val context = itemView.context
......
......@@ -75,7 +75,7 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
class ReactionViewHolder(
view: View,
private val listener: EmojiReactionListener?
) : RecyclerView.ViewHolder(view), View.OnClickListener {
) : RecyclerView.ViewHolder(view), View.OnClickListener, View.OnLongClickListener {
@Inject
lateinit var localRepository: LocalRepository
......@@ -121,6 +121,8 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
view_flipper_reaction.setOnClickListener(this@ReactionViewHolder)
text_count.setOnClickListener(this@ReactionViewHolder)
view_flipper_reaction.setOnLongClickListener(this@ReactionViewHolder)
text_count.setOnLongClickListener(this@ReactionViewHolder)
}
}
......@@ -132,6 +134,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
}
}
}
override fun onLongClick(v: View?): Boolean {
listener?.onReactionLongClicked(reaction.shortname, reaction.isCustom, reaction.url, reaction.usernames)
return true
}
}
class AddReactionViewHolder(
......
package chat.rocket.android.chatroom.di
import android.app.Application
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.chatroom.presentation.ChatRoomView
import chat.rocket.android.chatroom.ui.ChatRoomFragment
import chat.rocket.android.chatrooms.adapter.RoomUiModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.db.UserDao
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
......@@ -42,4 +49,30 @@ class ChatRoomFragmentModule {
@Provides
@PerFragment
fun provideChatRoomDao(manager: DatabaseManager): ChatRoomDao = manager.chatRoomDao()
@Provides
@PerFragment
fun provideUserDao(manager: DatabaseManager): UserDao = manager.userDao()
@Provides
@PerFragment
fun provideGetCurrentUserInteractor(
tokenRepository: TokenRepository,
@Named("currentServer") serverUrl: String,
userDao: UserDao
): GetCurrentUserInteractor {
return GetCurrentUserInteractor(tokenRepository, serverUrl, userDao)
}
@Provides
@PerFragment
fun provideRoomMapper(
context: Application,
repository: SettingsRepository,
userInteractor: GetCurrentUserInteractor,
@Named("currentServer") serverUrl: String,
permissionsInteractor: PermissionsInteractor
): RoomUiModelMapper {
return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl, permissionsInteractor)
}
}
......@@ -5,6 +5,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.core.internal.realtime.socket.model.State
......@@ -131,12 +132,7 @@ interface ChatRoomView : LoadingView, MessageView {
fun populateEmojiSuggestions(emojis: List<EmojiSuggestionUiModel>)
/**
* This user has joined the chat callback.
*
* @param userCanPost Whether the user can post a message or not.
*/
fun onJoined(userCanPost: Boolean)
fun onJoined(roomUiModel: RoomUiModel)
fun showReactionsPopup(messageId: String)
......@@ -147,9 +143,6 @@ interface ChatRoomView : LoadingView, MessageView {
*/
fun populateCommandSuggestions(commands: List<CommandSuggestionUiModel>)
/**
* Communicate whether it's a broadcast channel and if current user can post to it.
*/
fun onRoomUpdated(userCanPost: Boolean, channelIsBroadcast: Boolean, userCanMod: Boolean)
fun onRoomUpdated(roomUiModel: RoomUiModel)
}
......@@ -51,6 +51,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.EmojiSuggestionUiModel
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.draw.main.ui.DRAWING_BYTE_ARRAY_EXTRA_DATA
import chat.rocket.android.draw.main.ui.DrawingActivity
import chat.rocket.android.emoji.ComposerEditText
......@@ -60,6 +61,7 @@ import chat.rocket.android.emoji.EmojiKeyboardPopup
import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.ImageHelper
......@@ -84,6 +86,8 @@ import dagger.android.support.AndroidSupportInjection
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.emoji_image_row_item.*
import kotlinx.android.synthetic.main.emoji_row_item.*
import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.message_composer.*
......@@ -91,6 +95,7 @@ import kotlinx.android.synthetic.main.message_list.*
import timber.log.Timber
import java.io.File
import java.io.IOException
import kotlinx.android.synthetic.main.reaction_praises_list_item.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
......@@ -397,17 +402,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
empty_chat_view.isVisible = adapter.itemCount == 0
}
override fun onRoomUpdated(
userCanPost: Boolean,
channelIsBroadcast: Boolean,
userCanMod: Boolean
) {
override fun onRoomUpdated(roomUiModel: RoomUiModel) {
// TODO: We should rely solely on the user being able to post, but we cannot guarantee
// that the "(channels|groups).roles" endpoint is supported by the server in use.
ui {
setupMessageComposer(userCanPost)
isBroadcastChannel = channelIsBroadcast
if (isBroadcastChannel && !userCanMod) {
setupToolbar(roomUiModel.name.toString())
setupMessageComposer(roomUiModel)
isBroadcastChannel = roomUiModel.broadcast
if (isBroadcastChannel && !roomUiModel.canModerate) {
disableMenu = true
activity?.invalidateOptionsMenu()
}
......@@ -544,7 +546,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
ui {
button_send.isEnabled = true
text_message.isEnabled = true
clearMessageComposition(true)
}
}
......@@ -611,7 +612,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
override fun showGenericErrorMessage(){
override fun showGenericErrorMessage() {
ui {
showMessage(getString(R.string.msg_generic_error))
}
......@@ -688,6 +689,44 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.react(messageId, emoji.shortname)
}
override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List<String>) {
val layout = LayoutInflater.from(requireContext()).inflate(R.layout.reaction_praises_list_item, null)
val dialog = AlertDialog.Builder(requireContext())
.setView(layout)
.setCancelable(true)
with(layout) {
view_flipper.displayedChild = if (isCustom) 1 else 0
if (isCustom && url != null) {
val glideRequest = if (url.endsWith("gif", true)) {
GlideApp.with(requireContext()).asGif()
} else {
GlideApp.with(requireContext()).asBitmap()
}
glideRequest.load(url).into(emoji_image_view)
} else {
emoji_view.text = EmojiParser.parse(requireContext(), shortname)
}
var listing = ""
if (usernames.size == 1) {
listing = usernames.first()
} else {
usernames.forEachIndexed { index, username ->
listing += if (index == usernames.size - 1) "|$username" else "$username, "
}
listing = listing.replace(", |", " ${requireContext().getString(R.string.msg_and)} ")
}
text_view_usernames.text = requireContext().resources.getQuantityString(
R.plurals.msg_reacted_with_, usernames.size, listing, shortname)
dialog.show()
}
}
override fun showReactionsPopup(messageId: String) {
ui {
val emojiPickerPopup = EmojiPickerPopup(it)
......@@ -748,12 +787,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
override fun onJoined(userCanPost: Boolean) {
override fun onJoined(roomUiModel: RoomUiModel) {
ui {
input_container.isVisible = true
button_join_chat.isVisible = false
isSubscribed = true
setupMessageComposer(userCanPost)
}
}
......@@ -786,15 +824,25 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
private fun setupMessageComposer(canPost: Boolean) {
if (isReadOnly && !canPost) {
private fun setupMessageComposer(roomUiModel: RoomUiModel) {
if (isReadOnly || !roomUiModel.writable) {
text_room_is_read_only.isVisible = true
input_container.isVisible = false
text_room_is_read_only.setText(
if (isReadOnly) {
R.string.msg_this_room_is_read_only
} else {
// Not a read-only channel but user has been muted.
R.string.msg_muted_on_this_channel
}
)
} else if (!isSubscribed && roomTypeOf(chatRoomType) !is RoomType.DirectMessage) {
input_container.isVisible = false
button_join_chat.isVisible = true
button_join_chat.setOnClickListener { presenter.joinChat(chatRoomId) }
} else {
input_container.isVisible = true
text_room_is_read_only.isVisible = false
button_send.isVisible = false
button_show_attachment_options.alpha = 1f
button_show_attachment_options.isVisible = true
......
......@@ -6,5 +6,6 @@ data class ReactionUiModel(
val unicode: CharSequence,
val count: Int,
val usernames: List<String> = emptyList(),
var url: String? = null
)
\ No newline at end of file
var url: String? = null,
val isCustom: Boolean = false
)
......@@ -19,6 +19,7 @@ import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.helper.MessageHelper
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UserHelper
......@@ -31,6 +32,7 @@ import chat.rocket.android.server.domain.messageReadReceiptEnabled
import chat.rocket.android.server.domain.messageReadReceiptStoreUsers
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.orFalse
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.ifNotNullNorEmpty
import chat.rocket.android.util.extensions.isNotNullNorEmpty
......@@ -469,7 +471,7 @@ class UiModelMapper @Inject constructor(
val list = mutableListOf<ReactionUiModel>()
val customEmojis = EmojiRepository.getCustomEmojis()
it.getShortNames().forEach { shortname ->
val usernames = it.getUsernames(shortname) ?: emptyList()
val usernames = it.getUsernames(shortname).orEmpty()
val count = usernames.size
val custom = customEmojis.firstOrNull { emoji -> emoji.shortname == shortname }
list.add(
......@@ -478,7 +480,8 @@ class UiModelMapper @Inject constructor(
unicode = EmojiParser.parse(context, shortname),
count = count,
usernames = usernames,
url = custom?.url)
url = custom?.url,
isCustom = custom != null)
)
}
list
......
......@@ -8,9 +8,8 @@ import androidx.core.text.color
import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.checkIfMyself
import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.showLastMessage
import chat.rocket.android.server.domain.useRealName
......@@ -30,7 +29,8 @@ class RoomUiModelMapper(
private val context: Application,
private val settings: PublicSettings,
private val userInteractor: GetCurrentUserInteractor,
private val serverUrl: String
private val serverUrl: String,
private val permissions: PermissionsInteractor
) {
private val nameUnreadColor = ContextCompat.getColor(context, R.color.colorPrimaryText)
private val nameColor = ContextCompat.getColor(context, R.color.colorSecondaryText)
......@@ -97,7 +97,9 @@ class RoomUiModelMapper(
avatar = serverUrl.avatarUrl(name!!, isGroupOrChannel = true),
lastMessage = if(showLastMessage) { mapLastMessage(lastMessage?.sender?.id, lastMessage?.sender?.username,
lastMessage?.sender?.name, lastMessage?.message,
isDirectMessage = type is RoomType.DirectMessage)} else { null }
isDirectMessage = type is RoomType.DirectMessage)} else { null },
muted = muted.orEmpty(),
writable = isChannelWritable(muted)
)
}
}
......@@ -133,11 +135,18 @@ class RoomUiModelMapper(
alert = isUnread,
lastMessage = lastMessageMarkdown,
status = status,
username = if (type is RoomType.DirectMessage) name else null
username = if (type is RoomType.DirectMessage) name else null,
muted = muted.orEmpty(),
writable = isChannelWritable(muted)
)
}
}
private fun isChannelWritable(muted: List<String>?): Boolean {
val canWriteToReadOnlyChannels = permissions.canPostToReadOnlyChannels()
return canWriteToReadOnlyChannels || !muted.orEmpty().contains(currentUser?.username)
}
private fun roomType(type: String): String {
val resources = context.resources
return when (type) {
......@@ -205,4 +214,4 @@ class RoomUiModelMapper(
}
}
}
}
\ No newline at end of file
}
......@@ -14,5 +14,9 @@ data class RoomUiModel(
val alert: Boolean = false,
val lastMessage: CharSequence? = null,
val status: UserStatus? = null,
val username: String? = null
)
\ No newline at end of file
val username: String? = null,
val broadcast: Boolean = false,
val canModerate: Boolean = false,
val writable: Boolean = true,
val muted: List<String> = emptyList()
)
......@@ -12,6 +12,7 @@ import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.UserDao
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
......@@ -88,9 +89,10 @@ class ChatRoomsFragmentModule {
context: Application,
repository: SettingsRepository,
userInteractor: GetCurrentUserInteractor,
@Named("currentServer") serverUrl: String
@Named("currentServer") serverUrl: String,
permissionsInteractor: PermissionsInteractor
): RoomUiModelMapper {
return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl)
return RoomUiModelMapper(context, repository.get(serverUrl), userInteractor, serverUrl, permissionsInteractor)
}
@Provides
......
......@@ -3,9 +3,12 @@ package chat.rocket.android.chatrooms.infrastructure
import androidx.lifecycle.LiveData
import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.util.retryDB
import javax.inject.Inject
class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao){
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) {
Order.ACTIVITY -> dao.getAll()
......@@ -15,9 +18,10 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao){
}
}
fun search(query: String) = dao.searchSync(query)
suspend fun search(query: String) =
retryDB("roomSearch($query)") { dao.searchSync(query) }
fun count() = dao.count()
suspend fun count() = retryDB("roomsCount") { dao.count() }
enum class Order {
ACTIVITY,
......
......@@ -13,6 +13,7 @@ import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.domain.useSpecialCharsOnRoom
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.RoomType
......@@ -41,11 +42,31 @@ class ChatRoomsPresenter @Inject constructor(
private val client = manager.client
private val settings = settingsRepository.get(currentServer)
fun loadChatRoom(roomId: String) {
launchUI(strategy) {
view.showLoadingRoom("")
try {
val room = dbManager.getRoom(roomId)
if (room != null) {
loadChatRoom(room.chatRoom, true)
} else {
Timber.d("Error loading channel")
view.showGenericErrorMessage()
}
} catch (ex: Exception) {
Timber.d(ex, "Error loading channel")
view.showGenericErrorMessage()
} finally {
view.hideLoadingRoom()
}
}
}
fun loadChatRoom(chatRoom: RoomUiModel) {
launchUI(strategy) {
view.showLoadingRoom(chatRoom.name)
try {
val room = dbManager.getRoom(chatRoom.id)
val room = retryDB("getRoom(${chatRoom.id}") { dbManager.getRoom(chatRoom.id) }
if (room != null) {
loadChatRoom(room.chatRoom, true)
} else {
......@@ -56,7 +77,8 @@ class ChatRoomsPresenter @Inject constructor(
type = type.toString(),
name = username ?: name.toString(),
fullname = name.toString(),
open = open
open = open,
muted = muted
)
loadChatRoom(entity, false)
}
......
......@@ -84,8 +84,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomId?.let {
// TODO - bring back support to load a room from id.
//presenter.goToChatRoomWithId(it)
presenter.loadChatRoom(it)
chatRoomId = null
}
}
......
......@@ -27,7 +27,7 @@ import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.withContext
import timber.log.Timber
import java.security.InvalidParameterException
import java.lang.IllegalArgumentException
import kotlin.coroutines.experimental.coroutineContext
......@@ -171,6 +171,6 @@ fun Query.asSortingOrder(): ChatRoomsRepository.Order {
ChatRoomsRepository.Order.ACTIVITY
}
}
else -> throw InvalidParameterException("Should be ByName or ByActivity")
else -> throw IllegalArgumentException("Should be ByName or ByActivity")
}
}
......@@ -2,6 +2,7 @@ package chat.rocket.android.db
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import chat.rocket.android.db.model.AttachmentActionEntity
import chat.rocket.android.db.model.AttachmentEntity
import chat.rocket.android.db.model.AttachmentFieldEntity
......@@ -14,6 +15,7 @@ import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.emoji.internal.db.StringListConverter
@Database(
entities = [
......@@ -23,11 +25,12 @@ import chat.rocket.android.db.model.UserEntity
AttachmentFieldEntity::class, AttachmentActionEntity::class, UrlEntity::class,
ReactionEntity::class, MessagesSync::class
],
version = 10,
version = 12,
exportSchema = true
)
@TypeConverters(StringListConverter::class)
abstract class RCDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun chatRoomDao(): ChatRoomDao
abstract fun messageDao(): MessageDao
}
\ No newline at end of file
}
......@@ -5,6 +5,8 @@ import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import chat.rocket.android.emoji.internal.db.StringListConverter
@Entity(tableName = "chatrooms",
indices = [
......@@ -20,6 +22,7 @@ import androidx.room.PrimaryKey
ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["lastMessageUserId"])
]
)
@TypeConverters(StringListConverter::class)
data class ChatRoomEntity(
@PrimaryKey var id: String,
var subscriptionId: String,
......@@ -42,7 +45,8 @@ data class ChatRoomEntity(
var lastMessageText: String? = null,
var lastMessageUserId: String? = null,
var lastMessageTimestamp: Long? = null,
var broadcast: Boolean? = false
var broadcast: Boolean? = false,
var muted: List<String>? = null
)
data class ChatRoom(
......@@ -52,4 +56,4 @@ data class ChatRoom(
var status: String?,
var lastMessageUserName: String?,
var lastMessageUserFullName: String?
)
\ No newline at end of file
)
......@@ -5,6 +5,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
......@@ -36,7 +37,7 @@ class FavoriteMessagesPresenter @Inject constructor(
view.showLoading()
dbManager.getRoom(roomId)?.let {
val favoriteMessages =
client.getFavoriteMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
client.getFavoriteMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
val messageList = mapper.map(favoriteMessages.result, asNotReversed = true)
view.showFavoriteMessages(messageList)
offset += 1 * 30
......
......@@ -7,6 +7,7 @@ import chat.rocket.android.files.uimodel.FileUiModel
import chat.rocket.android.files.uimodel.FileUiModelMapper
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
......
......@@ -6,6 +6,7 @@ import chat.rocket.android.members.uimodel.MemberUiModel
import chat.rocket.android.members.uimodel.MemberUiModelMapper
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
......@@ -38,7 +39,7 @@ class MembersPresenter @Inject constructor(
view.showLoading()
dbManager.getRoom(roomId)?.let {
val members =
client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60)
client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60)
val memberUiModels = mapper.mapToUiModelList(members.result)
view.showMembers(memberUiModels, members.total)
offset += 1 * 60L
......
......@@ -5,6 +5,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
......@@ -36,7 +37,7 @@ class PinnedMessagesPresenter @Inject constructor(
view.showLoading()
dbManager.getRoom(roomId)?.let {
val pinnedMessages =
client.getPinnedMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
client.getPinnedMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
val messageList = mapper.map(pinnedMessages.result, asNotReversed = true)
view.showPinnedMessages(messageList)
offset += 1 * 30
......
......@@ -296,11 +296,8 @@ class PushManager @Inject constructor(
}
private fun getContentIntent(context: Context, notificationId: Int, pushMessage: PushMessage, grouped: Boolean = false): PendingIntent {
val notificationIntent = context.changeServerIntent(pushMessage.info.host, chatRoomId = pushMessage.info.roomId)
// TODO - add support to go directly to the chatroom
/*if (!isGrouped) {
notificationIntent.putExtra(EXTRA_ROOM_ID, pushMessage.info.roomId)
}*/
val roomId = if (!grouped) pushMessage.info.roomId else null
val notificationIntent = context.changeServerIntent(pushMessage.info.host, chatRoomId = roomId)
return PendingIntent.getActivity(context, random.nextInt(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
......
......@@ -22,6 +22,7 @@ import chat.rocket.core.internal.realtime.unsubscribe
import chat.rocket.core.internal.rest.chatRooms
import chat.rocket.core.model.Message
import chat.rocket.core.model.Myself
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.Channel
......@@ -45,6 +46,7 @@ class ConnectionManager(
private val roomMessagesChannels = LinkedHashMap<String, Channel<Message>>()
private val userDataChannels = ArrayList<Channel<Myself>>()
private val roomsChannels = LinkedHashMap<String, Channel<Room>>()
private val subscriptionIdMap = HashMap<String, String>()
private var subscriptionId: String? = null
......@@ -127,6 +129,18 @@ class ConnectionManager(
maxSize = 10) { batch ->
Timber.d("processing Stream batch: ${batch.size} - $batch")
dbManager.processChatRoomsBatch(batch)
batch.forEach {
//TODO - Do we need to handle Type.Removed and Type.Inserted here?
if (it.type == Type.Updated) {
if (it.data is Room) {
val room = it.data as Room
roomsChannels[it.data.id]?.let { channel ->
channel.offer(room)
}
}
}
}
}
val messagesActor = createBatchActor<Message>(messagesContext, parent = connectJob,
......@@ -241,6 +255,14 @@ class ConnectionManager(
fun removeUserDataChannel(channel: Channel<Myself>) = userDataChannels.remove(channel)
fun addRoomChannel(roomId: String, channel: Channel<Room>) {
roomsChannels[roomId] = channel
}
fun removeRoomChannel(roomId: String) {
roomsChannels.remove(roomId)
}
fun subscribeRoomMessages(roomId: String, channel: Channel<Message>) {
val oldSub = roomMessagesChannels.put(roomId, channel)
if (oldSub != null) {
......
......@@ -7,6 +7,7 @@ import chat.rocket.android.db.model.FullMessage
import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.util.retryDB
import chat.rocket.common.model.SimpleRoom
import chat.rocket.common.model.SimpleUser
import chat.rocket.core.model.Message
......@@ -135,14 +136,18 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
with(attachment) {
val fields = if (hasFields) {
withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(attachment._id)
retryDB("getAttachmentFields(${attachment._id})") {
dbManager.messageDao().getAttachmentFields(attachment._id)
}
}.map { Field(it.title, it.value) }
} else {
null
}
val actions = if (hasActions) {
withContext(CommonPool) {
dbManager.messageDao().getAttachmentActions(attachment._id)
retryDB("getAttachmentActions(${attachment._id})") {
dbManager.messageDao().getAttachmentActions(attachment._id)
}
}.mapNotNull { mapAction(it) }
} else {
null
......@@ -183,29 +188,6 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
return list
}
/*private suspend fun mapColorAttachmentWithFields(entity: AttachmentEntity): ColorAttachment {
val fields = withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(entity._id)
}.map { Field(it.title, it.value) }
return with(entity) {
ColorAttachment(
color = Color.Custom(color ?: DEFAULT_COLOR_STR),
text = text ?: "",
fallback = fallback,
fields = fields)
}
}
private suspend fun mapActionAttachment(attachment: AttachmentEntity): ActionsAttachment {
val actions = withContext(CommonPool) {
dbManager.messageDao().getAttachmentActions(attachment._id)
}.mapNotNull { mapAction(it) }
return with(attachment) {
// TODO - remove the default "vertical" value from here...
ActionsAttachment(title, actions, buttonAlignment ?: "vertical")
}
}*/
private fun mapAction(action: AttachmentActionEntity): Action? {
return when (action.type) {
"button" -> ButtonAction(action.type, action.text, action.url, action.isWebView,
......@@ -214,13 +196,4 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
else -> null
}
}
/*private suspend fun mapAuthorAttachment(attachment: AttachmentEntity): AuthorAttachment {
val fields = withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(attachment._id)
}.map { Field(it.title, it.value) }
return with(attachment) {
AuthorAttachment(authorLink!!, authorIcon, authorName, fields)
}
}*/
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.Operation
import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.util.retryDB
import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
......@@ -14,25 +15,31 @@ class DatabaseMessagesRepository(
) : MessagesRepository {
override suspend fun getById(id: String): Message? = withContext(CommonPool) {
dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) }
retryDB("getMessageById($id)") {
dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) }
}
}
override suspend fun getByRoomId(roomId: String): List<Message> = withContext(CommonPool) {
// FIXME - investigate how to avoid this distinctBy here, since DAO is returning a lot of
// duplicate rows (something related to our JOINS and relations on Room)
dbManager.messageDao().getMessagesByRoomId(roomId)
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
retryDB("getMessagesByRoomId($roomId)") {
dbManager.messageDao().getMessagesByRoomId(roomId)
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
}
}
override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> = withContext(CommonPool) {
dbManager.messageDao().getRecentMessagesByRoomId(roomId, count)
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
retryDB("getRecentMessagesByRoomId($roomId, $count)") {
dbManager.messageDao().getRecentMessagesByRoomId(roomId, count)
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
}
}
override suspend fun save(message: Message) {
......@@ -45,20 +52,24 @@ class DatabaseMessagesRepository(
override suspend fun removeById(id: String) {
withContext(CommonPool) {
dbManager.messageDao().delete(id)
retryDB("delete($id)") { dbManager.messageDao().delete(id) }
}
}
override suspend fun removeByRoomId(roomId: String) {
withContext(CommonPool) {
dbManager.messageDao().deleteByRoomId(roomId)
retryDB("deleteByRoomId($roomId)") {
dbManager.messageDao().deleteByRoomId(roomId)
}
}
}
override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
dbManager.messageDao().getUnsentMessages()
.distinctBy { it.message.message.id }
.let { mapper.map(it) }
retryDB("getUnsentMessages") {
dbManager.messageDao().getUnsentMessages()
.distinctBy { it.message.message.id }
.let { mapper.map(it) }
}
}
override suspend fun saveLastSyncDate(roomId: String, timeMillis: Long) {
......@@ -66,6 +77,8 @@ class DatabaseMessagesRepository(
}
override suspend fun getLastSyncDate(roomId: String): Long? = withContext(CommonPool) {
dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp }
retryDB("getLastSync($roomId)") {
dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp }
}
}
}
\ No newline at end of file
package chat.rocket.android.settings.password.presentation
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
......@@ -14,6 +15,7 @@ import javax.inject.Inject
class PasswordPresenter @Inject constructor(
private val view: PasswordView,
private val strategy: CancelStrategy,
private val analyticsManager: AnalyticsManager,
serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory
) {
......@@ -30,10 +32,12 @@ class PasswordPresenter @Inject constructor(
client.updateProfile(me.id, null, null, password, null)
}
analyticsManager.logResetPassword(true)
view.showPasswordSuccessfullyUpdatedMessage()
view.hideLoading()
} catch (exception: RocketChatException) {
analyticsManager.logResetPassword(false)
view.showPasswordFailsUpdateMessage(exception.message)
} finally {
view.hideLoading()
}
}
......
......@@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.about.ui.AboutFragment
......@@ -72,25 +73,35 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen
resources.getStringArray(R.array.settings_actions)[1] ->
activity?.startActivity(Intent(activity, PasswordActivity::class.java))
resources.getStringArray(R.array.settings_actions)[2] -> {
(activity as AppCompatActivity).addFragmentBackStack(
TAG_ABOUT_FRAGMENT,
R.id.fragment_container
) {
AboutFragment.newInstance()
}
}
resources.getStringArray(R.array.settings_actions)[2] -> shareApp()
resources.getStringArray(R.array.settings_actions)[3] -> shareApp()
resources.getStringArray(R.array.settings_actions)[3] -> showAppOnStore()
resources.getStringArray(R.array.settings_actions)[4] -> activity?.startActivity(
resources.getStringArray(R.array.settings_actions)[4] -> contactSupport()
resources.getStringArray(R.array.settings_actions)[5] -> activity?.startActivity(
context?.webViewIntent(
getString(R.string.license_url),
getString(R.string.title_licence)
)
)
resources.getStringArray(R.array.settings_actions)[5] -> contactSupport()
resources.getStringArray(R.array.settings_actions)[6] -> {
(activity as AppCompatActivity).addFragmentBackStack(
TAG_ABOUT_FRAGMENT,
R.id.fragment_container
) {
AboutFragment.newInstance()
}
}
}
}
private fun showAppOnStore() {
try {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.market_link).toUri()))
} catch (error: ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.play_store_link).toUri()))
}
}
......
package chat.rocket.android.util
import android.database.sqlite.SQLiteDatabaseLockedException
import chat.rocket.common.RocketChatNetworkErrorException
import kotlinx.coroutines.experimental.TimeoutCancellationException
import kotlinx.coroutines.experimental.delay
......@@ -8,6 +9,7 @@ import timber.log.Timber
import kotlin.coroutines.experimental.coroutineContext
const val DEFAULT_RETRY = 3
private const val DEFAULT_DB_RETRY = 5
suspend fun <T> retryIO(
description: String = "<missing description>",
......@@ -32,6 +34,33 @@ suspend fun <T> retryIO(
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
return block() // last attempt
}
suspend fun <T> retryDB(
description: String = "<missing description>",
times: Int = DEFAULT_DB_RETRY,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 500, // 0.5 second
factor: Double = 1.2,
block: suspend () -> T): T
{
var currentDelay = initialDelay
repeat(times - 1) { currentTry ->
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
try {
return block()
} catch (e: SQLiteDatabaseLockedException) {
Timber.d(e, "failed call($currentTry): $description")
e.printStackTrace()
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
return block() // last attempt
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center">
<ImageView
android:id="@+id/emoji_image_view"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
tools:src="@tools:sample/avatars" />
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/emoji_view"
android:layout_width="48dp"
android:layout_height="48dp"
android:foreground="?selectableItemBackground"
android:layout_gravity="center"
android:gravity="center"
android:textColor="#000000"
android:textSize="24sp"
tools:text="😀" />
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorWhite"
android:padding="16dp">
<ViewFlipper
android:id="@+id/view_flipper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:foregroundGravity="center"
app:layout_constraintTop_toTopOf="parent">
<include layout="@layout/emoji_row_item" />
<include layout="@layout/emoji_image_row_item" />
</ViewFlipper>
<TextView
android:id="@+id/text_view_usernames"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="@color/darkGray"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/view_flipper"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/view_flipper"
app:layout_constraintTop_toTopOf="@+id/view_flipper"
tools:text="Ann reacted with :grin:" />
</androidx.constraintlayout.widget.ConstraintLayout>
......@@ -16,6 +16,7 @@
<string name="title_settings">Einstellungen</string>
<string name="title_preferences">Preferences</string> <!-- TODO Add translation -->
<string name="title_change_password">Ändere Passwort</string>
<string name="title_rate_us">Bewerten Sie uns</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation -->
<string name="title_password">Ändere Passwort</string>
<string name="title_update_profile">Update Profil</string>
......@@ -58,11 +59,12 @@
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Ändere Passwort</item>
<item name="item_password">Über</item>
<item name="item_share_app">App teilen</item>
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
......@@ -151,6 +153,13 @@
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
<!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string>
<!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item>
<item quantity="other">%1$s reacted with %2$s</item>
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Privat</string>
......
......@@ -15,6 +15,7 @@
<string name="title_settings">Configuraciones</string>
<string name="title_preferences">Preferences</string> <!-- TODO Add translation -->
<string name="title_change_password">Cambia la contraseña</string>
<string name="title_rate_us">Nos califica</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation -->
<string name="title_password">Cambia la contraseña</string>
<string name="title_update_profile">Actualización del perfil</string>
......@@ -57,11 +58,12 @@
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Cambia la contraseña</item>
<item name="item_password">Acerca de</item>
<item name="item_share_app">Compartir aplicación</item>
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
......@@ -168,6 +170,11 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation -->
......
......@@ -16,6 +16,7 @@
<string name="title_settings">Paramètres</string>
<string name="title_preferences">Préférences</string>
<string name="title_change_password">Changer le mot de passe</string>
<string name="title_rate_us">évaluez nous</string>
<string name="title_admin_panel">Administration</string>
<string name="title_password">Changer le mot de passe</string>
<string name="title_update_profile">Mettre à jour le profil</string>
......@@ -57,12 +58,13 @@
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Préférences</item>
<item name="item_password">Changer le mot de passe</item>
<item name="item_password">À propos</item>
<item name="item_share_app">Partager l\'application</item>
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
......@@ -160,6 +162,11 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Privé</string>
......
......@@ -16,6 +16,7 @@
<string name="title_settings">सेटिंग्स</string>
<string name="title_preferences">प्राथमिकताएँ</string>
<string name="title_change_password">पासवर्ड बदलें</string>
<string name="title_rate_us">हमें रेटिंग दें</string>
<string name="title_admin_panel">एडमिन पैनल</string>
<string name="title_password">पासवर्ड बदलें</string>
<string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string>
......@@ -57,12 +58,13 @@
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">प्राथमिकताएँ</item>
<item name="item_password">पासवर्ड बदलें</item>
<item name="item_password">परिचय</item>
<item name="item_share_app">ऐप शेयर करें</item>
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
......@@ -174,6 +176,12 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="few">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="many">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">एनालिटिक्स ट्रैकिंग</string>
......
This diff is collapsed.
......@@ -18,12 +18,14 @@
<string name="title_settings">設定</string>
<string name="title_preferences">環境設定</string>
<string name="title_change_password">パスワードの変更</string>
<string name="title_rate_us">私たちを評価してください</string>
<string name="title_admin_panel">管理パネル</string>
<string name="title_password">パスワードの変更</string>
<string name="title_update_profile">プロフィールの更新</string>
<string name="title_about">About</string>
<string name="title_licence">Licence</string> <!-- TODO Add translation -->
<string name="title_create_channel">新しいチャネルを作成</string>
<string name="title_are_you_sure">本気ですか?</string>
<!-- Actions -->
<string name="action_connect">接続</string>
......@@ -54,16 +56,17 @@
<string name="action_create_server">新規サーバーを作成</string>
<string name="action_register">登録</string>
<string name="action_confirm">確認</string>
<string name="action_delete_account">Delete account</string> <!-- TODO Add translation -->
<string name="action_delete_account">アカウントを削除する</string>
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">環境設定</item>
<item name="item_password">パスワードの変更</item>
<item name="item_password">アプリ情報</item>
<item name="item_share_app">アプリを共有する</item>
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
......@@ -155,10 +158,14 @@
<string name="msg_continue_with_wordpress"><b>WordPress</b>でログイン</string>
<string name="msg_two_factor_authentication">二要素認証</string>
<string name="msg__your_2fa_code">あなたの 2FA コードは何ですか?</string>
<string name="msg_view_more">更に表示</string>
<string name="msg_view_less">隠す</string>
<string name="msg_muted_on_this_channel">あなたはこのチャンネルでミュートされています</string>
<!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item>
<item quantity="other">%1$s reacted with %2$s</item>
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">プライベート</string>
......@@ -213,7 +220,6 @@
<string name="action_msg_share">Share</string>
<string name="action_title_editing">メッセージの編集</string>
<string name="action_msg_add_reaction">リアクションする</string>
<!-- TODO - Add proper translation -->
<string name="action_msg_copy_permalink">パーマリンクのコピー</string>
<!-- Permission messages -->
......
......@@ -16,6 +16,7 @@
<string name="title_settings">Configurações</string>
<string name="title_preferences">Preferencias</string>
<string name="title_change_password">Alterar senha</string>
<string name="title_rate_us">nos avalie</string>
<string name="title_admin_panel">Painel administrativo</string>
<string name="title_password">Alterar senha</string>
<string name="title_update_profile">Editar perfil</string>
......@@ -57,12 +58,13 @@
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferencias</item>
<item name="item_preferences">Preferências</item>
<item name="item_password">Alterar senha</item>
<item name="item_password">Sobre</item>
<item name="item_share_app">Compartilhe o aplicativo</item>
<item name="item_share_app_licence">Licença</item>
<item name="item_share_app">Compartilhar app</item>
<item name="item_rate_us">Classifique-nos</item>
<item name="item_contact_us">Contate-nos</item>
<item name="item_licence">Licença</item>
<item name="item_about">Sobre</item>
</string-array>
<!-- Regular information messages -->
......@@ -161,6 +163,11 @@
<string name="msg_permalink_copied">Permalink copiado</string>
<string name="msg_send_email">Enviar e-mail</string>
<string name="msg_android_app_support">Suporte ao aplicativo Android</string>
<string name="msg_muted_on_this_channel">Você está silenciado neste canal</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reagiu com %2$s</item>
<item quantity="other">%1$s reagiram com %2$s</item>
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Privado</string>
......@@ -189,8 +196,8 @@
<string name="message_welcome">Bem-vindo, %s</string>
<string name="message_removed">Mensagem removida</string>
<string name="message_pinned">Pinou uma mensagem:</string>
<string name="message_muted">Usuário %1$s entrou no modo mudo por %2$s</string>
<string name="message_unmuted">Usuário %1$s saiu do modo mudo por %2$s</string>
<string name="message_muted">Usuário %1$s foi silenciado por %2$s</string>
<string name="message_unmuted">Usuário %1$s saiu do modo silenciado por %2$s</string>
<string name="message_role_add">%1$s foi definido %2$s por %3$s</string>
<string name="message_role_removed">%1$s não é mais %2$s por %3$s</string>
// TODO:Add proper translation.
......
......@@ -16,6 +16,7 @@
<string name="title_settings">Настройки</string>
<string name="title_preferences">Персональные</string>
<string name="title_change_password">Изменить пароль</string>
<string name="title_rate_us">оцените нас</string>
<string name="title_admin_panel">Панель админа</string>
<string name="title_password">Изменить пароль</string>
<string name="title_update_profile">Обновить профиль</string>
......@@ -57,12 +58,13 @@
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Персональные</item>
<item name="item_password">Изменить пароль</item>
<item name="item_password">О программе</item>
<item name="item_share_app">добавить приложение</item>
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
......@@ -159,6 +161,12 @@
<string name="msg_permalink_copied">Ссылка скопирована</string>
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="few">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="many">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Приватный</string>
......
......@@ -16,6 +16,7 @@
<string name="title_settings">Ayarlar</string>
<string name="title_preferences">Tercihler</string>
<string name="title_change_password">Şifre Değişikliği</string>
<string name="title_rate_us">Bizi değerlendirin</string>
<string name="title_admin_panel">Yönetici Paneli</string>
<string name="title_password">Şifrenizi Değiştirin</string>
<string name="title_update_profile">Profilinizi Düzenleyin</string>
......@@ -57,12 +58,13 @@
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Tercihler</item>
<item name="item_password">Şifre Değiştir</item>
<item name="item_password">Hakkında</item>
<item name="item_share_app">uygulamayı Paylaş</item>
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
......@@ -175,7 +177,11 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">İstatistik takibi</string>
<string name="msg_send_analytics_tracking">Uygulamanın gelişmesine katkıda bulunmak için anonim istatistik bilgisi gönder</string>
......
......@@ -16,6 +16,7 @@
<string name="title_settings">Налаштування</string>
<string name="title_preferences">Персональні</string>
<string name="title_change_password">Змінити пароль</string>
<string name="title_rate_us">Оцініть нас</string>
<string name="title_admin_panel">Панель адміністратора</string>
<string name="title_password">Змінити пароль</string>
<string name="title_update_profile">Оновити профіль</string>
......@@ -58,11 +59,12 @@
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change Password</item> <!-- TODO Add translation -->
<item name="item_password">About</item> <!-- TODO Add translation -->
<item name="item_share_app">поділитися прикладом</item>
<item name="item_share_app_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
......@@ -158,6 +160,12 @@
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="few">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="many">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Приватний</string>
......
......@@ -5,5 +5,6 @@
<string name="community_server_url" translatable="false">open.rocket.chat</string>
<string name="create_server_url" translatable="false">cloud.rocket.chat/trial</string>
<string name="play_store_link" translatable="false">https://play.google.com/store/apps/details?id=chat.rocket.android</string>
<string name="market_link" translatable="false">market://details?id=chat.rocket.android</string>
<string name="license_url" translatable="false">https://github.com/RocketChat/Rocket.Chat.Android/blob/develop/LICENSE</string>
</resources>
\ No newline at end of file
......@@ -28,6 +28,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="title_settings">Settings</string>
<string name="title_preferences">Preferences</string>
<string name="title_change_password">Change Password</string>
<string name="title_rate_us">Rate Us</string>
<string name="title_admin_panel">Admin panel</string>
<string name="title_password">Change Password</string>
<string name="title_update_profile">Update profile</string>
......@@ -70,11 +71,12 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferences</item>
<item name="item_password">Change Password</item>
<item name="item_password">About</item>
<item name="item_share_app">Share App</item>
<item name="item_share_app_licence">Licence</item>
<item name="item_password">Change password</item>
<item name="item_share_app">Share app</item>
<item name="item_rate_us">Rate us</item>
<item name="item_contact_us">Contact us</item>
<item name="item_licence">Licence</item>
<item name="item_about">About</item>
</string-array>
<!-- Regular information messages -->
......@@ -171,6 +173,10 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_permalink_copied">Permalink copied</string>
<string name="msg_send_email">Send email</string>
<string name="msg_android_app_support">Android app support</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item>
<item quantity="other">%1$s reacted with %2$s</item>
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Private</string>
......@@ -188,6 +194,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="msg_delete_description">Are you sure you want to delete this message</string>
<string name="msg_view_more">view more</string>
<string name="msg_view_less">view less</string>
<string name="msg_muted_on_this_channel">You are muted on this channel</string>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string>
......
......@@ -67,4 +67,11 @@ class AnswersAnalytics : Analytics {
)
override fun logOpenAdmin() = Answers.getInstance().logCustom(CustomEvent("open_admin"))
override fun logResetPassword(resetPasswordSucceeded: Boolean) =
Answers.getInstance()
.logCustom(
CustomEvent("reset_password")
.putCustomAttribute("resetPasswordSucceeded", resetPasswordSucceeded.toString())
)
}
......@@ -60,4 +60,9 @@ class GoogleAnalyticsForFirebase @Inject constructor(val context: Context) :
}
override fun logOpenAdmin() = firebaseAnalytics.logEvent("open_admin", null)
override fun logResetPassword(resetPasswordSucceeded: Boolean) =
firebaseAnalytics.logEvent("reset_password", Bundle(1).apply {
putBoolean("resetPasswordSucceeded", resetPasswordSucceeded)
})
}
package chat.rocket.android.push
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.work.Constraints
import androidx.work.NetworkType
......@@ -23,9 +24,12 @@ class FirebaseMessagingService : FirebaseMessagingService() {
}
override fun onMessageReceived(message: RemoteMessage) {
// XXX - for now this is ok, if we start to do network calls, use a Worker instead
message.data?.let {
pushManager.handle(bundleOf(*(it.map { Pair(it.key, it.value) }).toTypedArray()))
message.data?.let { data ->
val bundle = Bundle()
data.entries.forEach { entry ->
bundle.putString(entry.key, entry.value)
}
pushManager.handle(bundle)
}
}
......
......@@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
......@@ -22,6 +21,7 @@ import chat.rocket.android.emoji.internal.EmojiCategory
import chat.rocket.android.emoji.internal.EmojiPagerAdapter
import chat.rocket.android.emoji.internal.PREF_EMOJI_SKIN_TONE
import com.google.android.material.tabs.TabLayout
import kotlinx.android.synthetic.main.dialog_skin_tone_chooser.view.*
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
......@@ -77,59 +77,50 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
}
private fun showSkinToneChooser() {
val view = LayoutInflater.from(context).inflate(R.layout.color_select_popup, null)
val view = LayoutInflater.from(context).inflate(R.layout.dialog_skin_tone_chooser, null)
val dialog = AlertDialog.Builder(context, R.style.Dialog)
.setView(view)
.setTitle(context.getString(R.string.alert_title_default_skin_tone))
.setCancelable(true)
.create()
view.findViewById<TextView>(R.id.default_tone_text).also {
it.text = EmojiParser.parse(context, it.text)
}.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.Default)
}
with (view) {
image_view_default_tone.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.Default)
}
view.findViewById<TextView>(R.id.light_tone_text).also {
it.text = EmojiParser.parse(context, it.text)
}.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.LightTone)
}
image_view_light_tone.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.LightTone)
}
view.findViewById<TextView>(R.id.medium_light_text).also {
it.text = EmojiParser.parse(context, it.text)
}.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.MediumLightTone)
}
image_view_medium_light.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.MediumLightTone)
}
view.findViewById<TextView>(R.id.medium_tone_text).also {
it.text = EmojiParser.parse(context, it.text)
}.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.MediumTone)
}
image_view_medium_tone.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.MediumTone)
}
view.findViewById<TextView>(R.id.medium_dark_tone_text).also {
it.text = EmojiParser.parse(context, it.text)
}.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.MediumDarkTone)
}
image_view_medium_dark_tone.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.MediumDarkTone)
}
view.findViewById<TextView>(R.id.dark_tone_text).also {
it.text = EmojiParser.parse(context, it.text)
}.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.DarkTone)
image_view_dark_tone.setOnClickListener {
dialog.dismiss()
changeSkinTone(Fitzpatrick.DarkTone)
}
}
dialog.show()
}
private fun changeSkinTone(tone: Fitzpatrick) {
val drawable = ContextCompat.getDrawable(context, R.drawable.color_change_circle)!!
val drawable = ContextCompat.getDrawable(context, R.drawable.bg_skin_tone)!!
val wrappedDrawable = DrawableCompat.wrap(drawable)
DrawableCompat.setTint(wrappedDrawable, getFitzpatrickColor(tone))
(changeColorView as ImageView).setImageDrawable(wrappedDrawable)
......
......@@ -16,4 +16,14 @@ interface EmojiReactionListener {
* @param emojiShortname The shortname of the emoji (:grin:, :smiley:, etc).
*/
fun onReactionTouched(messageId: String, emojiShortname: String)
}
\ No newline at end of file
/**
* Callback when an added reaction is long-clicked.
*
* @param shortname The shortname of the emoji (:grin:, :smiley:, etc).
* @param isCustom Whether the reaction is custom or one of the defaults.
* @param url In case of a custom emoji, this is the url to find it. Can be null if not a custom.
* @param usernames The list of usernames of users who added the reaction.
*/
fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List<String>)
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/tone_default" />
<size
android:width="24dp"
android:height="24dp" />
</shape>
\ No newline at end of file
</shape>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/default_tone_text"
<ImageView
android:id="@+id/image_view_default_tone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:text="👌"
android:textSize="32sp"
android:layout_marginBottom="16dp"
android:tint="@color/tone_default"
app:layout_constraintEnd_toStartOf="@+id/light_tone_text"
app:layout_constraintBottom_toTopOf="@+id/image_view_medium_tone"
app:layout_constraintEnd_toStartOf="@+id/image_view_light_tone"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/color_change_circle" />
app:srcCompat="@drawable/bg_skin_tone" />
<TextView
android:id="@+id/light_tone_text"
<ImageView
android:id="@+id/image_view_light_tone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:text="👌🏻"
android:textSize="32sp"
android:tint="@color/tone_light"
app:layout_constraintEnd_toStartOf="@+id/medium_light_text"
app:layout_constraintEnd_toStartOf="@+id/image_view_medium_light"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/default_tone_text"
app:layout_constraintStart_toEndOf="@+id/image_view_default_tone"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/color_change_circle"
tools:text="👌" />
app:srcCompat="@drawable/bg_skin_tone" />
<TextView
android:id="@+id/medium_light_text"
<ImageView
android:id="@+id/image_view_medium_light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="👌🏼"
android:textSize="32sp"
android:layout_marginEnd="24dp"
android:tint="@color/tone_medium_light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/light_tone_text"
app:layout_constraintStart_toEndOf="@+id/image_view_light_tone"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/color_change_circle"
tools:text="👌" />
app:srcCompat="@drawable/bg_skin_tone" />
<TextView
android:id="@+id/medium_tone_text"
<ImageView
android:id="@+id/image_view_medium_tone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:text="👌🏽"
android:textSize="32sp"
android:tint="@color/tone_medium"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/medium_dark_tone_text"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/default_tone_text"
app:srcCompat="@drawable/color_change_circle"
tools:text="👌" />
app:layout_constraintEnd_toStartOf="@+id/image_view_medium_dark_tone"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="@+id/image_view_default_tone"
app:layout_constraintTop_toBottomOf="@+id/image_view_default_tone"
app:srcCompat="@drawable/bg_skin_tone" />
<TextView
android:id="@+id/medium_dark_tone_text"
<ImageView
android:id="@+id/image_view_medium_dark_tone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="16dp"
android:text="👌🏾"
android:textSize="32sp"
android:tint="@color/tone_medium_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/dark_tone_text"
app:layout_constraintBottom_toBottomOf="@+id/image_view_medium_tone"
app:layout_constraintEnd_toStartOf="@+id/image_view_dark_tone"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/medium_tone_text"
app:layout_constraintTop_toBottomOf="@+id/light_tone_text"
app:srcCompat="@drawable/color_change_circle"
tools:text="👌" />
app:layout_constraintStart_toEndOf="@+id/image_view_medium_tone"
app:layout_constraintTop_toTopOf="@+id/image_view_medium_tone"
app:srcCompat="@drawable/bg_skin_tone" />
<TextView
android:id="@+id/dark_tone_text"
<ImageView
android:id="@+id/image_view_dark_tone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:text="👌🏿"
android:textSize="32sp"
android:tint="@color/tone_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/medium_dark_tone_text"
app:layout_constraintTop_toBottomOf="@+id/medium_light_text"
app:srcCompat="@drawable/color_change_circle"
tools:text="👌" />
app:layout_constraintBottom_toBottomOf="@+id/image_view_medium_dark_tone"
app:layout_constraintEnd_toEndOf="@+id/image_view_medium_light"
app:layout_constraintStart_toEndOf="@+id/image_view_medium_dark_tone"
app:layout_constraintTop_toTopOf="@+id/image_view_medium_dark_tone"
app:srcCompat="@drawable/bg_skin_tone" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
</androidx.constraintlayout.widget.ConstraintLayout>
......@@ -43,7 +43,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/color_change_circle" />
app:srcCompat="@drawable/bg_skin_tone" />
<ImageView
android:id="@+id/emoji_search"
......
<resources>
<string name="msg_no_recent_emoji">No recent emoji</string>
<string name="alert_title_default_skin_tone">Default skin tone</string>
<string name="msg_reactions" translatable="false">Reactions</string>
</resources>
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