From 23061bbff65a291944e39564846dd4312a920221 Mon Sep 17 00:00:00 2001 From: Mahdi Nouri Date: Fri, 21 Dec 2018 01:58:28 +0330 Subject: [PATCH 1/2] Send message on SHIFT + ENTER key event --- .../chatroom/presentation/ChatRoomPresenter.kt | 15 +++++++++++++++ .../android/chatroom/presentation/ChatRoomView.kt | 3 ++- .../android/chatroom/ui/ChatRoomFragment.kt | 15 +++++++++------ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt index 0258ddd0f7..ef17999df2 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt @@ -2,6 +2,7 @@ package chat.rocket.android.chatroom.presentation import android.graphics.Bitmap import android.net.Uri +import android.view.KeyEvent import chat.rocket.android.R import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.event.SubscriptionTypeEvent @@ -1312,4 +1313,18 @@ class ChatRoomPresenter @Inject constructor( fun getUnfinishedMessage(): String? { return localRepository.get(draftKey) } + + fun onKeyEvent(keyCode: Int, keyEvent: KeyEvent): Boolean { + // TODO : use physical keyboard abstraction for better key event handling + when (keyCode) { + KeyEvent.KEYCODE_ENTER -> { + // TODO : check user's preference for shift or control key + if (keyEvent.isShiftPressed) { + view.sendMessage() + } + } + } + return true + } + } diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt index 15f241b2cb..572917f7d1 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomView.kt @@ -9,7 +9,6 @@ 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 -import chat.rocket.core.model.ChatRoom interface ChatRoomView : LoadingView, MessageView { @@ -42,6 +41,8 @@ interface ChatRoomView : LoadingView, MessageView { */ fun sendMessage(text: String) + fun sendMessage() + /** * Shows the username(s) of the user(s) who is/are typing in the chat room. * diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt index f8c851fd37..df08ab4ff8 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt @@ -316,6 +316,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) text_message.addTextChangedListener(EmojiKeyboardPopup.EmojiTextWatcher(text_message)) + text_message.setOnKeyListener { v, keyCode, event -> presenter.onKeyEvent(keyCode, event) } } override fun onDestroyView() { @@ -461,7 +462,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } - override fun sendMessage(text: String) { ui { if (!text.isBlank()) { @@ -474,6 +474,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } + override fun sendMessage() { + var textMessage = citation ?: "" + textMessage += text_message.textContent + sendMessage(textMessage) + clearMessageComposition(true) + } + override fun showTypingStatus(usernameList: List) { ui { when (usernameList.size) { @@ -532,7 +539,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } } - override fun clearMessageComposition(deleteMessage: Boolean) { ui { citation = null @@ -857,10 +863,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR } button_send.setOnClickListener { - var textMessage = citation ?: "" - textMessage += text_message.textContent - sendMessage(textMessage) - clearMessageComposition(true) + sendMessage() } button_show_attachment_options.setOnClickListener { From 3a913ceb0acb9d76e2f117ad891469397ce8608d Mon Sep 17 00:00:00 2001 From: Mahdi Nouri Date: Fri, 21 Dec 2018 17:59:25 +0330 Subject: [PATCH 2/2] Add ability to change physical keyboard message sending behaviour Applying this will result in: * Add ability to send message from physical keyboard with ENTER * Add ability to send message from physical keyboard with ENTER + SHIFT * Add ability to send message from physical keyboard with ENTER + CTRL * Add ability to disable sending message from physical keyboard --- .../presentation/ChatRoomPresenter.kt | 346 +++++++++++------- .../android/chatroom/ui/ChatRoomFragment.kt | 3 +- .../rocket/android/dagger/module/AppModule.kt | 44 ++- .../PhysicalKeyboardConfigInteractor.kt | 17 + .../presentation/PreferencesPresenter.kt | 40 +- .../PhysicalKeyboardConfigRepository.kt | 11 + ...redPrefPhysicalKeyboardConfigRepository.kt | 30 ++ .../preferences/ui/PreferencesFragment.kt | 32 ++ .../main/res/layout/fragment_preferences.xml | 54 ++- .../physical_keyboard_config_dialog.xml | 41 +++ app/src/main/res/values-de/strings.xml | 6 + app/src/main/res/values-es/strings.xml | 8 +- app/src/main/res/values-fr/strings.xml | 8 +- app/src/main/res/values-hi-rIN/strings.xml | 6 + app/src/main/res/values-it/strings.xml | 6 + app/src/main/res/values-ja/strings.xml | 8 +- app/src/main/res/values-pt-rBR/strings.xml | 6 + app/src/main/res/values-ru-rRU/strings.xml | 6 + app/src/main/res/values-tr/strings.xml | 8 +- app/src/main/res/values-uk/strings.xml | 8 +- app/src/main/res/values/strings.xml | 9 + .../android/emoji/PhysicalKeyboardConfig.kt | 19 + 22 files changed, 548 insertions(+), 168 deletions(-) create mode 100644 app/src/main/java/chat/rocket/android/preferences/interactor/PhysicalKeyboardConfigInteractor.kt create mode 100644 app/src/main/java/chat/rocket/android/preferences/repository/PhysicalKeyboardConfigRepository.kt create mode 100644 app/src/main/java/chat/rocket/android/preferences/repository/SharedPrefPhysicalKeyboardConfigRepository.kt create mode 100644 app/src/main/res/layout/physical_keyboard_config_dialog.xml create mode 100644 emoji/src/main/java/chat/rocket/android/emoji/PhysicalKeyboardConfig.kt diff --git a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt index ef17999df2..98387edfa8 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/presentation/ChatRoomPresenter.kt @@ -21,9 +21,11 @@ import chat.rocket.android.core.behaviours.showMessage import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.db.DatabaseManager import chat.rocket.android.emoji.EmojiRepository +import chat.rocket.android.emoji.PhysicalKeyboardConfig import chat.rocket.android.helper.MessageHelper import chat.rocket.android.helper.UserHelper import chat.rocket.android.infrastructure.LocalRepository +import chat.rocket.android.preferences.interactor.PhysicalKeyboardConfigInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.JobSchedulerInteractor @@ -104,10 +106,12 @@ class ChatRoomPresenter @Inject constructor( private val jobSchedulerInteractor: JobSchedulerInteractor, private val messageHelper: MessageHelper, private val dbManager: DatabaseManager, + private val physicalKeyboardConfigInteractor: PhysicalKeyboardConfigInteractor, getSettingsInteractor: GetSettingsInteractor, serverInteractor: GetCurrentServerInteractor, factory: ConnectionManagerFactory ) { + private val currentServer = serverInteractor.get()!! private val manager = factory.create(currentServer) private val client = manager.client @@ -154,11 +158,13 @@ class ChatRoomPresenter @Inject constructor( 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 - )) + view.onRoomUpdated( + roomUiModel = roomUiModel.copy( + broadcast = chatIsBroadcast, + canModerate = canModerate, + writable = roomUiModel.writable || canModerate + ) + ) } } @@ -183,9 +189,15 @@ class ChatRoomPresenter @Inject constructor( chatRoomId?.let { manager.addRoomChannel(it, roomChangesChannel) for (room in roomChangesChannel) { - dbManager.getRoom(room.id)?.let { - view.onRoomUpdated(roomMapper.map(chatRoom = it, showLastMessage = true)) - } + dbManager.getRoom(room.id) + ?.let { + view.onRoomUpdated( + roomMapper.map( + chatRoom = it, + showLastMessage = true + ) + ) + } } } } @@ -218,10 +230,10 @@ class ChatRoomPresenter @Inject constructor( val localMessages = messagesRepository.getRecentMessages(chatRoomId, 50) val oldMessages = mapper.map( localMessages, RoomUiModel( - roles = chatRoles, - // FIXME: Why are we fixing isRoom attribute to true here? - isBroadcast = chatIsBroadcast, isRoom = true - ) + roles = chatRoles, + // FIXME: Why are we fixing isRoom attribute to true here? + isBroadcast = chatIsBroadcast, isRoom = true + ) ) val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId) if (oldMessages.isNotEmpty() && lastSyncDate != null) { @@ -243,9 +255,10 @@ class ChatRoomPresenter @Inject constructor( Timber.e(ex) ex.message?.let { view.showMessage(it) - }.ifNull { - view.showGenericErrorMessage() } + .ifNull { + view.showGenericErrorMessage() + } } finally { view.hideLoading() } @@ -263,7 +276,8 @@ class ChatRoomPresenter @Inject constructor( ) { val messages = retryIO("loadAndShowMessages($chatRoomId, $chatRoomType, $offset") { - client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result + client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30) + .result } messagesRepository.saveAll(messages) @@ -293,7 +307,8 @@ class ChatRoomPresenter @Inject constructor( try { view.showLoading() val messages = retryIO("searchMessages($chatRoomId, $searchText)") { - client.searchMessages(chatRoomId, searchText).result + client.searchMessages(chatRoomId, searchText) + .result } view.showSearchedMessages( mapper.map( @@ -305,9 +320,10 @@ class ChatRoomPresenter @Inject constructor( Timber.e(ex) ex.message?.let { view.showMessage(it) - }.ifNull { - view.showGenericErrorMessage() } + .ifNull { + view.showGenericErrorMessage() + } } finally { view.hideLoading() } @@ -318,7 +334,8 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { try { // ignore message for now, will receive it on the stream - val id = UUID.randomUUID().toString() + val id = UUID.randomUUID() + .toString() if (messageId == null) { val username = userHelper.username() val newMessage = Message( @@ -512,9 +529,10 @@ class ChatRoomPresenter @Inject constructor( Timber.d(ex) ex.message?.let { view.showMessage(it) - }.ifNull { - view.showGenericErrorMessage() } + .ifNull { + view.showGenericErrorMessage() + } } finally { view.hideLoading() } @@ -588,7 +606,8 @@ class ChatRoomPresenter @Inject constructor( val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId) // lastSyncDate or 0. LastSyncDate could be in case when we sent some messages offline(and saved them locally), // but never has obtained chatMessages(or history) from remote. In this case we should sync all chat history from beginning - val instant = Instant.ofEpochMilli(lastSyncDate ?: 0).toString() + val instant = Instant.ofEpochMilli(lastSyncDate ?: 0) + .toString() // try { val messages = @@ -601,16 +620,21 @@ class ChatRoomPresenter @Inject constructor( Timber.d("History: $messages") if (messages.result.isNotEmpty()) { - val models = mapper.map(messages.result, RoomUiModel( - roles = chatRoles, - isBroadcast = chatIsBroadcast, - // FIXME: Why are we fixing isRoom attribute to true here? - isRoom = true - )) + val models = mapper.map( + messages.result, RoomUiModel( + roles = chatRoles, + isBroadcast = chatIsBroadcast, + // FIXME: Why are we fixing isRoom attribute to true here? + isRoom = true + ) + ) messagesRepository.saveAll(messages.result) //if success - saving last synced time //assume that BE returns ordered messages, the first message is the latest one - messagesRepository.saveLastSyncDate(chatRoomId, messages.result.first().timestamp) + messagesRepository.saveLastSyncDate( + chatRoomId, + messages.result.first().timestamp + ) launchUI(strategy) { view.showNewMessage(models, true) @@ -668,7 +692,8 @@ class ChatRoomPresenter @Inject constructor( fun citeMessage(roomName: String, roomType: String, messageId: String, mentionAuthor: Boolean) { launchUI(strategy) { val message = messagesRepository.getById(messageId) - val currentUsername: String? = userHelper.user()?.username + val currentUsername: String? = userHelper.user() + ?.username message?.let { msg -> val id = msg.id val username = msg.sender?.username ?: "" @@ -687,9 +712,9 @@ class ChatRoomPresenter @Inject constructor( replyMarkdown = "[ ]($currentServer/$chatRoomType/$room?msg=$id) $mention ", quotedMessage = mapper.map( message, RoomUiModel( - roles = chatRoles, - isBroadcast = chatIsBroadcast - ) + roles = chatRoles, + isBroadcast = chatIsBroadcast + ) ).last().preview?.message ?: "" ) } @@ -709,10 +734,11 @@ class ChatRoomPresenter @Inject constructor( fun copyMessage(messageId: String) { launchUI(strategy) { try { - messagesRepository.getById(messageId)?.let { m -> - view.copyToClipboard(m.message) - view.showMessage(R.string.msg_message_copied) - } + messagesRepository.getById(messageId) + ?.let { m -> + view.copyToClipboard(m.message) + view.showMessage(R.string.msg_message_copied) + } } catch (e: RocketChatException) { Timber.e(e) } @@ -801,7 +827,8 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { try { val members = retryIO("getMembers($chatRoomId, $chatRoomType, $offset)") { - client.getMembers(chatRoomId, roomTypeOf(chatRoomType), offset, 50).result + client.getMembers(chatRoomId, roomTypeOf(chatRoomType), offset, 50) + .result }.take(50) // Get only 50, the backend is returning 7k+ users usersRepository.saveAll(members) dbManager.processUsersBatch(members) @@ -903,9 +930,10 @@ class ChatRoomPresenter @Inject constructor( Timber.e(e, "Error while trying to favorite/unfavorite chat room.") e.message?.let { view.showMessage(it) - }.ifNull { - view.showGenericErrorMessage() } + .ifNull { + view.showGenericErrorMessage() + } } } } @@ -946,81 +974,88 @@ class ChatRoomPresenter @Inject constructor( // TODO: move this to new interactor or FetchChatRoomsInteractor? private suspend fun getChatRoomAsync(roomId: String): ChatRoom? = withContext(CommonPool) { 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 - ) + 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 = withContext(CommonPool) { + private suspend fun getChatRoomsAsync(name: String? = null): List = withContext( + CommonPool + ) { retryDB("getAllSync()") { - dbManager.chatRoomDao().getAllSync().filter { - if (name == null) { - return@filter true + dbManager.chatRoomDao() + .getAllSync() + .filter { + if (name == null) { + return@filter true + } + it.chatRoom.name == name || it.chatRoom.fullname == name } - 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 - ) + .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 + ) + } } - } } } @@ -1029,12 +1064,15 @@ class ChatRoomPresenter @Inject constructor( try { retryIO("joinChat($chatRoomId)") { client.joinChat(chatRoomId) } val canPost = permissions.canPostToReadOnlyChannels() - dbManager.getRoom(chatRoomId)?.let { - val roomUiModel = roomMapper.map(it, true).copy( - writable = canPost) - view.onJoined(roomUiModel = roomUiModel) - view.onRoomUpdated(roomUiModel = roomUiModel) - } + 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) } @@ -1073,15 +1111,17 @@ class ChatRoomPresenter @Inject constructor( fun copyPermalink(messageId: String) { launchUI(strategy) { try { - messagesRepository.getById(messageId)?.let { message -> - getChatRoomAsync(message.roomId)?.let { chatRoom -> - val models = mapper.map(message) - models.firstOrNull()?.permalink?.let { - view.copyToClipboard(it) - view.showMessage(R.string.msg_permalink_copied) + messagesRepository.getById(messageId) + ?.let { message -> + getChatRoomAsync(message.roomId)?.let { chatRoom -> + val models = mapper.map(message) + models.firstOrNull() + ?.permalink?.let { + view.copyToClipboard(it) + view.showMessage(R.string.msg_permalink_copied) + } } } - } } catch (ex: Exception) { Timber.e(ex) } @@ -1143,7 +1183,8 @@ class ChatRoomPresenter @Inject constructor( try { //TODO: cache the commands val commands = retryIO("commands(0, 100)") { - client.commands(0, 100).result + client.commands(0, 100) + .result } view.populateCommandSuggestions(commands.map { CommandSuggestionUiModel(it.command, it.description ?: "", listOf(it.command)) @@ -1156,14 +1197,15 @@ class ChatRoomPresenter @Inject constructor( fun loadEmojis() { launchUI(strategy) { - val emojiSuggestionUiModels = EmojiRepository.getAll().map { - EmojiSuggestionUiModel( - text = it.shortname.replaceFirst(":", ""), - pinned = false, - emoji = it, - searchList = listOf(it.shortname) - ) - } + val emojiSuggestionUiModels = EmojiRepository.getAll() + .map { + EmojiSuggestionUiModel( + text = it.shortname.replaceFirst(":", ""), + pinned = false, + emoji = it, + searchList = listOf(it.shortname) + ) + } view.populateEmojiSuggestions(emojis = emojiSuggestionUiModels) } } @@ -1231,12 +1273,13 @@ class ChatRoomPresenter @Inject constructor( typingStatusList.add(typingStatus.first) } } else { - typingStatusList.find { username -> username == typingStatus.first }?.let { - typingStatusList.remove(it) - if (typingStatus.second) { - typingStatusList.add(typingStatus.first) + typingStatusList.find { username -> username == typingStatus.first } + ?.let { + typingStatusList.remove(it) + if (typingStatus.second) { + typingStatusList.add(typingStatus.first) + } } - } } if (typingStatusList.isNotEmpty()) { @@ -1266,8 +1309,8 @@ class ChatRoomPresenter @Inject constructor( launchUI(strategy) { val viewModelStreamedMessage = mapper.map( streamedMessage, RoomUiModel( - roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true - ) + roles = chatRoles, isBroadcast = chatIsBroadcast, isRoom = true + ) ) val roomMessages = messagesRepository.getByRoomId(streamedMessage.roomId) @@ -1304,6 +1347,7 @@ class ChatRoomPresenter @Inject constructor( fun clearUnfinishedMessage() { localRepository.clear(draftKey) } + /** * Get unfinished message from local repository, when user left chat room without * sending a message and now the user is back. @@ -1315,16 +1359,34 @@ class ChatRoomPresenter @Inject constructor( } fun onKeyEvent(keyCode: Int, keyEvent: KeyEvent): Boolean { - // TODO : use physical keyboard abstraction for better key event handling - when (keyCode) { + return when (keyCode) { KeyEvent.KEYCODE_ENTER -> { - // TODO : check user's preference for shift or control key - if (keyEvent.isShiftPressed) { - view.sendMessage() + when (physicalKeyboardConfigInteractor.getConfig()) { + is PhysicalKeyboardConfig.Enter -> { + if (!keyEvent.isShiftPressed && !keyEvent.isCtrlPressed) { + view.sendMessage() + true + } else { + false + } + } + is PhysicalKeyboardConfig.EnterPlusControl -> { + if (keyEvent.isCtrlPressed) { + view.sendMessage() + } + true + } + is PhysicalKeyboardConfig.EnterPlusShift -> { + if (keyEvent.isShiftPressed) { + view.sendMessage() + } + true + } + else -> false } } + else -> false } - return true } } diff --git a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt index df08ab4ff8..517acf9af9 100644 --- a/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt +++ b/app/src/main/java/chat/rocket/android/chatroom/ui/ChatRoomFragment.kt @@ -6,7 +6,6 @@ import android.content.ClipData import android.content.ClipboardManager 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 @@ -316,7 +315,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) text_message.addTextChangedListener(EmojiKeyboardPopup.EmojiTextWatcher(text_message)) - text_message.setOnKeyListener { v, keyCode, event -> presenter.onKeyEvent(keyCode, event) } + text_message.setOnKeyListener { _, keyCode, event -> presenter.onKeyEvent(keyCode, event) } } override fun onDestroyView() { diff --git a/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt b/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt index 9fbe051396..330a3c81ca 100644 --- a/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt +++ b/app/src/main/java/chat/rocket/android/dagger/module/AppModule.kt @@ -22,28 +22,29 @@ import chat.rocket.android.db.DatabaseManagerFactory import chat.rocket.android.helper.MessageParser import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository +import chat.rocket.android.preferences.repository.PhysicalKeyboardConfigRepository +import chat.rocket.android.preferences.repository.SharedPrefPhysicalKeyboardConfigRepository import chat.rocket.android.push.GroupedPush import chat.rocket.android.push.PushManager import chat.rocket.android.server.domain.AccountsRepository import chat.rocket.android.server.domain.AnalyticsTrackingInteractor import chat.rocket.android.server.domain.AnalyticsTrackingRepository +import chat.rocket.android.server.domain.BasicAuthRepository import chat.rocket.android.server.domain.ChatRoomsRepository import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.GetAccountInteractor import chat.rocket.android.server.domain.GetAccountsInteractor +import chat.rocket.android.server.domain.GetBasicAuthInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.JobSchedulerInteractor import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MultiServerTokenRepository import chat.rocket.android.server.domain.PermissionsRepository +import chat.rocket.android.server.domain.SaveBasicAuthInteractor import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.UsersRepository -import chat.rocket.android.server.domain.BasicAuthRepository -import chat.rocket.android.server.domain.GetBasicAuthInteractor -import chat.rocket.android.server.domain.SaveBasicAuthInteractor -import chat.rocket.android.server.infraestructure.SharedPrefsBasicAuthRepository import chat.rocket.android.server.infraestructure.DatabaseMessageMapper import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl @@ -53,17 +54,17 @@ import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepos import chat.rocket.android.server.infraestructure.SharedPreferencesPermissionsRepository import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository import chat.rocket.android.server.infraestructure.SharedPrefsAnalyticsTrackingRepository +import chat.rocket.android.server.infraestructure.SharedPrefsBasicAuthRepository import chat.rocket.android.server.infraestructure.SharedPrefsConnectingServerRepository import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository import chat.rocket.android.util.AppJsonAdapterFactory -import chat.rocket.android.util.HttpLoggingInterceptor import chat.rocket.android.util.BasicAuthenticatorInterceptor +import chat.rocket.android.util.HttpLoggingInterceptor import chat.rocket.android.util.TimberLogger import chat.rocket.common.internal.FallbackSealedClassJsonAdapter import chat.rocket.common.internal.ISO8601Date import chat.rocket.common.model.TimestampAdapter import chat.rocket.common.util.CalendarISO8601Converter -import chat.rocket.common.util.Logger import chat.rocket.common.util.NoOpLogger import chat.rocket.common.util.PlatformLogger import chat.rocket.core.internal.AttachmentAdapterFactory @@ -124,7 +125,10 @@ class AppModule { @Provides @Singleton - fun provideOkHttpClient(logger: HttpLoggingInterceptor, basicAuthenticator: BasicAuthenticatorInterceptor): OkHttpClient { + fun provideOkHttpClient( + logger: HttpLoggingInterceptor, + basicAuthenticator: BasicAuthenticatorInterceptor + ): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(logger) .addInterceptor(basicAuthenticator) @@ -145,13 +149,16 @@ class AppModule { return OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient) .setRequestListeners(listeners) .setDownsampleEnabled(true) - .experiment().setPartialImageCachingEnabled(true).build() + .experiment() + .setPartialImageCachingEnabled(true) + .build() } @Provides @Singleton fun provideDraweeConfig(): DraweeConfig { - return DraweeConfig.newBuilder().build() + return DraweeConfig.newBuilder() + .build() } @Provides @@ -168,9 +175,10 @@ class AppModule { @Provides @Singleton - fun provideSharedPreferences(context: Application) = - context.getSharedPreferences("rocket.chat", Context.MODE_PRIVATE) - + fun provideSharedPreferences(context: Application) = context.getSharedPreferences( + "rocket.chat", + Context.MODE_PRIVATE + ) @Provides @ForMessages @@ -195,6 +203,14 @@ class AppModule { return SharedPrefsAnalyticsTrackingRepository(prefs) } + @Provides + @Singleton + fun provideSharedPrefPhysicalKeyboardConfigRepository( + prefs: SharedPreferences + ): PhysicalKeyboardConfigRepository { + return SharedPrefPhysicalKeyboardConfigRepository(prefs) + } + @Provides @ForAuthentication fun provideConnectingServerRepository(prefs: SharedPreferences): CurrentServerRepository { @@ -294,10 +310,10 @@ class AppModule { @Provides @Singleton - fun provideBasicAuthRepository ( + fun provideBasicAuthRepository( preferences: SharedPreferences, moshi: Moshi - ): BasicAuthRepository = + ): BasicAuthRepository = SharedPrefsBasicAuthRepository(preferences, moshi) @Provides diff --git a/app/src/main/java/chat/rocket/android/preferences/interactor/PhysicalKeyboardConfigInteractor.kt b/app/src/main/java/chat/rocket/android/preferences/interactor/PhysicalKeyboardConfigInteractor.kt new file mode 100644 index 0000000000..b24c12e044 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/preferences/interactor/PhysicalKeyboardConfigInteractor.kt @@ -0,0 +1,17 @@ +package chat.rocket.android.preferences.interactor + +import chat.rocket.android.emoji.PhysicalKeyboardConfig +import chat.rocket.android.preferences.repository.PhysicalKeyboardConfigRepository +import javax.inject.Inject + +class PhysicalKeyboardConfigInteractor @Inject constructor( + private val physicalKeyboardConfigRepository: PhysicalKeyboardConfigRepository +) { + + fun getConfig() = physicalKeyboardConfigRepository.getConfig() + + fun saveConfig(physicalKeyboardConfig: PhysicalKeyboardConfig) { + physicalKeyboardConfigRepository.saveConfig(physicalKeyboardConfig) + } + +} diff --git a/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesPresenter.kt b/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesPresenter.kt index 38eba6e6db..10728ac509 100644 --- a/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesPresenter.kt +++ b/app/src/main/java/chat/rocket/android/preferences/presentation/PreferencesPresenter.kt @@ -1,13 +1,26 @@ package chat.rocket.android.preferences.presentation +import chat.rocket.android.R +import chat.rocket.android.emoji.PhysicalKeyboardConfig +import chat.rocket.android.preferences.interactor.PhysicalKeyboardConfigInteractor import chat.rocket.android.server.domain.AnalyticsTrackingInteractor import javax.inject.Inject class PreferencesPresenter @Inject constructor( private val view: PreferencesView, - private val analyticsTrackingInteractor: AnalyticsTrackingInteractor + private val analyticsTrackingInteractor: AnalyticsTrackingInteractor, + private val physicalKeyboardConfigInteractor: PhysicalKeyboardConfigInteractor ) { + private var physicalKeyboardConfig: PhysicalKeyboardConfig = PhysicalKeyboardConfig.EnterPlusControl + set(value) { + field = value + physicalKeyboardConfigInteractor.saveConfig(value) + } + get() = physicalKeyboardConfigInteractor.getConfig() + + private var tempPhysicalKeyboardConfig = physicalKeyboardConfig + fun loadAnalyticsTrackingInformation() { view.setupAnalyticsTrackingView(analyticsTrackingInteractor.get()) } @@ -19,4 +32,27 @@ class PreferencesPresenter @Inject constructor( fun disableAnalyticsTracking() { analyticsTrackingInteractor.save(false) } -} \ No newline at end of file + + fun getPhysicalKeyboardRadioId(): Int { + return when (physicalKeyboardConfig) { + PhysicalKeyboardConfig.Enter -> R.id.radio_physical_keyboard_enter + PhysicalKeyboardConfig.EnterPlusShift -> R.id.radio_physical_keyboard_enter_shift + PhysicalKeyboardConfig.EnterPlusControl -> R.id.radio_physical_keyboard_enter_control + else -> R.id.radio_physical_keyboard_disabled + } + } + + fun onPhysicalKeyboardRadioChange(checkedId: Int) { + tempPhysicalKeyboardConfig = when (checkedId) { + R.id.radio_physical_keyboard_enter -> PhysicalKeyboardConfig.Enter + R.id.radio_physical_keyboard_enter_shift -> PhysicalKeyboardConfig.EnterPlusShift + R.id.radio_physical_keyboard_enter_control -> PhysicalKeyboardConfig.EnterPlusControl + else -> PhysicalKeyboardConfig.Disable + } + } + + fun onPhysicalKeyboardDialogOkClicked() { + physicalKeyboardConfig = tempPhysicalKeyboardConfig + } + +} diff --git a/app/src/main/java/chat/rocket/android/preferences/repository/PhysicalKeyboardConfigRepository.kt b/app/src/main/java/chat/rocket/android/preferences/repository/PhysicalKeyboardConfigRepository.kt new file mode 100644 index 0000000000..b77f312f39 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/preferences/repository/PhysicalKeyboardConfigRepository.kt @@ -0,0 +1,11 @@ +package chat.rocket.android.preferences.repository + +import chat.rocket.android.emoji.PhysicalKeyboardConfig + +interface PhysicalKeyboardConfigRepository { + + fun getConfig(): PhysicalKeyboardConfig + + fun saveConfig(physicalKeyboardConfig: PhysicalKeyboardConfig) + +} diff --git a/app/src/main/java/chat/rocket/android/preferences/repository/SharedPrefPhysicalKeyboardConfigRepository.kt b/app/src/main/java/chat/rocket/android/preferences/repository/SharedPrefPhysicalKeyboardConfigRepository.kt new file mode 100644 index 0000000000..c6ccbb6b10 --- /dev/null +++ b/app/src/main/java/chat/rocket/android/preferences/repository/SharedPrefPhysicalKeyboardConfigRepository.kt @@ -0,0 +1,30 @@ +package chat.rocket.android.preferences.repository + +import android.content.SharedPreferences +import chat.rocket.android.emoji.PhysicalKeyboardConfig +import javax.inject.Inject + +class SharedPrefPhysicalKeyboardConfigRepository @Inject constructor( + private val sharedPreferences: SharedPreferences +) : PhysicalKeyboardConfigRepository { + + override fun saveConfig(physicalKeyboardConfig: PhysicalKeyboardConfig) { + sharedPreferences.edit() + .putInt(PHYSICAL_KEYBOARD_CONFIG_KEY, physicalKeyboardConfig.state) + .apply() + } + + override fun getConfig(): PhysicalKeyboardConfig { + return PhysicalKeyboardConfig.Disable.stateToConfig( + sharedPreferences.getInt( + PHYSICAL_KEYBOARD_CONFIG_KEY, + PhysicalKeyboardConfig.EnterPlusControl.state + ) + ) + } + + companion object { + const val PHYSICAL_KEYBOARD_CONFIG_KEY = "PHYSICAL_KEYBOARD_CONFIG_KEY" + } + +} diff --git a/app/src/main/java/chat/rocket/android/preferences/ui/PreferencesFragment.kt b/app/src/main/java/chat/rocket/android/preferences/ui/PreferencesFragment.kt index 22abe36122..b25960fc3f 100644 --- a/app/src/main/java/chat/rocket/android/preferences/ui/PreferencesFragment.kt +++ b/app/src/main/java/chat/rocket/android/preferences/ui/PreferencesFragment.kt @@ -1,5 +1,6 @@ package chat.rocket.android.preferences.ui +import android.app.AlertDialog import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,16 +16,37 @@ import chat.rocket.android.preferences.presentation.PreferencesView import dagger.android.support.AndroidSupportInjection import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.fragment_preferences.* +import kotlinx.android.synthetic.main.physical_keyboard_config_dialog.view.* import javax.inject.Inject internal const val TAG_PREFERENCES_FRAGMENT = "PreferencesFragment" class PreferencesFragment : Fragment(), PreferencesView { + @Inject lateinit var presenter: PreferencesPresenter @Inject lateinit var analyticsManager: AnalyticsManager + private val physicalKeyboardDialog by lazy(LazyThreadSafetyMode.NONE) { + val dialogLayout = layoutInflater.inflate(R.layout.physical_keyboard_config_dialog, null) + dialogLayout.radio_physical_keyboard.setOnCheckedChangeListener { _, checkedId -> + presenter.onPhysicalKeyboardRadioChange(checkedId) + } + dialogLayout.radio_physical_keyboard.check(presenter.getPhysicalKeyboardRadioId()) + AlertDialog.Builder(context) + .setTitle(R.string.physical_keyboard_preference_title) + .setPositiveButton(R.string.msg_ok) { dialog, _ -> + presenter.onPhysicalKeyboardDialogOkClicked() + dialog.dismiss() + } + .setNegativeButton(R.string.msg_cancel) { dialog, _ -> + dialog.dismiss() + } + .setView(dialogLayout) + .create() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) AndroidSupportInjection.inject(this) @@ -84,6 +106,16 @@ class PreferencesFragment : Fragment(), PreferencesView { presenter.disableAnalyticsTracking() } } + physical_keyboard_root.setOnClickListener { + physicalKeyboardDialog.show() + } + } + + override fun onDestroyView() { + if (physicalKeyboardDialog.isShowing) { + physicalKeyboardDialog.dismiss() + } + super.onDestroyView() } companion object { diff --git a/app/src/main/res/layout/fragment_preferences.xml b/app/src/main/res/layout/fragment_preferences.xml index df859fb237..6a5c7d62af 100644 --- a/app/src/main/res/layout/fragment_preferences.xml +++ b/app/src/main/res/layout/fragment_preferences.xml @@ -1,10 +1,10 @@ - - \ No newline at end of file + + + + + + + + + + + diff --git a/app/src/main/res/layout/physical_keyboard_config_dialog.xml b/app/src/main/res/layout/physical_keyboard_config_dialog.xml new file mode 100644 index 0000000000..9c4afa0c97 --- /dev/null +++ b/app/src/main/res/layout/physical_keyboard_config_dialog.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 796aa664c9..36c5ae8b83 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -339,4 +339,10 @@ einreichen *erforderlich Ihr Bericht wurde gesendet! + + + Deaktivieren + Physische Tastatur + Konfiguration zum Senden einer Nachricht von einer physischen Tastatur + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ee7c2b76e4..d2faf2f2bb 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -343,5 +343,11 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + Inhabilitar + Teclado físico + Configuraciones para enviar un mensaje desde un teclado físico. + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9e047cc769..fbfa20e98c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -346,5 +346,11 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + Désactiver + Clavier physique + Configuration pour l\'envoi d\'un message à partir d\'un clavier physique + diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 49dbfa40ee..5f210f7776 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -345,4 +345,10 @@ जमा करें * आवश्यक आपकी रिपोर्ट भेज दी गई है! + + + अक्षम + भौतिक कीबोर्ड + भौतिक कीबोर्ड के साथ संदेश भेजने के लिए विन्यास + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6ed5e180d7..1bcdcc5c52 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -340,4 +340,10 @@ Invia *necessario Il tuo resoconto è stato inviato! + + + Disattivare + Tastiera fisica + Configurazione per l\'invio di un messaggio con una tastiera fisica + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 45d3333f19..d534dcc432 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -347,5 +347,11 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + 無効にする + 物理キーボード + 物理キーボードでメッセージを送信するための設定 + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index de57e7bcb3..550768e559 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -345,4 +345,10 @@ Enviar *obrigatório A mensagem foi reportada! + + + Desabilitar + Teclado físico + Configuração para enviar uma mensagem com um teclado físico + diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index aae058fe45..59027cd365 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -342,4 +342,10 @@ Отправить *требуется Ваша жалоба была отправлена! + + + запрещать + Физическая клавиатура + Конфигурация для отправки сообщения с физической клавиатуры + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 2a0a4aed48..e6696457d0 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -345,5 +345,11 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + Devre dışı + Fiziksel klavye + Fiziksel klavyeyle mesaj göndermek için yapılandırın + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 19881aafe6..df5298b6d7 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -343,5 +343,11 @@ Submit *required - Your report has been sent! + Your report has been sent! + + + Відключити + Фізична клавіатура + Конфігурація для надсилання повідомлень з фізичної клавіатури + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ad457877fd..ff2ec33561 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -360,4 +360,13 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin Submit *required Your report has been sent! + + + Physical Keyboard + Configuration for sending a message from a physical keyboard + Enter + Enter + Shift + Enter + Control + Disable + diff --git a/emoji/src/main/java/chat/rocket/android/emoji/PhysicalKeyboardConfig.kt b/emoji/src/main/java/chat/rocket/android/emoji/PhysicalKeyboardConfig.kt new file mode 100644 index 0000000000..19a98a45fd --- /dev/null +++ b/emoji/src/main/java/chat/rocket/android/emoji/PhysicalKeyboardConfig.kt @@ -0,0 +1,19 @@ +package chat.rocket.android.emoji + +sealed class PhysicalKeyboardConfig(val state: Int) { + + fun stateToConfig(state: Int): PhysicalKeyboardConfig { + return when (state) { + 0 -> Enter + 1 -> EnterPlusShift + 2 -> EnterPlusControl + else -> Disable + } + } + + object Enter : PhysicalKeyboardConfig(0) + object EnterPlusShift : PhysicalKeyboardConfig(1) + object EnterPlusControl : PhysicalKeyboardConfig(2) + object Disable : PhysicalKeyboardConfig(3) + +}