Unverified Commit c273d021 authored by Leonardo Aramaki's avatar Leonardo Aramaki Committed by GitHub

Merge branch 'develop-2.x' into Navigate-to-profile

parents 0f2caf90 f9ceaf6b
...@@ -39,6 +39,7 @@ import kotlinx.coroutines.experimental.CommonPool ...@@ -39,6 +39,7 @@ import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.experimental.runBlocking
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference
import javax.inject.Inject import javax.inject.Inject
...@@ -86,6 +87,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -86,6 +87,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
// TODO - remove this on the future, temporary migration stuff for pre-release versions. // TODO - remove this on the future, temporary migration stuff for pre-release versions.
migrateInternalTokens() migrateInternalTokens()
context = WeakReference(applicationContext)
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
EmojiRepository.load(this) EmojiRepository.load(this)
...@@ -259,6 +261,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -259,6 +261,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
override fun broadcastReceiverInjector(): AndroidInjector<BroadcastReceiver> { override fun broadcastReceiverInjector(): AndroidInjector<BroadcastReceiver> {
return broadcastReceiverInjector return broadcastReceiverInjector
} }
companion object {
var context: WeakReference<Context>? = null
fun getAppContext(): Context? {
return context?.get()
}
}
} }
private fun LocalRepository.setMigrated(migrated: Boolean) { private fun LocalRepository.setMigrated(migrated: Boolean) {
......
package chat.rocket.android.authentication.login.presentation package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.domain.model.TokenModel
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.NetworkHelper import chat.rocket.android.helper.NetworkHelper
import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.helper.UrlHelper import chat.rocket.android.helper.UrlHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.encodeToBase64 import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.isEmail
import chat.rocket.android.util.extensions.launchUI
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.Token import chat.rocket.common.model.Token
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -117,19 +113,19 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -117,19 +113,19 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
launchUI(strategy) { launchUI(strategy) {
try { try {
val services = client.settingsOauth().services val services = client.settingsOauth().services
val state = "{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
if (services.isNotEmpty()) { if (services.isNotEmpty()) {
val state = "{\"loginStyle\":\"popup\",\"credentialToken\":\"${generateRandomString(40)}\",\"isCordova\":true}".encodeToBase64()
var totalSocialAccountsEnabled = 0 var totalSocialAccountsEnabled = 0
if (settings.isFacebookAuthenticationEnabled()) { if (settings.isFacebookAuthenticationEnabled()) {
view.enableLoginByFacebook() // //TODO: Remove until we have this implemented
totalSocialAccountsEnabled++ // view.enableLoginByFacebook()
// totalSocialAccountsEnabled++
} }
if (settings.isGithubAuthenticationEnabled()) { if (settings.isGithubAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GITHUB) val clientId = getOauthClientId(services, SERVICE_NAME_GITHUB)
if (clientId != null) { if (clientId != null) {
view.setupGithubButtonListener(UrlHelper.getGithubOauthUrl(clientId, state), state) view.setupGithubButtonListener(OauthHelper.getGithubOauthUrl(clientId, state), state)
view.enableLoginByGithub() view.enableLoginByGithub()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
...@@ -137,7 +133,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -137,7 +133,7 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
if (settings.isGoogleAuthenticationEnabled()) { if (settings.isGoogleAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GOOGLE) val clientId = getOauthClientId(services, SERVICE_NAME_GOOGLE)
if (clientId != null) { if (clientId != null) {
view.setupGoogleButtonListener(UrlHelper.getGoogleOauthUrl(clientId, currentServer, state), state) view.setupGoogleButtonListener(OauthHelper.getGoogleOauthUrl(clientId, currentServer, state), state)
view.enableLoginByGoogle() view.enableLoginByGoogle()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
...@@ -145,42 +141,44 @@ class LoginPresenter @Inject constructor(private val view: LoginView, ...@@ -145,42 +141,44 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
if (settings.isLinkedinAuthenticationEnabled()) { if (settings.isLinkedinAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_LINKEDIN) val clientId = getOauthClientId(services, SERVICE_NAME_LINKEDIN)
if (clientId != null) { if (clientId != null) {
view.setupGoogleButtonListener(UrlHelper.getLinkedinOauthUrl(clientId, currentServer, state), state) view.setupLinkedinButtonListener(OauthHelper.getLinkedinOauthUrl(clientId, currentServer, state), state)
view.enableLoginByLinkedin() view.enableLoginByLinkedin()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
if (settings.isMeteorAuthenticationEnabled()) { if (settings.isMeteorAuthenticationEnabled()) {
view.enableLoginByMeteor() //TODO: Remove until we have this implemented
totalSocialAccountsEnabled++ // view.enableLoginByMeteor()
// totalSocialAccountsEnabled++
} }
if (settings.isTwitterAuthenticationEnabled()) { if (settings.isTwitterAuthenticationEnabled()) {
view.enableLoginByTwitter() //TODO: Remove until we have this implemented
totalSocialAccountsEnabled++ // view.enableLoginByTwitter()
// totalSocialAccountsEnabled++
} }
if (settings.isGitlabAuthenticationEnabled()) { if (settings.isGitlabAuthenticationEnabled()) {
val clientId = getOauthClientId(services, SERVICE_NAME_GILAB) val clientId = getOauthClientId(services, SERVICE_NAME_GILAB)
if (clientId != null) { if (clientId != null) {
view.setupGitlabButtonListener(UrlHelper.getGitlabOauthUrl(clientId, currentServer, state), state) view.setupGitlabButtonListener(OauthHelper.getGitlabOauthUrl(clientId, currentServer, state), state)
view.enableLoginByGitlab() view.enableLoginByGitlab()
totalSocialAccountsEnabled++ totalSocialAccountsEnabled++
} }
} }
if (totalSocialAccountsEnabled > 0) { if (totalSocialAccountsEnabled > 0) {
view.showOauthView() view.enableOauthView()
if (totalSocialAccountsEnabled > 3) { if (totalSocialAccountsEnabled > 3) {
view.setupFabListener() view.setupFabListener()
} }
} else { } else {
view.hideOauthView() view.disableOauthView()
} }
} else { } else {
view.hideOauthView() view.disableOauthView()
} }
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
Timber.e(exception) Timber.e(exception)
view.hideOauthView() view.disableOauthView()
} }
} }
} }
......
...@@ -72,18 +72,18 @@ interface LoginView : LoadingView, MessageView, InternetView { ...@@ -72,18 +72,18 @@ interface LoginView : LoadingView, MessageView, InternetView {
fun hideSignUpView() fun hideSignUpView()
/** /**
* Shows the oauth view if the login via social accounts is enabled by the server settings. * Enables and shows the oauth view if there is login via social accounts enabled by the server settings.
* *
* REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle], * REMARK: We must show at maximum *three* social accounts views ([enableLoginByFacebook], [enableLoginByGithub], [enableLoginByGoogle],
* [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter] or [enableLoginByGitlab]) for the oauth view. * [enableLoginByLinkedin], [enableLoginByMeteor], [enableLoginByTwitter] or [enableLoginByGitlab]) for the oauth view.
* If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s). * If the possibility of login via social accounts exceeds 3 different ways we should set up the FAB ([setupFabListener]) to show the remaining view(s).
*/ */
fun showOauthView() fun enableOauthView()
/** /**
* Hides the oauth view. * Disables and hides the Oauth view if there is not login via social accounts enabled by the server settings.
*/ */
fun hideOauthView() fun disableOauthView()
/** /**
* Shows the login button. * Shows the login button.
......
...@@ -33,6 +33,7 @@ internal const val REQUEST_CODE_FOR_OAUTH = 2 ...@@ -33,6 +33,7 @@ internal const val REQUEST_CODE_FOR_OAUTH = 2
class LoginFragment : Fragment(), LoginView { class LoginFragment : Fragment(), LoginView {
@Inject lateinit var presenter: LoginPresenter @Inject lateinit var presenter: LoginPresenter
private var isOauthViewEnable = false
private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener { private val layoutListener = ViewTreeObserver.OnGlobalLayoutListener {
areLoginOptionsNeeded() areLoginOptionsNeeded()
} }
...@@ -182,14 +183,15 @@ class LoginFragment : Fragment(), LoginView { ...@@ -182,14 +183,15 @@ class LoginFragment : Fragment(), LoginView {
text_new_to_rocket_chat.setVisible(false) text_new_to_rocket_chat.setVisible(false)
} }
override fun showOauthView() { override fun enableOauthView() {
isOauthViewEnable = true
showThreeSocialAccountsMethods() showThreeSocialAccountsMethods()
social_accounts_container.setVisible(true) social_accounts_container.setVisible(true)
} }
override fun hideOauthView() { override fun disableOauthView() {
isOauthViewEnable = false
social_accounts_container.setVisible(false) social_accounts_container.setVisible(false)
button_fab.setVisible(false)
} }
override fun showLoginButton() { override fun showLoginButton() {
...@@ -328,4 +330,17 @@ class LoginFragment : Fragment(), LoginView { ...@@ -328,4 +330,17 @@ class LoginFragment : Fragment(), LoginView {
.take(3) .take(3)
.forEach { it.setVisible(true) } .forEach { it.setVisible(true) }
} }
fun showOauthView() {
if (isOauthViewEnable) {
social_accounts_container.setVisible(true)
}
}
fun hideOauthView() {
if (isOauthViewEnable) {
social_accounts_container.setVisible(false)
button_fab.setVisible(false)
}
}
} }
\ No newline at end of file
...@@ -102,31 +102,39 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector { ...@@ -102,31 +102,39 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
return fragmentDispatchingAndroidInjector return fragmentDispatchingAndroidInjector
} }
fun showRoomTypeIcon(showRoomTypeIcon: Boolean) {
if (showRoomTypeIcon) {
val roomType = roomTypeOf(chatRoomType)
val drawable = when (roomType) {
is RoomType.Channel -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_channel, this)
}
is RoomType.PrivateGroup -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_lock, this)
}
is RoomType.DirectMessage -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_dm, this)
}
else -> null
}
drawable?.let {
val wrappedDrawable = DrawableHelper.wrapDrawable(it)
val mutableDrawable = wrappedDrawable.mutate()
DrawableHelper.tintDrawable(mutableDrawable, this, R.color.white)
DrawableHelper.compoundDrawable(text_room_name, mutableDrawable)
}
} else {
text_room_name.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
}
}
private fun setupToolbar() { private fun setupToolbar() {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false) supportActionBar?.setDisplayShowTitleEnabled(false)
text_room_name.textContent = chatRoomName text_room_name.textContent = chatRoomName
val roomType = roomTypeOf(chatRoomType) showRoomTypeIcon(true)
val drawable = when (roomType) {
is RoomType.Channel -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_channel, this)
}
is RoomType.PrivateGroup -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_lock, this)
}
is RoomType.DirectMessage -> {
DrawableHelper.getDrawableFromId(R.drawable.ic_room_dm, this)
}
else -> null
}
drawable?.let {
val wrappedDrawable = DrawableHelper.wrapDrawable(it)
val mutableDrawable = wrappedDrawable.mutate()
DrawableHelper.tintDrawable(mutableDrawable, this, R.color.white)
DrawableHelper.compoundDrawable(text_room_name, mutableDrawable)
}
toolbar.setNavigationOnClickListener { toolbar.setNavigationOnClickListener {
finishActivity() finishActivity()
......
...@@ -123,6 +123,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -123,6 +123,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
setupMessageComposer() setupMessageComposer()
setupSuggestionsView() setupSuggestionsView()
setupActionSnackbar() setupActionSnackbar()
activity?.apply {
(this as? ChatRoomActivity)?.showRoomTypeIcon(true)
}
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
......
...@@ -3,6 +3,9 @@ package chat.rocket.android.chatrooms.presentation ...@@ -3,6 +3,9 @@ package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.chatroom.viewmodel.ViewModelMapper import chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.main.presentation.MainNavigator import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.helper.ChatRoomsSortOrder
import chat.rocket.android.helper.Constants
import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.ConnectionManager import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
...@@ -20,12 +23,15 @@ import chat.rocket.core.internal.realtime.StreamMessage ...@@ -20,12 +23,15 @@ import chat.rocket.core.internal.realtime.StreamMessage
import chat.rocket.core.internal.realtime.Type import chat.rocket.core.internal.realtime.Type
import chat.rocket.core.internal.rest.spotlight import chat.rocket.core.internal.rest.spotlight
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import chat.rocket.core.model.Message
import chat.rocket.core.model.Room import chat.rocket.core.model.Room
import kotlinx.coroutines.experimental.* import kotlinx.coroutines.experimental.*
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.channels.Channel import kotlinx.coroutines.experimental.channels.Channel
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
...@@ -164,9 +170,53 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView, ...@@ -164,9 +170,53 @@ class ChatRoomsPresenter @Inject constructor(private val view: ChatRoomsView,
return getChatRoomsWithPreviews(sortedRooms) return getChatRoomsWithPreviews(sortedRooms)
} }
fun updateSortedChatRooms() {
val currentServer = serverInteractor.get()!!
launchUI(strategy) {
val roomList = getChatRoomsInteractor.get(currentServer)
view.updateChatRooms(sortRooms(roomList))
}
}
private fun sortRooms(chatRooms: List<ChatRoom>): List<ChatRoom> { private fun sortRooms(chatRooms: List<ChatRoom>): List<ChatRoom> {
val sortType = SharedPreferenceHelper.getInt(Constants.CHATROOM_SORT_TYPE_KEY, ChatRoomsSortOrder.ACTIVITY)
val groupByType = SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)
val openChatRooms = getOpenChatRooms(chatRooms) val openChatRooms = getOpenChatRooms(chatRooms)
return sortChatRooms(openChatRooms)
return when (sortType) {
ChatRoomsSortOrder.ALPHABETICAL -> {
when (groupByType) {
true -> openChatRooms.sortedWith(compareBy(ChatRoom::type).thenBy { it.name })
false -> openChatRooms.sortedWith(compareBy(ChatRoom::name))
}
}
ChatRoomsSortOrder.ACTIVITY -> {
when (groupByType) {
true -> openChatRooms.sortedWith(compareBy(ChatRoom::type).thenByDescending { it.lastMessage?.timestamp })
false -> openChatRooms.sortedByDescending { chatRoom ->
chatRoom.lastMessage?.timestamp
}
}
}
else -> {
openChatRooms
}
}
}
private fun compareBy(selector: KProperty1<ChatRoom, RoomType>): Comparator<ChatRoom> {
return Comparator { a, b -> getTypeConstant(a.type) - getTypeConstant(b.type) }
}
private fun getTypeConstant(roomType: RoomType): Int {
return when (roomType) {
is RoomType.Channel -> Constants.CHATROOM_CHANNEL
is RoomType.PrivateGroup -> Constants.CHATROOM_PRIVATE_GROUP
is RoomType.DirectMessage -> Constants.CHATROOM_DM
is RoomType.Livechat -> Constants.CHATROOM_LIVE_CHAT
else -> 0
}
} }
private fun updateRooms() { private fun updateRooms() {
......
package chat.rocket.android.chatrooms.ui package chat.rocket.android.chatrooms.ui
import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
...@@ -9,14 +12,20 @@ import android.support.v7.widget.DefaultItemAnimator ...@@ -9,14 +12,20 @@ import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import android.widget.CheckBox
import android.widget.RadioGroup
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter import chat.rocket.android.chatrooms.presentation.ChatRoomsPresenter
import chat.rocket.android.chatrooms.presentation.ChatRoomsView import chat.rocket.android.chatrooms.presentation.ChatRoomsView
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.helper.ChatRoomsSortOrder
import chat.rocket.android.helper.Constants
import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.util.extensions.* import chat.rocket.android.util.extensions.*
import chat.rocket.android.widget.DividerItemDecoration import chat.rocket.android.widget.DividerItemDecoration
import chat.rocket.common.model.RoomType
import chat.rocket.core.internal.realtime.State import chat.rocket.core.internal.realtime.State
import chat.rocket.core.model.ChatRoom import chat.rocket.core.model.ChatRoom
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
...@@ -28,15 +37,18 @@ import kotlinx.coroutines.experimental.async ...@@ -28,15 +37,18 @@ import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import javax.inject.Inject import javax.inject.Inject
class ChatRoomsFragment : Fragment(), ChatRoomsView { class ChatRoomsFragment : Fragment(), ChatRoomsView {
@Inject lateinit var presenter: ChatRoomsPresenter @Inject lateinit var presenter: ChatRoomsPresenter
@Inject lateinit var serverInteractor: GetCurrentServerInteractor @Inject lateinit var serverInteractor: GetCurrentServerInteractor
@Inject lateinit var settingsRepository: SettingsRepository @Inject lateinit var settingsRepository: SettingsRepository
@Inject lateinit var localRepository: LocalRepository @Inject lateinit var localRepository: LocalRepository
private lateinit var preferences: SharedPreferences
private var searchView: SearchView? = null private var searchView: SearchView? = null
private val handler = Handler() private val handler = Handler()
private var listJob: Job? = null private var listJob: Job? = null
private var sectionedAdapter: SimpleSectionedRecyclerViewAdapter? = null
companion object { companion object {
fun newInstance() = ChatRoomsFragment() fun newInstance() = ChatRoomsFragment()
...@@ -46,6 +58,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -46,6 +58,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
setHasOptionsMenu(true) setHasOptionsMenu(true)
preferences = context?.getSharedPreferences("temp", Context.MODE_PRIVATE)!!
} }
override fun onDestroy() { override fun onDestroy() {
...@@ -87,20 +100,76 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -87,20 +100,76 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
}) })
} }
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)
val groupByType = SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)
val radioGroup = dialogLayout.findViewById<RadioGroup>(R.id.radio_group_sort)
val groupByTypeCheckBox = dialogLayout.findViewById<CheckBox>(R.id.checkbox_group_by_type)
radioGroup.check(when (sortType) {
0 -> R.id.radio_sort_alphabetical
else -> R.id.radio_sort_activity
})
radioGroup.setOnCheckedChangeListener({ _, checkedId ->
run {
SharedPreferenceHelper.putInt(Constants.CHATROOM_SORT_TYPE_KEY, when (checkedId) {
R.id.radio_sort_alphabetical -> 0
R.id.radio_sort_activity -> 1
else -> 1
})
presenter.updateSortedChatRooms()
invalidateQueryOnSearch()
}
})
groupByTypeCheckBox.isChecked = groupByType
groupByTypeCheckBox.setOnCheckedChangeListener({ _, isChecked ->
SharedPreferenceHelper.putBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, isChecked)
presenter.updateSortedChatRooms()
invalidateQueryOnSearch()
})
val dialogSort = AlertDialog.Builder(context)
.setTitle(R.string.dialog_sort_title)
.setView(dialogLayout)
.setPositiveButton("Done", { dialog, _ -> dialog.dismiss() })
dialogSort.show()
}
}
return super.onOptionsItemSelected(item)
}
private fun invalidateQueryOnSearch(){
searchView?.let {
if (!searchView!!.isIconified){
queryChatRoomsByName(searchView!!.query.toString())
}
}
}
override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) { override suspend fun updateChatRooms(newDataSet: List<ChatRoom>) {
activity?.apply { activity?.apply {
listJob?.cancel() listJob?.cancel()
listJob = launch(UI) { listJob = launch(UI) {
val adapter = recycler_view.adapter as ChatRoomsAdapter val adapter = recycler_view.adapter as SimpleSectionedRecyclerViewAdapter
// FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android.dev/issues/5a90d4718cb3c2fa63b3f557?time=last-seven-days // FIXME https://fabric.io/rocketchat3/android/apps/chat.rocket.android.dev/issues/5a90d4718cb3c2fa63b3f557?time=last-seven-days
// TODO - fix this bug to reenable DiffUtil // TODO - fix this bug to reenable DiffUtil
val diff = async(CommonPool) { val diff = async(CommonPool) {
DiffUtil.calculateDiff(RoomsDiffCallback(adapter.dataSet, newDataSet)) DiffUtil.calculateDiff(RoomsDiffCallback(adapter.baseAdapter.dataSet, newDataSet))
}.await() }.await()
if (isActive) { if (isActive) {
adapter.updateRooms(newDataSet) adapter.baseAdapter.updateRooms(newDataSet)
diff.dispatchUpdatesTo(adapter) diff.dispatchUpdatesTo(adapter)
//Set sections always after data set is updated
setSections()
} }
} }
} }
...@@ -162,12 +231,45 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView { ...@@ -162,12 +231,45 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end))) resources.getDimensionPixelSize(R.dimen.divider_item_decorator_bound_end)))
recycler_view.itemAnimator = DefaultItemAnimator() recycler_view.itemAnimator = DefaultItemAnimator()
// TODO - use a ViewModel Mapper instead of using settings on the adapter // TODO - use a ViewModel Mapper instead of using settings on the adapter
recycler_view.adapter = ChatRoomsAdapter(
this, val baseAdapter = ChatRoomsAdapter(this,
settingsRepository.get(serverInteractor.get()!!), localRepository) { chatRoom -> settingsRepository.get(serverInteractor.get()!!), localRepository) { chatRoom -> presenter.loadChatRoom(chatRoom) }
presenter.loadChatRoom(chatRoom)
sectionedAdapter = SimpleSectionedRecyclerViewAdapter(this, R.layout.item_chatroom_header, R.id.text_chatroom_header, baseAdapter!!)
recycler_view.adapter = sectionedAdapter
}
}
private fun setSections() {
//Don't add section if not grouping by RoomType
if (!SharedPreferenceHelper.getBoolean(Constants.CHATROOM_GROUP_BY_TYPE_KEY, false)) {
sectionedAdapter?.clearSections()
return
}
val sections = ArrayList<SimpleSectionedRecyclerViewAdapter.Section>()
sectionedAdapter?.baseAdapter?.dataSet?.let {
var previousChatRoomType = ""
for ((position, chatRoom) in it.withIndex()) {
val type = chatRoom.type.toString()
if (type != previousChatRoomType) {
val title = when (type) {
RoomType.CHANNEL.toString() -> resources.getString(R.string.header_channel)
RoomType.PRIVATE_GROUP.toString() -> resources.getString(R.string.header_private_groups)
RoomType.DIRECT_MESSAGE.toString() -> resources.getString(R.string.header_direct_messages)
RoomType.LIVECHAT.toString() -> resources.getString(R.string.header_live_chats)
else -> resources.getString(R.string.header_unknown)
}
sections.add(SimpleSectionedRecyclerViewAdapter.Section(position, title))
}
previousChatRoomType = chatRoom.type.toString()
} }
} }
val dummy = arrayOfNulls<SimpleSectionedRecyclerViewAdapter.Section>(sections.size)
sectionedAdapter?.setSections(sections.toArray(dummy))
} }
private fun queryChatRoomsByName(name: String?): Boolean { private fun queryChatRoomsByName(name: String?): Boolean {
......
package chat.rocket.android.chatrooms.ui
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import java.util.*
class SimpleSectionedRecyclerViewAdapter(private val context: Context, private val sectionResourceId: Int, private val textResourceId: Int,
val baseAdapter: ChatRoomsAdapter) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var isValid = true
private val sectionsHeaders = SparseArray<Section>()
init {
baseAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
isValid = baseAdapter.itemCount > 0
notifyDataSetChanged()
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
isValid = baseAdapter.itemCount > 0
notifyItemRangeChanged(positionStart, itemCount)
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
isValid = baseAdapter.itemCount > 0
notifyItemRangeInserted(positionStart, itemCount)
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
isValid = baseAdapter.itemCount > 0
notifyItemRangeRemoved(positionStart, itemCount)
}
})
}
class SectionViewHolder(view: View, textResourceId: Int) : RecyclerView.ViewHolder(view) {
var title: TextView = view.findViewById<View>(textResourceId) as TextView
}
override fun onCreateViewHolder(parent: ViewGroup, typeView: Int): RecyclerView.ViewHolder {
return if (typeView == SECTION_TYPE) {
val view = LayoutInflater.from(context).inflate(sectionResourceId, parent, false)
SectionViewHolder(view, textResourceId)
} else {
baseAdapter.onCreateViewHolder(parent, typeView - 1)
}
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
if (isSectionHeaderPosition(position)) {
(viewHolder as SectionViewHolder).title.text = sectionsHeaders.get(position).title
} else {
baseAdapter.onBindViewHolder(viewHolder as ChatRoomsAdapter.ViewHolder, sectionedPositionToPosition(position))
}
}
override fun getItemViewType(position: Int): Int {
return if (isSectionHeaderPosition(position))
SECTION_TYPE
else
baseAdapter.getItemViewType(sectionedPositionToPosition(position)) + 1
}
class Section(internal var firstPosition: Int, var title: CharSequence) {
internal var sectionedPosition: Int = 0
}
fun setSections(sections: Array<Section>) {
sectionsHeaders.clear()
Arrays.sort(sections) { section1, section2 ->
when {
section1.firstPosition == section2.firstPosition -> 0
section1.firstPosition < section2.firstPosition -> -1
else -> 1
}
}
for ((offset, section) in sections.withIndex()) {
section.sectionedPosition = section.firstPosition + offset
sectionsHeaders.append(section.sectionedPosition, section)
}
notifyDataSetChanged()
}
fun clearSections(){
sectionsHeaders.clear()
notifyDataSetChanged()
}
fun positionToSectionedPosition(position: Int): Int {
var offset = 0
for (i in 0 until sectionsHeaders.size()) {
if (sectionsHeaders.valueAt(i).firstPosition > position) {
break
}
++offset
}
return position + offset
}
private fun sectionedPositionToPosition(sectionedPosition: Int): Int {
if (isSectionHeaderPosition(sectionedPosition)) {
return RecyclerView.NO_POSITION
}
var offset = 0
for (i in 0 until sectionsHeaders.size()) {
if (sectionsHeaders.valueAt(i).sectionedPosition > sectionedPosition) {
break
}
--offset
}
return sectionedPosition + offset
}
private fun isSectionHeaderPosition(position: Int): Boolean {
return sectionsHeaders.get(position) != null
}
override fun getItemId(position: Int): Long {
return when (isSectionHeaderPosition(position)) {
true -> (Integer.MAX_VALUE - sectionsHeaders.indexOfKey(position)).toLong()
false -> baseAdapter.getItemId(sectionedPositionToPosition(position))
}
}
override fun getItemCount(): Int {
return if (isValid) baseAdapter.itemCount + sectionsHeaders.size() else 0
}
companion object {
private const val SECTION_TYPE = 0
}
}
package chat.rocket.android.helper
object Constants {
const val CHATROOM_SORT_TYPE_KEY: String = "chatroom_sort_type"
const val CHATROOM_GROUP_BY_TYPE_KEY: String = "chatroom_group_by_type"
const val CHATROOM_GROUP_FAVOURITES_KEY: String = "chatroom_group_favourites"
//Used to sort chat rooms
const val CHATROOM_CHANNEL = 0
const val CHATROOM_PRIVATE_GROUP = 1
const val CHATROOM_DM = 2
const val CHATROOM_LIVE_CHAT = 3
}
object ChatRoomsSortOrder {
const val ALPHABETICAL: Int = 0
const val ACTIVITY: Int = 1
}
\ No newline at end of file
package chat.rocket.android.helper
object OauthHelper {
/**
* 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.
*/
fun getGithubOauthUrl(clientId: String, state: String): String {
return "https://github.com/login/oauth/authorize" +
"?client_id=$clientId" +
"&state=$state" +
"&scope=user:email"
}
/**
* Returns the Google Oauth URL.
*
* @param clientId The Google client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Google Oauth URL.
*/
fun getGoogleOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://accounts.google.com/o/oauth2/v2/auth" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/google?close" +
"&state=$state" +
"&response_type=code" +
"&scope=email%20profile"
}
/**
* Returns the Linkedin Oauth URL.
*
* @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.
*/
fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://linkedin.com/oauth/v2/authorization" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/linkedin?close" +
"&state=$state" +
"&response_type=code"
}
/**
* Returns the Gitlab Oauth URL.
*
* @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.
*/
fun getGitlabOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://gitlab.com/oauth/authorize" +
"?client_id=$clientId" +
"&redirect_uri=${UrlHelper.removeTrailingSlash(serverUrl)}/_oauth/gitlab?close" +
"&state=$state" +
"&response_type=code" +
"&scope=read_user"
}
}
\ No newline at end of file
package chat.rocket.android.helper
import android.content.SharedPreferences
import android.preference.PreferenceManager
import chat.rocket.android.app.RocketChatApplication
object SharedPreferenceHelper {
private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(RocketChatApplication.getAppContext())
private var editor: SharedPreferences.Editor? = sharedPreferences.edit()
//Add more methods for other types if needed
fun putInt(key: String, value: Int) {
editor!!.putInt(key, value).apply()
}
fun getInt(key: String, defaultValue: Int): Int {
return sharedPreferences.getInt(key, defaultValue)
}
fun putLong(key: String, value: Long) {
editor!!.putLong(key, value).apply()
}
fun getLong(key: String, defaultValue: Long): Long {
return sharedPreferences.getLong(key, defaultValue)
}
fun putString(key: String, value: String) {
editor!!.putString(key, value).apply()
}
fun getString(key: String, defaultValue: String): String? {
return sharedPreferences.getString(key, defaultValue)
}
fun putBoolean(key: String, value: Boolean) {
editor!!.putBoolean(key, value).apply()
}
fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return sharedPreferences.getBoolean(key, defaultValue)
}
fun remove(key: String) {
editor!!.remove(key).apply()
}
}
\ No newline at end of file
...@@ -22,7 +22,7 @@ object UrlHelper { ...@@ -22,7 +22,7 @@ object UrlHelper {
* @return The server logo URL. * @return The server logo URL.
*/ */
fun getServerLogoUrl(serverUrl: String, favicon: String): String = fun getServerLogoUrl(serverUrl: String, favicon: String): String =
removeTrailingSlash(serverUrl) + "/$favicon" removeTrailingSlash(serverUrl) + "/$favicon"
/** /**
* Returns the CAS URL. * Returns the CAS URL.
...@@ -35,49 +35,6 @@ object UrlHelper { ...@@ -35,49 +35,6 @@ object UrlHelper {
fun getCasUrl(casLoginUrl: String, serverUrl: String, token: String): String = fun getCasUrl(casLoginUrl: String, serverUrl: String, token: String): String =
removeTrailingSlash(casLoginUrl) + "?service=" + removeTrailingSlash(serverUrl) + "/_cas/" + token removeTrailingSlash(casLoginUrl) + "?service=" + removeTrailingSlash(serverUrl) + "/_cas/" + token
/**
* 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.
*/
fun getGithubOauthUrl(clientId: String, state: String): String =
"https://github.com/login/oauth/authorize?scope=user:email&client_id=$clientId&state=$state"
/**
* Returns the Google Oauth URL.
*
* @param clientId The Google client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The Google Oauth URL.
*/
fun getGoogleOauthUrl(clientId: String, serverUrl: String, state: String) =
"https://accounts.google.com/o/oauth2/v2/auth?client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/google?close&response_type=code&state=$state&scope=email%20profile"
/**
* Returns the Linkedin Oauth URL.
*
* @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.
*/
fun getLinkedinOauthUrl(clientId: String, serverUrl: String, state: String) =
"https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/linkedin?close&state=$state"
/**
* Returns the Gitlab Oauth URL.
*
* @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.
*/
fun getGitlabOauthUrl(clientId: String, serverUrl: String, state: String): String =
"https://gitlab.com/oauth/authorize?client_id=$clientId&redirect_uri=${removeTrailingSlash(serverUrl)}/_oauth/gitlab?close&response_type=code&state=$state&scope=read_user"
/** /**
* Returns the server's Terms of Service URL. * Returns the server's Terms of Service URL.
* *
......
...@@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity ...@@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import chat.rocket.android.R import chat.rocket.android.R
...@@ -83,7 +84,18 @@ class MembersFragment : Fragment(), MembersView { ...@@ -83,7 +84,18 @@ class MembersFragment : Fragment(), MembersView {
} else { } else {
adapter.appendData(dataSet) adapter.appendData(dataSet)
} }
if (this is ChatRoomActivity) {
this.showRoomTypeIcon(false)
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
(activity as ChatRoomActivity).showRoomTypeIcon(true)
return super.onOptionsItemSelected(item)
} }
return super.onOptionsItemSelected(item)
} }
override fun showLoading() = view_loading.setVisible(true) override fun showLoading() = view_loading.setVisible(true)
......
...@@ -78,8 +78,10 @@ class OauthWebViewActivity : AppCompatActivity() { ...@@ -78,8 +78,10 @@ class OauthWebViewActivity : AppCompatActivity() {
private fun setupWebView() { private fun setupWebView() {
with(web_view.settings) { with(web_view.settings) {
javaScriptEnabled = true javaScriptEnabled = true
// TODO This is required to make Google OAuth work, but we shoud use Custom Tabs instead. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968 // TODO Remove this workaround that is required to make Google OAuth to work. We should use Custom Tabs instead. See https://github.com/RocketChat/Rocket.Chat.Android/issues/968
userAgentString = "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19" if (webPageUrl.contains("google")) {
userAgentString = "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/43.0.2357.65 Mobile Safari/535.19"
}
} }
web_view.webViewClient = object : WebViewClient() { web_view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
<RadioGroup
android:id="@+id/radio_group_sort"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/radio_sort_alphabetical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/dialog_sort_by_alphabet"
android:textSize="18sp" />
<RadioButton
android:id="@+id/radio_sort_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/dialog_sort_by_activity"
android:textSize="18sp" />
</RadioGroup>
<CheckBox
android:id="@+id/checkbox_group_by_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:padding="8dp"
android:text="@string/dialog_group_by_type"
android:textSize="18sp" />
<!--TODO Add checkbox for "Group favourites after sdk support"-->
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/darkGray" />
<TextView
android:id="@+id/text_chatroom_header"
style="@style/ChatRooms.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:text="@string/chatroom_header" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/darkGray" />
</LinearLayout>
\ No newline at end of file
...@@ -9,4 +9,10 @@ ...@@ -9,4 +9,10 @@
app:actionViewClass="android.support.v7.widget.SearchView" app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" /> app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/action_sort"
android:icon="@drawable/ic_sort"
android:title="@string/menu_chatroom_sort"
app:showAsAction="always" />
</menu> </menu>
\ No newline at end of file
This diff is collapsed.
...@@ -149,4 +149,20 @@ ...@@ -149,4 +149,20 @@
<!-- Emoji message--> <!-- Emoji message-->
<string name="msg_no_recent_emoji">Nenhum emoji recente</string> <string name="msg_no_recent_emoji">Nenhum emoji recente</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Ordenar</string>
<string name="dialog_sort_title">Ordenar por</string>
<string name="dialog_sort_by_alphabet">Alfabeticamente</string>
<string name="dialog_sort_by_activity">Atividade</string>
<string name="dialog_group_by_type">Agrupar por tipo</string>
<string name="dialog_group_favourites">Grupos favoritos</string>
<string name="chatroom_header">Cabeçalho</string>
<!--ChatRooms Headers-->
<string name="header_channel">Canais</string>
<string name="header_private_groups">Grupos Privados</string>
<string name="header_direct_messages">Mensagens diretas</string>
<string name="header_live_chats">Live Chats</string>
<string name="header_unknown">Desconhecido</string>
</resources> </resources>
\ No newline at end of file
...@@ -150,4 +150,20 @@ ...@@ -150,4 +150,20 @@
<!-- Emoji message--> <!-- Emoji message-->
<string name="msg_no_recent_emoji">No recent emoji</string> <string name="msg_no_recent_emoji">No recent emoji</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Sort</string>
<string name="dialog_sort_title">Sort by</string>
<string name="dialog_sort_by_alphabet">Alphabetical</string>
<string name="dialog_sort_by_activity">Activity</string>
<string name="dialog_group_by_type">Group by type</string>
<string name="dialog_group_favourites">Group favourites</string>
<string name="chatroom_header">Header</string>
<!--ChatRooms Headers-->
<string name="header_channel">Channels</string>
<string name="header_private_groups">Private Groups</string>
<string name="header_direct_messages">Direct Messages</string>
<string name="header_live_chats">Live Chats</string>
<string name="header_unknown">Unknown</string>
</resources> </resources>
\ No newline at end of file
...@@ -71,6 +71,10 @@ ...@@ -71,6 +71,10 @@
<item name="android:paddingStart">@dimen/edit_text_margin</item> <item name="android:paddingStart">@dimen/edit_text_margin</item>
</style> </style>
<style name="ChatRooms.Header" parent="TextAppearance.AppCompat.Headline">
<item name="android:textSize">16sp</item>
</style>
<style name="ChatRoom.Name.TextView" parent="TextAppearance.AppCompat.Title"> <style name="ChatRoom.Name.TextView" parent="TextAppearance.AppCompat.Title">
<item name="android:ellipsize">end</item> <item name="android:ellipsize">end</item>
<item name="android:maxLines">1</item> <item name="android:maxLines">1</item>
......
...@@ -10,7 +10,7 @@ buildscript { ...@@ -10,7 +10,7 @@ buildscript {
} }
dependencies { dependencies {
classpath "com.android.tools.build:gradle:3.0.1" classpath 'com.android.tools.build:gradle:3.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.2.0' classpath 'com.google.gms:google-services:3.2.0'
......
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