Unverified Commit 38c960d8 authored by Ergashev Adizbek's avatar Ergashev Adizbek Committed by GitHub

Merge branch 'develop' into swipe-to-reply

parents 3b355afd 521f0a8b
......@@ -18,8 +18,8 @@ android {
applicationId "chat.rocket.android"
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 2060
versionName "3.3.0"
versionCode 2061
versionName "3.4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
......
......@@ -27,4 +27,5 @@ sealed class ScreenViewEvent(val screenName: String) {
object Preferences : ScreenViewEvent("PreferencesFragment")
object Profile : ScreenViewEvent("ProfileFragment")
object Settings : ScreenViewEvent("SettingsFragment")
object Directory : ScreenViewEvent("DirectoryFragment")
}
......@@ -4,7 +4,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.common.model.UserStatus
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
......
......@@ -24,7 +24,7 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.android.util.setupFabric
import chat.rocket.common.RocketChatException
......
package chat.rocket.android.authentication.infraestructure
package chat.rocket.android.authentication.infrastructure
import chat.rocket.android.authentication.domain.model.TokenModel
import chat.rocket.android.dagger.scope.PerActivity
......
package chat.rocket.android.authentication.infraestructure
package chat.rocket.android.authentication.infrastructure
import android.content.SharedPreferences
import androidx.core.content.edit
......
......@@ -16,7 +16,7 @@ import chat.rocket.android.server.domain.isLdapAuthenticationEnabled
import chat.rocket.android.server.domain.isPasswordResetEnabled
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.isEmail
......
......@@ -15,7 +15,7 @@ import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl
......
......@@ -25,18 +25,18 @@ interface LoginOptionsView : LoadingView, MessageView {
fun setupFacebookButtonListener(facebookOauthUrl: String, state: String)
/**
* Shows the "login by Github" view if it is enabled by the server settings.
* Shows the "login by GitHub" view if it is enabled by the server settings.
*
* REMARK: We must set up the Github button listener before enabling it
* REMARK: We must set up the GitHub button listener before enabling it
* [setupGithubButtonListener].
* @see [showAccountsView]
*/
fun enableLoginByGithub()
/**
* Setups the Github button.
* Setups the GitHub button.
*
* @param githubUrl The Github OAuth URL to authenticate with.
* @param githubUrl The GitHub OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
*/
......@@ -61,36 +61,36 @@ interface LoginOptionsView : LoadingView, MessageView {
fun setupGoogleButtonListener(googleUrl: String, state: String)
/**
* Shows the "login by Linkedin" view if it is enabled by the server settings.
* Shows the "login by LinkedIn" view if it is enabled by the server settings.
*
* REMARK: We must set up the Linkedin button listener before enabling it
* REMARK: We must set up the LinkedIn button listener before enabling it
* [setupLinkedinButtonListener].
* @see [showAccountsView]
*/
fun enableLoginByLinkedin()
/**
* Setups the Linkedin button.
* Setups the LinkedIn button.
*
* @param linkedinUrl The Linkedin OAuth URL to authenticate with.
* @param linkedinUrl The LinkedIn OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
*/
fun setupLinkedinButtonListener(linkedinUrl: String, state: String)
/**
* Shows the "login by Gitlab" view if it is enabled by the server settings.
* Shows the "login by GitLab" view if it is enabled by the server settings.
*
* REMARK: We must set up the Gitlab button listener before enabling it
* REMARK: We must set up the GitLab button listener before enabling it
* [setupGitlabButtonListener].
* @see [showAccountsView]
*/
fun enableLoginByGitlab()
/**
* Setups the Gitlab button.
* Setups the GitLab button.
*
* @param gitlabUrl The Gitlab OAuth URL to authenticate with.
* @param gitlabUrl The GitLab OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later
* (to protect against forgery attacks).
*/
......@@ -99,7 +99,7 @@ interface LoginOptionsView : LoadingView, MessageView {
/**
* Shows the "login by WordPress" view if it is enabled by the server settings.
*
* REMARK: We must set up the Gitlab button listener before enabling it [setupWordpressButtonListener].
* REMARK: We must set up the GitLab button listener before enabling it [setupWordpressButtonListener].
*/
fun enableLoginByWordpress()
......
package chat.rocket.android.authentication.loginoptions.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.Activity
import android.content.Intent
import android.graphics.PorterDuff
......@@ -7,10 +10,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.widget.Button
import android.widget.LinearLayout
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.isVisible
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
......@@ -28,6 +34,7 @@ import chat.rocket.android.webview.sso.ui.ssoWebViewIntent
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_authentication_login_options.*
import timber.log.Timber
import javax.inject.Inject
private const val SERVER_NAME = "server_name"
......@@ -61,6 +68,8 @@ internal const val REQUEST_CODE_FOR_OAUTH = 1
internal const val REQUEST_CODE_FOR_CAS = 2
internal const val REQUEST_CODE_FOR_SAML = 3
private const val DEFAULT_ANIMATION_DURATION = 400L
fun newInstance(
serverName: String,
state: String? = null,
......@@ -238,7 +247,6 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
enableLoginByLinkedin()
}
if (gitlabOauthUrl != null && state != null) {
setupGitlabButtonListener(gitlabOauthUrl.toString(), state.toString())
enableLoginByGitlab()
......@@ -390,11 +398,11 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
var isAccountsCollapsed = true
button_expand_collapse_accounts.setOnClickListener {
isAccountsCollapsed = if (isAccountsCollapsed) {
button_expand_collapse_accounts.rotateBy(180F, 400)
button_expand_collapse_accounts.rotateBy(180F, DEFAULT_ANIMATION_DURATION)
expandAccountsView()
false
} else {
button_expand_collapse_accounts.rotateBy(180F, 400)
button_expand_collapse_accounts.rotateBy(180F, DEFAULT_ANIMATION_DURATION)
collapseAccountsView()
true
}
......@@ -532,17 +540,73 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
}
private fun expandAccountsView() {
(0..accounts_container.childCount)
val buttons = (0..accounts_container.childCount)
.mapNotNull { accounts_container.getChildAt(it) as? Button }
.filter { it.isClickable && !it.isVisible }
.forEach { it.isVisible = true }
val optionHeight = accounts_container.getChildAt(1).height +
accounts_container.getChildAt(1).marginTop
val collapsedHeight = accounts_container.height
val expandedHeight = collapsedHeight + optionHeight * buttons.size
with(ValueAnimator.ofInt(collapsedHeight, expandedHeight)) {
addUpdateListener {
val params = accounts_container.layoutParams
params.height = animatedValue as Int
accounts_container.layoutParams = params
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
buttons.forEach {
it.isVisible = true
val anim = AlphaAnimation(0.0f, 1.0f)
anim.duration = DEFAULT_ANIMATION_DURATION
it.startAnimation(anim)
}
}
})
setDuration(DEFAULT_ANIMATION_DURATION).start()
}
}
private fun collapseAccountsView() {
(0..accounts_container.childCount)
val buttons = (0..accounts_container.childCount)
.mapNotNull { accounts_container.getChildAt(it) as? Button }
.filter { it.isClickable && it.isVisible }
.drop(3)
.forEach { it.isVisible = false }
val optionHeight = accounts_container.getChildAt(1).height +
accounts_container.getChildAt(1).marginTop
val expandedHeight = accounts_container.height
val collapsedHeight = expandedHeight - optionHeight * buttons.size
with(ValueAnimator.ofInt(expandedHeight, collapsedHeight)) {
addUpdateListener {
val params = accounts_container.layoutParams
params.height = animatedValue as Int
accounts_container.layoutParams = params
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animator: Animator) {
buttons.forEach {
val anim = AlphaAnimation(1.0f, 0.0f)
anim.duration = DEFAULT_ANIMATION_DURATION
anim.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {
Timber.d("Animation starts: $animation")
}
override fun onAnimationEnd(animation: Animation) {
it.isVisible = false
}
override fun onAnimationRepeat(animation: Animation) {
Timber.d("Animation repeats: $animation")
}
})
it.startAnimation(anim)
}
}
})
setDuration(DEFAULT_ANIMATION_DURATION).start()
}
}
}
......@@ -7,7 +7,7 @@ import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.launchUI
import kotlinx.coroutines.Dispatchers
......
......@@ -13,7 +13,7 @@ import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl
......
......@@ -3,7 +3,7 @@ package chat.rocket.android.authentication.resetpassword.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
......
......@@ -8,7 +8,7 @@ import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.SaveConnectingServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.isValidUrl
......
......@@ -13,7 +13,7 @@ import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.privacyPolicyUrl
......
......@@ -14,7 +14,7 @@ import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.isEmail
......
......@@ -4,7 +4,7 @@ import chat.rocket.android.chatdetails.domain.ChatDetails
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
......
......@@ -3,7 +3,7 @@ package chat.rocket.android.chatinformation.presentation
import chat.rocket.android.chatroom.uimodel.UiModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
......
......@@ -33,8 +33,8 @@ import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.domain.uploadMaxFileSize
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.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.state
import chat.rocket.android.util.extension.getByteArray
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
......
......@@ -4,9 +4,9 @@ import android.app.job.JobParameters
import android.app.job.JobService
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.DatabaseMessageMapper
import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.DatabaseMessageMapper
import chat.rocket.android.server.infrastructure.DatabaseMessagesRepository
import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message
import dagger.android.AndroidInjection
......
......@@ -10,7 +10,7 @@ import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.util.extensions.addFragment
import chat.rocket.android.util.extensions.textContent
import dagger.android.AndroidInjection
......
......@@ -74,6 +74,7 @@ import chat.rocket.android.helper.AndroidPermissionsHelper.hasCameraPermission
import chat.rocket.android.helper.AndroidPermissionsHelper.hasWriteExternalStoragePermission
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extension.createImageFile
import chat.rocket.android.util.extension.orFalse
import chat.rocket.android.util.extensions.circularRevealOrUnreveal
import chat.rocket.android.util.extensions.clearLightStatusBar
import chat.rocket.android.util.extensions.fadeIn
......@@ -150,14 +151,10 @@ private const val BUNDLE_CHAT_ROOM_MESSAGE = "chat_room_message"
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener,
ChatRoomAdapter.OnActionSelected, Drawable.Callback {
@Inject
lateinit var presenter: ChatRoomPresenter
@Inject
lateinit var parser: MessageParser
@Inject
lateinit var analyticsManager: AnalyticsManager
@Inject
lateinit var navigator: ChatRoomNavigator
@Inject lateinit var presenter: ChatRoomPresenter
@Inject lateinit var parser: MessageParser
@Inject lateinit var analyticsManager: AnalyticsManager
@Inject lateinit var navigator: ChatRoomNavigator
private lateinit var adapter: ChatRoomAdapter
internal lateinit var chatRoomId: String
private lateinit var chatRoomName: String
......@@ -470,6 +467,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
setupToolbar(roomUiModel.name.toString())
setupMessageComposer(roomUiModel)
isBroadcastChannel = roomUiModel.broadcast
isFavorite = roomUiModel.favorite.orFalse()
if (isBroadcastChannel && !roomUiModel.canModerate) {
disableMenu = true
activity?.invalidateOptionsMenu()
......@@ -606,45 +604,31 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun showMessage(message: String) {
ui {
showToast(message)
}
ui { showToast(message) }
}
override fun showMessage(resId: Int) {
ui {
showToast(resId)
}
ui { showToast(resId) }
}
override fun showGenericErrorMessage() {
ui {
showMessage(getString(R.string.msg_generic_error))
}
ui { showMessage(getString(R.string.msg_generic_error)) }
}
override fun populatePeopleSuggestions(members: List<PeopleSuggestionUiModel>) {
ui {
suggestions_view.addItems("@", members)
}
ui { suggestions_view.addItems("@", members) }
}
override fun populateRoomSuggestions(chatRooms: List<ChatRoomSuggestionUiModel>) {
ui {
suggestions_view.addItems("#", chatRooms)
}
ui { suggestions_view.addItems("#", chatRooms) }
}
override fun populateCommandSuggestions(commands: List<CommandSuggestionUiModel>) {
ui {
suggestions_view.addItems("/", commands)
}
ui { suggestions_view.addItems("/", commands) }
}
override fun populateEmojiSuggestions(emojis: List<EmojiSuggestionUiModel>) {
ui {
suggestions_view.addItems(":", emojis)
}
ui { suggestions_view.addItems(":", emojis) }
}
override fun copyToClipboard(message: String) {
......
......@@ -30,7 +30,7 @@ import chat.rocket.android.server.domain.baseUrl
import chat.rocket.android.server.domain.messageReadReceiptEnabled
import chat.rocket.android.server.domain.messageReadReceiptStoreUsers
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.isImage
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.ifNotNullNorEmpty
......
......@@ -167,7 +167,7 @@ class RoomUiModelMapper(
private fun roomType(type: String): String = with(context.resources) {
when (type) {
RoomType.CHANNEL -> getString(R.string.header_channel)
RoomType.CHANNEL -> getString(R.string.msg_channels)
RoomType.PRIVATE_GROUP -> getString(R.string.header_private_groups)
RoomType.DIRECT_MESSAGE -> getString(R.string.header_direct_messages)
RoomType.LIVECHAT -> getString(R.string.header_live_chats)
......
......@@ -15,9 +15,9 @@ 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
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.ConnectionManager
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.core.RocketChatClient
import dagger.Module
import dagger.Provides
......
......@@ -13,7 +13,7 @@ import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.SortingAndGroupingInteractor
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.server.infrastructure.ConnectionManager
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.android.util.retryIO
......@@ -50,6 +50,8 @@ class ChatRoomsPresenter @Inject constructor(
fun toSettings() = navigator.toSettings()
fun toDirectory() = navigator.toDirectory()
fun getCurrentServerName() = view.setupToolbar(currentServer)
fun getSortingAndGroupingPreferences() {
......
......@@ -9,6 +9,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
......@@ -44,7 +45,6 @@ internal const val TAG_CHAT_ROOMS_FRAGMENT = "ChatRoomsFragment"
private const val BUNDLE_CHAT_ROOM_ID = "BUNDLE_CHAT_ROOM_ID"
fun newInstance(chatRoomId: String?): Fragment = ChatRoomsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
......@@ -103,6 +103,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
with((activity as AppCompatActivity)) {
with(toolbar) {
setSupportActionBar(this)
supportActionBar?.setDisplayShowTitleEnabled(false)
setNavigationOnClickListener { presenter.toSettings() }
}
}
......@@ -122,10 +123,10 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
if (isSortByName) {
text_sort_by.text =
getString(R.string.msg_sort_by, getString(R.string.msg_sort_by_name).toLowerCase())
getString(R.string.msg_sort_by_placeholder, getString(R.string.msg_sort_by_name).toLowerCase())
} else {
text_sort_by.text = getString(
R.string.msg_sort_by,
R.string.msg_sort_by_placeholder,
getString(R.string.msg_sort_by_activity).toLowerCase()
)
}
......@@ -149,12 +150,14 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
// We need to show all the menu items here by invalidating the options to recreate the entire menu.
activity?.invalidateOptionsMenu()
queryChatRoomsByName(null)
hideDirectoryView()
return true
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
// We need to hide the all the menu items here.
menu.findItem(R.id.action_new_channel).isVisible = false
showDirectoryView()
return true
}
})
......@@ -282,6 +285,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
chat.rocket.android.sortingandgrouping.ui.TAG
)
}
text_directory.setOnClickListener { presenter.toDirectory() }
}
fun sortChatRoomsList(
......@@ -305,7 +310,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}
private fun changeSortByTitle(text: String) {
text_sort_by.text = getString(R.string.msg_sort_by, text.toLowerCase())
text_sort_by.text = getString(R.string.msg_sort_by_placeholder, text.toLowerCase())
}
private fun queryChatRoomsByName(name: String?): Boolean {
......@@ -324,4 +329,14 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
viewModel.setQuery(Query.ByActivity(isGroupByType))
}
}
private fun showDirectoryView() {
text_directory.isVisible = true
text_sort_by.isGone = true
}
private fun hideDirectoryView() {
text_directory.isGone = true
text_sort_by.isVisible = true
}
}
......@@ -9,7 +9,7 @@ import chat.rocket.android.chatrooms.adapter.LoadingItemHolder
import chat.rocket.android.chatrooms.adapter.RoomUiModelMapper
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
import chat.rocket.android.chatrooms.infrastructure.ChatRoomsRepository
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infrastructure.ConnectionManager
import chat.rocket.android.util.livedata.transform
import chat.rocket.android.util.livedata.wrap
import chat.rocket.android.util.retryIO
......
......@@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModelProvider
import chat.rocket.android.chatrooms.adapter.RoomUiModelMapper
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
import chat.rocket.android.chatrooms.infrastructure.ChatRoomsRepository
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infrastructure.ConnectionManager
import javax.inject.Inject
class ChatRoomsViewModelFactory @Inject constructor(
......
package chat.rocket.android.core.behaviours
interface AppLanguageView {
/**
* Updates the app language
*
* @param language The app language to be updated.
* @param country Opcional. The country code to be updated.
*/
fun updateLanguage(language: String, country: String? = null)
}
\ No newline at end of file
......@@ -4,7 +4,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.members.uimodel.MemberUiModelMapper
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.RoomType
......
......@@ -14,9 +14,15 @@ import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
@Singleton
@Component(modules = [AndroidSupportInjectionModule::class,
AppModule::class, ActivityBuilder::class, ServiceBuilder::class, ReceiverBuilder::class,
AndroidWorkerInjectionModule::class])
@Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ServiceBuilder::class,
ReceiverBuilder::class,
AndroidWorkerInjectionModule::class]
)
interface AppComponent {
@Component.Builder
......
......@@ -19,6 +19,7 @@ import chat.rocket.android.chatroom.ui.ChatRoomActivity
import chat.rocket.android.chatrooms.di.ChatRoomsFragmentProvider
import chat.rocket.android.createchannel.di.CreateChannelProvider
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.directory.di.DirectoryFragmentProvider
import chat.rocket.android.draw.main.di.DrawModule
import chat.rocket.android.draw.main.ui.DrawingActivity
import chat.rocket.android.favoritemessages.di.FavoriteMessagesFragmentProvider
......@@ -70,7 +71,8 @@ abstract class ActivityBuilder {
CreateChannelProvider::class,
ProfileFragmentProvider::class,
SettingsFragmentProvider::class,
AdminPanelWebViewFragmentProvider::class
AdminPanelWebViewFragmentProvider::class,
DirectoryFragmentProvider::class
]
)
abstract fun bindMainActivity(): MainActivity
......
......@@ -12,8 +12,8 @@ import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.AnswersAnalytics
import chat.rocket.android.analytics.GoogleAnalyticsForFirebase
import chat.rocket.android.authentication.infraestructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.authentication.infraestructure.SharedPreferencesTokenRepository
import chat.rocket.android.authentication.infrastructure.SharedPreferencesMultiServerTokenRepository
import chat.rocket.android.authentication.infrastructure.SharedPreferencesTokenRepository
import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.qualifier.ForAuthentication
import chat.rocket.android.dagger.qualifier.ForMessages
......@@ -44,19 +44,21 @@ import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.SortingAndGroupingRepository
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.infraestructure.DatabaseMessageMapper
import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infraestructure.MemoryUsersRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepository
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.server.infraestructure.SharedPrefsSortingAndGroupingRepository
import chat.rocket.android.server.infrastructure.CurrentLanguageRepository
import chat.rocket.android.server.infrastructure.SharedPrefsCurrentLanguageRepository
import chat.rocket.android.server.infrastructure.DatabaseMessageMapper
import chat.rocket.android.server.infrastructure.DatabaseMessagesRepository
import chat.rocket.android.server.infrastructure.JobSchedulerInteractorImpl
import chat.rocket.android.server.infrastructure.MemoryChatRoomsRepository
import chat.rocket.android.server.infrastructure.MemoryUsersRepository
import chat.rocket.android.server.infrastructure.SharedPreferencesAccountsRepository
import chat.rocket.android.server.infrastructure.SharedPreferencesPermissionsRepository
import chat.rocket.android.server.infrastructure.SharedPreferencesSettingsRepository
import chat.rocket.android.server.infrastructure.SharedPrefsAnalyticsTrackingRepository
import chat.rocket.android.server.infrastructure.SharedPrefsBasicAuthRepository
import chat.rocket.android.server.infrastructure.SharedPrefsConnectingServerRepository
import chat.rocket.android.server.infrastructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.server.infrastructure.SharedPrefsSortingAndGroupingRepository
import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.BasicAuthenticatorInterceptor
import chat.rocket.android.util.HttpLoggingInterceptor
......@@ -175,7 +177,6 @@ class AppModule {
fun provideSharedPreferences(context: Application) =
context.getSharedPreferences("rocket.chat", Context.MODE_PRIVATE)
@Provides
@ForMessages
fun provideMessagesSharedPreferences(context: Application) =
......@@ -211,6 +212,12 @@ class AppModule {
return SharedPrefsConnectingServerRepository(prefs)
}
@Provides
@Singleton
fun provideCurrentLanguageRepository(prefs: SharedPreferences): CurrentLanguageRepository {
return SharedPrefsCurrentLanguageRepository(prefs)
}
@Provides
@Singleton
fun provideSettingsRepository(localRepository: LocalRepository): SettingsRepository {
......
......@@ -6,7 +6,7 @@ import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.SharedPreferencesLocalRepository
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.server.infrastructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.TimberLogger
import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
......
......@@ -69,7 +69,7 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
@Query("""
$BASE_QUERY
$FILTER_NOT_OPENED
ORDER BY name
ORDER BY name COLLATE NOCASE
""")
abstract fun getAllAlphabetically(): LiveData<List<ChatRoom>>
......@@ -79,7 +79,7 @@ abstract class ChatRoomDao : BaseDao<ChatRoomEntity> {
$FILTER_NOT_OPENED
ORDER BY
$TYPE_ORDER,
name
name COLLATE NOCASE
""")
abstract fun getAllAlphabeticallyGrouped(): LiveData<List<ChatRoom>>
......
package chat.rocket.android.directory.adapter
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.directory.uimodel.DirectoryUiModel
import chat.rocket.android.util.extensions.inflate
private const val VIEW_TYPE_CHANNELS = 0
private const val VIEW_TYPE_USERS = 1
private const val VIEW_TYPE_GLOBAL_USERS = 2
class DirectoryAdapter(private val selector: Selector) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var isSortByChannels: Boolean = true
private var isSearchForGlobalUsers: Boolean = true
private var dataSet: List<DirectoryUiModel> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
when (viewType) {
VIEW_TYPE_CHANNELS -> DirectoryChannelViewHolder(
parent.inflate(R.layout.item_directory_channel)
)
VIEW_TYPE_USERS -> DirectoryUsersViewHolder(
parent.inflate(R.layout.item_directory_user)
)
VIEW_TYPE_GLOBAL_USERS -> DirectoryGlobalUsersViewHolder(
parent.inflate(R.layout.item_directory_user)
)
else -> throw IllegalStateException("viewType must be either VIEW_TYPE_CHANNELS, VIEW_TYPE_USERS or VIEW_TYPE_GLOBAL_USERS")
}
override fun getItemCount(): Int = dataSet.size
override fun getItemViewType(position: Int): Int {
return if (isSortByChannels) {
VIEW_TYPE_CHANNELS
} else {
if (isSearchForGlobalUsers) {
VIEW_TYPE_GLOBAL_USERS
} else {
VIEW_TYPE_USERS
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =
when (holder) {
is DirectoryChannelViewHolder -> bindDirectoryChannelViewHolder(holder, position)
is DirectoryUsersViewHolder -> bindDirectoryUsersViewHolder(holder, position)
is DirectoryGlobalUsersViewHolder -> bindDirectoryGlobalUsersViewHolder(
holder,
position
)
else -> throw IllegalStateException("Unable to bind ViewHolder. ViewHolder must be either DirectoryChannelViewHolder, DirectoryUsersViewHolder or DirectoryGlobalUsersViewHolder")
}
private fun bindDirectoryChannelViewHolder(holder: DirectoryChannelViewHolder, position: Int) {
with(dataSet[position]) {
holder.bind(this)
holder.itemView.setOnClickListener { selector.onChannelSelected(id, name) }
}
}
private fun bindDirectoryUsersViewHolder(holder: DirectoryUsersViewHolder, position: Int) {
with(dataSet[position]) {
holder.bind(this)
holder.itemView.setOnClickListener { selector.onUserSelected(username, name) }
}
}
private fun bindDirectoryGlobalUsersViewHolder(
holder: DirectoryGlobalUsersViewHolder,
position: Int
) {
with(dataSet[position]) {
holder.bind(this)
holder.itemView.setOnClickListener { selector.onGlobalUserSelected(username, name) }
}
}
fun clearData() {
dataSet = emptyList()
notifyDataSetChanged()
}
fun setSorting(isSortByChannels: Boolean, isSearchForGlobalUsers: Boolean) {
this.isSortByChannels = isSortByChannels
this.isSearchForGlobalUsers = isSearchForGlobalUsers
}
fun prependData(dataSet: List<DirectoryUiModel>) {
this.dataSet = dataSet
notifyItemRangeInserted(0, dataSet.size)
}
fun appendData(dataSet: List<DirectoryUiModel>) {
val previousDataSetSize = this.dataSet.size
this.dataSet += dataSet
notifyItemRangeInserted(previousDataSetSize, dataSet.size)
}
}
interface Selector {
fun onChannelSelected(channelId: String, channelName: String)
fun onUserSelected(username: String, name: String)
fun onGlobalUserSelected(username: String, name: String)
}
\ No newline at end of file
package chat.rocket.android.directory.adapter
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.directory.uimodel.DirectoryUiModel
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_directory_channel.view.*
class DirectoryChannelViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(directoryChannelUiModel: DirectoryUiModel) = with(itemView) {
Glide.with(image_avatar).load(directoryChannelUiModel.channelAvatarUri).into(image_avatar)
text_channel_name.text = directoryChannelUiModel.name
text_channel_description.text = directoryChannelUiModel.description
text_channel_total_members.text = directoryChannelUiModel.totalMembers
}
}
\ No newline at end of file
package chat.rocket.android.directory.adapter
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.directory.uimodel.DirectoryUiModel
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_directory_user.view.*
class DirectoryGlobalUsersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(directoryChannelUiModel: DirectoryUiModel) = with(itemView) {
Glide.with(image_avatar).load(directoryChannelUiModel.userAvatarUri).into(image_avatar)
text_user_name.text = directoryChannelUiModel.name
text_user_username.text = directoryChannelUiModel.username
with(text_server_url) {
text = directoryChannelUiModel.serverUrl
isVisible = true
}
}
}
\ No newline at end of file
package chat.rocket.android.directory.adapter
import android.view.View
import androidx.core.view.isGone
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.directory.uimodel.DirectoryUiModel
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_directory_user.view.*
class DirectoryUsersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(directoryChannelUiModel: DirectoryUiModel) = with(itemView) {
Glide.with(image_avatar).load(directoryChannelUiModel.userAvatarUri).into(image_avatar)
text_user_name.text = directoryChannelUiModel.name
text_user_username.text = directoryChannelUiModel.username
text_server_url.isGone = true
}
}
\ No newline at end of file
package chat.rocket.android.directory.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.directory.presentation.DirectoryView
import chat.rocket.android.directory.ui.DirectoryFragment
import dagger.Module
import dagger.Provides
@Module
class DirectoryFragmentModule {
@Provides
@PerFragment
fun directoryView(frag: DirectoryFragment): DirectoryView = frag
}
\ No newline at end of file
package chat.rocket.android.directory.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.directory.ui.DirectoryFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class DirectoryFragmentProvider {
@ContributesAndroidInjector(modules = [DirectoryFragmentModule::class])
@PerFragment
abstract fun provideDirectoryFragment(): DirectoryFragment
}
\ No newline at end of file
package chat.rocket.android.directory.presentation
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.directory.uimodel.DirectoryUiModelMapper
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.DirectoryRequestType
import chat.rocket.core.internal.rest.DirectoryWorkspaceType
import chat.rocket.core.internal.rest.createDirectMessage
import chat.rocket.core.internal.rest.directory
import chat.rocket.core.internal.rest.getInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
class DirectoryPresenter @Inject constructor(
private val view: DirectoryView,
private val navigator: MainNavigator,
private val strategy: CancelStrategy,
@Named("currentServer") private val currentServer: String,
private val dbManager: DatabaseManager,
private val userHelper: UserHelper,
val factory: RocketChatClientFactory,
private val mapper: DirectoryUiModelMapper
) {
private val client: RocketChatClient = factory.get(currentServer)
private var offset: Long = 0
fun loadAllDirectoryChannels(query: String? = null) {
launchUI(strategy) {
try {
view.showLoading()
val directoryResult = client.directory(
text = query,
directoryRequestType = DirectoryRequestType.Channels(),
offset = offset,
count = 60
)
val directoryUiModels = mapper.mapToUiModelList(directoryResult.result)
view.showChannels(directoryUiModels)
offset += 1 * 60L
} catch (exception: Exception) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
fun loadAllDirectoryUsers(isSearchForGlobalUsers: Boolean, query: String? = null) {
launchUI(strategy) {
try {
view.showLoading()
val directoryResult = client.directory(
text = query,
directoryRequestType = DirectoryRequestType.Users(),
directoryWorkspaceType = if (isSearchForGlobalUsers) {
DirectoryWorkspaceType.All()
} else {
DirectoryWorkspaceType.Local()
},
offset = offset,
count = 60
)
val directoryUiModels = mapper.mapToUiModelList(directoryResult.result)
view.showUsers(directoryUiModels)
offset += 1 * 60L
} catch (exception: Exception) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
fun updateSorting(
isSortByChannels: Boolean,
isSearchForGlobalUsers: Boolean,
query: String? = null
) {
resetOffset()
if (isSortByChannels) {
loadAllDirectoryChannels(query)
} else {
loadAllDirectoryUsers(isSearchForGlobalUsers, query)
}
}
fun toChannel(channelId: String, name: String) {
launchUI(strategy) {
try {
view.showLoading()
withContext(Dispatchers.Default) {
val chatRoom = client.getInfo(channelId, name, roomTypeOf(RoomType.CHANNEL))
navigator.toChatRoom(
chatRoomId = channelId,
chatRoomName = name,
chatRoomType = RoomType.CHANNEL,
isReadOnly = chatRoom.readonly,
chatRoomLastSeen = -1,
isSubscribed = false,
isCreator = false,
isFavorite = false
)
}
} catch (ex: Exception) {
Timber.e(ex)
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
fun tiDirectMessage(username: String, name: String) {
launchUI(strategy) {
try {
view.showLoading()
withContext(Dispatchers.Default) {
val directMessage = client.createDirectMessage(username)
val chatRoomEntity = ChatRoomEntity(
id = directMessage.id,
name = username,
description = null,
type = RoomType.DIRECT_MESSAGE,
fullname = name,
subscriptionId = "",
updatedAt = directMessage.updatedAt
)
dbManager.insertOrReplaceRoom(chatRoomEntity)
FetchChatRoomsInteractor(client, dbManager).refreshChatRooms()
navigator.toChatRoom(
chatRoomId = chatRoomEntity.id,
chatRoomName = chatRoomEntity.name,
chatRoomType = chatRoomEntity.type,
isReadOnly = false,
chatRoomLastSeen = -1,
isSubscribed = chatRoomEntity.open,
isCreator = true,
isFavorite = false
)
}
} catch (ex: Exception) {
Timber.e(ex)
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
private fun resetOffset() {
offset = 0
}
}
\ No newline at end of file
package chat.rocket.android.directory.presentation
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.android.directory.uimodel.DirectoryUiModel
interface DirectoryView : MessageView, LoadingView {
/**
* Shows the list of directory channels.
*
* @param dataSet The data set to show.
*/
fun showChannels(dataSet: List<DirectoryUiModel>)
/**
* Shows the list of directory users.
*
* @param dataSet The data set to show.
*/
fun showUsers(dataSet: List<DirectoryUiModel>)
}
\ No newline at end of file
package chat.rocket.android.directory.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.directory.adapter.DirectoryAdapter
import chat.rocket.android.directory.adapter.Selector
import chat.rocket.android.directory.presentation.DirectoryPresenter
import chat.rocket.android.directory.presentation.DirectoryView
import chat.rocket.android.directory.uimodel.DirectoryUiModel
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.util.extension.onQueryTextListener
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.isNotNullNorBlank
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_directory.*
import kotlinx.android.synthetic.main.fragment_settings.view_loading
import javax.inject.Inject
internal const val TAG_DIRECTORY_FRAGMENT = "DirectoryFragment"
fun newInstance(): Fragment = DirectoryFragment()
class DirectoryFragment : Fragment(), DirectoryView {
@Inject lateinit var analyticsManager: AnalyticsManager
@Inject lateinit var presenter: DirectoryPresenter
private var isSortByChannels: Boolean = true
private var isSearchForGlobalUsers: Boolean = false
private val linearLayoutManager = LinearLayoutManager(context)
private val directoryAdapter = DirectoryAdapter(object : Selector {
override fun onChannelSelected(channelId: String, channelName: String) {
presenter.toChannel(channelId, channelName)
}
override fun onUserSelected(username: String, name: String) {
presenter.tiDirectMessage(username, name)
}
override fun onGlobalUserSelected(username: String, name: String) {
presenter.tiDirectMessage(username, name)
}
})
private val hashtagDrawable by lazy {
DrawableHelper.getDrawableFromId(R.drawable.ic_hashtag_16dp, text_sort_by.context)
}
private val userDrawable by lazy {
DrawableHelper.getDrawableFromId(R.drawable.ic_user_16dp, text_sort_by.context)
}
private val arrowDownDrawable by lazy {
DrawableHelper.getDrawableFromId(R.drawable.ic_arrow_down, text_sort_by.context)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_directory)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
setupRecyclerView()
setupListeners()
presenter.loadAllDirectoryChannels()
analyticsManager.logScreenView(ScreenViewEvent.Directory)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.directory, menu)
val searchMenuItem = menu.findItem(R.id.action_search)
val searchView = searchMenuItem?.actionView as SearchView
with(searchView) {
setIconifiedByDefault(false)
maxWidth = Integer.MAX_VALUE
onQueryTextListener { updateSorting(isSortByChannels, isSearchForGlobalUsers, it) }
}
searchMenuItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
updateSorting(isSortByChannels, isSearchForGlobalUsers, reload = true)
return true
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
return true
}
})
}
override fun showChannels(dataSet: List<DirectoryUiModel>) {
ui {
if (directoryAdapter.itemCount == 0) {
directoryAdapter.prependData(dataSet)
if (dataSet.size >= 60) {
recycler_view.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(
page: Int,
totalItemsCount: Int,
recyclerView: RecyclerView
) {
presenter.loadAllDirectoryChannels()
}
})
}
} else {
directoryAdapter.appendData(dataSet)
}
}
}
override fun showUsers(dataSet: List<DirectoryUiModel>) {
ui {
if (directoryAdapter.itemCount == 0) {
directoryAdapter.prependData(dataSet)
if (dataSet.size >= 60) {
recycler_view.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(
page: Int,
totalItemsCount: Int,
recyclerView: RecyclerView
) {
presenter.loadAllDirectoryUsers(isSearchForGlobalUsers)
}
})
}
} else {
directoryAdapter.appendData(dataSet)
}
}
}
override fun showMessage(resId: Int) {
ui { showToast(resId) }
}
override fun showMessage(message: String) {
ui { showToast(message) }
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
override fun showLoading() {
view_loading.isVisible = true
}
override fun hideLoading() {
view_loading.isVisible = false
}
fun updateSorting(
isSortByChannels: Boolean,
isSearchForGlobalUsers: Boolean,
query: String? = null,
reload: Boolean = false
) {
if (query.isNotNullNorBlank() || reload) {
directoryAdapter.clearData()
presenter.updateSorting(isSortByChannels, isSearchForGlobalUsers, query)
}
if (this.isSortByChannels != isSortByChannels ||
this.isSearchForGlobalUsers != isSearchForGlobalUsers
) {
this.isSortByChannels = isSortByChannels
this.isSearchForGlobalUsers = isSearchForGlobalUsers
updateSortByTitle()
with(directoryAdapter) {
clearData()
setSorting(isSortByChannels, isSearchForGlobalUsers)
}
presenter.updateSorting(isSortByChannels, isSearchForGlobalUsers, query)
}
}
private fun setupToolbar() {
with((activity as AppCompatActivity)) {
with(toolbar) {
setSupportActionBar(this)
title = getString(R.string.msg_directory)
setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
setNavigationOnClickListener { activity?.onBackPressed() }
}
}
}
private fun setupRecyclerView() {
ui {
with(recycler_view) {
layoutManager = linearLayoutManager
addItemDecoration(DividerItemDecoration(it, DividerItemDecoration.HORIZONTAL))
adapter = directoryAdapter
}
}
}
private fun setupListeners() {
text_sort_by.setOnClickListener {
activity?.supportFragmentManager?.let {
showDirectorySortingBottomSheetFragment(isSortByChannels, isSearchForGlobalUsers, it)
}
}
}
private fun updateSortByTitle() {
if (isSortByChannels) {
text_sort_by.text = getString(R.string.msg_channels)
DrawableHelper.compoundLeftAndRightDrawable(
text_sort_by,
hashtagDrawable,
arrowDownDrawable
)
} else {
text_sort_by.text = getString(R.string.msg_users)
DrawableHelper.compoundLeftAndRightDrawable(
text_sort_by,
userDrawable,
arrowDownDrawable
)
}
}
}
\ No newline at end of file
package chat.rocket.android.directory.ui
import DrawableHelper
import android.content.DialogInterface
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.FragmentManager
import chat.rocket.android.R
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.bottom_seet_fragment_directory_sorting.*
fun showDirectorySortingBottomSheetFragment(
isSortByChannels: Boolean,
isSearchForGlobalUsers: Boolean,
supportFragmentManager: FragmentManager
) = DirectorySortingBottomSheetFragment().apply {
arguments = Bundle(2).apply {
putBoolean(BUNDLE_IS_SORT_BY_CHANNELS, isSortByChannels)
putBoolean(BUNDLE_IS_SEARCH_FOR_GLOBAL_USERS, isSearchForGlobalUsers)
}
}.show(supportFragmentManager, TAG)
internal const val TAG = "DirectorySortingBottomSheetFragment"
private const val BUNDLE_IS_SORT_BY_CHANNELS = "is_sort_by_channels"
private const val BUNDLE_IS_SEARCH_FOR_GLOBAL_USERS = "is_search_for_global_users"
class DirectorySortingBottomSheetFragment : BottomSheetDialogFragment() {
private var isSortByChannels = true
private var isSearchForGlobalUsers = false
private val hashtagDrawable by lazy {
DrawableHelper.getDrawableFromId(R.drawable.ic_hashtag_16dp, requireContext())
}
private val userDrawable by lazy {
DrawableHelper.getDrawableFromId(R.drawable.ic_user_16dp, requireContext())
}
private val checkDrawable by lazy {
DrawableHelper.getDrawableFromId(R.drawable.ic_check, requireContext())
}
private val directoryFragment by lazy {
activity?.supportFragmentManager?.findFragmentByTag(TAG_DIRECTORY_FRAGMENT) as DirectoryFragment
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.run {
isSortByChannels = getBoolean(BUNDLE_IS_SORT_BY_CHANNELS)
isSearchForGlobalUsers = getBoolean(BUNDLE_IS_SEARCH_FOR_GLOBAL_USERS)
}
?: requireNotNull(arguments) { "no arguments supplied when the bottom sheet fragment was instantiated" }
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
inflater.inflate(R.layout.bottom_seet_fragment_directory_sorting, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupView()
setupListeners()
}
override fun onCancel(dialog: DialogInterface?) {
super.onCancel(dialog)
directoryFragment.updateSorting(isSortByChannels, isSearchForGlobalUsers)
}
private fun setupView() {
if (isSortByChannels) {
checkSelection(text_channels, hashtagDrawable)
} else {
checkSelection(text_users, userDrawable)
}
switch_global_users.isChecked = isSearchForGlobalUsers
}
private fun setupListeners() {
text_channels.setOnClickListener {
checkSelection(text_channels, hashtagDrawable)
uncheckSelection(text_users, userDrawable)
isSortByChannels = true
}
text_users.setOnClickListener {
checkSelection(text_users, userDrawable)
uncheckSelection(text_channels, hashtagDrawable)
isSortByChannels = false
}
switch_global_users.setOnCheckedChangeListener { _, isChecked ->
isSearchForGlobalUsers = isChecked
}
}
private fun checkSelection(textView: TextView, leftDrawable: Drawable) {
context?.let {
DrawableHelper.compoundLeftAndRightDrawable(
textView,
leftDrawable,
checkDrawable
)
}
}
private fun uncheckSelection(textView: TextView, leftDrawable: Drawable) {
context?.let {
DrawableHelper.compoundLeftDrawable(
textView,
leftDrawable
)
}
}
}
\ No newline at end of file
package chat.rocket.android.directory.uimodel
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.core.model.DirectoryResult
class DirectoryUiModel(
private val directoryResult: DirectoryResult,
private val baseUrl: String?
) {
val id: String = directoryResult.id
val channelAvatarUri: String?
val userAvatarUri: String?
val name: String = directoryResult.name
val username: String = "@${directoryResult.username}"
val serverUrl: String = "" // TODO
val description: String = "" // TODO
val totalMembers: String = "" // TODO
init {
channelAvatarUri = getChannelAvatar()
userAvatarUri = getUserAvatar()
}
private fun getChannelAvatar(): String? {
return baseUrl?.avatarUrl(name, isGroupOrChannel = true)
}
private fun getUserAvatar(): String? {
return directoryResult.username?.let {
baseUrl?.avatarUrl(it)
}
}
}
package chat.rocket.android.directory.uimodel
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.baseUrl
import chat.rocket.core.model.DirectoryResult
import chat.rocket.core.model.Value
import javax.inject.Inject
import javax.inject.Named
class DirectoryUiModelMapper @Inject constructor(
getSettingsInteractor: GetSettingsInteractor,
@Named("currentServer") private val currentServer: String
) {
private var settings: Map<String, Value<Any>> = getSettingsInteractor.get(currentServer)
private val baseUrl = settings.baseUrl()
fun mapToUiModelList(directoryList: List<DirectoryResult>): List<DirectoryUiModel> {
return directoryList.map { DirectoryUiModel(it, baseUrl) }
}
}
\ No newline at end of file
......@@ -3,7 +3,7 @@ package chat.rocket.android.favoritemessages.presentation
import chat.rocket.android.chatroom.uimodel.UiModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
......
......@@ -5,7 +5,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
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.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
......
......@@ -3,7 +3,7 @@ package chat.rocket.android.helper
object JitsiHelper {
/**
* Returns the for the Jitsi video conferencing URL.
* Returns the Jitsi video conferencing URL.
*
* @param isSecureProtocol True if using SSL, false otherwise - from the public settings.
* @param domain The Jitsi domain - from public settings.
......
......@@ -94,10 +94,16 @@ class MessageParser @Inject constructor(
}
private fun getMention(user: SimpleUser): String {
user.id?.let {
if (SYSTEM_MENTIONS.contains(it)) {
return "@$it"
}
}
return if (settings.useRealName()) {
user.name ?: "@${user.username}"
user.name ?: user.username.orEmpty()
} else {
"@${user.username}"
user.username.orEmpty()
}
}
......@@ -527,5 +533,7 @@ class MessageParser @Inject constructor(
*/
private val WEB_URL = Pattern.compile(
"($WEB_URL_WITH_PROTOCOL|$WEB_URL_WITHOUT_PROTOCOL)")
private val SYSTEM_MENTIONS = arrayOf("all", "here")
}
}
......@@ -15,11 +15,11 @@ object OauthHelper {
"\"isCordova\":true}").encodeToBase64()
/**
* Returns the Github Oauth URL.
* Returns the GitHub Oauth URL.
*
* @param clientId The GitHub client ID.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Github Oauth URL.
* @return The GitHub Oauth URL.
*/
fun getGithubOauthUrl(clientId: String, state: String): String {
return "https://github.com/login/oauth/authorize" +
......@@ -46,12 +46,12 @@ object OauthHelper {
}
/**
* Returns the Linkedin Oauth URL.
* Returns the LinkedIn Oauth URL.
*
* @param clientId The Linkedin client ID.
* @param clientId The LinkedIn client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Linkedin Oauth URL.
* @return The LinkedIn Oauth URL.
*/
fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://linkedin.com/oauth/v2/authorization" +
......@@ -62,13 +62,13 @@ object OauthHelper {
}
/**
* Returns the Gitlab Oauth URL.
* Returns the GitLab Oauth URL.
*
* @param host The Gitlab host.
* @param clientId The Gitlab client ID.
* @param host The GitLab host.
* @param clientId The GitLab client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Gitlab Oauth URL.
* @return The GitLab Oauth URL.
*/
fun getGitlabOauthUrl(
host: String? = "https://gitlab.com",
......
package chat.rocket.android.main.di
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.behaviours.AppLanguageView
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.main.presentation.MainNavigator
......@@ -16,6 +17,12 @@ class MainModule {
@PerActivity
fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity)
@Provides
@PerActivity
fun appLanguageView(activity: MainActivity): AppLanguageView {
return activity
}
@Provides
@PerActivity
fun provideJob() = Job()
......
......@@ -5,6 +5,7 @@ import chat.rocket.android.authentication.ui.newServerIntent
import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.chatrooms.ui.TAG_CHAT_ROOMS_FRAGMENT
import chat.rocket.android.createchannel.ui.TAG_CREATE_CHANNEL_FRAGMENT
import chat.rocket.android.directory.ui.TAG_DIRECTORY_FRAGMENT
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.profile.ui.TAG_PROFILE_FRAGMENT
import chat.rocket.android.server.ui.changeServerIntent
......@@ -28,6 +29,12 @@ class MainNavigator(internal val activity: MainActivity) {
}
}
fun toDirectory() {
activity.addFragmentBackStack(TAG_DIRECTORY_FRAGMENT, R.id.fragment_container) {
chat.rocket.android.directory.ui.newInstance()
}
}
fun toCreateChannel() {
activity.addFragmentBackStack(TAG_CREATE_CHANNEL_FRAGMENT, R.id.fragment_container) {
chat.rocket.android.createchannel.ui.newInstance()
......
package chat.rocket.android.main.presentation
import chat.rocket.android.core.behaviours.AppLanguageView
import chat.rocket.android.push.GroupedPush
import chat.rocket.android.server.domain.GetCurrentLanguageInteractor
import chat.rocket.android.server.domain.RefreshPermissionsInteractor
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import javax.inject.Inject
import javax.inject.Named
class MainPresenter @Inject constructor(
@Named("currentServer") private val currentServerUrl: String,
private val mainNavigator: MainNavigator,
private val appLanguageView: AppLanguageView,
private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val refreshPermissionsInteractor: RefreshPermissionsInteractor,
private val connectionManagerFactory: ConnectionManagerFactory,
private var getLanguageInteractor: GetCurrentLanguageInteractor,
private val groupedPush: GroupedPush
) {
......@@ -31,4 +35,13 @@ class MainPresenter @Inject constructor(
}
fun showChatList(chatRoomId: String? = null) = mainNavigator.toChatList(chatRoomId)
fun getAppLanguage() {
with(getLanguageInteractor) {
getLanguage()?.let { language ->
appLanguageView.updateLanguage(language, getCountry())
}
}
}
}
\ No newline at end of file
......@@ -3,10 +3,14 @@ package chat.rocket.android.main.ui
import android.app.Activity
import android.app.NotificationManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.os.LocaleList
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.core.behaviours.AppLanguageView
import chat.rocket.android.main.presentation.MainPresenter
import chat.rocket.android.push.refreshPushToken
import chat.rocket.android.server.ui.INTENT_CHAT_ROOM_ID
......@@ -15,10 +19,11 @@ import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector
import dagger.android.support.HasSupportFragmentInjector
import java.util.*
import javax.inject.Inject
class MainActivity : AppCompatActivity(), HasActivityInjector,
HasSupportFragmentInjector {
HasSupportFragmentInjector, AppLanguageView {
@Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
@Inject
......@@ -30,11 +35,11 @@ class MainActivity : AppCompatActivity(), HasActivityInjector,
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
refreshPushToken()
with(presenter) {
connect()
getAppLanguage()
intent.getStringExtra(INTENT_CHAT_ROOM_ID).let {
clearNotificationsForChatRoom(it)
showChatList(it)
......@@ -53,6 +58,28 @@ class MainActivity : AppCompatActivity(), HasActivityInjector,
override fun supportFragmentInjector(): AndroidInjector<Fragment> =
fagmentDispatchingAndroidInjector
override fun updateLanguage(language: String, country: String?) {
val locale: Locale = if (country != null) {
Locale(language, country)
} else {
Locale(language)
}
Locale.setDefault(locale)
val config = Configuration()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.locales = LocaleList(locale)
} else {
config.locale = locale
}
// TODO We need to check out a better way to use createConfigurationContext
// instead of updateConfiguration here since it is deprecated.
resources.updateConfiguration(config, resources.displayMetrics)
}
private fun clearAppNotifications() =
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancelAll()
}
......@@ -15,10 +15,10 @@ class MembersAdapter(
) : RecyclerView.Adapter<MembersAdapter.ViewHolder>() {
private var dataSet: List<MemberUiModel> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MembersAdapter.ViewHolder =
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(parent.inflate(R.layout.item_member))
override fun onBindViewHolder(holder: MembersAdapter.ViewHolder, position: Int) =
override fun onBindViewHolder(holder: ViewHolder, position: Int) =
holder.bind(dataSet[position], listener)
override fun getItemCount(): Int = dataSet.size
......
......@@ -6,7 +6,7 @@ import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.helper.UserHelper
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.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
......
......@@ -102,15 +102,11 @@ class MembersFragment : Fragment(), MembersView {
}
override fun showMessage(resId: Int) {
ui {
showToast(resId)
}
ui { showToast(resId) }
}
override fun showMessage(message: String) {
ui {
showToast(message)
}
ui { showToast(message) }
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
......
......@@ -2,7 +2,7 @@ package chat.rocket.android.mentions.presentention
import chat.rocket.android.chatroom.uimodel.UiModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
......
......@@ -3,7 +3,7 @@ package chat.rocket.android.pinnedmessages.presentation
import chat.rocket.android.chatroom.uimodel.UiModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
......
......@@ -11,8 +11,8 @@ import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.RemoveAccountInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.compressImageAndGetByteArray
import chat.rocket.android.util.extension.launchUI
......
......@@ -141,7 +141,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
}
override fun showProfileUpdateSuccessfullyMessage() {
showMessage(getString(R.string.msg_profile_update_successfully))
showMessage(getString(R.string.msg_profile_updated_successfully))
}
override fun invalidateToken(token: String) = invalidateFirebaseToken(token)
......@@ -267,8 +267,8 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
text_email.asObservable()
) { text_name, text_username, text_email ->
return@combineLatest (text_name.toString() != currentName ||
text_username.toString() != currentUsername ||
text_email.toString() != currentEmail)
text_username.toString() != currentUsername ||
text_email.toString() != currentEmail)
}.subscribe { isValid ->
activity?.invalidateOptionsMenu()
if (isValid) {
......
......@@ -7,7 +7,7 @@ import android.content.Intent
import androidx.core.app.RemoteInput
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.sendMessage
import dagger.android.AndroidInjection
......
package chat.rocket.android.server.domain
import chat.rocket.android.server.infrastructure.CurrentLanguageRepository
import javax.inject.Inject
class GetCurrentLanguageInteractor @Inject constructor(
private val repository: CurrentLanguageRepository
) {
fun getLanguage(): String? = repository.getLanguage()
fun getCountry(): String? = repository.getCountry()
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.permissions
import kotlinx.coroutines.Dispatchers
......
package chat.rocket.android.server.domain
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.settings
import kotlinx.coroutines.Dispatchers
......
package chat.rocket.android.server.domain
import chat.rocket.android.server.infrastructure.CurrentLanguageRepository
import javax.inject.Inject
class SaveCurrentLanguageInteractor @Inject constructor(
private val repository: CurrentLanguageRepository
) {
fun save(language: String, country: String?) = repository.save(language, country)
}
\ No newline at end of file
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import androidx.lifecycle.MutableLiveData
import chat.rocket.android.db.DatabaseManager
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import chat.rocket.android.db.DatabaseManagerFactory
import timber.log.Timber
......
package chat.rocket.android.server.infrastructure
interface CurrentLanguageRepository {
fun save(language: String, country: String? = null)
fun getLanguage(): String?
fun getCountry(): String?
}
\ No newline at end of file
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.*
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.Operation
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import android.app.job.JobInfo
import android.app.job.JobScheduler
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import chat.rocket.android.server.domain.ChatRoomsRepository
import chat.rocket.core.model.ChatRoom
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.domain.UsersRepository.Query
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import android.os.Build
import chat.rocket.android.BuildConfig
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import android.content.SharedPreferences
import androidx.core.content.edit
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.PermissionsRepository
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.LocalRepository.Companion.SETTINGS_KEY
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import android.content.SharedPreferences
import chat.rocket.android.server.domain.AnalyticsTrackingRepository
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import android.content.SharedPreferences
import androidx.core.content.edit
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import android.content.SharedPreferences
import chat.rocket.android.server.domain.CurrentServerRepository
......
package chat.rocket.android.server.infrastructure
import android.content.SharedPreferences
private const val CURRENT_LANGUAGE = "current_language"
private const val CURRENT_LANGUAGE_COUNTRY = "current_language_country"
class SharedPrefsCurrentLanguageRepository(private val preferences: SharedPreferences) :
CurrentLanguageRepository {
override fun save(language: String, country: String?) {
with(preferences) {
edit().putString(CURRENT_LANGUAGE, language).apply()
edit().putString(CURRENT_LANGUAGE_COUNTRY, country).apply()
}
}
override fun getLanguage(): String? {
return preferences.getString(CURRENT_LANGUAGE, "")
}
override fun getCountry(): String? {
return preferences.getString(CURRENT_LANGUAGE_COUNTRY, "")
}
}
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import android.content.SharedPreferences
import chat.rocket.android.server.domain.CurrentServerRepository
......
package chat.rocket.android.server.infraestructure
package chat.rocket.android.server.infrastructure
import android.content.SharedPreferences
import chat.rocket.android.server.domain.SortingAndGroupingRepository
......
......@@ -9,7 +9,7 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.util.ifNull
import javax.inject.Inject
......
......@@ -26,9 +26,9 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.RemoveAccountInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.ConnectionManager
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.VersionInfo
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.casUrl
......@@ -452,7 +452,7 @@ abstract class CheckServerPresenter constructor(
/**
* Returns the OAuth client ID of a [serviceMap].
* REMARK: This function works for common OAuth providers (Google, Facebook, Github and so on)
* REMARK: This function works for common OAuth providers (Google, Facebook, GitHub and so on)
* as well as custom OAuth.
*
* @param serviceMap The service map to get the OAuth client ID.
......
......@@ -40,7 +40,6 @@ class ServersAdapter(
}
}
private fun bindServerViewHolder(holder: ServerViewHolder, position: Int) {
val account = servers[position]
holder.bind(account)
......
......@@ -4,7 +4,7 @@ import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
......
......@@ -5,12 +5,14 @@ import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.server.domain.AnalyticsTrackingInteractor
import chat.rocket.android.server.domain.GetCurrentLanguageInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.RemoveAccountInteractor
import chat.rocket.android.server.domain.SaveCurrentLanguageInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.gethash
import chat.rocket.android.util.extension.launchUI
......@@ -41,7 +43,8 @@ class SettingsPresenter @Inject constructor(
getCurrentServerInteractor: GetCurrentServerInteractor,
removeAccountInteractor: RemoveAccountInteractor,
databaseManagerFactory: DatabaseManagerFactory,
connectionManagerFactory: ConnectionManagerFactory
connectionManagerFactory: ConnectionManagerFactory,
private val saveLanguageInteractor: SaveCurrentLanguageInteractor
) : CheckServerPresenter(
strategy = strategy,
factory = rocketChatClientFactory,
......@@ -123,6 +126,10 @@ class SettingsPresenter @Inject constructor(
}
}
fun saveLocale(language: String, country: String? = null) {
saveLanguageInteractor.save(language, country)
}
fun toProfile() = navigator.toProfile()
fun toAdmin() = tokenRepository.get(currentServer)?.let {
......
......@@ -17,6 +17,7 @@ import chat.rocket.android.BuildConfig
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.core.behaviours.AppLanguageView
import chat.rocket.android.helper.TextHelper.getDeviceAndAppInformation
import chat.rocket.android.settings.presentation.SettingsPresenter
import chat.rocket.android.settings.presentation.SettingsView
......@@ -33,7 +34,7 @@ internal const val TAG_SETTINGS_FRAGMENT = "SettingsFragment"
fun newInstance(): Fragment = SettingsFragment()
class SettingsFragment : Fragment(), SettingsView {
class SettingsFragment : Fragment(), SettingsView, AppLanguageView {
@Inject lateinit var analyticsManager: AnalyticsManager
@Inject lateinit var presenter: SettingsPresenter
......@@ -74,14 +75,14 @@ class SettingsFragment : Fragment(), SettingsView {
text_contact_us.setOnClickListener { contactSupport() }
text_language.setOnClickListener {}
text_language.setOnClickListener { changeLanguage() }
text_review_this_app.setOnClickListener { showAppOnStore() }
text_share_this_app.setOnClickListener { shareApp() }
text_license.setOnClickListener {
presenter.toLicense(getString(R.string.license_url), getString(R.string.title_licence))
presenter.toLicense(getString(R.string.license_url), getString(R.string.title_license))
}
text_app_version.text = getString(R.string.msg_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
......@@ -109,6 +110,11 @@ class SettingsFragment : Fragment(), SettingsView {
}
}
override fun updateLanguage(language: String, country: String?) {
presenter.saveLocale(language, country)
activity?.recreate()
}
override fun invalidateToken(token: String) = invalidateFirebaseToken(token)
override fun showLoading() {
......@@ -155,6 +161,38 @@ class SettingsFragment : Fragment(), SettingsView {
}
}
private fun changeLanguage() {
context?.let {
AlertDialog.Builder(it)
.setTitle(R.string.title_choose_language)
.setSingleChoiceItems(
resources.getStringArray(R.array.languages), -1
) { dialog, option ->
when (option) {
0 -> updateLanguage("en")
1 -> updateLanguage("ar")
2 -> updateLanguage("de")
3 -> updateLanguage("es")
4 -> updateLanguage("fa")
5 -> updateLanguage("fr")
6 -> updateLanguage("hi", "IN")
7 -> updateLanguage("it")
8 -> updateLanguage("ja")
9 -> updateLanguage("pt", "BR")
10 -> updateLanguage("pt", "PT")
11 -> updateLanguage("ru", "RU")
12 -> updateLanguage("tr")
13 -> updateLanguage("uk")
14 -> updateLanguage("zh", "CN")
15 -> updateLanguage("zh", "TW")
}
dialog.dismiss()
}
.create()
.show()
}
}
private fun showAppOnStore() {
try {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.market_link).toUri()))
......
......@@ -140,7 +140,7 @@ class SortingAndGroupingBottomSheetFragment : BottomSheetDialogFragment(), Sorti
}
private fun changeSortByTitle(text: String) {
text_sort_by.text = getString(R.string.msg_sort_by, text.toLowerCase())
text_sort_by.text = getString(R.string.msg_sort_by_placeholder, text.toLowerCase())
}
private fun checkSelection(textView: TextView, @DrawableRes leftDrawable: Int) {
......
......@@ -9,7 +9,7 @@ import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.isJitsiEnabled
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.retryIO
......
......@@ -2,7 +2,7 @@ package chat.rocket.android.util.extensions
import chat.rocket.android.db.model.MessageEntity
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.registerPushToken
import chat.rocket.core.model.Message
......
......@@ -75,6 +75,8 @@ fun String.lowercaseUrl(): String? = HttpUrl.parse(this)?.run {
fun String?.isNotNullNorEmpty(): Boolean = this != null && this.isNotEmpty()
fun String?.isNotNullNorBlank(): Boolean = this != null && this.isNotBlank()
inline fun String?.ifNotNullNotEmpty(block: (String) -> Unit) {
if (this != null && this.isNotEmpty()) {
block(this)
......
......@@ -6,7 +6,7 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.JitsiHelper
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infrastructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="7dp"
android:viewportWidth="12"
android:viewportHeight="7">
<path
android:fillColor="#EFEFEF"
android:fillType="nonZero"
android:pathData="M6,4.9512L10.4571,0.5122C10.7415,0.229 11.2013,0.229 11.4857,0.5122L11.4857,0.5122C11.7686,0.7939 11.7695,1.2516 11.4878,1.5345C11.4871,1.5352 11.4864,1.5359 11.4857,1.5366L6.7057,6.2972C6.3155,6.6858 5.6845,6.6858 5.2943,6.2972L0.5143,1.5366C0.2314,1.2549 0.2305,0.7972 0.5122,0.5143C0.5129,0.5136 0.5136,0.5129 0.5143,0.5122L0.5143,0.5122C0.7987,0.229 1.2585,0.229 1.5429,0.5122L3.5143,2.4756L6,4.9512Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#E8F2FF"
android:fillType="evenOdd"
android:pathData="M4,0L44,0A4,4 0,0 1,48 4L48,44A4,4 0,0 1,44 48L4,48A4,4 0,0 1,0 44L0,4A4,4 0,0 1,4 0z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="#1D74F5"
android:fillType="nonZero"
android:pathData="M20.4021,27.5L18.3698,27.5C19.0883,28.6535 20.1515,29.57 21.4153,30.1057C20.9935,29.3908 20.6468,28.5043 20.4021,27.5ZM20.126,26C20.0438,25.3608 20,24.6906 20,24C20,23.4873 20.0241,22.9859 20.0702,22.5L17.5419,22.5C17.4304,22.982 17.3714,23.4841 17.3714,24C17.3714,24.697 17.479,25.3689 17.6785,26L20.126,26ZM20.2908,21C20.5358,19.7892 20.9248,18.7257 21.4153,17.8943C19.9739,18.5052 18.7934,19.6117 18.0876,21L20.2908,21ZM27.7092,21L29.9124,21C29.2066,19.6117 28.0261,18.5052 26.5847,17.8943C27.0752,18.7257 27.4642,19.7892 27.7092,21ZM27.9298,22.5C27.9759,22.9859 28,23.4873 28,24C28,24.6906 27.9562,25.3608 27.874,26L30.3215,26C30.521,25.3689 30.6286,24.697 30.6286,24C30.6286,23.4841 30.5696,22.982 30.4581,22.5L27.9298,22.5ZM27.5979,27.5C27.3532,28.5043 27.0065,29.3908 26.5847,30.1057C27.8485,29.57 28.9117,28.6535 29.6302,27.5L27.5979,27.5ZM21.8745,27.5C22.3155,29.3609 23.1025,30.6 24,30.6C24.8975,30.6 25.6845,29.3609 26.1255,27.5L21.8745,27.5ZM21.6127,26L26.3873,26C26.4606,25.3841 26.5,24.7291 26.5,24.05C26.5,23.516 26.4756,22.9969 26.4296,22.5L21.5704,22.5C21.5244,22.9969 21.5,23.516 21.5,24.05C21.5,24.7291 21.5394,25.3841 21.6127,26ZM21.787,21L26.213,21C25.7943,18.9188 24.9604,17.5 24,17.5C23.0396,17.5 22.2057,18.9188 21.787,21ZM24,32C19.5822,32 16,28.4178 16,24C16,19.5813 19.5822,16 24,16C28.4187,16 32,19.5813 32,24C32,28.4178 28.4187,32 24,32Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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