Commit 0e76e49a authored by Samer Alabi's avatar Samer Alabi

Merge branch 'develop' into room-info

parents 771dadee 4c9ae11a
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="rocket_chat_images"
path="Android/data/chat.rocket.android.dev/files/Pictures" />
</paths>
\ No newline at end of file
......@@ -110,6 +110,15 @@
<meta-data
android:name="io.fabric.ApiKey"
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
</application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="chat.rocket.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
package chat.rocket.android.authentication.server.ui
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
......@@ -11,6 +12,7 @@ import android.widget.ScrollView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.core.text.color
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
......@@ -51,6 +53,8 @@ class ServerFragment : Fragment(), ServerView {
lateinit var analyticsManager: AnalyticsManager
private var deepLinkInfo: LoginDeepLinkInfo? = null
private var protocol = "https://"
private var isDomainAppended = false
private var appendedText = ""
private lateinit var serverUrlDisposable: Disposable
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
if (KeyboardHelper.isSoftKeyboardShown(scroll_view.rootView)) {
......@@ -131,7 +135,7 @@ class ServerFragment : Fragment(), ServerView {
}
private fun setupOnClickListener() =
ui { _ ->
ui {
button_connect.setOnClickListener {
presenter.checkServer("$protocol${text_server_url.textContent.sanitize()}")
}
......@@ -244,17 +248,42 @@ class ServerFragment : Fragment(), ServerView {
private fun subscribeEditText() {
serverUrlDisposable = text_server_url.asObservable()
.filter { it.isNotBlank() }
.subscribe {
if ("$protocol${it.toString()}".isValidUrl()) {
enableButtonConnect()
} else {
disableButtonConnect()
}
}
.subscribe { processUserInput(it.toString()) }
}
private fun unsubscribeEditText() = serverUrlDisposable.dispose()
private fun processUserInput(text: String) {
if (text.last().toString() == "." && !isDomainAppended) {
addDomain()
} else if (isDomainAppended && text != appendedText) {
removeDomain()
}
if ("$protocol$text".isValidUrl()) {
enableButtonConnect()
} else {
disableButtonConnect()
}
}
private fun addDomain() {
val cursorPosition = text_server_url.length()
text_server_url.append(SpannableStringBuilder()
.color(R.color.colorAuthenticationSecondaryText) { append("rocket.chat") })
text_server_url.setSelection(cursorPosition)
appendedText = text_server_url.text.toString()
isDomainAppended = true
}
private fun removeDomain() {
text_server_url.setText(
text_server_url.text.toString().substring(0, text_server_url.selectionEnd)
)
text_server_url.setSelection(text_server_url.length())
isDomainAppended = false
}
private fun enableUserInput() {
enableButtonConnect()
text_server_url.isEnabled = true
......
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)
}
}
......@@ -15,6 +15,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.RoomUiModelMapper
import chat.rocket.android.core.behaviours.showMessage
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
......@@ -34,11 +35,10 @@ import chat.rocket.android.server.domain.uploadMimeTypeFilter
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.state
import chat.rocket.android.util.extension.compressImageAndGetByteArray
import chat.rocket.android.util.extension.getByteArray
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.exhaustive
import chat.rocket.android.util.retryDB
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.RoomType
......@@ -74,6 +74,7 @@ import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.ChatRoomRole
import chat.rocket.core.model.Command
import chat.rocket.core.model.Message
import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.android.UI
......@@ -82,7 +83,6 @@ import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import org.threeten.bp.Instant
import timber.log.Timber
import java.io.InputStream
import java.util.*
import javax.inject.Inject
......@@ -98,6 +98,7 @@ class ChatRoomPresenter @Inject constructor(
private val analyticsManager: AnalyticsManager,
private val userHelper: UserHelper,
private val mapper: UiModelMapper,
private val roomMapper: RoomUiModelMapper,
private val jobSchedulerInteractor: JobSchedulerInteractor,
private val messageHelper: MessageHelper,
private val dbManager: DatabaseManager,
......@@ -120,6 +121,7 @@ class ChatRoomPresenter @Inject constructor(
private var typingStatusSubscriptionId: String? = null
private var lastState = manager.state
private var typingStatusList = arrayListOf<String>()
private val roomChangesChannel = Channel<Room>(Channel.CONFLATED)
fun setupChatRoom(
roomId: String,
......@@ -127,7 +129,7 @@ class ChatRoomPresenter @Inject constructor(
roomType: String,
chatRoomMessage: String? = null
) {
launchUI(strategy) {
launch(CommonPool + strategy.jobs) {
try {
chatRoles = if (roomTypeOf(roomType) !is RoomType.DirectMessage) {
client.chatRoomRoles(roomType = roomTypeOf(roomType), roomName = roomName)
......@@ -137,16 +139,21 @@ class ChatRoomPresenter @Inject constructor(
chatRoles = emptyList()
} finally {
// User has at least an 'owner' or 'moderator' role.
val userCanMod = isOwnerOrMod()
val chatRoom = dbManager.getRoom(roomId)
val muted = chatRoom?.chatRoom?.muted ?: emptyList()
val canModerate = isOwnerOrMod()
// Can post anyway if has the 'post-readonly' permission on server.
val userCanPost = userCanMod || permissions.canPostToReadOnlyChannels() ||
!muted.contains(currentLoggedUsername)
chatIsBroadcast = chatRoom?.chatRoom?.run {
broadcast
} ?: false
view.onRoomUpdated(userCanPost, chatIsBroadcast, userCanMod)
val room = dbManager.getRoom(roomId)
room?.let {
chatIsBroadcast = it.chatRoom.broadcast ?: false
val roomUiModel = roomMapper.map(it, true)
launchUI(strategy) {
view.onRoomUpdated(roomUiModel = roomUiModel.copy(
broadcast = chatIsBroadcast,
canModerate = canModerate,
writable = roomUiModel.writable || canModerate
))
}
}
loadMessages(roomId, roomType, clearDataSet = true)
chatRoomMessage?.let { messageHelper.messageIdFromPermalink(it) }
?.let { messageId ->
......@@ -158,10 +165,26 @@ class ChatRoomPresenter @Inject constructor(
true
)
}
subscribeRoomChanges()
}
}
}
private suspend fun subscribeRoomChanges() {
chatRoomId?.let {
manager.addRoomChannel(it, roomChangesChannel)
for (room in roomChangesChannel) {
dbManager.getRoom(room.id)?.let {
view.onRoomUpdated(roomMapper.map(chatRoom = it, showLastMessage = true))
}
}
}
}
private fun unsubscribeRoomChanges() {
chatRoomId?.let { manager.removeRoomChannel(it) }
}
private fun isOwnerOrMod(): Boolean {
return chatRoles.firstOrNull { it.user.username == currentLoggedUsername }?.roles?.any {
it == "owner" || it == "moderator"
......@@ -898,77 +921,81 @@ class ChatRoomPresenter @Inject constructor(
// TODO: move this to new interactor or FetchChatRoomsInteractor?
private suspend fun getChatRoomAsync(roomId: String): ChatRoom? = withContext(CommonPool) {
return@withContext dbManager.chatRoomDao().getSync(roomId)?.let {
with(it.chatRoom) {
ChatRoom(
id = id,
subscriptionId = subscriptionId,
type = roomTypeOf(type),
unread = unread,
broadcast = broadcast ?: false,
alert = alert,
fullName = fullname,
name = name,
favorite = favorite ?: false,
default = isDefault ?: false,
readonly = readonly,
open = open,
lastMessage = null,
archived = false,
status = null,
user = null,
userMentions = userMentions,
client = client,
announcement = null,
description = null,
groupMentions = groupMentions,
roles = null,
topic = null,
lastSeen = this.lastSeen,
timestamp = timestamp,
updatedAt = updatedAt
)
retryDB("getRoom($roomId)") {
dbManager.chatRoomDao().getSync(roomId)?.let {
with(it.chatRoom) {
ChatRoom(
id = id,
subscriptionId = subscriptionId,
type = roomTypeOf(type),
unread = unread,
broadcast = broadcast ?: false,
alert = alert,
fullName = fullname,
name = name,
favorite = favorite ?: false,
default = isDefault ?: false,
readonly = readonly,
open = open,
lastMessage = null,
archived = false,
status = null,
user = null,
userMentions = userMentions,
client = client,
announcement = null,
description = null,
groupMentions = groupMentions,
roles = null,
topic = null,
lastSeen = this.lastSeen,
timestamp = timestamp,
updatedAt = updatedAt
)
}
}
}
}
// TODO: move this to new interactor or FetchChatRoomsInteractor?
private suspend fun getChatRoomsAsync(name: String? = null): List<ChatRoom> = withContext(CommonPool) {
return@withContext dbManager.chatRoomDao().getAllSync().filter {
if (name == null) {
return@filter true
}
it.chatRoom.name == name || it.chatRoom.fullname == name
}.map {
with(it.chatRoom) {
ChatRoom(
id = id,
subscriptionId = subscriptionId,
type = roomTypeOf(type),
unread = unread,
broadcast = broadcast ?: false,
alert = alert,
fullName = fullname,
name = name ?: "",
favorite = favorite ?: false,
default = isDefault ?: false,
readonly = readonly,
open = open,
lastMessage = null,
archived = false,
status = null,
user = null,
userMentions = userMentions,
client = client,
announcement = null,
description = null,
groupMentions = groupMentions,
roles = null,
topic = null,
lastSeen = this.lastSeen,
timestamp = timestamp,
updatedAt = updatedAt
)
retryDB("getAllSync()") {
dbManager.chatRoomDao().getAllSync().filter {
if (name == null) {
return@filter true
}
it.chatRoom.name == name || it.chatRoom.fullname == name
}.map {
with(it.chatRoom) {
ChatRoom(
id = id,
subscriptionId = subscriptionId,
type = roomTypeOf(type),
unread = unread,
broadcast = broadcast ?: false,
alert = alert,
fullName = fullname,
name = name ?: "",
favorite = favorite ?: false,
default = isDefault ?: false,
readonly = readonly,
open = open,
lastMessage = null,
archived = false,
status = null,
user = null,
userMentions = userMentions,
client = client,
announcement = null,
description = null,
groupMentions = groupMentions,
roles = null,
topic = null,
lastSeen = this.lastSeen,
timestamp = timestamp,
updatedAt = updatedAt
)
}
}
}
}
......@@ -978,7 +1005,12 @@ class ChatRoomPresenter @Inject constructor(
try {
retryIO("joinChat($chatRoomId)") { client.joinChat(chatRoomId) }
val canPost = permissions.canPostToReadOnlyChannels()
view.onJoined(canPost)
dbManager.getRoom(chatRoomId)?.let {
val roomUiModel = roomMapper.map(it, true).copy(
writable = canPost)
view.onJoined(roomUiModel = roomUiModel)
view.onRoomUpdated(roomUiModel = roomUiModel)
}
} catch (ex: RocketChatException) {
Timber.e(ex)
}
......@@ -1148,6 +1180,7 @@ class ChatRoomPresenter @Inject constructor(
}
fun disconnect() {
unsubscribeRoomChanges()
unsubscribeTypingStatus()
if (chatRoomId != null) {
unsubscribeMessages(chatRoomId.toString())
......
......@@ -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)
}
......@@ -8,8 +8,10 @@ import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.provider.MediaStore
import android.text.SpannableStringBuilder
import android.view.KeyEvent
import android.view.LayoutInflater
......@@ -23,6 +25,7 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.core.content.FileProvider
import androidx.core.text.bold
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
......@@ -48,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
......@@ -73,6 +77,8 @@ import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.extension.createImageFile
import chat.rocket.android.util.extensions.getBitmpap
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.socket.model.State
......@@ -86,6 +92,9 @@ import kotlinx.android.synthetic.main.fragment_chat_room.*
import kotlinx.android.synthetic.main.message_attachment_options.*
import kotlinx.android.synthetic.main.message_composer.*
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
......@@ -125,6 +134,7 @@ private const val BUNDLE_CHAT_ROOM_TYPE = "chat_room_type"
private const val BUNDLE_IS_CHAT_ROOM_READ_ONLY = "is_chat_room_read_only"
private const val REQUEST_CODE_FOR_PERFORM_SAF = 42
private const val REQUEST_CODE_FOR_DRAW = 101
private const val REQUEST_CODE_FOR_PERFORM_CAMERA = 102
private const val BUNDLE_CHAT_ROOM_LAST_SEEN = "chat_room_last_seen"
private const val BUNDLE_CHAT_ROOM_IS_SUBSCRIBED = "chat_room_is_subscribed"
private const val BUNDLE_CHAT_ROOM_IS_CREATOR = "chat_room_is_creator"
......@@ -192,6 +202,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
internal val description by lazy { dialogView.findViewById<EditText>(R.id.text_file_description) }
internal val audioVideoAttachment by lazy { dialogView.findViewById<FrameLayout>(R.id.audio_video_attachment) }
internal val textFile by lazy { dialogView.findViewById<TextView>(R.id.text_file_name) }
private var takenPhotoUri: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......@@ -280,12 +291,19 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (resultData != null && resultCode == Activity.RESULT_OK) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_FOR_PERFORM_SAF -> showFileAttachmentDialog(resultData.data)
REQUEST_CODE_FOR_DRAW -> showDrawAttachmentDialog(
resultData.getByteArrayExtra(DRAWING_BYTE_ARRAY_EXTRA_DATA)
)
REQUEST_CODE_FOR_PERFORM_CAMERA -> takenPhotoUri?.let { uri ->
uri.getBitmpap(requireContext())?.let { bitmap ->
presenter.uploadImage(chatRoomId, "image/png", uri, bitmap, "")
}
}
REQUEST_CODE_FOR_PERFORM_SAF -> resultData?.data?.let {
showFileAttachmentDialog(it)
}
REQUEST_CODE_FOR_DRAW -> resultData?.getByteArrayExtra(DRAWING_BYTE_ARRAY_EXTRA_DATA)?.let {
showDrawAttachmentDialog(it)
}
}
}
}
......@@ -380,17 +398,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()
}
......@@ -527,7 +542,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
ui {
button_send.isEnabled = true
text_message.isEnabled = true
clearMessageComposition(true)
}
}
......@@ -769,12 +783,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)
}
}
......@@ -807,8 +820,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
private fun setupMessageComposer(canPost: Boolean) {
if (!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(
......@@ -824,6 +837,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
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
......@@ -880,7 +895,19 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
hideAttachmentOptions()
}
button_files.setOnClickListener {
button_add_reaction.setOnClickListener { _ ->
openEmojiKeyboardPopup()
}
button_take_a_photo.setOnClickListener {
dispatchTakePictureIntent()
handler.postDelayed({
hideAttachmentOptions()
}, 400)
}
button_attach_a_file.setOnClickListener {
handler.postDelayed({
presenter.selectFile()
}, 200)
......@@ -890,10 +917,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}, 400)
}
button_add_reaction.setOnClickListener { _ ->
openEmojiKeyboardPopup()
}
button_drawing.setOnClickListener {
activity?.let { fragmentActivity ->
if (!ImageHelper.canWriteToExternalStorage(fragmentActivity)) {
......@@ -911,6 +934,26 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
private fun dispatchTakePictureIntent() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
// Create the File where the photo should go
val photoFile: File? = try {
activity?.createImageFile()
} catch (ex: IOException) {
Timber.e(ex)
null
}
// Continue only if the File was successfully created
photoFile?.also {
takenPhotoUri = FileProvider.getUriForFile(
requireContext(), "chat.rocket.android.fileprovider", it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, takenPhotoUri)
startActivityForResult(takePictureIntent, REQUEST_CODE_FOR_PERFORM_CAMERA)
}
}
}
private fun getUnfinishedMessage() {
val unfinishedMessage = presenter.getUnfinishedMessage(chatRoomId)
if (unfinishedMessage.isNotBlank()) {
......
......@@ -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
}
......@@ -15,5 +15,8 @@ data class RoomUiModel(
val lastMessage: CharSequence? = null,
val status: UserStatus? = null,
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 {
......
......@@ -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
}
}
......
......@@ -19,6 +19,7 @@ import chat.rocket.android.util.extensions.exhaustive
import chat.rocket.android.util.extensions.removeTrailingSlash
import chat.rocket.android.util.extensions.toEntity
import chat.rocket.android.util.extensions.userId
import chat.rocket.android.util.retryDB
import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.SimpleUser
......@@ -91,12 +92,14 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
}
fun logout() {
database.clearAllTables()
suspend fun logout() {
retryDB("clearAllTables") { database.clearAllTables() }
}
suspend fun getRoom(id: String) = withContext(dbManagerContext) {
chatRoomDao().getSync(id)
retryDB("getRoom($id)") {
chatRoomDao().getSync(id)
}
}
fun processUsersBatch(users: List<User>) {
......@@ -151,7 +154,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
fun updateSelfUser(myself: Myself) {
launch(dbManagerContext) {
val user = userDao().getUser(myself.id)
val user = retryDB("getUser(${myself.id})") { userDao().getUser(myself.id) }
val entity = user?.copy(
name = myself.name ?: user.name,
username = myself.username ?: user.username,
......@@ -335,7 +338,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
private suspend fun updateRoom(data: Room): ChatRoomEntity? {
return chatRoomDao().getSync(data.id)?.let { current ->
return retryDB("getChatRoom(${data.id})") { chatRoomDao().getSync(data.id) }?.let { current ->
with(data) {
val chatRoom = current.chatRoom
......@@ -376,7 +379,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
context.getString(R.string.msg_sent_attachment)
private suspend fun updateSubscription(data: Subscription): ChatRoomEntity? {
return chatRoomDao().getSync(data.roomId)?.let { current ->
return retryDB("getRoom(${data.roomId}") { chatRoomDao().getSync(data.roomId) }?.let { current ->
with(data) {
val userId = if (type is RoomType.DirectMessage) {
......@@ -551,39 +554,42 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
}
private fun findUser(userId: String): String? = userDao().findUser(userId)
private suspend fun findUser(userId: String): String? =
retryDB("findUser($userId)") { userDao().findUser(userId) }
private fun doOperation(operation: Operation) {
when (operation) {
is Operation.ClearStatus -> userDao().clearStatus()
is Operation.UpdateRooms -> {
Timber.d("Running ChatRooms transaction: remove: ${operation.toRemove} - insert: ${operation.toInsert} - update: ${operation.toUpdate}")
private suspend fun doOperation(operation: Operation) {
retryDB(description = "doOperation($operation)") {
when (operation) {
is Operation.ClearStatus -> userDao().clearStatus()
is Operation.UpdateRooms -> {
Timber.d("Running ChatRooms transaction: remove: ${operation.toRemove} - insert: ${operation.toInsert} - update: ${operation.toUpdate}")
chatRoomDao().update(operation.toInsert, operation.toUpdate, operation.toRemove)
}
is Operation.InsertRooms -> {
chatRoomDao().insertOrReplace(operation.chatRooms)
}
is Operation.CleanInsertRooms -> {
chatRoomDao().cleanInsert(operation.chatRooms)
}
is Operation.InsertUsers -> {
val time = measureTimeMillis { userDao().upsert(operation.users) }
Timber.d("Upserted users batch(${operation.users.size}) in $time MS")
}
is Operation.InsertUser -> {
userDao().insert(operation.user)
}
is Operation.UpsertUser -> {
userDao().upsert(operation.user)
}
is Operation.InsertMessages -> {
messageDao().insert(operation.list)
}
is Operation.SaveLastSync -> {
messageDao().saveLastSync(operation.sync)
}
}.exhaustive
chatRoomDao().update(operation.toInsert, operation.toUpdate, operation.toRemove)
}
is Operation.InsertRooms -> {
chatRoomDao().insertOrReplace(operation.chatRooms)
}
is Operation.CleanInsertRooms -> {
chatRoomDao().cleanInsert(operation.chatRooms)
}
is Operation.InsertUsers -> {
val time = measureTimeMillis { userDao().upsert(operation.users) }
Timber.d("Upserted users batch(${operation.users.size}) in $time MS")
}
is Operation.InsertUser -> {
userDao().insert(operation.user)
}
is Operation.UpsertUser -> {
userDao().upsert(operation.user)
}
is Operation.InsertMessages -> {
messageDao().insert(operation.list)
}
is Operation.SaveLastSync -> {
messageDao().saveLastSync(operation.sync)
}
}.exhaustive
}
}
}
......
......@@ -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
......
......@@ -62,10 +62,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
setContentView(R.layout.activity_main)
refreshPushToken()
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
println("ChatRoomId: $chatRoomId")
presenter.clearNotificationsForChatroom(chatRoomId)
presenter.connect()
......
......@@ -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
......
......@@ -29,7 +29,6 @@ import chat.rocket.core.internal.rest.setAvatar
import chat.rocket.core.internal.rest.updateProfile
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import java.lang.Exception
import java.util.*
import javax.inject.Inject
......@@ -58,20 +57,17 @@ class ProfilePresenter @Inject constructor(
) {
private val serverUrl = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(serverUrl)
private val myselfId = userHelper.user()?.id ?: ""
private var myselfName = userHelper.user()?.name ?: ""
private var myselfUsername = userHelper.username() ?: ""
private var myselfEmailAddress = userHelper.user()?.emails?.getOrNull(0)?.address ?: ""
private val user = userHelper.user()
fun loadUserProfile() {
launchUI(strategy) {
view.showLoading()
try {
view.showProfile(
serverUrl.avatarUrl(myselfUsername),
myselfName,
myselfUsername,
myselfEmailAddress
serverUrl.avatarUrl(user?.username ?: ""),
user?.name ?: "",
user?.username ?: "",
user?.emails?.getOrNull(0)?.address ?: ""
)
} catch (exception: RocketChatException) {
view.showMessage(exception)
......@@ -85,14 +81,16 @@ class ProfilePresenter @Inject constructor(
launchUI(strategy) {
view.showLoading()
try {
retryIO { client.updateProfile(myselfId, email, name, username) }
myselfEmailAddress = email
myselfName = name
myselfUsername = username
view.showProfileUpdateSuccessfullyMessage()
loadUserProfile()
user?.id?.let { id ->
retryIO { client.updateProfile(id, email, name, username) }
view.showProfileUpdateSuccessfullyMessage()
view.showProfile(
serverUrl.avatarUrl(user.username ?: ""),
name,
username,
email
)
}
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
......@@ -117,7 +115,7 @@ class ProfilePresenter @Inject constructor(
uriInteractor.getInputStream(uri)
}
}
view.reloadUserAvatar(serverUrl.avatarUrl(myselfUsername))
user?.username?.let { view.reloadUserAvatar(it) }
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
......@@ -144,7 +142,8 @@ class ProfilePresenter @Inject constructor(
byteArray?.inputStream()
}
}
view.reloadUserAvatar(serverUrl.avatarUrl(myselfUsername))
user?.username?.let { view.reloadUserAvatar(it) }
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
......@@ -161,8 +160,10 @@ class ProfilePresenter @Inject constructor(
launchUI(strategy) {
view.showLoading()
try {
retryIO { client.resetAvatar(myselfId) }
view.reloadUserAvatar(serverUrl.avatarUrl(myselfUsername))
user?.id?.let { id ->
retryIO { client.resetAvatar(id) }
}
user?.username?.let { view.reloadUserAvatar(it) }
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
......
......@@ -217,7 +217,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
hideUpdateAvatarOptions()
}
button_take_photo.setOnClickListener {
button_take_a_photo.setOnClickListener {
dispatchTakePicture(REQUEST_CODE_FOR_PERFORM_CAMERA)
hideUpdateAvatarOptions()
}
......
......@@ -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.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
......@@ -6,14 +6,24 @@
android:orientation="vertical">
<Button
android:id="@+id/button_files"
android:id="@+id/button_take_a_photo"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="20dp"
android:drawableStart="@drawable/ic_photo_camera_black_24dp"
android:gravity="start|center"
android:text="@string/action_take_a_photo" />
<Button
android:id="@+id/button_attach_a_file"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="20dp"
android:drawableStart="@drawable/ic_files_24dp"
android:gravity="start|center"
android:text="@string/action_files" />
android:text="@string/action_attach_a_files" />
<Button
android:id="@+id/button_drawing"
......
......@@ -16,14 +16,14 @@
android:text="@string/action_select_photo_from_gallery" />
<Button
android:id="@+id/button_take_photo"
android:id="@+id/button_take_a_photo"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_photo_camera_black_24dp"
android:drawablePadding="20dp"
android:gravity="start|center"
android:text="@string/action_take_photo" />
android:text="@string/action_take_a_photo" />
<Button
android:id="@+id/button_reset_avatar"
......
......@@ -39,7 +39,7 @@
<string name="action_create_channel">Erstelle Raum</string>
<string name="action_create">Erstelle</string>
<string name="action_logout">Abmelden</string>
<string name="action_files">Dateien</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_confirm_password">Bestätige Passwort Änderung</string>
<string name="action_join_chat">Trete Chat bei</string>
<string name="action_add_account">Erstelle Account</string>
......@@ -50,7 +50,7 @@
<string name="action_drawing">Zeichnung</string>
<string name="action_save_to_gallery">Sichern in Gallerie</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_photo">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_a_photo">Take a photo from galery</string> <!-- TODO Add translation -->
<string name="action_reset_avatar">Reset avatar</string> <!-- TODO Add translation -->
<string name="action_connect_server">Connect with a server</string> <!-- TODO Add translation -->
<string name="action_join_community">Join in the community</string> <!-- TODO Add translation -->
......
......@@ -38,7 +38,7 @@
<string name="action_create_channel">Crear canal</string>
<string name="action_create">Create</string>
<string name="action_logout">Cerrar sesión</string>
<string name="action_files">Archivos</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_confirm_password">Confirmar cambio de contraseña</string>
<string name="action_join_chat">Unirse al chat</string>
<string name="action_add_account">Añadir cuenta</string>
......@@ -49,7 +49,7 @@
<string name="action_drawing">Dibujo</string>
<string name="action_save_to_gallery">Guardar en la galería</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_photo">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_a_photo">Take a photo</string> <!-- TODO Add translation -->
<string name="action_reset_avatar">Reset avatar</string> <!-- TODO Add translation -->
<string name="action_connect_server">Connect with a server</string> <!-- TODO Add translation -->
<string name="action_join_community">Join in the community</string> <!-- TODO Add translation -->
......
......@@ -39,7 +39,7 @@
<string name="action_create_channel">Créer salon</string>
<string name="action_create">Créer</string>
<string name="action_logout">Se déconnecter</string>
<string name="action_files">Fichiers</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_confirm_password">Confirmer le mot de passe</string>
<string name="action_join_chat">Rejoignez le chat</string>
<string name="action_add_account">Ajouter un compte</string>
......@@ -50,7 +50,7 @@
<string name="action_drawing">Dessin</string>
<string name="action_save_to_gallery">Sauvegarder vers la gallerie</string>
<string name="action_select_photo_from_gallery">Sélectionner depuis la gallerie</string>
<string name="action_take_photo">Prendre une photo</string>
<string name="action_take_a_photo">Prendre une photo</string>
<string name="action_reset_avatar">Réinitialiser l\'avatar</string>
<string name="action_connect_server">Connect with a server</string> <!-- TODO Add translation -->
<string name="action_join_community">Join in the community</string> <!-- TODO Add translation -->
......
......@@ -39,7 +39,7 @@
<string name="action_create_channel">चैनल बनाएं</string>
<string name="action_create">बनाएं</string>
<string name="action_logout">लोग आउट करें</string>
<string name="action_files">फ़ाइलें</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_confirm_password">पासवर्ड परिवर्तन की पुष्टि करें</string>
<string name="action_join_chat">चैट में शामिल हों</string>
<string name="action_add_account">खाता जोड़ो</string>
......@@ -50,7 +50,7 @@
<string name="action_save_to_gallery">गैलरी में सहेजें</string>
<string name="action_drawing">चित्रकारी</string>
<string name="action_select_photo_from_gallery">गैलरी से फोटो का चयन करें</string>
<string name="action_take_photo">फोटो खेचिये</string>
<string name="action_take_a_photo">फोटो खेचिये</string>
<string name="action_reset_avatar">अवतार रीसेट करें</string>
<string name="action_connect_server">सर्वर से कनेक्ट करें</string>
<string name="action_join_community">समुदाय में शामिल हों</string>
......
<resources>
<!-- Titles -->
<string name="title_sign_in_your_server">Accedi al tuo server</string>
<string name="title_log_in">Accesso</string>
<string name="title_share_the_app">Condividi la App</string>
<string name="title_register_username">Registra nome utente</string>
<string name="title_reset_password">Annullare password</string>
<string name="title_sign_up">Iscrizione</string>
<string name="title_authentication">Autenticazione</string>
<string name="title_legal_terms">Termini Legali</string>
<string name="title_chats">Stanze</string>
<string name="title_profile">Profilo</string>
<string name="title_members">Membri</string>
<string name="title_counted_members">(%d) Membri</string>
<string name="title_settings">Parametri</string>
<string name="title_preferences">Preferenze</string>
<string name="title_change_password">Cambiare Password</string>
<string name="title_rate_us">Valutaci</string>
<string name="title_admin_panel">Pannello di Amministrazione</string>
<string name="title_password">Password</string>
<string name="title_update_profile">Aggiorna Profilo</string>
<string name="title_about">Informazioni</string>
<string name="title_create_channel">Crea Canale</string>
<string name="title_are_you_sure">Sei sicuro che vuoi uscire ?</string>
<string name="title_channel_details">Channel Details</string> <!-- TODO add translation -->
<string name="title_topic">Sujet</string> <!-- TODO add translation -->
<string name="title_announcement">Annonce</string> <!-- TODO add translation -->
<string name="title_description">La description</string> <!-- TODO add translation -->
<!-- Actions -->
<string name="action_connect">Collega</string>
<string name="action_use_this_username">Usa questo nome utente</string>
<string name="action_terms_of_service">Termini di Servizio</string>
<string name="action_privacy_policy">Politica sulla Riservatezza</string>
<string name="action_search">Cerca</string>
<string name="action_update">Aggiorna</string>
<string name="action_settings">Parametri</string>
<string name="action_create_channel">Crea canale</string>
<string name="action_create">Crea</string>
<string name="action_logout">Disconnettersi</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO ADD translation -->
<string name="action_confirm_password">Conferma Cambio Password</string>
<string name="action_join_chat">Iscriviti alla stanza</string>
<string name="action_add_account">Aggiungi utente</string>
<string name="action_online">In linea</string>
<string name="action_away">Lontano</string>
<string name="action_busy">Occupato</string>
<string name="action_invisible">Invisibile</string>
<string name="action_drawing">Disegno</string>
<string name="action_save_to_gallery">Salva in galleria</string>
<string name="action_select_photo_from_gallery">Seleziona foto dalla galleria</string>
<string name="action_take_a_photo">Take a photo</string> <!-- TODO ADD translation -->
<string name="action_reset_avatar">Reimposta avatar</string>
<string name="action_connect_server">Collegati a un server</string>
<string name="action_join_community">Entra nella community</string>
<string name="action_create_server">Crea un nuovo server</string>
<string name="action_register">Registra</string>
<string name="action_confirm">Conferma</string>
<string name="action_delete_account">Elimina utente</string>
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferenze</item>
<item name="item_password">Cambiare Password</item>
<item name="item_share_app">Condividi App</item>
<item name="item_rate_us">Valutaci</item>
<item name="item_password">Informazioni</item>
</string-array>
<!-- Regular information messages -->
<string name="msg_generic_error">Mi dispiace, si è verificato un errore, per favore riprova</string>
<string name="msg_no_data_to_display">Nessun dato da visualizzare</string>
<string name="msg_check_this_out">Controllalo</string>
<string name="msg_share_using">Condividi utilizzando</string>
<string name="msg_profile_update_successfully">Aggiornamento del profilo con successo</string>
<string name="msg_username">Nome utente</string>
<string name="msg_username_or_email">Nome utente o e-mail</string>
<string name="msg_password">Password</string>
<string name="msg_name">Nome</string>
<string name="msg_email">E-mail</string>
<string name="msg_avatar_url">Avatar URL</string>
<string name="msg_or_continue_using_social_accounts">O continuare utilizzando un account social</string>
<string name="msg_new_user">Nuovo utente? %1$s</string>
<string name="msg_forgot__your_password">Hai dimenticato la tua password?</string>
<string name="msg_reset">Reimposta</string>
<string name="msg_check_your_email_to_reset_your_password">E-mail inviata! Controlla la tua casella di posta per reimpostare la password.</string>
<string name="msg_invalid_email">Per favore digita una e-mail valida</string>
<string name="msg_new_user_agreement">Procedendo accetti i nostri\n%1$s e %2$s</string>
<string name="msg_yesterday">Ieri</string>
<string name="msg_today">Oggi</string>
<string name="msg_message">Messaggio</string>
<string name="msg_this_room_is_read_only">Questa stanza è di sola lettura</string>
<string name="msg_invalid_2fa_code">Invalido Codice 2FA non valido</string>
<string name="msg_invalid_file">Documento non valido</string>
<string name="msg_invalid_server_url">URL del server non valido</string>
<string name="msg_content_description_log_in_using_facebook">Accedi usando Facebook</string>
<string name="msg_content_description_log_in_using_github">Accedi usando Github</string>
<string name="msg_content_description_log_in_using_google">Accedi usando Google</string>
<string name="msg_content_description_log_in_using_linkedin">Accedi usando Linkedin</string>
<string name="msg_content_description_log_in_using_meteor">Accedi usando Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Accedi usando Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Accedi usando Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Accedi usando WordPress</string>
<string name="msg_content_description_send_message">Invia messaggio</string>
<string name="msg_content_description_show_more_login_options">Mostra ulteriori opzioni di accesso</string>
<string name="msg_content_description_show_attachment_options">Mostra opzioni per allegati</string>
<string name="msg_you">Tu</string>
<string name="msg_unknown">Sconosciuto</string>
<string name="msg_email_address">Indirizzo e-mail</string>
<string name="msg_utc_offset">Fuso orario UTC</string>
<string name="msg_new_password">Inserire Nuova Password</string>
<string name="msg_confirm_password">Confermare Nuova Password</string>
<string name="msg_channel_name">Nome Canale</string>
<string name="msg_search">Ricerca</string>
<string name="msg_unread_messages">Messaggi non letti</string>
<string name="msg_preview_video">Video</string>
<string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">Documento</string>
<string name="msg_no_messages_yet">Nessun nuovo messaggio</string>
<string name="msg_ok">OK</string>
<string name="msg_update_app_version_in_order_to_continue">Versione server non aggiornata. Si prega di contattare l\'amministratore del server per aggiornare la versione del server per continuare.</string>
<string name="msg_ver_not_recommended">Sembra che la versione del tuo server sia inferiore alla versione consigliata %1$s.\nÈ ancora possibile accedere ma è possibile che si verifichino comportamenti imprevisti.</string>
<string name="msg_ver_not_minimum">Sembra che la versione del tuo server sia inferiore alla versione minima richiesta %1$s.\nSi prega di aggiornare il server per accedere!</string>
<string name="msg_no_chat_title">Nessun messaggio chat</string>
<string name="msg_no_chat_description">Inizia a conversare per vedere i tuoi\nmessagggi qui.</string>
<string name="msg_cancel">CANCELLARE</string>
<string name="msg_http_insecure">Quando si utilizza HTTP, ci si connette a un server non sicuro. Non ti consigliamo di farlo.</string>
<string name="msg_error_checking_server_version">Si è verificato un errore durante il controllo della versione del server, riprovare</string>
<string name="msg_invalid_server_protocol">Il protocollo selezionato non è accettato da questo server, prova a utilizzare HTTPS</string>
<string name="msg_image_saved_successfully">L\'immagine è stata salvata nella galleria</string>
<string name="msg_image_saved_failed">Impossibile salvare l\'immagine</string>
<string name="msg_edited">(modificato)</string>
<string name="msg_and">\u0020e\u0020</string>
<string name="msg_is_typing">\u0020sta scrivendo…</string>
<string name="msg_are_typing">\u0020stanno scrivendo…</string>
<string name="msg_several_users_are_typing">Alcuni utenti stanno scrivendo…</string>
<string name="msg_no_search_found">Nessun risultato trovato</string>
<string name="msg_log_out">Disconnessione…</string>
<string name="msg_upload_file">Caricamento documento</string>
<string name="msg_file_description">Descrizione documento</string>
<string name="msg_send">Inviare</string>
<string name="msg_sent_attachment">Inviato allegato</string>
<string name="msg_welcome_to_rocket_chat">Benvenuto in Rocket.Chat </string>
<string name="msg_team_communication">Team Communication</string>
<string name="msg_login_with_email">Accedi con <b>e-mail</b></string>
<string name="msg_create_account">Crea un utente</string>
<string name="msg_continue_with_facebook">Continua con <b>Facebook</b></string>
<string name="msg_continue_with_github">Continua con <b>Github</b></string>
<string name="msg_continue_with_google">Continua con <b>Google</b></string>
<string name="msg_continue_with_linkedin">Continua con <b>Linkedin</b></string>
<string name="msg_continue_with_gitlab">Continua con <b>GitLab</b></string>
<string name="msg_continue_with_wordpress">Continua con <b>WordPress</b></string>
<string name="msg_two_factor_authentication">Autenticazione a 2 fattori (2FA)</string>
<string name="msg__your_2fa_code">Qual è il tuo codice 2FA ?</string>
<string name="msg_permalink_copied">Permalink copiato</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s ha reagito con %2$s</item>
<item quantity="other">%1$s ha reagito con %2$s</item>
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Privato</string>
<string name="msg_public_channel">Pubblico</string>
<string name="msg_private_channel_description">Solo tu e i membri invitati possono accedere a questo canale</string>
<string name="msg_public_channel_description">Tutti possono accedere a questo canale</string>
<string name="msg_ready_only_channel">Canale a sola lettura</string>
<string name="msg_ready_only_channel_description">Solo l\'amministratore può scrivere nuovi messaggi</string>
<string name="msg_invite_members">Invita i partecipanti per il canale</string>
<string name="msg_member_already_added">Hai già selezionato questo utente</string>
<string name="msg_member_not_found">Partecipante non trovato</string>
<string name="msg_channel_created_successfully">Canale creato con successo</string>
<string name="msg_message_copied">Messaggio copiato</string>
<string name="msg_delete_message">Cancellare Messaggio</string>
<string name="msg_delete_description">Sei sicuro di voler eliminare questo messaggio ?</string>
<string name="msg_view_more">vedere di più</string>
<string name="msg_view_less">vedere di meno</string>
<string name="msg_muted_on_this_channel">Sei disattivato su questo canale</string>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">Tracciamento Analitico</string>
<string name="msg_send_analytics_tracking">Invia statistiche anonime per migliorare questa app</string>
<string name="msg_do_not_send_analytics_tracking">Non inviare statiche anonime per migliorare questa app</string>
<string name="msg_not_applicable_since_it_is_a_foss_version">Non applicabile poiché è una versione FOSS</string>
<!-- System messages -->
<string name="message_room_name_changed">Nome della camera cambiato in: %1$s da %2$s</string>
<string name="message_user_added_by">Utente %1$s aggiunto da %2$s</string>
<string name="message_user_removed_by">Utente %1$s rimosso da %2$s</string>
<string name="message_user_left">Ha lasciato il canale.</string>
<string name="message_user_joined_channel">È entrato nel canale.</string>
<string name="message_welcome">Benvenuto %s</string>
<string name="message_removed">Messaggio rimosso</string>
<string name="message_pinned">Messaggio segnato:</string>
<string name="message_muted">Utente %1$s disattivato da %2$s</string>
<string name="message_unmuted">Utente %1$s riattivato da %2$s</string>
<string name="message_role_add">%1$s ha il ruolo %2$s aggiunto da %3$s</string>
<string name="message_role_removed">%1$s ha il ruolo %2$s rimosso da %3$s</string>
<string name="message_credentials_saved_successfully">Credenziali salvate con successo</string>
<!-- Message actions -->
<string name="action_msg_reply">Rispondi</string>
<string name="action_msg_info">Informazioni messaggio</string>
<string name="action_msg_edit">Modifica</string>
<string name="action_msg_copy">Copia</string>
<string name="action_msg_quote">Citazione</string>
<string name="action_msg_delete">Cancella</string>
<string name="action_msg_pin">Messaggio importante</string>
<string name="action_msg_unpin">Messaggio normale</string>
<string name="action_msg_star">Messaggio preferito</string>
<string name="action_msg_unstar">Messaggio normale</string>
<string name="action_msg_share">Condividi</string>
<string name="action_title_editing">Modifica messaggio</string>
<string name="action_msg_add_reaction">Aggiungi reazione</string>
<string name="action_msg_copy_permalink">Copia permalink</string>3
<!-- Permission messages -->
<string name="permission_editing_not_allowed">La modifica non è consentita</string>
<string name="permission_deleting_not_allowed">La cancellazione non è consentita</string>
<string name="permission_pinning_not_allowed">Segnare come importante non consentito</string>
<string name="permission_starring_not_allowed">Segnare come preferito non consentito</string>
<!-- Search message -->
<string name="title_search_message">Cerca messaggio</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Stanza preferita</string>
<string name="title_unfavorite_chat">Stanza normale</string>
<!-- Members List -->
<string name="title_members_list">Partecipanti</string>
<!-- Mentions -->
<string name="msg_mentions">Menziona</string>
<string name="msg_no_mention">Togli menzione</string>
<string name="msg_all_the_mentions_appear_here">Tutte le menzioni\nappaiono qui</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">Messaggi importanti</string>
<string name="no_pinned_messages">Nessun messaggio importante</string>
<string name="no_pinned_description">Tutti i messaggi importanti\nappaiono qui</string>
<!-- Favorite Messages -->
<string name="title_favorite_messages">Messaggi preferiti</string>
<string name="no_favorite_messages">Nessun messaggio preferito</string>
<string name="no_favorite_description">Tutti i messaggi preferiti\nappaiono qui</string>
<!-- Files -->
<string name="title_files">Documenti</string>
<string name="title_files_total">Totale Documenti (%d)</string>
<string name="msg_no_files">Nessun documento</string>
<string name="msg_all_files_appear_here">Tutti i documenti appaiono qui</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">Il documento di dimensione %1$d bytes eccede la massima dimensione di caricamento di %2$d bytes</string>
<!-- Socket status -->
<string name="status_connected">Collegato</string>
<string name="status_disconnected">Non collegato</string>
<string name="status_connecting">Collegamento</string>
<string name="status_authenticating">Autenticazione</string>
<string name="status_disconnecting">Disconessione</string>
<string name="status_waiting">Collegamento fra %d secondi</string>
<!--Suggestions-->
<string name="suggest_all_description">Notifica tutti in questa stanza</string>
<string name="suggest_here_description">Notifica gli utenti attivi di questa stanza</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">Mostra ༼ つ ◕_◕ ༽つ prima del tuo messaggio</string>
<string name="Slash_LennyFace_Description">Mostra ( ͡° ͜ʖ ͡°) dopo il tuo messaggio</string>
<string name="Slash_Shrug_Description">Mostra ¯\_(ツ)_/¯ dopo il tuo messaggio</string>
<string name="Slash_Tableflip_Description">Mostra (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Mostra ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Crea un nuovo canale</string>
<string name="Show_the_keyboard_shortcut_list">Mostra l\'elenco delle scorciatoie da tastiera</string>
<string name="Invite_user_to_join_channel_all_from">Invita tutti gli utenti da [#channel] a unirsi a questo canale</string>
<string name="Invite_user_to_join_channel_all_to">Invita tutti gli utenti da questo canale a partecipare a [#channel]</string>
<string name="Archive">Archivia</string>
<string name="Remove_someone_from_room">Rimuovi qualcuno dalla stanza</string>
<string name="Leave_the_current_channel">Lascia il canale corrente</string>
<string name="Displays_action_text">Visualizza il testo dell\'azione</string>
<string name="Direct_message_someone">Invia un messaggio diretto a qualcuno</string>
<string name="Mute_someone_in_room">Disattiva qualcuno nella stanza</string>
<string name="Unmute_someone_in_room">Riattiva qualcuno nella stanza</string>
<string name="Invite_user_to_join_channel">Invita un utente a unirsi a questo canale</string>
<string name="Unarchive">Non archiviare</string>
<string name="Join_the_given_channel">Unisciti al canale indicato</string>
<string name="Guggy_Command_Description">Genera una gif in base al testo fornito</string>
<string name="Slash_Topic_Description">Imposta argomento</string>
<!-- Emoji message-->
<string name="msg_no_recent_emoji">No recent emojis</string>
<string name="alert_title_default_skin_tone">Default skin tone</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Ordinare</string>
<string name="dialog_sort_title">Ordinare per</string>
<string name="dialog_sort_by_alphabet">Alfabeto</string>
<string name="dialog_sort_by_activity">Attività</string>
<string name="dialog_group_by_type">Raggruppa per tipo</string>
<string name="dialog_group_favourites">Raggruppa preferiti</string>
<string name="dialog_button_done">Fatto</string>
<string name="chatroom_header">Intestazione</string>
<!--ChatRooms Headers-->
<string name="header_channel">Canali</string>
<string name="header_private_groups">Gruppi Privati</string>
<string name="header_direct_messages">Messaggi Diretti</string>
<string name="header_live_chats">Stanza in diretta</string>
<string name="header_unknown">Sconosciuto</string>
<!--Notifications-->
<string name="share_label">Modifica messaggio condiviso</string>
<string name="notif_action_reply_hint">Risposta</string>
<string name="notif_error_sending">La risposta è fallita. Per favore riprova.</string>
<string name="notif_success_sending">Messaggio inviato a %1$s!</string>
<string name="read_by">Letto da</string>
<string name="message_information_title">Informazioni Messaggio</string>
<string name="message_room_changed_privacy">Il tipo di stanza è cambiato in: %1$s da %2$s</string>
</resources>
......@@ -24,7 +24,7 @@
<string name="title_update_profile">プロフィールの更新</string>
<string name="title_about">About</string>
<string name="title_create_channel">新しいチャネルを作成</string>
<string name="title_are_you_sure">本気ですか?</string>
<string name="title_are_you_sure">本気ですか?</string>
<string name="title_channel_details">Channel Details</string> <!-- TODO add translation -->
<string name="title_topic">トピック</string>
<string name="title_announcement">発表</string>
......@@ -41,7 +41,7 @@
<string name="action_create_channel">チャンネル作成</string>
<string name="action_create">作ります</string>
<string name="action_logout">ログアウト</string>
<string name="action_files">ファイル</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_confirm_password">変更したパスワードの確認</string>
<string name="action_join_chat">チャットに参加</string>
<string name="action_add_account">サーバーの追加</string>
......@@ -52,7 +52,7 @@
<string name="action_drawing">絵を描く</string>
<string name="action_save_to_gallery">ギャラリーに保存</string>
<string name="action_select_photo_from_gallery">ギャラリーの写真を選択</string>
<string name="action_take_photo">写真を撮る</string>
<string name="action_take_a_photo">写真を撮る</string>
<string name="action_reset_avatar">アバターをリセット</string>
<string name="action_connect_server">サーバーに接続</string>
<string name="action_join_community">コミュニティに参加</string>
......
......@@ -39,7 +39,7 @@
<string name="action_create_channel">Criar chat</string>
<string name="action_create">Criar</string>
<string name="action_logout">Sair</string>
<string name="action_files">Arquivos</string>
<string name="action_attach_a_files">Anexar um arquivo</string>
<string name="action_confirm_password">Confirme a nova senha</string>
<string name="action_join_chat">Entrar no Chat</string>
<string name="action_add_account">Adicionar conta</string>
......@@ -50,7 +50,7 @@
<string name="action_drawing">Desenhando</string>
<string name="action_save_to_gallery">Salvar na galeria</string>
<string name="action_select_photo_from_gallery">Escolher foto da galeria</string>
<string name="action_take_photo">Tirar foto</string>
<string name="action_take_a_photo">Tirar uma foto</string>
<string name="action_reset_avatar">Resetar avatar</string>
<string name="action_connect_server">Conectar com um servidor</string>
<string name="action_join_community">Junte-se à comunidade</string>
......
......@@ -39,7 +39,7 @@
<string name="action_create_channel">Создать канал</string>
<string name="action_create">Создать</string>
<string name="action_logout">Выйти</string>
<string name="action_files">Файлы</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_confirm_password">Подтверждение изменения пароля</string>
<string name="action_join_chat">Присоединиться к чату</string>
<string name="action_add_account">Добавить аккаунт</string>
......@@ -50,7 +50,7 @@
<string name="action_drawing">Рисунок</string>
<string name="action_save_to_gallery">Сохранить в галерею</string>
<string name="action_select_photo_from_gallery">Выбрать из галереи</string>
<string name="action_take_photo">Сделать снимок</string>
<string name="action_take_a_photo">Сделать снимок</string>
<string name="action_reset_avatar">Восстановить аватар</string>
<string name="action_connect_server">Соединиться с сервером</string>
<string name="action_join_community">Сообщество</string>
......
......@@ -39,7 +39,7 @@
<string name="action_create_channel">Yeni Kanal Oluştur</string>
<string name="action_create">Oluştur</string>
<string name="action_logout">Çıkış Yap</string>
<string name="action_files">Dosyalar</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_confirm_password">Şifre Değişikliğini Onaylayın</string>
<string name="action_join_chat">Sohbete Bağlan</string>
<string name="action_add_account">Hesap Ekle</string>
......@@ -50,7 +50,7 @@
<string name="action_drawing">Çizim</string>
<string name="action_save_to_gallery">Galeriye kaydet</string>
<string name="action_select_photo_from_gallery">Galeriden resim seç</string>
<string name="action_take_photo">Fotoğraf çek</string>
<string name="action_take_a_photo">Fotoğraf çek</string>
<string name="action_reset_avatar">Avatarı kaldır</string>
<string name="action_connect_server">Connect with a server</string> <!-- TODO Add translation -->
<string name="action_join_community">Join in the community</string> <!-- TODO Add translation -->
......
......@@ -39,7 +39,7 @@
<string name="action_create_channel">Створити канал</string>
<string name="action_create">Створити</string>
<string name="action_logout">Вийти</string>
<string name="action_files">Файли</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_confirm_password">Підтвердження зміни пароля</string>
<string name="action_join_chat">Приєднатися до чату</string>
<string name="action_add_account">Додати аккаунт</string>
......@@ -50,7 +50,7 @@
<string name="action_drawing">Малюнок</string>
<string name="action_save_to_gallery">Зберегти до галереї</string>
<string name="action_select_photo_from_gallery">Вибрати з галереї</string>
<string name="action_take_photo">Зробити знімок</string>
<string name="action_take_a_photo">Зробити знімок</string>
<string name="action_reset_avatar">Відновити аватар</string>
<string name="action_connect_server">Connect with a server</string> <!-- TODO Add translation -->
<string name="action_join_community">Join in the community</string> <!-- TODO Add translation -->
......
......@@ -51,7 +51,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="action_create_channel">Create channel</string>
<string name="action_create">Create</string>
<string name="action_logout">Logout</string>
<string name="action_files">Files</string>
<string name="action_attach_a_files">Attach a file</string>
<string name="action_confirm_password">Confirm Password Change</string>
<string name="action_join_chat">Join Chat</string>
<string name="action_add_account">Add account</string>
......@@ -62,7 +62,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="action_drawing">Drawing</string>
<string name="action_save_to_gallery">Save to gallery</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string>
<string name="action_take_photo">Take photo</string>
<string name="action_take_a_photo">Take a photo</string>
<string name="action_reset_avatar">Reset avatar</string>
<string name="action_connect_server">Connect with a server</string>
<string name="action_join_community">Join in the community</string>
......
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="rocket_chat_images"
path="Android/data/chat.rocket.android/files/Pictures" />
</paths>
\ No newline at end of file
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)
......
<?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"
......
......@@ -2,13 +2,19 @@ package chat.rocket.android.util.extension
import android.content.Intent
import android.graphics.Bitmap
import android.os.Environment
import android.provider.MediaStore
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.withContext
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.IOException
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
/**
* Compress a [Bitmap] image.
......@@ -104,4 +110,16 @@ fun Fragment.dispatchTakePicture(requestCode: Int) {
if (takePictureIntent.resolveActivity(context?.packageManager) != null) {
startActivityForResult(takePictureIntent, requestCode)
}
}
@Throws(IOException::class)
fun FragmentActivity.createImageFile(): File {
// Create an image file name
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"PNG_${timeStamp}_", /* prefix */
".png", /* suffix */
storageDir /* directory */
)
}
\ No newline at end of file
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