Unverified Commit c09f75ff authored by Rafael Kellermann Streit's avatar Rafael Kellermann Streit Committed by GitHub

Merge branch 'develop-2.x' into empty-pin-msg

parents e9c2fc4a 29dcfdae
......@@ -19,18 +19,12 @@ Since both the versions use `kotlin` for some or all of their classes, following
- After checking out to `develop` branch as mentioned above, simply import the project in Android Studio.
#### v2+
- This version requires the [Kotlin SDK](https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK) for Rocket.Chat. Clone the Kotlin SDK in the **same directory** as the android repository by running `git clone https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK.git`. Make sure that the android repository and the kotlin sdk have the same immediate parent directory.
- First, a build is required for the SDK. Change your directory to the SDK directory by running `cd Rocket.Chat.Kotlin.SDK/` in your terminal. Any of the following approaches can be followed to successfully build the SDK.
- **Command Line** - (Within the kotlin SDK directory) Run `./gradlew clean && ./gradlew assemble` to successfully build the project.
- **Android Studio** - Import the project in Android Studio. Go to `Build > Make Project` to build the SDK successfully.
After following the above methods, follow the following steps in your terminal window:
- This version requires the [Kotlin SDK](https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK) for Rocket.Chat. Clone the Kotlin SDK in by running `git clone https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK.git`.
- First, a build is required for the SDK, so that required jar files are generated. Make sure that the android repository and the kotlin sdk have the same immediate parent directory. Change the current directory to `Rocket.Chat.Android/app` and run the `build-sdk.sh` which will result in creating of the required jar file `core*.jar` and `common*.jar` in `Rocket.Chat.Android/app/libs`,by the following steps in your terminal window:
```
cd ..
cd Rocket.Chat.Android/app/libs
ls
cd Rocket.Chat.Android/app
./build-sdk.sh
```
Two `jar` files will be found in this directory (the `common` and `core` jar files), this indicates that the SDK was built correctly.
- After the SDK has been built successfully, import the project in Android Studio and build it by following `Build > Make Project`.
## How to run
### Command Line
......
......@@ -20,7 +20,9 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:networkSecurityConfig="@xml/network_security_config">
android:supportsRtl="true">
<activity
android:name=".authentication.ui.AuthenticationActivity"
android:configChanges="orientation"
......@@ -109,4 +111,4 @@
android:theme="@style/AppTheme"/>
</application>
</manifest>
\ No newline at end of file
</manifest>
......@@ -86,31 +86,39 @@ class LoginFragment : Fragment(), LoginView {
}
private fun tintEditTextDrawableStart() {
activity?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, this)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, this)
ui {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_assignment_ind_black_24dp, it)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, it)
val drawables = arrayOf(personDrawable, lockDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.tintDrawables(drawables, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_username_or_email, text_password), drawables)
}
}
override fun showLoading() {
view_loading.setVisible(true)
ui {
view_loading.setVisible(true)
}
}
override fun hideLoading() {
view_loading.setVisible(false)
ui {
view_loading.setVisible(false)
}
}
override fun showMessage(resId: Int) {
showToast(resId)
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() {
......@@ -118,152 +126,208 @@ class LoginFragment : Fragment(), LoginView {
}
override fun showFormView() {
text_username_or_email.setVisible(true)
text_password.setVisible(true)
ui {
text_username_or_email.setVisible(true)
text_password.setVisible(true)
}
}
override fun hideFormView() {
text_username_or_email.setVisible(false)
text_password.setVisible(false)
ui {
text_username_or_email.setVisible(false)
text_password.setVisible(false)
}
}
override fun setupLoginButtonListener() {
button_log_in.setOnClickListener {
presenter.authenticateWithUserAndPassword(text_username_or_email.textContent, text_password.textContent)
ui {
button_log_in.setOnClickListener {
presenter.authenticateWithUserAndPassword(text_username_or_email.textContent,
text_password.textContent)
}
}
}
override fun enableUserInput() {
button_log_in.isEnabled = true
text_username_or_email.isEnabled = true
text_password.isEnabled = true
ui {
button_log_in.isEnabled = true
text_username_or_email.isEnabled = true
text_password.isEnabled = true
}
}
override fun disableUserInput() {
button_log_in.isEnabled = false
text_username_or_email.isEnabled = false
text_password.isEnabled = false
ui {
button_log_in.isEnabled = false
text_username_or_email.isEnabled = false
text_password.isEnabled = false
}
}
override fun showCasButton() {
button_cas.setVisible(true)
ui {
button_cas.setVisible(true)
}
}
override fun hideCasButton() {
button_cas.setVisible(false)
ui {
button_cas.setVisible(false)
}
}
override fun setupCasButtonListener(casUrl: String, casToken: String) {
button_cas.setOnClickListener {
startActivityForResult(context?.casWebViewIntent(casUrl, casToken), REQUEST_CODE_FOR_CAS)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
ui { activity ->
button_cas.setOnClickListener {
startActivityForResult(activity.casWebViewIntent(casUrl, casToken),
REQUEST_CODE_FOR_CAS)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun showSignUpView() {
text_new_to_rocket_chat.setVisible(true)
ui {
text_new_to_rocket_chat.setVisible(true)
}
}
override fun setupSignUpView() {
val signUp = getString(R.string.title_sign_up)
val newToRocketChat = String.format(getString(R.string.msg_new_user), signUp)
ui {
val signUp = getString(R.string.title_sign_up)
val newToRocketChat = String.format(getString(R.string.msg_new_user), signUp)
text_new_to_rocket_chat.text = newToRocketChat
text_new_to_rocket_chat.text = newToRocketChat
val signUpListener = object : ClickableSpan() {
override fun onClick(view: View) = presenter.signup()
}
val signUpListener = object : ClickableSpan() {
override fun onClick(view: View) = presenter.signup()
}
TextHelper.addLink(text_new_to_rocket_chat, arrayOf(signUp), arrayOf(signUpListener))
TextHelper.addLink(text_new_to_rocket_chat, arrayOf(signUp), arrayOf(signUpListener))
}
}
override fun hideSignUpView() {
text_new_to_rocket_chat.setVisible(false)
ui {
text_new_to_rocket_chat.setVisible(false)
}
}
override fun enableOauthView() {
isOauthViewEnable = true
showThreeSocialAccountsMethods()
social_accounts_container.setVisible(true)
ui {
isOauthViewEnable = true
showThreeSocialAccountsMethods()
social_accounts_container.setVisible(true)
}
}
override fun disableOauthView() {
isOauthViewEnable = false
social_accounts_container.setVisible(false)
ui {
isOauthViewEnable = false
social_accounts_container.setVisible(false)
}
}
override fun showLoginButton() {
button_log_in.setVisible(true)
ui {
button_log_in.setVisible(true)
}
}
override fun hideLoginButton() {
button_log_in.setVisible(false)
ui {
button_log_in.setVisible(false)
}
}
override fun enableLoginByFacebook() {
button_facebook.isClickable = true
ui {
button_facebook.isClickable = true
}
}
override fun enableLoginByGithub() {
button_github.isClickable = true
ui {
button_github.isClickable = true
}
}
override fun setupGithubButtonListener(githubUrl: String, state: String) {
button_github.setOnClickListener {
startActivityForResult(context?.oauthWebViewIntent(githubUrl, state), REQUEST_CODE_FOR_OAUTH)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
ui { activity ->
button_github.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(githubUrl, state), REQUEST_CODE_FOR_OAUTH)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun enableLoginByGoogle() {
button_google.isClickable = true
ui {
button_google.isClickable = true
}
}
// TODO: Use custom tabs instead of web view. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968
override fun setupGoogleButtonListener(googleUrl: String, state: String) {
button_google.setOnClickListener {
startActivityForResult(context?.oauthWebViewIntent(googleUrl, state), REQUEST_CODE_FOR_OAUTH)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
ui { activity ->
button_google.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(googleUrl, state), REQUEST_CODE_FOR_OAUTH)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun enableLoginByLinkedin() {
button_linkedin.isClickable = true
ui {
button_linkedin.isClickable = true
}
}
override fun setupLinkedinButtonListener(linkedinUrl: String, state: String) {
button_linkedin.setOnClickListener {
startActivityForResult(context?.oauthWebViewIntent(linkedinUrl, state), REQUEST_CODE_FOR_OAUTH)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
ui { activity ->
button_linkedin.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(linkedinUrl, state), REQUEST_CODE_FOR_OAUTH)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun enableLoginByMeteor() {
button_meteor.isClickable = true
ui {
button_meteor.isClickable = true
}
}
override fun enableLoginByTwitter() {
button_twitter.isClickable = true
ui {
button_twitter.isClickable = true
}
}
override fun enableLoginByGitlab() {
button_gitlab.isClickable = true
ui {
button_gitlab.isClickable = true
}
}
override fun setupGitlabButtonListener(gitlabUrl: String, state: String) {
button_gitlab.setOnClickListener {
startActivityForResult(context?.oauthWebViewIntent(gitlabUrl, state), REQUEST_CODE_FOR_OAUTH)
activity?.overridePendingTransition(R.anim.slide_up, R.anim.hold)
ui { activity ->
button_gitlab.setOnClickListener {
startActivityForResult(activity.oauthWebViewIntent(gitlabUrl, state), REQUEST_CODE_FOR_OAUTH)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun setupFabListener() {
button_fab.setVisible(true)
button_fab.setOnClickListener({
button_fab.hide()
showRemainingSocialAccountsView()
scrollToBottom()
})
ui {
button_fab.setVisible(true)
button_fab.setOnClickListener({
button_fab.hide()
showRemainingSocialAccountsView()
scrollToBottom()
})
}
}
override fun setupGlobalListener() {
......@@ -276,19 +340,23 @@ class LoginFragment : Fragment(), LoginView {
}
override fun alertWrongUsernameOrEmail() {
vibrateSmartPhone()
text_username_or_email.shake()
text_username_or_email.requestFocus()
ui {
vibrateSmartPhone()
text_username_or_email.shake()
text_username_or_email.requestFocus()
}
}
override fun alertWrongPassword() {
vibrateSmartPhone()
text_password.shake()
text_password.requestFocus()
ui {
vibrateSmartPhone()
text_password.shake()
text_password.requestFocus()
}
}
override fun alertNotRecommendedVersion() {
context?.let {
ui {
AlertDialog.Builder(it)
.setMessage(getString(R.string.msg_ver_not_recommended, BuildConfig.RECOMMENDED_SERVER_VERSION))
.setPositiveButton(R.string.msg_ok, null)
......@@ -298,7 +366,7 @@ class LoginFragment : Fragment(), LoginView {
}
override fun blockAndAlertNotRequiredVersion() {
context?.let {
ui {
AlertDialog.Builder(it)
.setMessage(getString(R.string.msg_ver_not_minimum, BuildConfig.REQUIRED_SERVER_VERSION))
.setOnDismissListener { activity?.onBackPressed() }
......@@ -313,14 +381,14 @@ class LoginFragment : Fragment(), LoginView {
(0..social_accounts_container.childCount)
.mapNotNull { social_accounts_container.getChildAt(it) as? ImageButton }
.filter { it.isClickable }
.forEach { it.setVisible(true)}
.forEach { ui { it.setVisible(true) }}
}, 1000)
}
// Scrolling to the bottom of the screen.
private fun scrollToBottom() {
scroll_view.postDelayed({
scroll_view.fullScroll(ScrollView.FOCUS_DOWN)
ui { scroll_view.fullScroll(ScrollView.FOCUS_DOWN) }
}, 1250)
}
......@@ -350,13 +418,13 @@ class LoginFragment : Fragment(), LoginView {
.forEach { it.setVisible(true) }
}
fun showOauthView() {
private fun showOauthView() {
if (isOauthViewEnable) {
social_accounts_container.setVisible(true)
}
}
fun hideOauthView() {
private fun hideOauthView() {
if (isOauthViewEnable) {
social_accounts_container.setVisible(false)
button_fab.setVisible(false)
......
......@@ -59,26 +59,36 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
}
override fun alertBlankUsername() {
vibrateSmartPhone()
text_username.shake()
ui {
vibrateSmartPhone()
text_username.shake()
}
}
override fun showLoading() {
disableUserInput()
view_loading.setVisible(true)
ui {
disableUserInput()
view_loading.setVisible(true)
}
}
override fun hideLoading() {
view_loading.setVisible(false)
enableUserInput()
ui {
view_loading.setVisible(false)
enableUserInput()
}
}
override fun showMessage(resId: Int) {
showToast(resId)
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() {
......@@ -86,10 +96,10 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
}
private fun tintEditTextDrawableStart() {
activity?.apply {
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, this)
ui {
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, it)
DrawableHelper.wrapDrawable(atDrawable)
DrawableHelper.tintDrawable(atDrawable, this, R.color.colorDrawableTintGrey)
DrawableHelper.tintDrawable(atDrawable, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_username, atDrawable)
}
}
......
......@@ -47,21 +47,29 @@ class ServerFragment : Fragment(), ServerView {
override fun showInvalidServerUrlMessage() = showMessage(getString(R.string.msg_invalid_server_url))
override fun showLoading() {
enableUserInput(false)
view_loading.setVisible(true)
ui {
enableUserInput(false)
view_loading.setVisible(true)
}
}
override fun hideLoading() {
view_loading.setVisible(false)
enableUserInput(true)
ui {
view_loading.setVisible(false)
enableUserInput(true)
}
}
override fun showMessage(resId: Int){
showToast(resId)
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() {
......
......@@ -25,7 +25,7 @@ class SignupFragment : Fragment(), SignupView {
} else {
bottom_container.apply {
postDelayed({
setVisible(true)
ui { setVisible(true) }
}, 3)
}
}
......@@ -64,45 +64,61 @@ class SignupFragment : Fragment(), SignupView {
}
override fun alertBlankName() {
vibrateSmartPhone()
text_name.shake()
text_name.requestFocus()
ui {
vibrateSmartPhone()
text_name.shake()
text_name.requestFocus()
}
}
override fun alertBlankUsername() {
vibrateSmartPhone()
text_username.shake()
text_username.requestFocus()
ui {
vibrateSmartPhone()
text_username.shake()
text_username.requestFocus()
}
}
override fun alertEmptyPassword() {
vibrateSmartPhone()
text_password.shake()
text_password.requestFocus()
ui {
vibrateSmartPhone()
text_password.shake()
text_password.requestFocus()
}
}
override fun alertBlankEmail() {
vibrateSmartPhone()
text_email.shake()
text_email.requestFocus()
ui {
vibrateSmartPhone()
text_email.shake()
text_email.requestFocus()
}
}
override fun showLoading() {
enableUserInput(false)
view_loading.setVisible(true)
ui {
enableUserInput(false)
view_loading.setVisible(true)
}
}
override fun hideLoading() {
view_loading.setVisible(false)
enableUserInput(true)
ui {
view_loading.setVisible(false)
enableUserInput(true)
}
}
override fun showMessage(resId: Int) {
showToast(resId)
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() {
......@@ -110,15 +126,15 @@ class SignupFragment : Fragment(), SignupView {
}
private fun tintEditTextDrawableStart() {
activity?.apply {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, this)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, this)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, this)
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, this)
ui {
val personDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_person_black_24dp, it)
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, it)
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_lock_black_24dp, it)
val emailDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, it)
val drawables = arrayOf(personDrawable, atDrawable, lockDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.tintDrawables(drawables, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(arrayOf(text_name, text_username, text_password, text_email), drawables)
}
}
......
......@@ -63,8 +63,10 @@ class TwoFAFragment : Fragment(), TwoFAView {
}
override fun alertBlankTwoFactorAuthenticationCode() {
vibrateSmartPhone()
text_two_factor_auth.shake()
ui {
vibrateSmartPhone()
text_two_factor_auth.shake()
}
}
override fun alertInvalidTwoFactorAuthenticationCode() {
......@@ -72,30 +74,38 @@ class TwoFAFragment : Fragment(), TwoFAView {
}
override fun showLoading() {
enableUserInput(false)
view_loading.setVisible(true)
ui {
enableUserInput(false)
view_loading.setVisible(true)
}
}
override fun hideLoading() {
view_loading.setVisible(false)
enableUserInput(true)
ui {
view_loading.setVisible(false)
enableUserInput(true)
}
}
override fun showMessage(resId: Int) {
showToast(resId)
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
private fun tintEditTextDrawableStart() {
activity?.apply {
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, this)
ui {
val lockDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_vpn_key_black_24dp, it)
DrawableHelper.wrapDrawable(lockDrawable)
DrawableHelper.tintDrawable(lockDrawable, this, R.color.colorDrawableTintGrey)
DrawableHelper.tintDrawable(lockDrawable, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_two_factor_auth, lockDrawable)
}
}
......
......@@ -18,6 +18,7 @@ import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.*
import androidx.core.content.systemService
import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.*
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
......@@ -183,28 +184,28 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun showMessages(dataSet: List<BaseViewModel<*>>) {
// track the message sent immediately after the current message
var prevMessageViewModel: MessageViewModel? = null
// Loop over received messages to determine first unread
for (i in dataSet.indices) {
val msgModel = dataSet[i]
if (msgModel is MessageViewModel) {
val msg = msgModel.rawData
if (msg.timestamp < chatRoomLastSeen) {
// This message was sent before the last seen of the room. Hence, it was seen.
// if there is a message after (below) this, mark it firstUnread.
if (prevMessageViewModel != null) {
prevMessageViewModel.isFirstUnread = true
ui {
// track the message sent immediately after the current message
var prevMessageViewModel: MessageViewModel? = null
// Loop over received messages to determine first unread
for (i in dataSet.indices) {
val msgModel = dataSet[i]
if (msgModel is MessageViewModel) {
val msg = msgModel.rawData
if (msg.timestamp < chatRoomLastSeen) {
// This message was sent before the last seen of the room. Hence, it was seen.
// if there is a message after (below) this, mark it firstUnread.
if (prevMessageViewModel != null) {
prevMessageViewModel.isFirstUnread = true
}
break
}
break
prevMessageViewModel = msgModel
}
prevMessageViewModel = msgModel
}
}
activity?.apply {
if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(chatRoomType, chatRoomName, presenter,
reactionListener = this@ChatRoomFragment)
......@@ -282,11 +283,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun sendMessage(text: String) {
if (!text.isBlank()) {
if (!text.startsWith("/")) {
presenter.sendMessage(chatRoomId, text, editingMessageId)
} else {
presenter.runCommand(text, chatRoomId)
ui {
if (!text.isBlank()) {
if (!text.startsWith("/")) {
presenter.sendMessage(chatRoomId, text, editingMessageId)
} else {
presenter.runCommand(text, chatRoomId)
}
}
}
}
......@@ -301,43 +304,55 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun showNewMessage(message: List<BaseViewModel<*>>) {
adapter.prependData(message)
recycler_view.scrollToPosition(0)
verticalScrollOffset.set(0)
ui {
adapter.prependData(message)
recycler_view.scrollToPosition(0)
verticalScrollOffset.set(0)
}
}
override fun disableSendMessageButton() {
button_send.isEnabled = false
ui {
button_send.isEnabled = false
}
}
override fun enableSendMessageButton(sendFailed: Boolean) {
button_send.isEnabled = true
text_message.isEnabled = true
if (!sendFailed) {
clearMessageComposition()
ui {
button_send.isEnabled = true
text_message.isEnabled = true
if (!sendFailed) {
clearMessageComposition()
}
}
}
override fun clearMessageComposition() {
citation = null
editingMessageId = null
text_message.textContent = ""
actionSnackbar.dismiss()
ui {
citation = null
editingMessageId = null
text_message.textContent = ""
actionSnackbar.dismiss()
}
}
override fun dispatchUpdateMessage(index: Int, message: List<BaseViewModel<*>>) {
adapter.updateItem(message.last())
if (message.size > 1) {
adapter.prependData(listOf(message.first()))
ui {
adapter.updateItem(message.last())
if (message.size > 1) {
adapter.prependData(listOf(message.first()))
}
}
}
override fun dispatchDeleteMessage(msgId: String) {
adapter.removeItem(msgId)
ui {
adapter.removeItem(msgId)
}
}
override fun showReplyingAction(username: String, replyMarkdown: String, quotedMessage: String) {
activity?.apply {
ui {
citation = replyMarkdown
actionSnackbar.title = username
actionSnackbar.text = quotedMessage
......@@ -352,41 +367,55 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
override fun showLoading() = view_loading.setVisible(true)
override fun showLoading() {
ui { view_loading.setVisible(true) }
}
override fun hideLoading() = view_loading.setVisible(false)
override fun hideLoading() {
ui { view_loading.setVisible(false) }
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showMessage(resId: Int) {
showToast(resId)
ui {
showToast(resId)
}
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun populatePeopleSuggestions(members: List<PeopleSuggestionViewModel>) {
suggestions_view.addItems("@", members)
ui {
suggestions_view.addItems("@", members)
}
}
override fun populateRoomSuggestions(chatRooms: List<ChatRoomSuggestionViewModel>) {
suggestions_view.addItems("#", chatRooms)
ui {
suggestions_view.addItems("#", chatRooms)
}
}
override fun populateCommandSuggestions(commands: List<CommandSuggestionViewModel>) {
suggestions_view.addItems("/", commands)
ui {
suggestions_view.addItems("/", commands)
}
}
override fun copyToClipboard(message: String) {
activity?.apply {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
ui {
val clipboard: ClipboardManager = it.systemService()
clipboard.primaryClip = ClipData.newPlainText("", message)
}
}
override fun showEditingAction(roomId: String, messageId: String, text: String) {
activity?.apply {
ui {
actionSnackbar.title = getString(R.string.action_title_editing)
actionSnackbar.text = text
actionSnackbar.show()
......@@ -422,7 +451,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun showReactionsPopup(messageId: String) {
context?.let {
ui {
val emojiPickerPopup = EmojiPickerPopup(it)
emojiPickerPopup.listener = object : EmojiListenerAdapter() {
override fun onEmojiAdded(emoji: Emoji) {
......@@ -435,11 +464,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setReactionButtonIcon(@DrawableRes drawableId: Int) {
button_add_reaction.setImageResource(drawableId)
button_add_reaction.setTag(drawableId)
button_add_reaction.tag = drawableId
}
override fun showFileSelection(filter: Array<String>) {
activity?.let {
ui {
if (ContextCompat.checkSelfPermission(it, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(it,
......@@ -459,7 +488,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
1 -> {
if (!(grantResults.isNotEmpty() && grantResults.first() == PackageManager.PERMISSION_GRANTED)) {
handler.postDelayed({
hideAttachmentOptions()
ui { hideAttachmentOptions() }
}, 400)
}
}
......@@ -471,7 +500,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun showConnectionState(state: State) {
activity?.apply {
ui {
connection_status_text.fadeIn()
handler.removeCallbacks(dismissStatus)
when (state) {
......@@ -489,10 +518,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun onJoined() {
input_container.setVisible(true)
button_join_chat.setVisible(false)
isSubscribed = true
setupMessageComposer()
ui {
input_container.setVisible(true)
button_join_chat.setVisible(false)
isSubscribed = true
setupMessageComposer()
}
}
private val dismissStatus = {
......
......@@ -16,6 +16,7 @@ import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_pinned_messages.*
import javax.inject.Inject
......@@ -62,22 +63,30 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
presenter.loadPinnedMessages(chatRoomId)
}
override fun showLoading() = view_loading.setVisible(true)
override fun showLoading() {
ui { view_loading.setVisible(true) }
}
override fun hideLoading() = view_loading.setVisible(false)
override fun hideLoading() {
ui { view_loading.setVisible(false) }
}
override fun showMessage(resId: Int) {
showToast(resId)
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showPinnedMessages(pinnedMessages: List<BaseViewModel<*>>) {
activity?.apply {
ui {
if (recycler_view_pinned.adapter == null) {
// TODO - add a better constructor for this case...
adapter = ChatRoomAdapter(chatRoomType, chatRoomName, null, false)
......
......@@ -270,6 +270,7 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
// TODO - Temporary stuff, remove when adding DB support
private suspend fun subscribeRoomUpdates() {
manager.addStatusChannel(stateChannel)
view.showConnectionState(client.state)
manager.addRoomsAndSubscriptionsChannel(subscriptionsChannel)
launch(CommonPool + strategy.jobs) {
for (message in subscriptionsChannel) {
......
......@@ -24,7 +24,6 @@ import chat.rocket.android.util.extensions.textContent
import chat.rocket.common.model.RoomType
import chat.rocket.core.model.ChatRoom
import com.facebook.drawee.view.SimpleDraweeView
import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_chat.view.*
import kotlinx.android.synthetic.main.unread_messages_badge.view.*
......
......@@ -32,9 +32,11 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_chat_rooms.*
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.NonCancellable.isActive
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject
......@@ -101,8 +103,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_sort -> {
val dialogLayout = layoutInflater.inflate(R.layout.chatroom_sort_dialog, null)
val sortType = SharedPreferenceHelper.getInt(Constants.CHATROOM_SORT_TYPE_KEY, ChatRoomsSortOrder.ACTIVITY)
......@@ -154,50 +156,57 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}
override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) {
activity?.apply {
listJob?.cancel()
listJob = launch(UI) {
val adapter = recycler_view.adapter as SimpleSectionedRecyclerViewAdapter
// FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android/issues/5ac2916c36c7b235275ccccf
// TODO - fix this bug to re-enable DiffUtil
/*val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await()*/
if (isActive) {
adapter.baseAdapter.updateRooms(newDataSet)
// TODO - fix crash to re-enable diff.dispatchUpdatesTo(adapter)
adapter.notifyDataSetChanged()
//Set sections always after data set is updated
setSections()
}
listJob?.cancel()
listJob = ui {
val adapter = recycler_view.adapter as SimpleSectionedRecyclerViewAdapter
// FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android/issues/5ac2916c36c7b235275ccccf
// TODO - fix this bug to re-enable DiffUtil
/*val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await()*/
if (isActive) {
adapter.baseAdapter.updateRooms(newDataSet)
// TODO - fix crash to re-enable diff.dispatchUpdatesTo(adapter)
adapter.notifyDataSetChanged()
//Set sections always after data set is updated
setSections()
}
}
}
override fun showNoChatRoomsToDisplay() = text_no_data_to_display.setVisible(true)
override fun showNoChatRoomsToDisplay() {
ui { text_no_data_to_display.setVisible(true) }
}
override fun showLoading() = view_loading.setVisible(true)
override fun showLoading(){
ui { view_loading.setVisible(true) }
}
override fun hideLoading() {
if (view_loading != null) {
ui {
view_loading.setVisible(false)
}
}
override fun showMessage(resId: Int) {
showToast(resId)
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showConnectionState(state: State) {
activity?.apply {
Timber.d("Got new state: $state")
ui {
connection_status_text.fadeIn()
handler.removeCallbacks(dismissStatus)
when (state) {
......@@ -221,22 +230,25 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}
private fun setupToolbar() {
(activity as AppCompatActivity).supportActionBar?.title = getString(R.string.title_chats)
(activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_chats)
}
private fun setupRecyclerView() {
activity?.apply {
recycler_view.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(this,
ui {
recycler_view.layoutManager = LinearLayoutManager(it, LinearLayoutManager.VERTICAL, false)
recycler_view.addItemDecoration(DividerItemDecoration(it,
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_start),
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end)))
recycler_view.itemAnimator = DefaultItemAnimator()
// TODO - use a ViewModel Mapper instead of using settings on the adapter
val baseAdapter = ChatRoomsAdapter(this,
settingsRepository.get(serverInteractor.get()!!), localRepository) { chatRoom -> presenter.loadChatRoom(chatRoom) }
val baseAdapter = ChatRoomsAdapter(it,
settingsRepository.get(serverInteractor.get()!!), localRepository) {
chatRoom -> presenter.loadChatRoom(chatRoom)
}
sectionedAdapter = SimpleSectionedRecyclerViewAdapter(this, R.layout.item_chatroom_header, R.id.text_chatroom_header, baseAdapter!!)
sectionedAdapter = SimpleSectionedRecyclerViewAdapter(it,
R.layout.item_chatroom_header, R.id.text_chatroom_header, baseAdapter)
recycler_view.adapter = sectionedAdapter
}
}
......
package chat.rocket.android.members.presentation
import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.member.ui.newInstance
import chat.rocket.android.members.ui.newInstance
class MembersNavigator(internal val activity: ChatRoomActivity) {
......
package chat.rocket.android.member.ui
package chat.rocket.android.members.ui
import android.os.Bundle
import android.support.design.widget.BottomSheetDialogFragment
......
......@@ -19,6 +19,7 @@ import chat.rocket.android.members.viewmodel.MemberViewModel
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.widget.DividerItemDecoration
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_members.*
......@@ -70,7 +71,7 @@ class MembersFragment : Fragment(), MembersView {
}
override fun showMembers(dataSet: List<MemberViewModel>, total: Long) {
activity?.apply {
ui {
setupToolbar(total)
if (adapter.itemCount == 0) {
adapter.prependData(dataSet)
......@@ -84,8 +85,8 @@ class MembersFragment : Fragment(), MembersView {
} else {
adapter.appendData(dataSet)
}
if (this is ChatRoomActivity) {
this.showRoomTypeIcon(false)
if (it is ChatRoomActivity) {
it.showRoomTypeIcon(false)
}
}
}
......@@ -98,29 +99,37 @@ class MembersFragment : Fragment(), MembersView {
return super.onOptionsItemSelected(item)
}
override fun showLoading() = view_loading.setVisible(true)
override fun showLoading() {
ui { view_loading.setVisible(true) }
}
override fun hideLoading() = view_loading.setVisible(false)
override fun hideLoading() {
ui { view_loading.setVisible(false) }
}
override fun showMessage(resId: Int) {
showToast(resId)
ui {
showToast(resId)
}
}
override fun showMessage(message: String) {
showToast(message)
ui {
showToast(message)
}
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
private fun setupRecyclerView() {
activity?.apply {
ui {
recycler_view.layoutManager = linearLayoutManager
recycler_view.addItemDecoration(DividerItemDecoration(this))
recycler_view.addItemDecoration(DividerItemDecoration(it))
recycler_view.adapter = adapter
}
}
private fun setupToolbar(totalMembers: Long) {
(activity as ChatRoomActivity).setupToolbarTitle(getString(R.string.title_members, totalMembers))
(activity as ChatRoomActivity?)?.setupToolbarTitle(getString(R.string.title_members, totalMembers))
}
}
\ No newline at end of file
......@@ -15,7 +15,7 @@ import java.util.concurrent.CopyOnWriteArrayList
class ConnectionManager(internal val client: RocketChatClient) {
private val statusChannelList = CopyOnWriteArrayList<Channel<State>>()
private val statusChannel = Channel<State>()
private val statusChannel = Channel<State>(Channel.CONFLATED)
private var connectJob: Job? = null
private val roomAndSubscriptionChannels = ArrayList<Channel<StreamMessage<BaseRoom>>>()
......@@ -67,7 +67,8 @@ class ConnectionManager(internal val client: RocketChatClient) {
}
for (channel in statusChannelList) {
channel.send(status)
Timber.d("Sending status: $status to $channel")
channel.offer(status)
}
}
}
......
......@@ -11,7 +11,10 @@ import chat.rocket.android.util.extensions.asObservable
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.textContent
import android.support.v7.view.ActionMode
import chat.rocket.android.util.extensions.ui
import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.fragment_password.*
import javax.inject.Inject
......@@ -19,6 +22,7 @@ import javax.inject.Inject
class PasswordFragment: Fragment(), PasswordView, android.support.v7.view.ActionMode.Callback {
@Inject lateinit var presenter: PasswordPresenter
private var actionMode: ActionMode? = null
private val disposables = CompositeDisposable()
companion object {
fun newInstance() = PasswordFragment()
......@@ -34,13 +38,20 @@ class PasswordFragment: Fragment(), PasswordView, android.support.v7.view.Action
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listenToChanges()
disposables.add(listenToChanges())
}
override fun onDestroyView() {
disposables.clear()
super.onDestroyView()
}
override fun hideLoading() {
layout_new_password.visibility = View.VISIBLE
layout_confirm_password.visibility = View.VISIBLE
view_loading.visibility = View.GONE
ui {
layout_new_password.visibility = View.VISIBLE
layout_confirm_password.visibility = View.VISIBLE
view_loading.visibility = View.GONE
}
}
override fun onActionItemClicked(mode: ActionMode, menuItem: MenuItem): Boolean {
......@@ -69,9 +80,11 @@ class PasswordFragment: Fragment(), PasswordView, android.support.v7.view.Action
}
override fun showLoading() {
layout_new_password.visibility = View.GONE
layout_confirm_password.visibility = View.GONE
view_loading.visibility = View.VISIBLE
ui {
layout_new_password.visibility = View.GONE
layout_confirm_password.visibility = View.GONE
view_loading.visibility = View.VISIBLE
}
}
override fun showPasswordFailsUpdateMessage(error: String?) {
......@@ -84,8 +97,9 @@ class PasswordFragment: Fragment(), PasswordView, android.support.v7.view.Action
private fun finishActionMode() = actionMode?.finish()
private fun listenToChanges() {
Observables.combineLatest(text_new_password.asObservable(), text_confirm_password.asObservable()).subscribe {
private fun listenToChanges(): Disposable {
return Observables.combineLatest(text_new_password.asObservable(),
text_confirm_password.asObservable()).subscribe {
val textPassword = text_new_password.textContent
val textConfirmPassword = text_confirm_password.textContent
......@@ -97,7 +111,9 @@ class PasswordFragment: Fragment(), PasswordView, android.support.v7.view.Action
}
private fun showToast(msg: String?) {
Toast.makeText(context, msg, Toast.LENGTH_LONG).show()
ui {
Toast.makeText(it, msg, Toast.LENGTH_LONG).show()
}
}
private fun startActionMode() {
......
package chat.rocket.android.util.extensions
import android.os.Looper
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentActivity
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
inline fun Fragment.ui(crossinline block: (activity: FragmentActivity) -> Unit): Job? {
// Checking first for activity and view saves us from some synchronyzed and thread local checks
if (activity != null && view != null) {
// If we already are running on the Main Thread (UI Thread), just go ahead and execute the block
return if (Looper.getMainLooper() == Looper.myLooper()) {
block(activity!!)
null
} else {
// Launch a Job on the UI context and check again if the activity and view are still valid
launch(UI) {
if (activity != null && view != null) {
block(activity!!)
}
}
}
}
return null
}
\ No newline at end of file
......@@ -10,14 +10,14 @@
android:paddingTop="@dimen/chat_item_top_and_bottom_padding"
android:paddingBottom="@dimen/chat_item_top_and_bottom_padding">
<include
android:id="@+id/layout_avatar"
layout="@layout/avatar"
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
app:roundedCornerRadius="3dp"
android:layout_marginTop="6dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/text_chat_name"
......@@ -25,7 +25,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/layout_avatar"
app:layout_constraintStart_toEndOf="@id/image_avatar"
android:textDirection="locale"
tools:text="General"/>
......@@ -51,7 +51,7 @@
app:layout_constraintTop_toBottomOf="@id/text_chat_name"
app:layout_constraintEnd_toStartOf="@id/layout_unread_messages_badge"
android:textDirection="locale"
tools:text="You: Type something"/>
tools:text="You: Type something that is very big and need at least to lines, or maybe even more"/>
<include
android:id="@+id/layout_unread_messages_badge"
......
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
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