Unverified Commit 7fc56c24 authored by Ergashev Adizbek's avatar Ergashev Adizbek Committed by GitHub

Merge branch 'develop' into favorite-channel-fix

parents 19ac82c2 d9f8a831
package chat.rocket.android.about.di
import chat.rocket.android.about.ui.AboutFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class AboutFragmentProvider {
@ContributesAndroidInjector()
abstract fun provideAboutFragment(): AboutFragment
}
package chat.rocket.android.about.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
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.main.ui.MainActivity
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_about.*
import javax.inject.Inject
internal const val TAG_ABOUT_FRAGMENT = "AboutFragment"
class AboutFragment : Fragment() {
@Inject
lateinit var analyticsManager: AnalyticsManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_about, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupViews()
analyticsManager.logScreenView(ScreenViewEvent.About)
}
override fun onResume() {
super.onResume()
setupToolbar()
}
private fun setupViews() {
text_version_name.text = BuildConfig.VERSION_NAME
text_build_number.text = getString(
R.string.msg_build, BuildConfig.VERSION_CODE,
BuildConfig.GIT_SHA, BuildConfig.FLAVOR
)
}
private fun setupToolbar() {
with((activity as MainActivity).toolbar) {
title = getString(R.string.title_about)
setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
setNavigationOnClickListener { activity?.onBackPressed() }
}
}
companion object {
fun newInstance() = AboutFragment()
}
}
...@@ -10,7 +10,7 @@ interface Analytics { ...@@ -10,7 +10,7 @@ interface Analytics {
* Logs the login event. * Logs the login event.
* *
* @param event The [AuthenticationEvent] used to log in. * @param event The [AuthenticationEvent] used to log in.
* @param loginSucceeded True if successful logged in, false otherwise. * @param loginSucceeded True if logged in successfully, false otherwise.
*/ */
fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) {} fun logLogin(event: AuthenticationEvent, loginSucceeded: Boolean) {}
...@@ -18,7 +18,7 @@ interface Analytics { ...@@ -18,7 +18,7 @@ interface Analytics {
* Logs the sign up event. * Logs the sign up event.
* *
* @param event The [AuthenticationEvent] used to sign up. * @param event The [AuthenticationEvent] used to sign up.
* @param signUpSucceeded True if successful signed up, false otherwise. * @param signUpSucceeded True if signed up successfully, false otherwise.
*/ */
fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) {} fun logSignUp(event: AuthenticationEvent, signUpSucceeded: Boolean) {}
......
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
...@@ -43,7 +43,7 @@ object DrawableHelper { ...@@ -43,7 +43,7 @@ object DrawableHelper {
/** /**
* Tints an array of Drawable. * Tints an array of Drawable.
* *
* REMARK: you MUST always wrap the array of Drawable before tint it. * REMARK: you MUST always wrap the array of Drawable before tinting it.
* *
* @param drawables The array of Drawable to tint. * @param drawables The array of Drawable to tint.
* @param context The context. * @param context The context.
...@@ -78,7 +78,7 @@ object DrawableHelper { ...@@ -78,7 +78,7 @@ object DrawableHelper {
* *
* @param textView The array of TextView. * @param textView The array of TextView.
* @param drawables The array of Drawable. * @param drawables The array of Drawable.
* @see compoundDrawable * @see compoundLeftDrawable
*/ */
fun compoundDrawables(textView: Array<TextView>, drawables: Array<Drawable>) { fun compoundDrawables(textView: Array<TextView>, drawables: Array<Drawable>) {
if (textView.size != drawables.size) { if (textView.size != drawables.size) {
...@@ -97,7 +97,7 @@ object DrawableHelper { ...@@ -97,7 +97,7 @@ object DrawableHelper {
* @param drawable The Drawable. * @param drawable The Drawable.
* @see compoundDrawables * @see compoundDrawables
*/ */
fun compoundDrawable(textView: TextView, drawable: Drawable) = fun compoundLeftDrawable(textView: TextView, drawable: Drawable) =
textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
/** /**
...@@ -105,11 +105,26 @@ object DrawableHelper { ...@@ -105,11 +105,26 @@ object DrawableHelper {
* *
* @param textView The TextView. * @param textView The TextView.
* @param drawable The Drawable. * @param drawable The Drawable.
* @see compoundDrawable * @see compoundLeftDrawable
*/ */
fun compoundRightDrawable(textView: TextView, drawable: Drawable) = fun compoundRightDrawable(textView: TextView, drawable: Drawable) =
textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null) textView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, null)
/**
* Compounds a Drawable (to appear on the left and right side of a text) into a TextView.
*
* @param textView The TextView.
* @param leftDrawable The left Drawable.
* @param rightDrawable The right Drawable.
* @see compoundLeftDrawable
*/
fun compoundLeftAndRightDrawable(
textView: TextView,
leftDrawable: Drawable,
rightDrawable: Drawable
) =
textView.setCompoundDrawablesWithIntrinsicBounds(leftDrawable, null, rightDrawable, null)
/** /**
* Returns the user status drawable. * Returns the user status drawable.
* *
......
...@@ -115,7 +115,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -115,7 +115,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
// TODO - remove this // TODO - remove this
checkCurrentServer() checkCurrentServer()
// TODO - FIXME - we need to proper inject the EmojiRepository and initialize it properly // TODO - FIXME - we need to properly inject and initialize the EmojiRepository
loadEmojis() loadEmojis()
} }
...@@ -176,7 +176,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -176,7 +176,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
val currentServer = getCurrentServerInteractor.get() val currentServer = getCurrentServerInteractor.get()
currentServer?.let { server -> currentServer?.let { server ->
GlobalScope.launch { GlobalScope.launch {
val client = factory.create(server) val client = factory.get(server)
EmojiRepository.setCurrentServerUrl(server) EmojiRepository.setCurrentServerUrl(server)
val customEmojiList = mutableListOf<Emoji>() val customEmojiList = mutableListOf<Emoji>()
try { try {
......
...@@ -60,7 +60,7 @@ class LoginPresenter @Inject constructor( ...@@ -60,7 +60,7 @@ class LoginPresenter @Inject constructor(
private fun setupConnectionInfo(serverUrl: String) { private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl currentServer = serverUrl
client = factory.create(currentServer) client = factory.get(currentServer)
settings = settingsInteractor.get(currentServer) settings = settingsInteractor.get(currentServer)
} }
......
...@@ -169,7 +169,7 @@ class LoginOptionsPresenter @Inject constructor( ...@@ -169,7 +169,7 @@ class LoginOptionsPresenter @Inject constructor(
private fun setupConnectionInfo(serverUrl: String) { private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl currentServer = serverUrl
client = factory.create(currentServer) client = factory.get(currentServer)
settings = settingsInteractor.get(currentServer) settings = settingsInteractor.get(currentServer)
} }
......
...@@ -38,7 +38,7 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -38,7 +38,7 @@ class RegisterUsernamePresenter @Inject constructor(
val settingsInteractor: GetSettingsInteractor val settingsInteractor: GetSettingsInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.get(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun registerUsername(username: String, userId: String, authToken: String) { fun registerUsername(username: String, userId: String, authToken: String) {
......
...@@ -140,7 +140,7 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView { ...@@ -140,7 +140,7 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_20dp, it) val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_20dp, it)
DrawableHelper.wrapDrawable(atDrawable) DrawableHelper.wrapDrawable(atDrawable)
DrawableHelper.tintDrawable(atDrawable, it, R.color.colorDrawableTintGrey) DrawableHelper.tintDrawable(atDrawable, it, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawable(text_username, atDrawable) DrawableHelper.compoundLeftDrawable(text_username, atDrawable)
} }
} }
......
...@@ -21,7 +21,7 @@ class ResetPasswordPresenter @Inject constructor( ...@@ -21,7 +21,7 @@ class ResetPasswordPresenter @Inject constructor(
serverInteractor: GetConnectingServerInteractor serverInteractor: GetConnectingServerInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.get(currentServer)
fun resetPassword(email: String) { fun resetPassword(email: String) {
launchUI(strategy) { launchUI(strategy) {
......
...@@ -21,7 +21,7 @@ interface ResetPasswordView : LoadingView, MessageView { ...@@ -21,7 +21,7 @@ interface ResetPasswordView : LoadingView, MessageView {
fun enableButtonConnect() fun enableButtonConnect()
/** /**
* Disables the button to reset the password when the user entered an invalid email address * Disables the button to reset the password when the user has entered an invalid email address
*/ */
fun disableButtonConnect() fun disableButtonConnect()
} }
\ No newline at end of file
...@@ -26,7 +26,7 @@ interface VersionCheckView { ...@@ -26,7 +26,7 @@ interface VersionCheckView {
fun versionOk() {} fun versionOk() {}
/** /**
* Alters the user this protocol is invalid. This is optional. * Alters the user that this protocol is invalid. This is optional.
*/ */
fun errorInvalidProtocol() {} fun errorInvalidProtocol() {}
......
...@@ -44,7 +44,7 @@ class SignupPresenter @Inject constructor( ...@@ -44,7 +44,7 @@ class SignupPresenter @Inject constructor(
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun signup(name: String, username: String, password: String, email: String) { fun signup(name: String, username: String, password: String, email: String) {
val client = factory.create(currentServer) val client = factory.get(currentServer)
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
......
...@@ -51,7 +51,7 @@ class TwoFAPresenter @Inject constructor( ...@@ -51,7 +51,7 @@ class TwoFAPresenter @Inject constructor(
twoFactorAuthenticationCode: String twoFactorAuthenticationCode: String
) { ) {
launchUI(strategy) { launchUI(strategy) {
val client = factory.create(currentServer) val client = factory.get(currentServer)
view.showLoading() view.showLoading()
try { try {
// The token is saved via the client TokenProvider // The token is saved via the client TokenProvider
......
...@@ -200,7 +200,7 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView { ...@@ -200,7 +200,7 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView {
val wrappedDrawable = DrawableHelper.wrapDrawable(it) val wrappedDrawable = DrawableHelper.wrapDrawable(it)
val mutableDrawable = wrappedDrawable.mutate() val mutableDrawable = wrappedDrawable.mutate()
DrawableHelper.tintDrawable(mutableDrawable, context!!, R.color.colorPrimary) DrawableHelper.tintDrawable(mutableDrawable, context!!, R.color.colorPrimary)
DrawableHelper.compoundDrawable(name, mutableDrawable) DrawableHelper.compoundLeftDrawable(name, mutableDrawable)
} }
} }
......
...@@ -44,7 +44,6 @@ class MessageInfoFragment : Fragment(), MessageInfoView { ...@@ -44,7 +44,6 @@ class MessageInfoFragment : Fragment(), MessageInfoView {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
val bundle = arguments val bundle = arguments
if (bundle != null) { if (bundle != null) {
...@@ -52,6 +51,8 @@ class MessageInfoFragment : Fragment(), MessageInfoView { ...@@ -52,6 +51,8 @@ class MessageInfoFragment : Fragment(), MessageInfoView {
} else { } else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" } requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
} }
setHasOptionsMenu(true)
} }
override fun onCreateView( override fun onCreateView(
......
...@@ -268,7 +268,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -268,7 +268,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
arguments?.run { arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "") chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
...@@ -292,6 +291,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -292,6 +291,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
navigator = navigator, navigator = navigator,
analyticsManager = analyticsManager analyticsManager = analyticsManager
) )
setHasOptionsMenu(true)
} }
override fun onCreateView( override fun onCreateView(
......
...@@ -10,7 +10,6 @@ import chat.rocket.android.dagger.scope.PerFragment ...@@ -10,7 +10,6 @@ import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.ChatRoomDao import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.UserDao import chat.rocket.android.db.UserDao
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentUserInteractor import chat.rocket.android.server.domain.GetCurrentUserInteractor
import chat.rocket.android.server.domain.PermissionsInteractor import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
...@@ -45,7 +44,7 @@ class ChatRoomsFragmentModule { ...@@ -45,7 +44,7 @@ class ChatRoomsFragmentModule {
factory: RocketChatClientFactory, factory: RocketChatClientFactory,
@Named("currentServer") currentServer: String @Named("currentServer") currentServer: String
): RocketChatClient { ): RocketChatClient {
return factory.create(currentServer) return factory.get(currentServer)
} }
@Provides @Provides
......
...@@ -10,6 +10,7 @@ import chat.rocket.android.helper.UserHelper ...@@ -10,6 +10,7 @@ import chat.rocket.android.helper.UserHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.main.presentation.MainNavigator import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.server.domain.SettingsRepository 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.useRealName
import chat.rocket.android.server.domain.useSpecialCharsOnRoom import chat.rocket.android.server.domain.useSpecialCharsOnRoom
import chat.rocket.android.server.infraestructure.ConnectionManager import chat.rocket.android.server.infraestructure.ConnectionManager
...@@ -35,6 +36,7 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -35,6 +36,7 @@ class ChatRoomsPresenter @Inject constructor(
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: MainNavigator, private val navigator: MainNavigator,
@Named("currentServer") private val currentServer: String, @Named("currentServer") private val currentServer: String,
private val sortingAndGroupingInteractor: SortingAndGroupingInteractor,
private val dbManager: DatabaseManager, private val dbManager: DatabaseManager,
manager: ConnectionManager, manager: ConnectionManager,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
...@@ -44,29 +46,42 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -44,29 +46,42 @@ class ChatRoomsPresenter @Inject constructor(
private val client = manager.client private val client = manager.client
private val settings = settingsRepository.get(currentServer) private val settings = settingsRepository.get(currentServer)
fun toCreateChannel() = navigator.toCreateChannel()
fun toSettings() = navigator.toSettings()
fun getCurrentServerName() = view.setupToolbar(currentServer)
fun getSortingAndGroupingPreferences() {
with(sortingAndGroupingInteractor) {
view.setupSortingAndGrouping(
getSortByName(currentServer),
getUnreadOnTop(currentServer),
getGroupByType(currentServer),
getGroupByFavorites(currentServer)
)
}
}
fun loadChatRoom(roomId: String) { fun loadChatRoom(roomId: String) {
launchUI(strategy) { launchUI(strategy) {
view.showLoadingRoom("")
try { try {
val room = dbManager.getRoom(roomId) val room = dbManager.getRoom(roomId)
if (room != null) { if (room != null) {
loadChatRoom(room.chatRoom, true) loadChatRoom(room.chatRoom, true)
} else { } else {
Timber.d("Error loading channel") Timber.e("Error loading channel")
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error loading channel") Timber.e(ex, "Error loading channel")
view.showGenericErrorMessage() view.showGenericErrorMessage()
} finally {
view.hideLoadingRoom()
} }
} }
} }
fun loadChatRoom(chatRoom: RoomUiModel) { fun loadChatRoom(chatRoom: RoomUiModel) {
launchUI(strategy) { launchUI(strategy) {
view.showLoadingRoom(chatRoom.name)
try { try {
val room = retryDB("getRoom(${chatRoom.id}") { dbManager.getRoom(chatRoom.id) } val room = retryDB("getRoom(${chatRoom.id}") { dbManager.getRoom(chatRoom.id) }
if (room != null) { if (room != null) {
...@@ -86,10 +101,8 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -86,10 +101,8 @@ class ChatRoomsPresenter @Inject constructor(
} }
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error loading channel") Timber.e(ex, "Error loading channel")
view.showGenericErrorMessage() view.showGenericErrorMessage()
} finally {
view.hideLoadingRoom()
} }
} }
} }
...@@ -97,11 +110,12 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -97,11 +110,12 @@ class ChatRoomsPresenter @Inject constructor(
suspend fun loadChatRoom(chatRoom: ChatRoomEntity, local: Boolean = false) { suspend fun loadChatRoom(chatRoom: ChatRoomEntity, local: Boolean = false) {
with(chatRoom) { with(chatRoom) {
val isDirectMessage = roomTypeOf(type) is RoomType.DirectMessage val isDirectMessage = roomTypeOf(type) is RoomType.DirectMessage
val roomName = if (settings.useSpecialCharsOnRoom() || (isDirectMessage && settings.useRealName())) { val roomName =
fullname ?: name if (settings.useSpecialCharsOnRoom() || (isDirectMessage && settings.useRealName())) {
} else { fullname ?: name
name } else {
} name
}
val myself = getCurrentUser() val myself = getCurrentUser()
if (myself?.username == null) { if (myself?.username == null) {
...@@ -131,14 +145,14 @@ class ChatRoomsPresenter @Inject constructor( ...@@ -131,14 +145,14 @@ class ChatRoomsPresenter @Inject constructor(
} }
navigator.toChatRoom( navigator.toChatRoom(
chatRoomId = id, chatRoomId = id,
chatRoomName = roomName, chatRoomName = roomName,
chatRoomType = type, chatRoomType = type,
isReadOnly = readonly ?: false, isReadOnly = readonly ?: false,
chatRoomLastSeen = lastSeen ?: -1, chatRoomLastSeen = lastSeen ?: -1,
isSubscribed = open, isSubscribed = open,
isCreator = ownerId == myself.id || isDirectMessage, isCreator = ownerId == myself.id || isDirectMessage,
isFavorite = favorite ?: false isFavorite = favorite ?: false
) )
} }
} }
......
...@@ -4,7 +4,27 @@ import chat.rocket.android.core.behaviours.LoadingView ...@@ -4,7 +4,27 @@ import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView import chat.rocket.android.core.behaviours.MessageView
interface ChatRoomsView : LoadingView, MessageView { interface ChatRoomsView : LoadingView, MessageView {
fun showLoadingRoom(name: CharSequence)
fun hideLoadingRoom() /**
* Setups the toolbar with the current logged in server name.
*
* @param serverName The current logged in server name to show on Toolbar.
*/
fun setupToolbar(serverName: String)
/**
* Setups the sorting and grouping in the bases of the user preference for
* the current logged in server.
*
* @param isSortByName True if sorting by name, false otherwise.
* @param isUnreadOnTop True if grouping by unread on top, false otherwise.
* @param isGroupByType True if grouping by type , false otherwise.
* @param isGroupByFavorites True if grouping by favorites, false otherwise.
*/
fun setupSortingAndGrouping(
isSortByName: Boolean,
isUnreadOnTop: Boolean,
isGroupByType: Boolean,
isGroupByFavorites: Boolean
)
} }
\ No newline at end of file
...@@ -22,7 +22,7 @@ class CreateChannelPresenter @Inject constructor( ...@@ -22,7 +22,7 @@ class CreateChannelPresenter @Inject constructor(
val serverInteractor: GetCurrentServerInteractor, val serverInteractor: GetCurrentServerInteractor,
val factory: RocketChatClientFactory val factory: RocketChatClientFactory
) { ) {
private val client: RocketChatClient = factory.create(serverInteractor.get()!!) private val client: RocketChatClient = factory.get(serverInteractor.get()!!)
fun createChannel( fun createChannel(
roomType: RoomType, roomType: RoomType,
...@@ -35,7 +35,6 @@ class CreateChannelPresenter @Inject constructor( ...@@ -35,7 +35,6 @@ class CreateChannelPresenter @Inject constructor(
view.disableUserInput() view.disableUserInput()
try { try {
client.createChannel(roomType, channelName, usersList, readOnly) client.createChannel(roomType, channelName, usersList, readOnly)
view.prepareToShowChatList()
view.showChannelCreatedSuccessfullyMessage() view.showChannelCreatedSuccessfullyMessage()
toChatList() toChatList()
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
......
...@@ -28,12 +28,6 @@ interface CreateChannelView : LoadingView, MessageView { ...@@ -28,12 +28,6 @@ interface CreateChannelView : LoadingView, MessageView {
*/ */
fun hideSuggestionViewInProgress() fun hideSuggestionViewInProgress()
/**
* Shows the navigation drawer with the chat item checked before showing the chat list.
* This function is invoked after successfully created the channel.
*/
fun prepareToShowChatList()
/** /**
* Shows a message that a channel was successfully created. * Shows a message that a channel was successfully created.
*/ */
......
...@@ -9,7 +9,6 @@ import android.view.ViewGroup ...@@ -9,7 +9,6 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
...@@ -19,7 +18,6 @@ import chat.rocket.android.analytics.AnalyticsManager ...@@ -19,7 +18,6 @@ import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.createchannel.presentation.CreateChannelPresenter import chat.rocket.android.createchannel.presentation.CreateChannelPresenter
import chat.rocket.android.createchannel.presentation.CreateChannelView import chat.rocket.android.createchannel.presentation.CreateChannelView
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.members.adapter.MembersAdapter import chat.rocket.android.members.adapter.MembersAdapter
import chat.rocket.android.members.uimodel.MemberUiModel import chat.rocket.android.members.uimodel.MemberUiModel
import chat.rocket.android.util.extension.asObservable import chat.rocket.android.util.extension.asObservable
...@@ -32,17 +30,18 @@ import com.google.android.material.chip.Chip ...@@ -32,17 +30,18 @@ import com.google.android.material.chip.Chip
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_create_channel.* import kotlinx.android.synthetic.main.fragment_create_channel.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
internal const val TAG_CREATE_CHANNEL_FRAGMENT = "CreateChannelFragment" internal const val TAG_CREATE_CHANNEL_FRAGMENT = "CreateChannelFragment"
fun newInstance() = CreateChannelFragment()
class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback { class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback {
@Inject @Inject lateinit var presenter: CreateChannelPresenter
lateinit var createChannelPresenter: CreateChannelPresenter @Inject lateinit var analyticsManager: AnalyticsManager
@Inject
lateinit var analyticsManager: AnalyticsManager
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private val adapter: MembersAdapter = MembersAdapter { private val adapter: MembersAdapter = MembersAdapter {
it.username?.run { processSelectedMember(this) } it.username?.run { processSelectedMember(this) }
...@@ -52,10 +51,6 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback ...@@ -52,10 +51,6 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
private var isChannelReadOnly: Boolean = false private var isChannelReadOnly: Boolean = false
private var memberList = arrayListOf<String>() private var memberList = arrayListOf<String>()
companion object {
fun newInstance() = CreateChannelFragment()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -93,7 +88,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback ...@@ -93,7 +88,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
override fun onActionItemClicked(mode: ActionMode, menuItem: MenuItem): Boolean { override fun onActionItemClicked(mode: ActionMode, menuItem: MenuItem): Boolean {
return when (menuItem.itemId) { return when (menuItem.itemId) {
R.id.action_create_channel -> { R.id.action_create_channel -> {
createChannelPresenter.createChannel( presenter.createChannel(
roomTypeOf(channelType), roomTypeOf(channelType),
text_channel_name.text.toString(), text_channel_name.text.toString(),
memberList, memberList,
...@@ -165,17 +160,6 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback ...@@ -165,17 +160,6 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
view_member_suggestion_loading.isVisible = false view_member_suggestion_loading.isVisible = false
} }
override fun prepareToShowChatList() {
with(activity as MainActivity) {
setCheckedNavDrawerItem(R.id.menu_action_chats)
openDrawer()
getDrawerLayout().postDelayed(1000) {
closeDrawer()
createChannelPresenter.toChatList()
}
}
}
override fun showChannelCreatedSuccessfullyMessage() { override fun showChannelCreatedSuccessfullyMessage() {
showMessage(getString(R.string.msg_channel_created_successfully)) showMessage(getString(R.string.msg_channel_created_successfully))
} }
...@@ -191,8 +175,14 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback ...@@ -191,8 +175,14 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
} }
private fun setupToolBar() { private fun setupToolBar() {
(activity as AppCompatActivity?)?.supportActionBar?.title = with((activity as AppCompatActivity)) {
getString(R.string.title_create_channel) with(toolbar) {
setSupportActionBar(this)
title = getString(R.string.title_create_channel)
setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
setNavigationOnClickListener { activity?.onBackPressed() }
}
}
} }
private fun setupViewListeners() { private fun setupViewListeners() {
...@@ -247,7 +237,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback ...@@ -247,7 +237,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
.filter { t -> t.isNotBlank() } .filter { t -> t.isNotBlank() }
.subscribe { .subscribe {
if (it.length >= 3) { if (it.length >= 3) {
createChannelPresenter.searchUser(it.toString()) presenter.searchUser(it.toString())
} else { } else {
view_member_suggestion.isVisible = false view_member_suggestion.isVisible = false
} }
......
package chat.rocket.android.dagger.module package chat.rocket.android.dagger.module
import chat.rocket.android.about.di.AboutFragmentProvider
import chat.rocket.android.authentication.di.AuthenticationModule import chat.rocket.android.authentication.di.AuthenticationModule
import chat.rocket.android.authentication.login.di.LoginFragmentProvider import chat.rocket.android.authentication.login.di.LoginFragmentProvider
import chat.rocket.android.authentication.loginoptions.di.LoginOptionsFragmentProvider import chat.rocket.android.authentication.loginoptions.di.LoginOptionsFragmentProvider
...@@ -29,13 +28,14 @@ import chat.rocket.android.main.ui.MainActivity ...@@ -29,13 +28,14 @@ import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.members.di.MembersFragmentProvider import chat.rocket.android.members.di.MembersFragmentProvider
import chat.rocket.android.mentions.di.MentionsFragmentProvider import chat.rocket.android.mentions.di.MentionsFragmentProvider
import chat.rocket.android.pinnedmessages.di.PinnedMessagesFragmentProvider import chat.rocket.android.pinnedmessages.di.PinnedMessagesFragmentProvider
import chat.rocket.android.preferences.di.PreferencesFragmentProvider
import chat.rocket.android.profile.di.ProfileFragmentProvider import chat.rocket.android.profile.di.ProfileFragmentProvider
import chat.rocket.android.server.di.ChangeServerModule import chat.rocket.android.server.di.ChangeServerModule
import chat.rocket.android.server.ui.ChangeServerActivity import chat.rocket.android.server.ui.ChangeServerActivity
import chat.rocket.android.servers.di.ServersBottomSheetFragmentProvider
import chat.rocket.android.settings.di.SettingsFragmentProvider import chat.rocket.android.settings.di.SettingsFragmentProvider
import chat.rocket.android.settings.password.di.PasswordFragmentProvider import chat.rocket.android.settings.password.di.PasswordFragmentProvider
import chat.rocket.android.settings.password.ui.PasswordActivity import chat.rocket.android.settings.password.ui.PasswordActivity
import chat.rocket.android.sortingandgrouping.di.SortingAndGroupingBottomSheetFragmentProvider
import chat.rocket.android.userdetails.di.UserDetailsFragmentProvider import chat.rocket.android.userdetails.di.UserDetailsFragmentProvider
import chat.rocket.android.videoconference.di.VideoConferenceModule import chat.rocket.android.videoconference.di.VideoConferenceModule
import chat.rocket.android.videoconference.ui.VideoConferenceActivity import chat.rocket.android.videoconference.ui.VideoConferenceActivity
...@@ -65,11 +65,11 @@ abstract class ActivityBuilder { ...@@ -65,11 +65,11 @@ abstract class ActivityBuilder {
@ContributesAndroidInjector( @ContributesAndroidInjector(
modules = [MainModule::class, modules = [MainModule::class,
ChatRoomsFragmentProvider::class, ChatRoomsFragmentProvider::class,
ServersBottomSheetFragmentProvider::class,
SortingAndGroupingBottomSheetFragmentProvider::class,
CreateChannelProvider::class, CreateChannelProvider::class,
ProfileFragmentProvider::class, ProfileFragmentProvider::class,
SettingsFragmentProvider::class, SettingsFragmentProvider::class,
AboutFragmentProvider::class,
PreferencesFragmentProvider::class,
AdminPanelWebViewFragmentProvider::class AdminPanelWebViewFragmentProvider::class
] ]
) )
......
...@@ -27,23 +27,23 @@ import chat.rocket.android.push.PushManager ...@@ -27,23 +27,23 @@ import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.AccountsRepository import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.AnalyticsTrackingInteractor import chat.rocket.android.server.domain.AnalyticsTrackingInteractor
import chat.rocket.android.server.domain.AnalyticsTrackingRepository import chat.rocket.android.server.domain.AnalyticsTrackingRepository
import chat.rocket.android.server.domain.BasicAuthRepository
import chat.rocket.android.server.domain.ChatRoomsRepository import chat.rocket.android.server.domain.ChatRoomsRepository
import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetAccountInteractor import chat.rocket.android.server.domain.GetAccountInteractor
import chat.rocket.android.server.domain.GetAccountsInteractor import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetBasicAuthInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.JobSchedulerInteractor import chat.rocket.android.server.domain.JobSchedulerInteractor
import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.domain.MultiServerTokenRepository import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.domain.PermissionsRepository import chat.rocket.android.server.domain.PermissionsRepository
import chat.rocket.android.server.domain.SaveBasicAuthInteractor
import chat.rocket.android.server.domain.SettingsRepository import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.SortingAndGroupingRepository
import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.UsersRepository import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.domain.BasicAuthRepository
import chat.rocket.android.server.domain.GetBasicAuthInteractor
import chat.rocket.android.server.domain.SaveBasicAuthInteractor
import chat.rocket.android.server.infraestructure.SharedPrefsBasicAuthRepository
import chat.rocket.android.server.infraestructure.DatabaseMessageMapper import chat.rocket.android.server.infraestructure.DatabaseMessageMapper
import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl
...@@ -53,11 +53,13 @@ import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepos ...@@ -53,11 +53,13 @@ import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepos
import chat.rocket.android.server.infraestructure.SharedPreferencesPermissionsRepository import chat.rocket.android.server.infraestructure.SharedPreferencesPermissionsRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository
import chat.rocket.android.server.infraestructure.SharedPrefsAnalyticsTrackingRepository 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.SharedPrefsConnectingServerRepository
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.server.infraestructure.SharedPrefsSortingAndGroupingRepository
import chat.rocket.android.util.AppJsonAdapterFactory import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.HttpLoggingInterceptor
import chat.rocket.android.util.BasicAuthenticatorInterceptor import chat.rocket.android.util.BasicAuthenticatorInterceptor
import chat.rocket.android.util.HttpLoggingInterceptor
import chat.rocket.android.util.TimberLogger import chat.rocket.android.util.TimberLogger
import chat.rocket.common.internal.FallbackSealedClassJsonAdapter import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date import chat.rocket.common.internal.ISO8601Date
...@@ -123,7 +125,10 @@ class AppModule { ...@@ -123,7 +125,10 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideOkHttpClient(logger: HttpLoggingInterceptor, basicAuthenticator: BasicAuthenticatorInterceptor): OkHttpClient { fun provideOkHttpClient(
logger: HttpLoggingInterceptor,
basicAuthenticator: BasicAuthenticatorInterceptor
): OkHttpClient {
return OkHttpClient.Builder() return OkHttpClient.Builder()
.addInterceptor(logger) .addInterceptor(logger)
.addInterceptor(basicAuthenticator) .addInterceptor(basicAuthenticator)
...@@ -194,6 +199,12 @@ class AppModule { ...@@ -194,6 +199,12 @@ class AppModule {
return SharedPrefsAnalyticsTrackingRepository(prefs) return SharedPrefsAnalyticsTrackingRepository(prefs)
} }
@Provides
@Singleton
fun provideSortingAndGroupingRepository(prefs: SharedPreferences): SortingAndGroupingRepository {
return SharedPrefsSortingAndGroupingRepository(prefs)
}
@Provides @Provides
@ForAuthentication @ForAuthentication
fun provideConnectingServerRepository(prefs: SharedPreferences): CurrentServerRepository { fun provideConnectingServerRepository(prefs: SharedPreferences): CurrentServerRepository {
...@@ -293,10 +304,10 @@ class AppModule { ...@@ -293,10 +304,10 @@ class AppModule {
@Provides @Provides
@Singleton @Singleton
fun provideBasicAuthRepository ( fun provideBasicAuthRepository(
preferences: SharedPreferences, preferences: SharedPreferences,
moshi: Moshi moshi: Moshi
): BasicAuthRepository = ): BasicAuthRepository =
SharedPrefsBasicAuthRepository(preferences, moshi) SharedPrefsBasicAuthRepository(preferences, moshi)
@Provides @Provides
......
...@@ -139,7 +139,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -139,7 +139,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
/* /*
* Creates a list of data base operations * Creates a list of database operations
*/ */
fun processChatRoomsBatch(batch: List<StreamMessage<BaseRoom>>) { fun processChatRoomsBatch(batch: List<StreamMessage<BaseRoom>>) {
GlobalScope.launch(dbManagerContext) { GlobalScope.launch(dbManagerContext) {
......
...@@ -5,7 +5,6 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -5,7 +5,6 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -23,7 +22,7 @@ class FavoriteMessagesPresenter @Inject constructor( ...@@ -23,7 +22,7 @@ class FavoriteMessagesPresenter @Inject constructor(
private val mapper: UiModelMapper, private val mapper: UiModelMapper,
val factory: RocketChatClientFactory val factory: RocketChatClientFactory
) { ) {
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.get(currentServer)
private var offset: Int = 0 private var offset: Int = 0
/** /**
......
...@@ -7,7 +7,6 @@ import chat.rocket.android.files.uimodel.FileUiModel ...@@ -7,7 +7,6 @@ import chat.rocket.android.files.uimodel.FileUiModel
import chat.rocket.android.files.uimodel.FileUiModelMapper import chat.rocket.android.files.uimodel.FileUiModelMapper
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -25,7 +24,7 @@ class FilesPresenter @Inject constructor( ...@@ -25,7 +24,7 @@ class FilesPresenter @Inject constructor(
private val mapper: FileUiModelMapper, private val mapper: FileUiModelMapper,
val factory: RocketChatClientFactory val factory: RocketChatClientFactory
) { ) {
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.get(currentServer)
private var offset: Int = 0 private var offset: Int = 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
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
...@@ -24,6 +24,24 @@ class UserHelper @Inject constructor( ...@@ -24,6 +24,24 @@ class UserHelper @Inject constructor(
*/ */
fun username(): String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY, null) fun username(): String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY, null)
/**
* Return the name for the current logged [User].
*/
fun name(): String? = user()?.name
/**
* Return the display name for the given [user].
* If setting 'Use_Real_Name' is true then the real name will be given, otherwise the username
* without the '@' is yielded.
*/
fun displayName(user: User) = getCurrentServerInteractor.get()?.let {
if (settingsRepository.get(it).useRealName()) {
user.name
} else {
user.username
}
}
/** /**
* Return the display name for the given [user]. * Return the display name for the given [user].
* If setting 'Use_Real_Name' is true then the real name will be given, otherwise the username * If setting 'Use_Real_Name' is true then the real name will be given, otherwise the username
......
package chat.rocket.android.main.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import chat.rocket.common.model.UserStatus
import kotlinx.android.synthetic.main.item_change_status.view.*
class StatusViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(listener: (UserStatus) -> Unit) {
with(itemView) {
text_online.setOnClickListener { listener(UserStatus.Online()) }
text_away.setOnClickListener { listener(UserStatus.Away()) }
text_busy.setOnClickListener { listener(UserStatus.Busy()) }
text_invisible.setOnClickListener { listener(UserStatus.Offline()) }
}
}
}
\ No newline at end of file
...@@ -4,7 +4,6 @@ import androidx.lifecycle.LifecycleOwner ...@@ -4,7 +4,6 @@ import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.main.presentation.MainNavigator import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
...@@ -13,16 +12,13 @@ import kotlinx.coroutines.Job ...@@ -13,16 +12,13 @@ import kotlinx.coroutines.Job
@Module @Module
class MainModule { class MainModule {
@Provides
@PerActivity
fun provideJob() = Job()
@Provides @Provides
@PerActivity @PerActivity
fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity) fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity)
@Provides @Provides
fun provideMainView(activity: MainActivity): MainView = activity @PerActivity
fun provideJob() = Job()
@Provides @Provides
fun provideLifecycleOwner(activity: MainActivity): LifecycleOwner = activity fun provideLifecycleOwner(activity: MainActivity): LifecycleOwner = activity
......
...@@ -3,51 +3,53 @@ package chat.rocket.android.main.presentation ...@@ -3,51 +3,53 @@ package chat.rocket.android.main.presentation
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.authentication.ui.newServerIntent import chat.rocket.android.authentication.ui.newServerIntent
import chat.rocket.android.chatroom.ui.chatRoomIntent import chat.rocket.android.chatroom.ui.chatRoomIntent
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.chatrooms.ui.TAG_CHAT_ROOMS_FRAGMENT import chat.rocket.android.chatrooms.ui.TAG_CHAT_ROOMS_FRAGMENT
import chat.rocket.android.createchannel.ui.CreateChannelFragment
import chat.rocket.android.createchannel.ui.TAG_CREATE_CHANNEL_FRAGMENT import chat.rocket.android.createchannel.ui.TAG_CREATE_CHANNEL_FRAGMENT
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.profile.ui.ProfileFragment
import chat.rocket.android.profile.ui.TAG_PROFILE_FRAGMENT import chat.rocket.android.profile.ui.TAG_PROFILE_FRAGMENT
import chat.rocket.android.server.ui.changeServerIntent import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.settings.ui.SettingsFragment
import chat.rocket.android.settings.ui.TAG_SETTINGS_FRAGMENT import chat.rocket.android.settings.ui.TAG_SETTINGS_FRAGMENT
import chat.rocket.android.util.extensions.addFragment import chat.rocket.android.util.extensions.addFragment
import chat.rocket.android.webview.adminpanel.ui.AdminPanelWebViewFragment import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.webview.adminpanel.ui.TAG_ADMIN_PANEL_WEB_VIEW_FRAGMENT
import chat.rocket.android.webview.ui.webViewIntent
class MainNavigator(internal val activity: MainActivity) { class MainNavigator(internal val activity: MainActivity) {
fun toChatList(chatRoomId: String? = null) { fun toChatList(chatRoomId: String? = null) {
activity.addFragment(TAG_CHAT_ROOMS_FRAGMENT, R.id.fragment_container) { activity.addFragment(TAG_CHAT_ROOMS_FRAGMENT, R.id.fragment_container) {
ChatRoomsFragment.newInstance(chatRoomId) chat.rocket.android.chatrooms.ui.newInstance(chatRoomId)
} }
} }
fun toCreateChannel() { fun toSettings() {
activity.addFragment(TAG_CREATE_CHANNEL_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(TAG_SETTINGS_FRAGMENT, R.id.fragment_container) {
CreateChannelFragment.newInstance() chat.rocket.android.settings.ui.newInstance()
} }
} }
fun toUserProfile() { fun toCreateChannel() {
activity.addFragment(TAG_PROFILE_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(TAG_CREATE_CHANNEL_FRAGMENT, R.id.fragment_container) {
ProfileFragment.newInstance() chat.rocket.android.createchannel.ui.newInstance()
} }
} }
fun toSettings() { fun toProfile() {
activity.addFragment(TAG_SETTINGS_FRAGMENT, R.id.fragment_container) { activity.addFragmentBackStack(TAG_PROFILE_FRAGMENT, R.id.fragment_container) {
SettingsFragment.newInstance() chat.rocket.android.profile.ui.newInstance()
} }
} }
fun toAdminPanel(webPageUrl: String, userToken: String) { fun toAdminPanel(webPageUrl: String, userToken: String) {
activity.addFragment("AdminPanelWebViewFragment", R.id.fragment_container) { activity.addFragmentBackStack(TAG_ADMIN_PANEL_WEB_VIEW_FRAGMENT, R.id.fragment_container) {
AdminPanelWebViewFragment.newInstance(webPageUrl, userToken) chat.rocket.android.webview.adminpanel.ui.newInstance(webPageUrl, userToken)
} }
} }
fun toLicense(licenseUrl: String, licenseTitle: String) {
activity.startActivity(activity.webViewIntent(licenseUrl, licenseTitle))
}
fun toChatRoom( fun toChatRoom(
chatRoomId: String, chatRoomId: String,
chatRoomName: String, chatRoomName: String,
......
package chat.rocket.android.main.presentation
import chat.rocket.android.authentication.server.presentation.VersionCheckView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.android.main.uimodel.NavHeaderUiModel
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.presentation.TokenView
import chat.rocket.common.model.UserStatus
interface MainView : MessageView, VersionCheckView, TokenView {
/**
* Shows the current user status.
*
* @see [UserStatus]
*/
fun showUserStatus(userStatus: UserStatus)
/**
* Setups the user account info (displayed in the nav. header)
*
* @param uiModel The [NavHeaderUiModel].
*/
fun setupUserAccountInfo(uiModel: NavHeaderUiModel)
/**
* Setups the server account list.
*
* @param serverAccountList The list of server accounts.
*/
fun setupServerAccountList(serverAccountList: List<Account>)
fun closeServerSelection()
fun showProgress()
fun hideProgress()
}
\ No newline at end of file
package chat.rocket.android.main.ui
import android.view.Menu
import android.view.MenuItem
import chat.rocket.android.R
internal fun MainActivity.setupMenu(menu: Menu) {
with(menu) {
add(
R.id.menu_section_one,
R.id.menu_action_chats,
Menu.NONE,
R.string.title_chats
).setIcon(R.drawable.ic_chat_bubble_black_24dp)
.isChecked = true
add(
R.id.menu_section_one,
R.id.menu_action_create_channel,
Menu.NONE,
R.string.action_create_channel
).setIcon(R.drawable.ic_create_black_24dp)
add(
R.id.menu_section_two,
R.id.menu_action_profile,
Menu.NONE,
R.string.title_profile
).setIcon(R.drawable.ic_person_black_20dp)
add(
R.id.menu_section_two,
R.id.menu_action_settings,
Menu.NONE,
R.string.title_settings
).setIcon(R.drawable.ic_settings_black_24dp)
if (permissions.canSeeTheAdminPanel()) {
add(
R.id.menu_section_two,
R.id.menu_action_admin_panel,
Menu.NONE,
R.string.title_admin_panel
).setIcon(R.drawable.ic_settings_black_24dp)
}
add(
R.id.menu_section_three,
R.id.menu_action_logout,
Menu.NONE,
R.string.action_logout
).setIcon(R.drawable.ic_logout_black_24dp)
setGroupCheckable(R.id.menu_section_one, true, true)
setGroupCheckable(R.id.menu_section_two, true, true)
setGroupCheckable(R.id.menu_section_three, true, true)
}
}
internal fun MainActivity.onNavDrawerItemSelected(menuItem: MenuItem) {
when (menuItem.itemId) {
R.id.menu_action_chats-> presenter.toChatList()
R.id.menu_action_create_channel -> presenter.toCreateChannel()
R.id.menu_action_profile -> presenter.toUserProfile()
R.id.menu_action_settings -> presenter.toSettings()
R.id.menu_action_admin_panel -> presenter.toAdminPanel()
R.id.menu_action_logout -> showLogoutDialog()
}
}
package chat.rocket.android.main.uimodel
import chat.rocket.common.model.UserStatus
data class NavHeaderUiModel(
val userDisplayName: String?,
val userStatus: UserStatus?,
val userAvatar: String?,
val serverUrl: String,
val serverLogo: String?
)
\ No newline at end of file
package chat.rocket.android.main.uimodel
import chat.rocket.android.server.domain.*
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.core.model.Myself
import javax.inject.Inject
class NavHeaderUiModelMapper @Inject constructor(
serverInteractor: GetCurrentServerInteractor,
getSettingsInteractor: GetSettingsInteractor
) {
private val currentServer = serverInteractor.get()!!
private var settings: PublicSettings = getSettingsInteractor.get(currentServer)
fun mapToUiModel(me: Myself): NavHeaderUiModel {
val displayName = mapDisplayName(me)
val status = me.status
val avatar = me.username?.let { currentServer.avatarUrl(it) }
val image = settings.wideTile() ?: settings.faviconLarge()
val logo = image?.let { currentServer.serverLogoUrl(it) }
return NavHeaderUiModel(displayName, status, avatar, currentServer, logo)
}
private fun mapDisplayName(me: Myself): String? {
val username = me.username
val realName = me.name
val senderName = if (settings.useRealName()) realName else username
return senderName ?: username
}
}
\ No newline at end of file
...@@ -27,7 +27,7 @@ class MembersPresenter @Inject constructor( ...@@ -27,7 +27,7 @@ class MembersPresenter @Inject constructor(
val factory: RocketChatClientFactory, val factory: RocketChatClientFactory,
private val userHelper: UserHelper private val userHelper: UserHelper
) { ) {
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.get(currentServer)
private var offset: Long = 0 private var offset: Long = 0
/** /**
......
...@@ -18,7 +18,7 @@ class MentionsPresenter @Inject constructor( ...@@ -18,7 +18,7 @@ class MentionsPresenter @Inject constructor(
private val mapper: UiModelMapper, private val mapper: UiModelMapper,
val factory: RocketChatClientFactory val factory: RocketChatClientFactory
) { ) {
private val client = factory.create(currentServer) private val client = factory.get(currentServer)
private var offset: Long = 0 private var offset: Long = 0
/** /**
......
...@@ -5,7 +5,6 @@ import chat.rocket.android.core.lifecycle.CancelStrategy ...@@ -5,7 +5,6 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
...@@ -23,7 +22,7 @@ class PinnedMessagesPresenter @Inject constructor( ...@@ -23,7 +22,7 @@ class PinnedMessagesPresenter @Inject constructor(
private val mapper: UiModelMapper, private val mapper: UiModelMapper,
val factory: RocketChatClientFactory val factory: RocketChatClientFactory
) { ) {
private val client: RocketChatClient = factory.create(currentServer) private val client: RocketChatClient = factory.get(currentServer)
private var offset: Int = 0 private var offset: Int = 0
/** /**
......
package chat.rocket.android.preferences.presentation
import chat.rocket.android.server.domain.AnalyticsTrackingInteractor
import javax.inject.Inject
class PreferencesPresenter @Inject constructor(
private val view: PreferencesView,
private val analyticsTrackingInteractor: AnalyticsTrackingInteractor
) {
fun loadAnalyticsTrackingInformation() {
view.setupAnalyticsTrackingView(analyticsTrackingInteractor.get())
}
fun enableAnalyticsTracking() {
analyticsTrackingInteractor.save(true)
}
fun disableAnalyticsTracking() {
analyticsTrackingInteractor.save(false)
}
}
\ No newline at end of file
package chat.rocket.android.preferences.presentation
interface PreferencesView {
/**
* Setups the analytics tracking view.
*
* @param isAnalyticsTrackingEnabled Whether the analytics tracking is enabled
*/
fun setupAnalyticsTrackingView(isAnalyticsTrackingEnabled: Boolean)
}
\ No newline at end of file
package chat.rocket.android.preferences.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
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.main.ui.MainActivity
import chat.rocket.android.preferences.presentation.PreferencesPresenter
import chat.rocket.android.preferences.presentation.PreferencesView
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_preferences.*
import javax.inject.Inject
internal const val TAG_PREFERENCES_FRAGMENT = "PreferencesFragment"
class PreferencesFragment : Fragment(), PreferencesView {
@Inject
lateinit var presenter: PreferencesPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_preferences, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupListeners()
presenter.loadAnalyticsTrackingInformation()
analyticsManager.logScreenView(ScreenViewEvent.Preferences)
}
override fun onResume() {
setupToolbar()
super.onResume()
}
override fun setupAnalyticsTrackingView(isAnalyticsTrackingEnabled: Boolean) {
if (BuildConfig.FLAVOR == "foss") {
switch_analytics_tracking.isChecked = false
switch_analytics_tracking.isEnabled = false
text_analytics_tracking_description.text =
getString(R.string.msg_not_applicable_since_it_is_a_foss_version)
return
}
if (isAnalyticsTrackingEnabled) {
text_analytics_tracking_description.text =
getString(R.string.msg_send_analytics_tracking)
} else {
text_analytics_tracking_description.text =
getString(R.string.msg_do_not_send_analytics_tracking)
}
switch_analytics_tracking.isChecked = isAnalyticsTrackingEnabled
}
private fun setupToolbar() {
with((activity as MainActivity).toolbar) {
title = getString(R.string.title_preferences)
setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
setNavigationOnClickListener { activity?.onBackPressed() }
}
}
private fun setupListeners() {
switch_analytics_tracking.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
text_analytics_tracking_description.text =
getString(R.string.msg_send_analytics_tracking)
presenter.enableAnalyticsTracking()
} else {
text_analytics_tracking_description.text =
getString(R.string.msg_do_not_send_analytics_tracking)
presenter.disableAnalyticsTracking()
}
}
}
companion object {
fun newInstance() = PreferencesFragment()
}
}
...@@ -15,20 +15,19 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory ...@@ -15,20 +15,19 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.compressImageAndGetByteArray import chat.rocket.android.util.extension.compressImageAndGetByteArray
import chat.rocket.android.util.extension.gethash
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extension.toHex
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.model.UserStatus
import chat.rocket.common.model.userStatusOf
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.deleteOwnAccount import chat.rocket.core.internal.realtime.setDefaultStatus
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.resetAvatar import chat.rocket.core.internal.rest.resetAvatar
import chat.rocket.core.internal.rest.setAvatar import chat.rocket.core.internal.rest.setAvatar
import chat.rocket.core.internal.rest.updateProfile import chat.rocket.core.internal.rest.updateProfile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
...@@ -56,18 +55,23 @@ class ProfilePresenter @Inject constructor( ...@@ -56,18 +55,23 @@ class ProfilePresenter @Inject constructor(
navigator = navigator navigator = navigator
) { ) {
private val serverUrl = serverInteractor.get()!! private val serverUrl = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(serverUrl) private val client: RocketChatClient = factory.get(serverUrl)
private val user = userHelper.user() private val user = userHelper.user()
fun loadUserProfile() { fun loadUserProfile() {
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
val me = retryIO(description = "serverInfo", times = 5) {
client.me()
}
view.showProfile( view.showProfile(
serverUrl.avatarUrl(user?.username ?: ""), me.status.toString(),
user?.name ?: "", serverUrl.avatarUrl(me.username ?: ""),
user?.username ?: "", me.name ?: "",
user?.emails?.getOrNull(0)?.address ?: "" me.username ?: "",
me.emails?.getOrNull(0)?.address ?: ""
) )
} catch (exception: RocketChatException) { } catch (exception: RocketChatException) {
view.showMessage(exception) view.showMessage(exception)
...@@ -82,9 +86,17 @@ class ProfilePresenter @Inject constructor( ...@@ -82,9 +86,17 @@ class ProfilePresenter @Inject constructor(
view.showLoading() view.showLoading()
try { try {
user?.id?.let { id -> user?.id?.let { id ->
retryIO { client.updateProfile(userId = id, email = email, name = name, username = username) } retryIO {
client.updateProfile(
userId = id,
email = email,
name = name,
username = username
)
}
view.showProfileUpdateSuccessfullyMessage() view.showProfileUpdateSuccessfullyMessage()
view.showProfile( view.showProfile(
user.status.toString(),
serverUrl.avatarUrl(user.username ?: ""), serverUrl.avatarUrl(user.username ?: ""),
name, name,
username, username,
...@@ -176,26 +188,17 @@ class ProfilePresenter @Inject constructor( ...@@ -176,26 +188,17 @@ class ProfilePresenter @Inject constructor(
} }
} }
fun deleteAccount(password: String) { fun updateStatus(status: UserStatus) {
launchUI(strategy) { launchUI(strategy) {
view.showLoading()
try { try {
withContext(Dispatchers.Default) { client.setDefaultStatus(status)
// REMARK: Backend API is only working with a lowercase hash. } catch (exception: RocketChatException) {
// https://github.com/RocketChat/Rocket.Chat/issues/12573
retryIO { client.deleteOwnAccount(password.gethash().toHex().toLowerCase()) }
setupConnectionInfo(serverUrl)
logout(null)
}
} catch (exception: Exception) {
exception.message?.let { exception.message?.let {
view.showMessage(it) view.showMessage(it)
}.ifNull { }.ifNull {
view.showGenericErrorMessage() view.showGenericErrorMessage()
} }
} finally {
view.hideLoading()
} }
} }
} }
} }
\ No newline at end of file
...@@ -9,12 +9,19 @@ interface ProfileView : TokenView, LoadingView, MessageView { ...@@ -9,12 +9,19 @@ interface ProfileView : TokenView, LoadingView, MessageView {
/** /**
* Shows the user profile. * Shows the user profile.
* *
* @param status The user status.
* @param avatarUrl The user avatar URL. * @param avatarUrl The user avatar URL.
* @param name The user display name. * @param name The user display name.
* @param username The user username. * @param username The user username.
* @param email The user email. * @param email The user email.
*/ */
fun showProfile(avatarUrl: String, name: String, username: String, email: String?) fun showProfile(
status: String,
avatarUrl: String,
name: String,
username: String,
email: String?
)
/** /**
* Reloads the user avatar (after successfully updating it). * Reloads the user avatar (after successfully updating it).
......
...@@ -2,7 +2,6 @@ package chat.rocket.android.profile.ui ...@@ -2,7 +2,6 @@ package chat.rocket.android.profile.ui
import DrawableHelper import DrawableHelper
import android.app.Activity import android.app.Activity
import androidx.appcompat.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Build import android.os.Build
...@@ -12,8 +11,8 @@ import android.view.Menu ...@@ -12,8 +11,8 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.MenuInflater import android.widget.RadioGroup
import android.widget.EditText import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.net.toUri import androidx.core.net.toUri
...@@ -33,10 +32,13 @@ import chat.rocket.android.util.extensions.showToast ...@@ -33,10 +32,13 @@ import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.invalidateFirebaseToken import chat.rocket.android.util.invalidateFirebaseToken
import chat.rocket.common.model.UserStatus
import chat.rocket.common.model.userStatusOf
import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.backends.pipeline.Fresco
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.avatar_profile.* import kotlinx.android.synthetic.main.avatar_profile.*
import kotlinx.android.synthetic.main.fragment_profile.* import kotlinx.android.synthetic.main.fragment_profile.*
import kotlinx.android.synthetic.main.update_avatar_options.* import kotlinx.android.synthetic.main.update_avatar_options.*
...@@ -47,24 +49,21 @@ internal const val TAG_PROFILE_FRAGMENT = "ProfileFragment" ...@@ -47,24 +49,21 @@ internal const val TAG_PROFILE_FRAGMENT = "ProfileFragment"
private const val REQUEST_CODE_FOR_PERFORM_SAF = 1 private const val REQUEST_CODE_FOR_PERFORM_SAF = 1
private const val REQUEST_CODE_FOR_PERFORM_CAMERA = 2 private const val REQUEST_CODE_FOR_PERFORM_CAMERA = 2
fun newInstance() = ProfileFragment()
class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
@Inject @Inject lateinit var presenter: ProfilePresenter
lateinit var presenter: ProfilePresenter @Inject lateinit var analyticsManager: AnalyticsManager
@Inject private var currentStatus = ""
lateinit var analyticsManager: AnalyticsManager
private var currentName = "" private var currentName = ""
private var currentUsername = "" private var currentUsername = ""
private var currentEmail = "" private var currentEmail = ""
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private val editTextsDisposable = CompositeDisposable() private val editTextsDisposable = CompositeDisposable()
companion object {
fun newInstance() = ProfileFragment()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true) setHasOptionsMenu(true)
} }
...@@ -78,11 +77,12 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -78,11 +77,12 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar() setupToolbar()
setupListeners()
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart() tintEditTextDrawableStart()
} }
presenter.loadUserProfile() presenter.loadUserProfile()
setupListeners()
subscribeEditTexts() subscribeEditTexts()
analyticsManager.logScreenView(ScreenViewEvent.Profile) analyticsManager.logScreenView(ScreenViewEvent.Profile)
...@@ -112,25 +112,21 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -112,25 +112,21 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
super.onPrepareOptionsMenu(menu) super.onPrepareOptionsMenu(menu)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun showProfile(
super.onCreateOptionsMenu(menu, inflater) status: String,
inflater.inflate(R.menu.profile, menu) avatarUrl: String,
} name: String,
username: String,
override fun onOptionsItemSelected(item: MenuItem): Boolean { email: String?
when (item.itemId) { ) {
R.id.action_delete_account -> showDeleteAccountDialog()
}
return true
}
override fun showProfile(avatarUrl: String, name: String, username: String, email: String?) {
ui { ui {
text_status.text = getString(R.string.status, status.capitalize())
image_avatar.setImageURI(avatarUrl) image_avatar.setImageURI(avatarUrl)
text_name.textContent = name text_name.textContent = name
text_username.textContent = username text_username.textContent = username
text_email.textContent = email ?: "" text_email.textContent = email ?: ""
currentStatus = status
currentName = name currentName = name
currentUsername = username currentUsername = username
currentEmail = email ?: "" currentEmail = email ?: ""
...@@ -142,7 +138,6 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -142,7 +138,6 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
override fun reloadUserAvatar(avatarUrl: String) { override fun reloadUserAvatar(avatarUrl: String) {
Fresco.getImagePipeline().evictFromCache(avatarUrl.toUri()) Fresco.getImagePipeline().evictFromCache(avatarUrl.toUri())
image_avatar.setImageURI(avatarUrl) image_avatar.setImageURI(avatarUrl)
(activity as MainActivity).setAvatar(avatarUrl)
} }
override fun showProfileUpdateSuccessfullyMessage() { override fun showProfileUpdateSuccessfullyMessage() {
...@@ -205,10 +200,19 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -205,10 +200,19 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} }
private fun setupToolbar() { private fun setupToolbar() {
(activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_profile) with((activity as AppCompatActivity)) {
with(toolbar) {
setSupportActionBar(this)
title = getString(R.string.title_profile)
setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
setNavigationOnClickListener { activity?.onBackPressed() }
}
}
} }
private fun setupListeners() { private fun setupListeners() {
text_status.setOnClickListener { showStatusDialog(currentStatus) }
image_avatar.setOnClickListener { showUpdateAvatarOptions() } image_avatar.setOnClickListener { showUpdateAvatarOptions() }
view_dim.setOnClickListener { hideUpdateAvatarOptions() } view_dim.setOnClickListener { hideUpdateAvatarOptions() }
...@@ -293,15 +297,38 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback { ...@@ -293,15 +297,38 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
} }
} }
fun showDeleteAccountDialog() { private fun showStatusDialog(currentStatus: String) {
context?.let { val dialogLayout = layoutInflater.inflate(R.layout.dialog_status, null)
val passwordEText = EditText(context); val radioGroup = dialogLayout.findViewById<RadioGroup>(R.id.radio_group_status)
val mDialogView = LayoutInflater.from(it).inflate(R.layout.item_account_delete, null)
val mBuilder = AlertDialog.Builder(it) radioGroup.check(
when (userStatusOf(currentStatus)) {
is UserStatus.Online -> R.id.radio_button_online
is UserStatus.Away -> R.id.radio_button_away
is UserStatus.Busy -> R.id.radio_button_busy
else -> R.id.radio_button_invisible
}
)
var newStatus: UserStatus = userStatusOf(currentStatus)
radioGroup.setOnCheckedChangeListener { _, checkId ->
when (checkId) {
R.id.radio_button_online -> newStatus = UserStatus.Online()
R.id.radio_button_away -> newStatus = UserStatus.Away()
R.id.radio_button_busy -> newStatus = UserStatus.Busy()
else -> newStatus = UserStatus.Offline()
}
}
mBuilder.setView(mDialogView).setPositiveButton(R.string.action_delete_account) { _, _ -> context?.let {
presenter.deleteAccount(passwordEText.text.toString()) AlertDialog.Builder(it)
}.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }.create().show() .setView(dialogLayout)
.setPositiveButton(R.string.msg_change_status) { dialog, _ ->
presenter.updateStatus(newStatus)
text_status.text = getString(R.string.status, newStatus.toString().capitalize())
this.currentStatus = newStatus.toString()
dialog.dismiss()
}.show()
} }
} }
} }
...@@ -69,7 +69,7 @@ class PermissionsInteractor @Inject constructor( ...@@ -69,7 +69,7 @@ class PermissionsInteractor @Inject constructor(
} }
fun canSeeTheAdminPanel(): Boolean { fun isAdministrationEnabled(): Boolean {
currentServerUrl()?.let { serverUrl -> currentServerUrl()?.let { serverUrl ->
val viewStatistics = val viewStatistics =
permissionsRepository.get(serverUrl, VIEW_STATISTICS) permissionsRepository.get(serverUrl, VIEW_STATISTICS)
......
...@@ -20,7 +20,7 @@ class RefreshPermissionsInteractor @Inject constructor( ...@@ -20,7 +20,7 @@ class RefreshPermissionsInteractor @Inject constructor(
fun refreshAsync(server: String) { fun refreshAsync(server: String) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
try { try {
factory.create(server).let { client -> factory.get(server).let { client ->
val permissions = retryIO( val permissions = retryIO(
description = "permissions", description = "permissions",
times = 5, times = 5,
......
...@@ -74,7 +74,7 @@ class RefreshSettingsInteractor @Inject constructor( ...@@ -74,7 +74,7 @@ class RefreshSettingsInteractor @Inject constructor(
suspend fun refresh(server: String) { suspend fun refresh(server: String) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
factory.create(server).let { client -> factory.get(server).let { client ->
val settings = retryIO( val settings = retryIO(
description = "settings", description = "settings",
times = 5, times = 5,
......
package chat.rocket.android.server.domain
import javax.inject.Inject
class SortingAndGroupingInteractor @Inject constructor(val repository: SortingAndGroupingRepository) {
fun save(
currentServerUrl: String,
isSortByName: Boolean,
isUnreadOnTop: Boolean,
isGroupByType: Boolean,
isGroupByFavorites: Boolean
) = repository.save(
currentServerUrl,
isSortByName,
isUnreadOnTop,
isGroupByType,
isGroupByFavorites
)
fun getSortByName(currentServerUrl: String): Boolean =
repository.getSortByName(currentServerUrl)
fun getUnreadOnTop(currentServerUrl: String): Boolean =
repository.getUnreadOnTop(currentServerUrl)
fun getGroupByType(currentServerUrl: String): Boolean =
repository.getGroupByType(currentServerUrl)
fun getGroupByFavorites(currentServerUrl: String): Boolean =
repository.getGroupByFavorites(currentServerUrl)
}
\ No newline at end of file
package chat.rocket.android.server.domain
interface SortingAndGroupingRepository {
fun save(
currentServerUrl: String,
isSortByName: Boolean,
isUnreadOnTop: Boolean,
isGroupByType: Boolean,
isGroupByFavorites: Boolean
)
fun getSortByName(currentServerUrl: String): Boolean
fun getUnreadOnTop(currentServerUrl: String): Boolean
fun getGroupByType(currentServerUrl: String): Boolean
fun getGroupByFavorites(currentServerUrl: String): Boolean
}
\ No newline at end of file
package chat.rocket.android.server.infraestructure package chat.rocket.android.server.infraestructure
import chat.rocket.android.db.DatabaseManagerFactory import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.infrastructure.LocalRepository
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
...@@ -20,7 +19,7 @@ class ConnectionManagerFactory @Inject constructor( ...@@ -20,7 +19,7 @@ class ConnectionManagerFactory @Inject constructor(
} }
Timber.d("Returning FRESH Manager for: $url") Timber.d("Returning FRESH Manager for: $url")
val manager = ConnectionManager(factory.create(url), dbFactory.create(url)) val manager = ConnectionManager(factory.get(url), dbFactory.create(url))
cache[url] = manager cache[url] = manager
return manager return manager
} }
......
...@@ -18,7 +18,7 @@ class RocketChatClientFactory @Inject constructor( ...@@ -18,7 +18,7 @@ class RocketChatClientFactory @Inject constructor(
) { ) {
private val cache = HashMap<String, RocketChatClient>() private val cache = HashMap<String, RocketChatClient>()
fun create(url: String): RocketChatClient { fun get(url: String): RocketChatClient {
cache[url]?.let { cache[url]?.let {
Timber.d("Returning CACHED client for: $url") Timber.d("Returning CACHED client for: $url")
return it return it
......
package chat.rocket.android.server.infraestructure
import android.content.SharedPreferences
import chat.rocket.android.server.domain.SortingAndGroupingRepository
private const val SORT_BY_NAME_KEY = "SORT_BY_NAME_KEY"
private const val UNREAD_ON_TOP_KEY = "UNREAD_ON_TOP_KEY"
private const val GROUP_BY_TYPE_KEY = "GROUP_BY_TYPE_KEY"
private const val GROUP_BY_FAVORITES_KEY = "GROUP_BY_FAVORITES_KEY"
class SharedPrefsSortingAndGroupingRepository(private val preferences: SharedPreferences) :
SortingAndGroupingRepository {
override fun save(
currentServerUrl: String,
isSortByName: Boolean,
isUnreadOnTop: Boolean,
isGroupByType: Boolean,
isGroupByFavorites: Boolean
) {
preferences.edit().putBoolean(SORT_BY_NAME_KEY + currentServerUrl, isSortByName).apply()
preferences.edit().putBoolean(UNREAD_ON_TOP_KEY + currentServerUrl, isUnreadOnTop).apply()
preferences.edit().putBoolean(GROUP_BY_TYPE_KEY + currentServerUrl, isGroupByType).apply()
preferences.edit().putBoolean(GROUP_BY_FAVORITES_KEY + currentServerUrl, isGroupByFavorites)
.apply()
}
override fun getSortByName(currentServerUrl: String): Boolean =
preferences.getBoolean(SORT_BY_NAME_KEY + currentServerUrl, false)
override fun getUnreadOnTop(currentServerUrl: String): Boolean =
preferences.getBoolean(UNREAD_ON_TOP_KEY + currentServerUrl, false)
override fun getGroupByType(currentServerUrl: String): Boolean =
preferences.getBoolean(GROUP_BY_TYPE_KEY + currentServerUrl, false)
override fun getGroupByFavorites(currentServerUrl: String): Boolean =
preferences.getBoolean(GROUP_BY_FAVORITES_KEY + currentServerUrl, false)
}
\ No newline at end of file
...@@ -105,7 +105,7 @@ abstract class CheckServerPresenter constructor( ...@@ -105,7 +105,7 @@ abstract class CheckServerPresenter constructor(
internal fun setupConnectionInfo(serverUrl: String) { internal fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl currentServer = serverUrl
client = factory.create(serverUrl) client = factory.get(serverUrl)
managerFactory?.create(serverUrl)?.let { managerFactory?.create(serverUrl)?.let {
manager = it manager = it
} }
......
package chat.rocket.android.main.adapter package chat.rocket.android.servers.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
class AddAccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) class AddNewServerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
\ No newline at end of file \ No newline at end of file
package chat.rocket.android.main.adapter package chat.rocket.android.servers.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import androidx.core.view.isInvisible
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import kotlinx.android.synthetic.main.item_account.view.* import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_server.view.*
class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class ServerViewHolder(itemView: View, private val currentServerUrl: String) :
RecyclerView.ViewHolder(itemView) {
fun bind(account: Account) { fun bind(account: Account) {
with(itemView) { with(itemView) {
server_logo.setImageURI(account.serverLogo) Glide.with(context).load(account.serverLogo).into(image_server)
text_server_name.text = account.serverUrl
text_server_url.text = account.serverUrl text_server_url.text = account.serverUrl
text_username.text = account.userName image_check.isInvisible = currentServerUrl != account.serverUrl
} }
} }
} }
\ No newline at end of file
package chat.rocket.android.main.adapter package chat.rocket.android.servers.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.common.model.UserStatus
private const val VIEW_TYPE_CHANGE_STATUS = 0 private const val VIEW_TYPE_SERVER = 0
private const val VIEW_TYPE_ACCOUNT = 1 private const val VIEW_TYPE_ADD_NEW_SERVER = 1
private const val VIEW_TYPE_ADD_ACCOUNT = 2
class AccountsAdapter( class ServersAdapter(
private val accounts: List<Account>, private val servers: List<Account>,
private val currentServerUrl: String,
private val selector: Selector private val selector: Selector
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) { return when (viewType) {
VIEW_TYPE_CHANGE_STATUS -> StatusViewHolder(parent.inflate(R.layout.item_change_status)) VIEW_TYPE_SERVER -> ServerViewHolder(
VIEW_TYPE_ACCOUNT -> AccountViewHolder(parent.inflate(R.layout.item_account)) parent.inflate(R.layout.item_server), currentServerUrl
else -> AddAccountViewHolder(parent.inflate(R.layout.item_add_account)) )
else -> AddNewServerViewHolder(parent.inflate(R.layout.item_add_new_server))
} }
} }
override fun getItemCount() = accounts.size + 2 override fun getItemCount() = servers.size + 1
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when { return when {
position == 0 -> VIEW_TYPE_CHANGE_STATUS position < servers.size -> VIEW_TYPE_SERVER
position <= accounts.size -> VIEW_TYPE_ACCOUNT else -> VIEW_TYPE_ADD_NEW_SERVER
else -> VIEW_TYPE_ADD_ACCOUNT
} }
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) { when (holder) {
is StatusViewHolder -> bindStatusViewHolder(holder) is ServerViewHolder -> bindServerViewHolder(holder, position)
is AccountViewHolder -> bindAccountViewHolder(holder, position) is AddNewServerViewHolder -> bindAddNewServerViewHolder(holder)
is AddAccountViewHolder -> bindAddAccountViewHolder(holder)
} }
} }
private fun bindStatusViewHolder(holder: StatusViewHolder) {
holder.bind { userStatus -> selector.onStatusSelected(userStatus) }
}
private fun bindAccountViewHolder(holder: AccountViewHolder, position: Int) { private fun bindServerViewHolder(holder: ServerViewHolder, position: Int) {
val account = accounts[position - 1] val account = servers[position]
holder.bind(account) holder.bind(account)
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener { selector.onServerSelected(account.serverUrl) }
selector.onAccountSelected(account.serverUrl)
}
} }
private fun bindAddAccountViewHolder(holder: AddAccountViewHolder) { private fun bindAddNewServerViewHolder(holder: AddNewServerViewHolder) {
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener { selector.onAddNewServerSelected() }
selector.onAddedAccountSelected()
}
} }
} }
interface Selector { interface Selector {
fun onStatusSelected(userStatus: UserStatus) fun onServerSelected(serverUrl: String)
fun onAccountSelected(serverUrl: String) fun onAddNewServerSelected()
fun onAddedAccountSelected()
} }
\ No newline at end of file
package chat.rocket.android.preferences.di package chat.rocket.android.servers.di
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.preferences.presentation.PreferencesView import chat.rocket.android.servers.presentation.ServersView
import chat.rocket.android.preferences.ui.PreferencesFragment import chat.rocket.android.servers.ui.ServersBottomSheetFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@Module @Module
class PreferencesFragmentModule { class ServersBottomSheetFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun preferencesView(frag: PreferencesFragment): PreferencesView { fun serversView(frag: ServersBottomSheetFragment): ServersView = frag
return frag
}
} }
\ No newline at end of file
package chat.rocket.android.preferences.di package chat.rocket.android.servers.di
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.preferences.ui.PreferencesFragment import chat.rocket.android.servers.ui.ServersBottomSheetFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
@Module @Module
abstract class PreferencesFragmentProvider { abstract class ServersBottomSheetFragmentProvider {
@ContributesAndroidInjector(modules = [PreferencesFragmentModule::class]) @ContributesAndroidInjector(modules = [ServersBottomSheetFragmentModule::class])
@PerFragment @PerFragment
abstract fun providePreferencesFragment(): PreferencesFragment abstract fun provideServersBottomSheetFragment(): ServersBottomSheetFragment
} }
\ No newline at end of file
package chat.rocket.android.servers.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.util.extension.launchUI
import chat.rocket.common.util.ifNull
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
class ServersPresenter @Inject constructor(
private val view: ServersView,
private val navigator: MainNavigator,
private val strategy: CancelStrategy,
private val getAccountsInteractor: GetAccountsInteractor,
@Named("currentServer") private val currentServerUrl: String
) {
fun getAllServers() {
launchUI(strategy) {
try {
view.showServerList(getAccountsInteractor.get(), currentServerUrl)
} catch (exception: Exception) {
Timber.e(exception, "Error loading servers")
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
}
}
fun changeServer(serverUrl: String) {
if (currentServerUrl != serverUrl) {
navigator.switchOrAddNewServer(serverUrl)
} else {
view.hideServerView()
}
}
fun addNewServer() {
view.hideServerView()
navigator.toServerScreen()
}
}
\ No newline at end of file
package chat.rocket.android.servers.presentation
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.android.server.domain.model.Account
interface ServersView : MessageView {
/**
* Shows the server list.
*
* @param serverList The list of server to show.
* @param currentServerUrl The current logged in server url.
*/
fun showServerList(serverList: List<Account>, currentServerUrl: String)
/**
* Hides the servers view.
*/
fun hideServerView()
}
\ No newline at end of file
package chat.rocket.android.servers.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import chat.rocket.android.R
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.servers.adapter.Selector
import chat.rocket.android.servers.adapter.ServersAdapter
import chat.rocket.android.servers.presentation.ServersPresenter
import chat.rocket.android.servers.presentation.ServersView
import chat.rocket.android.util.extensions.showToast
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.bottom_sheet_fragment_servers.*
import javax.inject.Inject
const val TAG = "ServersBottomSheetFragment"
class ServersBottomSheetFragment : BottomSheetDialogFragment(), ServersView {
@Inject
lateinit var presenter: ServersPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
inflater.inflate(R.layout.bottom_sheet_fragment_servers, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.getAllServers()
}
override fun showServerList(serverList: List<Account>, currentServerUrl: String) {
recycler_view.layoutManager = LinearLayoutManager(context)
recycler_view.adapter = ServersAdapter(serverList, currentServerUrl, object : Selector {
override fun onServerSelected(serverUrl: String) {
presenter.changeServer(serverUrl)
}
override fun onAddNewServerSelected() {
presenter.addNewServer()
}
})
}
override fun hideServerView() = dismiss()
override fun showMessage(resId: Int) {
showToast(resId)
}
override fun showMessage(message: String) {
showToast(message)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
}
\ No newline at end of file
package chat.rocket.android.settings.di package chat.rocket.android.settings.di
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.settings.presentation.SettingsView import chat.rocket.android.settings.presentation.SettingsView
import chat.rocket.android.settings.ui.SettingsFragment import chat.rocket.android.settings.ui.SettingsFragment
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import kotlinx.coroutines.Job
@Module @Module
class SettingsFragmentModule { class SettingsFragmentModule {
...@@ -20,13 +18,7 @@ class SettingsFragmentModule { ...@@ -20,13 +18,7 @@ class SettingsFragmentModule {
@Provides @Provides
@PerFragment @PerFragment
fun settingsLifecycleOwner(frag: SettingsFragment): LifecycleOwner { fun settingsLifecycleOwner(fragment: SettingsFragment): LifecycleOwner {
return frag return fragment
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
} }
} }
\ No newline at end of file
package chat.rocket.android.settings.di package chat.rocket.android.settings.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.settings.ui.SettingsFragment import chat.rocket.android.settings.ui.SettingsFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector ...@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class SettingsFragmentProvider { abstract class SettingsFragmentProvider {
@ContributesAndroidInjector(modules = [SettingsFragmentModule::class]) @ContributesAndroidInjector(modules = [SettingsFragmentModule::class])
@PerFragment
abstract fun provideSettingsFragment(): SettingsFragment abstract fun provideSettingsFragment(): SettingsFragment
} }
\ No newline at end of file
...@@ -8,7 +8,6 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory ...@@ -8,7 +8,6 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.updateProfile import chat.rocket.core.internal.rest.updateProfile
import javax.inject.Inject import javax.inject.Inject
...@@ -22,7 +21,7 @@ class PasswordPresenter @Inject constructor( ...@@ -22,7 +21,7 @@ class PasswordPresenter @Inject constructor(
factory: RocketChatClientFactory factory: RocketChatClientFactory
) { ) {
private val serverUrl = serverInteractor.get()!! private val serverUrl = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(serverUrl) private val client: RocketChatClient = factory.get(serverUrl)
fun updatePassword(password: String) { fun updatePassword(password: String) {
launchUI(strategy) { launchUI(strategy) {
......
package chat.rocket.android.settings.presentation
import chat.rocket.android.core.lifecycle.CancelStrategy
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.GetCurrentServerInteractor
import chat.rocket.android.server.domain.PermissionsInteractor
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.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.gethash
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extension.toHex
import chat.rocket.android.util.extensions.adminPanelUrl
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.deleteOwnAccount
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.serverInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Named
class SettingsPresenter @Inject constructor(
private val view: SettingsView,
private val strategy: CancelStrategy,
private val navigator: MainNavigator,
@Named("currentServer") private val currentServer: String,
private val userHelper: UserHelper,
private val analyticsTrackingInteractor: AnalyticsTrackingInteractor,
private val tokenRepository: TokenRepository,
private val permissions: PermissionsInteractor,
private val rocketChatClientFactory: RocketChatClientFactory,
getCurrentServerInteractor: GetCurrentServerInteractor,
removeAccountInteractor: RemoveAccountInteractor,
databaseManagerFactory: DatabaseManagerFactory,
connectionManagerFactory: ConnectionManagerFactory
) : CheckServerPresenter(
strategy = strategy,
factory = rocketChatClientFactory,
serverInteractor = getCurrentServerInteractor,
removeAccountInteractor = removeAccountInteractor,
tokenRepository = tokenRepository,
dbManagerFactory = databaseManagerFactory,
managerFactory = connectionManagerFactory,
tokenView = view,
navigator = navigator
) {
fun setupView() {
launchUI(strategy) {
try {
val serverInfo = retryIO(description = "serverInfo", times = 5) {
rocketChatClientFactory.get(currentServer).serverInfo()
}
val me = retryIO(description = "serverInfo", times = 5) {
rocketChatClientFactory.get(currentServer).me()
}
userHelper.user()?.let { user ->
view.setupSettingsView(
currentServer.avatarUrl(me.username ?: ""),
userHelper.displayName(user) ?: me.username ?: "",
me.status.toString(),
permissions.isAdministrationEnabled(),
analyticsTrackingInteractor.get(),
true,
serverInfo.version
)
}
} catch (exception: Exception) {
Timber.d(exception, "Error getting server info")
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
}
}
fun enableAnalyticsTracking(isEnabled: Boolean) {
analyticsTrackingInteractor.save(isEnabled)
}
fun logout() {
setupConnectionInfo(currentServer)
super.logout(null) // TODO null?
}
fun deleteAccount(password: String) {
launchUI(strategy) {
view.showLoading()
try {
withContext(Dispatchers.Default) {
// REMARK: Backend API is only working with a lowercase hash.
// https://github.com/RocketChat/Rocket.Chat/issues/12573
retryIO {
rocketChatClientFactory.get(currentServer)
.deleteOwnAccount(password.gethash().toHex().toLowerCase())
}
setupConnectionInfo(currentServer)
logout(null)
}
} catch (exception: Exception) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
fun toProfile() = navigator.toProfile()
fun toAdmin() = tokenRepository.get(currentServer)?.let {
navigator.toAdminPanel(currentServer.adminPanelUrl(), it.authToken)
}
fun toLicense(licenseUrl: String, licenseTitle: String) =
navigator.toLicense(licenseUrl, licenseTitle)
}
\ No newline at end of file
package chat.rocket.android.settings.presentation package chat.rocket.android.settings.presentation
interface SettingsView import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import chat.rocket.android.server.presentation.TokenView
interface SettingsView : TokenView, LoadingView, MessageView {
/**
* Setups the settings view.
*
* @param avatar The user avatar.
* @param displayName The user display name.
* @param status The user status.
* @param isAdministrationEnabled True if the administration is enabled, false otherwise.
* @param isAnalyticsTrackingEnabled True if the analytics tracking is enabled, false otherwise.
* @param isDeleteAccountEnabled True if the delete account is enabled, false otherwise.
* @param serverVersion The version of the current logged in server.
*/
fun setupSettingsView(
avatar: String,
displayName: String,
status: String,
isAdministrationEnabled: Boolean,
isAnalyticsTrackingEnabled: Boolean,
isDeleteAccountEnabled: Boolean,
serverVersion: String
)
}
...@@ -7,35 +7,36 @@ import android.os.Bundle ...@@ -7,35 +7,36 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import chat.rocket.android.BuildConfig
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.about.ui.AboutFragment
import chat.rocket.android.about.ui.TAG_ABOUT_FRAGMENT
import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.helper.TextHelper.getDeviceAndAppInformation import chat.rocket.android.helper.TextHelper.getDeviceAndAppInformation
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.settings.presentation.SettingsPresenter
import chat.rocket.android.preferences.ui.PreferencesFragment
import chat.rocket.android.preferences.ui.TAG_PREFERENCES_FRAGMENT
import chat.rocket.android.settings.password.ui.PasswordActivity
import chat.rocket.android.settings.presentation.SettingsView import chat.rocket.android.settings.presentation.SettingsView
import chat.rocket.android.util.extensions.addFragmentBackStack
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.webview.ui.webViewIntent import chat.rocket.android.util.invalidateFirebaseToken
import com.bumptech.glide.Glide
import dagger.android.support.AndroidSupportInjection import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_settings.* import kotlinx.android.synthetic.main.fragment_settings.*
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal const val TAG_SETTINGS_FRAGMENT = "SettingsFragment" internal const val TAG_SETTINGS_FRAGMENT = "SettingsFragment"
class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListener { fun newInstance(): Fragment = SettingsFragment()
@Inject
lateinit var analyticsManager: AnalyticsManager class SettingsFragment : Fragment(), SettingsView {
@Inject lateinit var analyticsManager: AnalyticsManager
@Inject lateinit var presenter: SettingsPresenter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -51,79 +52,92 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen ...@@ -51,79 +52,92 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupToolbar() setupToolbar()
setupListView() presenter.setupView()
analyticsManager.logScreenView(ScreenViewEvent.Settings) analyticsManager.logScreenView(ScreenViewEvent.Settings)
} }
override fun onResume() { override fun setupSettingsView(
// FIXME - gambiarra ahead. will fix when moving to new androidx Navigation avatar: String,
(activity as? MainActivity)?.setupNavigationView() displayName: String,
super.onResume() status: String,
} isAdministrationEnabled: Boolean,
isAnalyticsTrackingEnabled: Boolean,
isDeleteAccountEnabled: Boolean,
serverVersion: String
) {
context?.let { Glide.with(it).load(avatar).into(image_avatar) }
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { text_display_name.text = displayName
when (parent?.getItemAtPosition(position).toString()) {
resources.getStringArray(R.array.settings_actions)[0] -> { text_status.text = status
(activity as AppCompatActivity).addFragmentBackStack(
TAG_PREFERENCES_FRAGMENT,
R.id.fragment_container
) {
PreferencesFragment.newInstance()
}
}
resources.getStringArray(R.array.settings_actions)[1] -> profile_container.setOnClickListener { presenter.toProfile() }
activity?.startActivity(Intent(activity, PasswordActivity::class.java))
// TODO (https://github.com/RocketChat/Rocket.Chat.Android/pull/1918) text_contact_us.setOnClickListener { contactSupport() }
resources.getStringArray(R.array.settings_actions)[2] -> showToast("Coming soon")
resources.getStringArray(R.array.settings_actions)[3] -> shareApp() text_language.setOnClickListener {}
resources.getStringArray(R.array.settings_actions)[4] -> showAppOnStore() 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))
}
resources.getStringArray(R.array.settings_actions)[5] -> contactSupport() text_app_version.text = getString(R.string.msg_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
resources.getStringArray(R.array.settings_actions)[6] -> activity?.startActivity( text_server_version.text = getString(R.string.msg_server_version, serverVersion)
context?.webViewIntent(
getString(R.string.license_url),
getString(R.string.title_licence)
)
)
resources.getStringArray(R.array.settings_actions)[7] -> { text_logout.setOnClickListener { showLogoutDialog()}
(activity as AppCompatActivity).addFragmentBackStack(
TAG_ABOUT_FRAGMENT, with(text_administration) {
R.id.fragment_container isVisible = isAdministrationEnabled
) { setOnClickListener { presenter.toAdmin() }
AboutFragment.newInstance() }
}
with(switch_crash_report) {
isChecked = isAnalyticsTrackingEnabled
isEnabled = BuildConfig.FLAVOR == "play"
setOnCheckedChangeListener { _, isChecked ->
presenter.enableAnalyticsTracking(isChecked)
} }
} }
}
private fun showAppOnStore() { with(text_delete_account) {
try { isVisible = isDeleteAccountEnabled
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.market_link).toUri())) setOnClickListener { showDeleteAccountDialog() }
} catch (error: ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.play_store_link).toUri()))
} }
} }
private fun setupListView() { override fun invalidateToken(token: String) = invalidateFirebaseToken(token)
settings_list.onItemClickListener = this
override fun showLoading() {
view_loading.isVisible = true
} }
private fun setupToolbar() { override fun hideLoading() {
(activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_settings) view_loading.isVisible = false
} }
private fun shareApp() { override fun showMessage(resId: Int) {
with(Intent(Intent.ACTION_SEND)) { showToast(resId)
type = "text/plain" }
putExtra(Intent.EXTRA_SUBJECT, getString(R.string.msg_check_this_out))
putExtra(Intent.EXTRA_TEXT, getString(R.string.play_store_link)) override fun showMessage(message: String) {
startActivity(Intent.createChooser(this, getString(R.string.msg_share_using))) showToast(message)
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
private fun setupToolbar() {
with((activity as AppCompatActivity)) {
with(toolbar) {
setSupportActionBar(this)
title = getString(R.string.title_settings)
setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
setNavigationOnClickListener { activity?.onBackPressed() }
}
} }
} }
...@@ -142,7 +156,42 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen ...@@ -142,7 +156,42 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen
} }
} }
companion object { private fun showAppOnStore() {
fun newInstance() = SettingsFragment() try {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.market_link).toUri()))
} catch (error: ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.play_store_link).toUri()))
}
}
private fun shareApp() {
with(Intent(Intent.ACTION_SEND)) {
type = "text/plain"
putExtra(Intent.EXTRA_SUBJECT, getString(R.string.msg_check_this_out))
putExtra(Intent.EXTRA_TEXT, getString(R.string.play_store_link))
startActivity(Intent.createChooser(this, getString(R.string.msg_share_using)))
}
}
private fun showLogoutDialog() {
context?.let {
val builder = AlertDialog.Builder(it)
builder.setTitle(R.string.title_are_you_sure)
.setPositiveButton(R.string.action_logout) { _, _ -> presenter.logout()}
.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }
.create()
.show()
}
}
private fun showDeleteAccountDialog() {
context?.let {
AlertDialog.Builder(it)
.setView(LayoutInflater.from(it).inflate(R.layout.dialog_delete_account, null))
.setPositiveButton(R.string.msg_delete_account) { _, _ ->
presenter.deleteAccount(EditText(context).text.toString())
}.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }.create()
.show()
}
} }
} }
package chat.rocket.android.sortingandgrouping.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.sortingandgrouping.presentation.SortingAndGroupingView
import chat.rocket.android.sortingandgrouping.ui.SortingAndGroupingBottomSheetFragment
import dagger.Module
import dagger.Provides
@Module
class SortingAndGroupingBottomSheetFragmentModule {
@Provides
@PerFragment
fun sortingAndGroupingView(frag: SortingAndGroupingBottomSheetFragment): SortingAndGroupingView =
frag
}
\ No newline at end of file
package chat.rocket.android.sortingandgrouping.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.sortingandgrouping.ui.SortingAndGroupingBottomSheetFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class SortingAndGroupingBottomSheetFragmentProvider {
@ContributesAndroidInjector(modules = [SortingAndGroupingBottomSheetFragmentModule::class])
@PerFragment
abstract fun provideSortingAndGroupingBottomSheetFragment(): SortingAndGroupingBottomSheetFragment
}
\ No newline at end of file
package chat.rocket.android.sortingandgrouping.presentation
import chat.rocket.android.server.domain.SortingAndGroupingInteractor
import javax.inject.Inject
import javax.inject.Named
class SortingAndGroupingPresenter @Inject constructor(
private val view: SortingAndGroupingView,
private val sortingAndGroupingInteractor: SortingAndGroupingInteractor,
@Named("currentServer") private val currentServerUrl: String
) {
fun getSortingAndGroupingPreferences() {
with(sortingAndGroupingInteractor) {
view.showSortingAndGroupingPreferences(
getSortByName(currentServerUrl),
getUnreadOnTop(currentServerUrl),
getGroupByType(currentServerUrl),
getGroupByFavorites(currentServerUrl)
)
}
}
fun saveSortingAndGroupingPreferences(
isSortByName: Boolean,
isUnreadOnTop: Boolean,
isGroupByType: Boolean,
isGroupByFavorites: Boolean
) {
sortingAndGroupingInteractor.save(
currentServerUrl,
isSortByName,
isUnreadOnTop,
isGroupByType,
isGroupByFavorites
)
}
}
\ No newline at end of file
package chat.rocket.android.sortingandgrouping.presentation
interface SortingAndGroupingView {
/**
* Shows the sorting and grouping preferences for the current logged in server.
*
* @param isSortByName True if sorting by name, false otherwise.
* @param isUnreadOnTop True if grouping by unread on top, false otherwise.
* @param isGroupByType True if grouping by type , false otherwise.
* @param isGroupByFavorites True if grouping by favorites, false otherwise.
*/
fun showSortingAndGroupingPreferences(
isSortByName: Boolean,
isUnreadOnTop: Boolean,
isGroupByType: Boolean,
isGroupByFavorites: Boolean
)
}
\ No newline at end of file
package chat.rocket.android.sortingandgrouping.ui
import DrawableHelper
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.DrawableRes
import chat.rocket.android.R
import chat.rocket.android.chatrooms.ui.ChatRoomsFragment
import chat.rocket.android.chatrooms.ui.TAG_CHAT_ROOMS_FRAGMENT
import chat.rocket.android.sortingandgrouping.presentation.SortingAndGroupingPresenter
import chat.rocket.android.sortingandgrouping.presentation.SortingAndGroupingView
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.bottom_sheet_fragment_sort_by.*
import javax.inject.Inject
const val TAG = "SortingAndGroupingBottomSheetFragment"
class SortingAndGroupingBottomSheetFragment : BottomSheetDialogFragment(), SortingAndGroupingView {
@Inject
lateinit var presenter: SortingAndGroupingPresenter
private var isSortByName = false
private var isUnreadOnTop = false
private var isGroupByType = false
private var isGroupByFavorites = false
private val chatRoomFragment by lazy {
activity?.supportFragmentManager?.findFragmentByTag(TAG_CHAT_ROOMS_FRAGMENT) as ChatRoomsFragment
}
private val filterDrawable by lazy { R.drawable.ic_filter_20dp }
private val activityDrawable by lazy { R.drawable.ic_activity_20dp }
private val unreadOnTopDrawable by lazy { R.drawable.ic_unread_20dp }
private val groupByTypeDrawable by lazy { R.drawable.ic_group_by_type_20dp }
private val groupByFavoritesDrawable by lazy { R.drawable.ic_favorites_20dp }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
inflater.inflate(R.layout.bottom_sheet_fragment_sort_by, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.getSortingAndGroupingPreferences()
setupListeners()
}
override fun onCancel(dialog: DialogInterface?) {
super.onCancel(dialog)
presenter.saveSortingAndGroupingPreferences(
isSortByName,
isUnreadOnTop,
isGroupByType,
isGroupByFavorites
)
}
override fun showSortingAndGroupingPreferences(
isSortByName: Boolean,
isUnreadOnTop: Boolean,
isGroupByType: Boolean,
isGroupByFavorites: Boolean
) {
this.isSortByName = isSortByName
this.isUnreadOnTop = isUnreadOnTop
this.isGroupByType = isGroupByType
this.isGroupByFavorites = isGroupByFavorites
if (isSortByName) {
changeSortByTitle(getString(R.string.msg_sort_by_name))
checkSelection(text_name, filterDrawable)
} else {
changeSortByTitle(getString(R.string.msg_sort_by_activity))
checkSelection(text_activity, activityDrawable)
}
if (isUnreadOnTop) checkSelection(text_unread_on_top, unreadOnTopDrawable)
if (isGroupByType) checkSelection(text_group_by_type, groupByTypeDrawable)
if (isGroupByFavorites) checkSelection(text_group_by_favorites, groupByFavoritesDrawable)
}
private fun setupListeners() {
text_name.setOnClickListener {
changeSortByTitle(getString(R.string.msg_sort_by_name))
checkSelection(text_name, filterDrawable)
uncheckSelection(text_activity, activityDrawable)
isSortByName = true
sortChatRoomsList()
}
text_activity.setOnClickListener {
changeSortByTitle(getString(R.string.msg_sort_by_activity))
checkSelection(text_activity, activityDrawable)
uncheckSelection(text_name, filterDrawable)
isSortByName = false
sortChatRoomsList()
}
text_unread_on_top.setOnClickListener {
isUnreadOnTop = if (isUnreadOnTop) {
uncheckSelection(text_unread_on_top, unreadOnTopDrawable)
false
} else {
checkSelection(text_unread_on_top, unreadOnTopDrawable)
true
}
sortChatRoomsList()
}
text_group_by_type.setOnClickListener {
isGroupByType = if (isGroupByType) {
uncheckSelection(text_group_by_type, groupByTypeDrawable)
false
} else {
checkSelection(text_group_by_type, groupByTypeDrawable)
true
}
sortChatRoomsList()
}
text_group_by_favorites.setOnClickListener {
isGroupByFavorites = if (isGroupByFavorites) {
uncheckSelection(text_group_by_favorites, groupByFavoritesDrawable)
false
} else {
checkSelection(text_group_by_favorites, groupByFavoritesDrawable)
true
}
sortChatRoomsList()
}
}
private fun changeSortByTitle(text: String) {
text_sort_by.text = getString(R.string.msg_sort_by, text.toLowerCase())
}
private fun checkSelection(textView: TextView, @DrawableRes leftDrawable: Int) {
context?.let {
DrawableHelper.compoundLeftAndRightDrawable(
textView,
DrawableHelper.getDrawableFromId(leftDrawable, it),
DrawableHelper.getDrawableFromId(R.drawable.ic_check, it)
)
}
}
private fun uncheckSelection(textView: TextView, @DrawableRes leftDrawable: Int) {
context?.let {
DrawableHelper.compoundLeftDrawable(
textView,
DrawableHelper.getDrawableFromId(leftDrawable, it)
)
}
}
private fun sortChatRoomsList() {
chatRoomFragment.sortChatRoomsList(
isSortByName,
isUnreadOnTop,
isGroupByType,
isGroupByFavorites
)
}
}
\ No newline at end of file
...@@ -19,7 +19,7 @@ suspend fun RocketChatClientFactory.registerPushToken( ...@@ -19,7 +19,7 @@ suspend fun RocketChatClientFactory.registerPushToken(
accounts.forEach { account -> accounts.forEach { account ->
try { try {
retryIO(description = "register push token: ${account.serverUrl}") { retryIO(description = "register push token: ${account.serverUrl}") {
create(account.serverUrl).registerPushToken(token) get(account.serverUrl).registerPushToken(token)
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error registering Push token for ${account.serverUrl}") Timber.d(ex, "Error registering Push token for ${account.serverUrl}")
......
...@@ -16,9 +16,17 @@ import dagger.android.support.DaggerFragment ...@@ -16,9 +16,17 @@ import dagger.android.support.DaggerFragment
import kotlinx.android.synthetic.main.fragment_admin_panel_web_view.* import kotlinx.android.synthetic.main.fragment_admin_panel_web_view.*
import javax.inject.Inject import javax.inject.Inject
internal const val TAG_ADMIN_PANEL_WEB_VIEW_FRAGMENT = "AdminPanelWebViewFragment"
private const val BUNDLE_WEB_PAGE_URL = "web_page_url" private const val BUNDLE_WEB_PAGE_URL = "web_page_url"
private const val BUNDLE_USER_TOKEN = "user_token" private const val BUNDLE_USER_TOKEN = "user_token"
fun newInstance(webPageUrl: String, userToken: String) = AdminPanelWebViewFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_WEB_PAGE_URL, webPageUrl)
putString(BUNDLE_USER_TOKEN, userToken)
}
}
class AdminPanelWebViewFragment : DaggerFragment() { class AdminPanelWebViewFragment : DaggerFragment() {
private lateinit var webPageUrl: String private lateinit var webPageUrl: String
private lateinit var userToken: String private lateinit var userToken: String
...@@ -30,7 +38,8 @@ class AdminPanelWebViewFragment : DaggerFragment() { ...@@ -30,7 +38,8 @@ class AdminPanelWebViewFragment : DaggerFragment() {
arguments?.run { arguments?.run {
webPageUrl = getString(BUNDLE_WEB_PAGE_URL, "") webPageUrl = getString(BUNDLE_WEB_PAGE_URL, "")
userToken = getString(BUNDLE_USER_TOKEN, "") userToken = getString(BUNDLE_USER_TOKEN, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" } }
?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
} }
override fun onCreateView( override fun onCreateView(
...@@ -49,7 +58,7 @@ class AdminPanelWebViewFragment : DaggerFragment() { ...@@ -49,7 +58,7 @@ class AdminPanelWebViewFragment : DaggerFragment() {
private fun setupToolbar() { private fun setupToolbar() {
(activity as AppCompatActivity?)?.supportActionBar?.title = (activity as AppCompatActivity?)?.supportActionBar?.title =
getString(R.string.title_admin_panel) getString(R.string.title_admin_panel)
} }
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
...@@ -70,13 +79,4 @@ class AdminPanelWebViewFragment : DaggerFragment() { ...@@ -70,13 +79,4 @@ class AdminPanelWebViewFragment : DaggerFragment() {
} }
web_view.loadUrl(webPageUrl) web_view.loadUrl(webPageUrl)
} }
companion object {
fun newInstance(webPageUrl: String, userToken: String) = AdminPanelWebViewFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_WEB_PAGE_URL, webPageUrl)
putString(BUNDLE_USER_TOKEN, userToken)
}
}
}
} }
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:centerColor="#30000000"
android:endColor="#00000000"
android:startColor="#C0000000"
android:type="linear" />
</shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M10,10m-9.25,0a9.25,9.25 0,1 1,18.5 0a9.25,9.25 0,1 1,-18.5 0"
android:strokeWidth="1.5"
android:strokeColor="#9EA2A8" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M10,4.004V10l4,4"
android:strokeWidth="1.5"
android:strokeColor="#9EA2A8" />
</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="#00000000"
android:fillType="evenOdd"
android:pathData="M4.5,0.5L43.5,0.5A4,4 0,0 1,47.5 4.5L47.5,43.5A4,4 0,0 1,43.5 47.5L4.5,47.5A4,4 0,0 1,0.5 43.5L0.5,4.5A4,4 0,0 1,4.5 0.5z"
android:strokeWidth="1"
android:strokeColor="#CBCED1" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M33.0625,23.5625L24.4375,23.5625L24.4375,14.9375C24.4375,14.6959 24.2416,14.5 24,14.5C23.7584,14.5 23.5625,14.6959 23.5625,14.9375L23.5625,23.5625L14.9375,23.5625C14.6959,23.5625 14.5,23.7584 14.5,24C14.5,24.2416 14.6959,24.4375 14.9375,24.4375L23.5625,24.4375L23.5625,33.0625C23.5625,33.3041 23.7584,33.5 24,33.5C24.2416,33.5 24.4375,33.3041 24.4375,33.0625L24.4375,24.4375L33.0625,24.4375C33.3041,24.4375 33.5,24.2416 33.5,24C33.5,23.7584 33.3041,23.5625 33.0625,23.5625Z"
android:strokeWidth="1"
android:strokeColor="#9EA2A8" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="4dp"
android:viewportWidth="8"
android:viewportHeight="4">
<path
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="M1.20711,0L6.79289,0C7.06904,0 7.29289,0.22386 7.29289,0.5C7.29289,0.63261 7.24021,0.75979 7.14645,0.85355L4.35355,3.64645C4.15829,3.84171 3.84171,3.84171 3.64645,3.64645L0.85355,0.85355C0.65829,0.65829 0.65829,0.34171 0.85355,0.14645C0.94732,0.05268 1.0745,0 1.20711,0Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="17dp"
android:height="12dp"
android:viewportWidth="17"
android:viewportHeight="12">
<path
android:fillColor="#1D74F5"
android:fillType="nonZero"
android:pathData="M5.5875,9.7331L14.7078,0.6128C15.0463,0.2744 15.595,0.2744 15.9335,0.6128L15.9335,0.6128C16.2719,0.9513 16.2719,1.5 15.9335,1.8385L6.2225,11.5494C5.832,11.9399 5.1989,11.9399 4.8083,11.5494L0.6489,7.39C0.2905,7.0316 0.2905,6.4506 0.6489,6.0922L0.6489,6.0922C1.0072,5.7338 1.5883,5.7338 1.9466,6.0922L5.5875,9.7331Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="#1D74F5" android:tint="#1D74F5"
android:viewportHeight="24.0" android:viewportWidth="24"
android:viewportWidth="24.0"> android:viewportHeight="24">
<path <path
android:fillColor="#FF1D74F5" android:fillColor="#FF1D74F5"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" /> android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="19">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M14.882,16.72l-0.933,-5.437 3.95,-3.85 -5.458,-0.793L10,1.695 7.56,6.64 2.1,7.434l3.95,3.85 -0.933,5.435L10,14.153l4.882,2.566z"
android:strokeWidth="1.5"
android:strokeColor="#9EA2A8" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M5,4L5,18"
android:strokeWidth="1.5"
android:strokeColor="#9EA2A8"
android:strokeLineCap="round" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M2,16L5,19"
android:strokeWidth="1.5"
android:strokeColor="#9EA2A8"
android:strokeLineCap="round" />
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M8,16L5,19"
android:strokeWidth="1.5"
android:strokeColor="#9EA2A8"
android:strokeLineCap="round" />
<path
android:fillColor="#9EA2A8"
android:fillType="evenOdd"
android:pathData="M17.4434,7.0605L16.3545,3.8916L15.1973,7.0605L17.4434,7.0605ZM15.8467,2.8271L16.9453,2.8271L19.5479,10L18.4834,10L17.7559,7.8516L14.9189,7.8516L14.1426,10L13.1465,10L15.8467,2.8271Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="#9EA2A8"
android:fillType="evenOdd"
android:pathData="M13.2295,19.1943l4.3994,-5.5127l-4.0771,0l0,-0.8545l5.3271,0l0,0.835l-4.4238,5.4834l4.4238,0l0,0.8545l-5.6494,0z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M5,4v14M12,5h10.88M12,10h8.88M12,15h5.88M2,16l3,3M8,16l-3,3"
android:strokeWidth="1.5"
android:strokeColor="#9EA2A8"
android:strokeLineCap="round" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M12.0455,10.4052C12.5308,8.6894 14.1082,7.4323 15.9794,7.4323C17.8505,7.4323 19.428,8.6894 19.9132,10.4052L22.04,10.4052C22.5923,10.4052 23.04,10.8529 23.04,11.4052L23.04,11.6348C23.04,12.1871 22.5923,12.6348 22.04,12.6348L19.9132,12.6348C19.428,14.3506 17.8505,15.6077 15.9794,15.6077C14.1082,15.6077 12.5308,14.3506 12.0455,12.6348L1,12.6348C0.4477,12.6348 0,12.1871 0,11.6348L0,11.4052C0,10.8529 0.4477,10.4052 1,10.4052L12.0455,10.4052ZM15.9794,13.3781C17.0055,13.3781 17.8374,12.5462 17.8374,11.52C17.8374,10.4938 17.0055,9.6619 15.9794,9.6619C14.9532,9.6619 14.1213,10.4938 14.1213,11.52C14.1213,12.5462 14.9532,13.3781 15.9794,13.3781ZM8.5471,14.8645C10.4182,14.8645 11.9957,16.1217 12.481,17.8374L22.04,17.8374C22.5923,17.8374 23.04,18.2851 23.04,18.8374L23.04,19.0671C23.04,19.6194 22.5923,20.0671 22.04,20.0671L12.481,20.0671C11.9957,21.7828 10.4182,23.04 8.5471,23.04C6.676,23.04 5.0985,21.7828 4.6132,20.0671L1,20.0671C0.4477,20.0671 0,19.6194 0,19.0671L0,18.8374C-0,18.2851 0.4477,17.8374 1,17.8374L4.6132,17.8374C5.0985,16.1217 6.676,14.8645 8.5471,14.8645ZM8.5471,20.8103C9.5733,20.8103 10.4052,19.9784 10.4052,18.9523C10.4052,17.9261 9.5733,17.0942 8.5471,17.0942C7.5209,17.0942 6.689,17.9261 6.689,18.9523C6.689,19.9784 7.5209,20.8103 8.5471,20.8103ZM7.0606,0C8.9318,0 10.5092,1.2572 10.9945,2.9729L22.04,2.9729C22.5923,2.9729 23.04,3.4206 23.04,3.9729L23.04,4.2026C23.04,4.7549 22.5923,5.2026 22.04,5.2026L10.9945,5.2026C10.5092,6.9183 8.9318,8.1755 7.0606,8.1755C5.1895,8.1755 3.612,6.9183 3.1268,5.2026L1,5.2026C0.4477,5.2026 0,4.7549 0,4.2026L0,3.9729C-0,3.4206 0.4477,2.9729 1,2.9729L3.1268,2.9729C3.612,1.2572 5.1895,0 7.0606,0ZM7.0606,5.9458C8.0868,5.9458 8.9187,5.1139 8.9187,4.0877C8.9187,3.0616 8.0868,2.2297 7.0606,2.2297C6.0345,2.2297 5.2026,3.0616 5.2026,4.0877C5.2026,5.1139 6.0345,5.9458 7.0606,5.9458Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="M16.523,11.0833C16.907,11.0833 17.218,11.3942 17.218,11.7782L17.218,16.6582C17.218,17.9513 16.169,19.0002 14.876,19.0002L3.343,19.0002C2.049,19.0002 1,17.9513 1,16.6582L1,5.1263C1,3.8333 2.049,2.7843 3.343,2.7843L8.149,2.7843C8.533,2.7843 8.845,3.0953 8.845,3.4792C8.845,3.8623 8.533,4.1743 8.149,4.1743L3.343,4.1743C2.816,4.1743 2.39,4.6003 2.39,5.1263L2.39,16.6582C2.39,17.1842 2.816,17.6103 3.343,17.6103L14.876,17.6103C15.401,17.6103 15.828,17.1842 15.828,16.6582L15.828,11.7782C15.828,11.3942 16.139,11.0833 16.523,11.0833ZM18.256,2.1318C19.25,3.1248 19.247,4.7428 18.256,5.7337L9.501,14.4877L5.5647,15.6828C5.0363,15.8432 4.4778,15.5449 4.3173,15.0164C4.2598,14.8269 4.2598,14.6246 4.3174,14.4352L5.513,10.5007L14.268,1.7468C15.263,0.7518 16.876,0.7508 17.87,1.7458L18.256,2.1318ZM17.273,4.7508C17.722,4.3028 17.723,3.5638 17.273,3.1147L16.887,2.7288C16.436,2.2768 15.703,2.2768 15.251,2.7298L14.599,3.3808L16.656,5.3678L17.273,4.7508ZM8.764,13.2587L15.673,6.3507L13.616,4.3637L6.742,11.2367L5.86,14.1407L8.764,13.2587Z"
android:strokeWidth="0.1"
android:strokeColor="#FFFFFF" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>
<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>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M18.976,9.783C15.808,6.408 12.812,4.75 10,4.75c-2.812,0 -5.808,1.658 -8.976,5.033C4.28,13.166 7.278,14.829 10,14.829c2.722,0 5.72,-1.663 8.976,-5.046z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9EA2A8"/>
<path
android:pathData="M7.965,11.783a2.75,2.75 0,0 0,3.915 -3.661l-3.915,3.66z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9EA2A8"/>
<path
android:pathData="M3.025,15.047L16.105,3.14"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#9EA2A8"
android:strokeLineCap="round"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/toolbar_layout"
layout="@layout/layout_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar_layout">
<com.google.android.material.chip.ChipGroup
android:id="@+id/members_chips"
style="@style/Widget.MaterialComponents.Chip.Entry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:visibility="gone"
app:chipSpacing="3dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</com.google.android.material.chip.ChipGroup>
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:indicatorColor="@color/colorPrimary"
app:indicatorName="BallPulseIndicator"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/text_search_member"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@android:color/transparent"
android:hint="@string/msg_search"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/members_chips" />
<View
android:id="@+id/separator_1"
android:layout_width="match_parent"
android:layout_height="0.2dp"
android:background="@color/colorDividerMessageComposer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_search_member" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/separator_1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout" android:id="@+id/fragment_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".main.ui.MainActivity"> tools:context=".main.ui.MainActivity" />
\ No newline at end of file
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/layout_app_bar"
layout="@layout/app_bar" />
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<FrameLayout
android:id="@+id/navigation_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start">
<com.google.android.material.navigation.NavigationView
android:id="@+id/view_navigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:headerLayout="@layout/nav_header" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/accounts_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/nav_header_height"
android:alpha="0"
android:background="@color/colorWhite"
android:elevation="20dp"
android:visibility="gone" />
</FrameLayout>
</androidx.drawerlayout.widget.DrawerLayout>
\ No newline at end of file
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.
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