Unverified Commit 109c0ab4 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito Committed by GitHub

Merge branch 'develop' into patch-2

parents 10045e06 84bdd7c6
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 {
* Logs the login event.
*
* @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) {}
......@@ -18,7 +18,7 @@ interface Analytics {
* Logs the sign up event.
*
* @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) {}
......
import android.content.Context
import android.graphics.drawable.Drawable
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import android.widget.TextView
import chat.rocket.android.R
import chat.rocket.common.model.UserStatus
......@@ -43,7 +43,7 @@ object DrawableHelper {
/**
* 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 context The context.
......@@ -78,7 +78,7 @@ object DrawableHelper {
*
* @param textView The array of TextView.
* @param drawables The array of Drawable.
* @see compoundDrawable
* @see compoundLeftDrawable
*/
fun compoundDrawables(textView: Array<TextView>, drawables: Array<Drawable>) {
if (textView.size != drawables.size) {
......@@ -97,7 +97,7 @@ object DrawableHelper {
* @param drawable The Drawable.
* @see compoundDrawables
*/
fun compoundDrawable(textView: TextView, drawable: Drawable) =
fun compoundLeftDrawable(textView: TextView, drawable: Drawable) =
textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
/**
......@@ -105,11 +105,26 @@ object DrawableHelper {
*
* @param textView The TextView.
* @param drawable The Drawable.
* @see compoundDrawable
* @see compoundLeftDrawable
*/
fun compoundRightDrawable(textView: TextView, drawable: Drawable) =
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.
*
......
......@@ -115,7 +115,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
// TODO - remove this
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()
}
......@@ -176,7 +176,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
val currentServer = getCurrentServerInteractor.get()
currentServer?.let { server ->
GlobalScope.launch {
val client = factory.create(server)
val client = factory.get(server)
EmojiRepository.setCurrentServerUrl(server)
val customEmojiList = mutableListOf<Emoji>()
try {
......
......@@ -60,7 +60,7 @@ class LoginPresenter @Inject constructor(
private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(currentServer)
client = factory.get(currentServer)
settings = settingsInteractor.get(currentServer)
}
......
......@@ -169,7 +169,7 @@ class LoginOptionsPresenter @Inject constructor(
private fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(currentServer)
client = factory.get(currentServer)
settings = settingsInteractor.get(currentServer)
}
......
......@@ -38,7 +38,7 @@ class RegisterUsernamePresenter @Inject constructor(
val settingsInteractor: GetSettingsInteractor
) {
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()!!)
fun registerUsername(username: String, userId: String, authToken: String) {
......
......@@ -140,7 +140,7 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_20dp, it)
DrawableHelper.wrapDrawable(atDrawable)
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(
serverInteractor: GetConnectingServerInteractor
) {
private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer)
private val client: RocketChatClient = factory.get(currentServer)
fun resetPassword(email: String) {
launchUI(strategy) {
......
......@@ -21,7 +21,7 @@ interface ResetPasswordView : LoadingView, MessageView {
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()
}
\ No newline at end of file
......@@ -26,7 +26,7 @@ interface VersionCheckView {
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() {}
......
......@@ -44,7 +44,7 @@ class SignupPresenter @Inject constructor(
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun signup(name: String, username: String, password: String, email: String) {
val client = factory.create(currentServer)
val client = factory.get(currentServer)
launchUI(strategy) {
view.showLoading()
try {
......
......@@ -51,7 +51,7 @@ class TwoFAPresenter @Inject constructor(
twoFactorAuthenticationCode: String
) {
launchUI(strategy) {
val client = factory.create(currentServer)
val client = factory.get(currentServer)
view.showLoading()
try {
// The token is saved via the client TokenProvider
......
......@@ -200,7 +200,7 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView {
val wrappedDrawable = DrawableHelper.wrapDrawable(it)
val mutableDrawable = wrappedDrawable.mutate()
DrawableHelper.tintDrawable(mutableDrawable, context!!, R.color.colorPrimary)
DrawableHelper.compoundDrawable(name, mutableDrawable)
DrawableHelper.compoundLeftDrawable(name, mutableDrawable)
}
}
......
......@@ -13,7 +13,7 @@ internal fun ChatDetailsFragment.setupMenu(menu: Menu) {
with(settings.get(it)) {
if (isJitsiEnabled()) {
if (roomTypeOf(chatRoomType) !is RoomType.DirectMessage && !isJitsiEnabledForChannels()) {
return
return@let
}
menu.add(
Menu.NONE,
......
......@@ -44,7 +44,6 @@ class MessageInfoFragment : Fragment(), MessageInfoView {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
val bundle = arguments
if (bundle != null) {
......@@ -52,6 +51,8 @@ class MessageInfoFragment : Fragment(), MessageInfoView {
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
setHasOptionsMenu(true)
}
override fun onCreateView(
......
......@@ -5,6 +5,7 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
......@@ -61,9 +62,13 @@ import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.ImageHelper
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.AndroidPermissionsHelper
import chat.rocket.android.helper.AndroidPermissionsHelper.getCameraPermission
import chat.rocket.android.helper.AndroidPermissionsHelper.getWriteExternalStoragePermission
import chat.rocket.android.helper.AndroidPermissionsHelper.hasCameraPermission
import chat.rocket.android.helper.AndroidPermissionsHelper.hasWriteExternalStoragePermission
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extension.createImageFile
import chat.rocket.android.util.extensions.circularRevealOrUnreveal
......@@ -81,6 +86,7 @@ import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.socket.model.State
import com.bumptech.glide.Glide
import com.google.android.material.snackbar.Snackbar
import dagger.android.support.AndroidSupportInjection
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
......@@ -268,7 +274,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
......@@ -292,6 +297,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
navigator = navigator,
analyticsManager = analyticsManager
)
setHasOptionsMenu(true)
}
override fun onCreateView(
......@@ -903,8 +910,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_add_reaction_or_show_keyboard.setOnClickListener { toggleKeyboard() }
button_take_a_photo.setOnClickListener {
dispatchTakePictureIntent()
// Check for camera permission
context?.let {
if(hasCameraPermission(it)) {
dispatchTakePictureIntent()
} else {
getCameraPermission(this)
}
}
handler.postDelayed({
hideAttachmentOptions()
}, 400)
......@@ -922,11 +935,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_drawing.setOnClickListener {
activity?.let { fragmentActivity ->
if (!ImageHelper.canWriteToExternalStorage(fragmentActivity)) {
ImageHelper.checkWritingPermission(fragmentActivity)
if (!hasWriteExternalStoragePermission(fragmentActivity)) {
getWriteExternalStoragePermission(this)
} else {
val intent = Intent(fragmentActivity, DrawingActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_FOR_DRAW)
dispatchDrawingIntent()
}
}
......@@ -937,6 +949,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
private fun dispatchDrawingIntent() {
val intent = Intent(activity, DrawingActivity::class.java)
startActivityForResult(intent, REQUEST_CODE_FOR_DRAW)
}
private fun dispatchTakePictureIntent() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
// Create the File where the photo should go
......@@ -957,6 +974,44 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when(requestCode) {
AndroidPermissionsHelper.CAMERA_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted
dispatchTakePictureIntent()
} else {
// permission denied
Snackbar.make(
root_layout,
R.string.msg_camera_permission_denied,
Snackbar.LENGTH_SHORT
).show()
}
return
}
AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted
dispatchDrawingIntent()
} else {
// permission denied
Snackbar.make(
root_layout,
R.string.msg_storage_permission_denied,
Snackbar.LENGTH_SHORT
).show()
}
return
}
}
}
private fun getDraftMessage() {
val unfinishedMessage = presenter.getDraftUnfinishedMessage()
if (unfinishedMessage.isNotNullNorEmpty()) {
......
......@@ -10,7 +10,6 @@ import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.DatabaseManager
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.PermissionsInteractor
import chat.rocket.android.server.domain.PublicSettings
......@@ -45,7 +44,7 @@ class ChatRoomsFragmentModule {
factory: RocketChatClientFactory,
@Named("currentServer") currentServer: String
): RocketChatClient {
return factory.create(currentServer)
return factory.get(currentServer)
}
@Provides
......
......@@ -10,6 +10,7 @@ import chat.rocket.android.helper.UserHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.SortingAndGroupingInteractor
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.domain.useSpecialCharsOnRoom
import chat.rocket.android.server.infraestructure.ConnectionManager
......@@ -35,6 +36,7 @@ class ChatRoomsPresenter @Inject constructor(
private val strategy: CancelStrategy,
private val navigator: MainNavigator,
@Named("currentServer") private val currentServer: String,
private val sortingAndGroupingInteractor: SortingAndGroupingInteractor,
private val dbManager: DatabaseManager,
manager: ConnectionManager,
private val localRepository: LocalRepository,
......@@ -44,29 +46,42 @@ class ChatRoomsPresenter @Inject constructor(
private val client = manager.client
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) {
launchUI(strategy) {
view.showLoadingRoom("")
try {
val room = dbManager.getRoom(roomId)
if (room != null) {
loadChatRoom(room.chatRoom, true)
} else {
Timber.d("Error loading channel")
Timber.e("Error loading channel")
view.showGenericErrorMessage()
}
} catch (ex: Exception) {
Timber.d(ex, "Error loading channel")
Timber.e(ex, "Error loading channel")
view.showGenericErrorMessage()
} finally {
view.hideLoadingRoom()
}
}
}
fun loadChatRoom(chatRoom: RoomUiModel) {
launchUI(strategy) {
view.showLoadingRoom(chatRoom.name)
try {
val room = retryDB("getRoom(${chatRoom.id}") { dbManager.getRoom(chatRoom.id) }
if (room != null) {
......@@ -86,10 +101,8 @@ class ChatRoomsPresenter @Inject constructor(
}
}
} catch (ex: Exception) {
Timber.d(ex, "Error loading channel")
Timber.e(ex, "Error loading channel")
view.showGenericErrorMessage()
} finally {
view.hideLoadingRoom()
}
}
}
......@@ -97,11 +110,12 @@ class ChatRoomsPresenter @Inject constructor(
suspend fun loadChatRoom(chatRoom: ChatRoomEntity, local: Boolean = false) {
with(chatRoom) {
val isDirectMessage = roomTypeOf(type) is RoomType.DirectMessage
val roomName = if (settings.useSpecialCharsOnRoom() || (isDirectMessage && settings.useRealName())) {
fullname ?: name
} else {
name
}
val roomName =
if (settings.useSpecialCharsOnRoom() || (isDirectMessage && settings.useRealName())) {
fullname ?: name
} else {
name
}
val myself = getCurrentUser()
if (myself?.username == null) {
......@@ -131,14 +145,14 @@ class ChatRoomsPresenter @Inject constructor(
}
navigator.toChatRoom(
chatRoomId = id,
chatRoomName = roomName,
chatRoomType = type,
isReadOnly = readonly ?: false,
chatRoomLastSeen = lastSeen ?: -1,
isSubscribed = open,
isCreator = ownerId == myself.id || isDirectMessage,
isFavorite = favorite ?: false
chatRoomId = id,
chatRoomName = roomName,
chatRoomType = type,
isReadOnly = readonly ?: false,
chatRoomLastSeen = lastSeen ?: -1,
isSubscribed = open,
isCreator = ownerId == myself.id || isDirectMessage,
isFavorite = favorite ?: false
)
}
}
......
......@@ -4,7 +4,27 @@ import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.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(
val serverInteractor: GetCurrentServerInteractor,
val factory: RocketChatClientFactory
) {
private val client: RocketChatClient = factory.create(serverInteractor.get()!!)
private val client: RocketChatClient = factory.get(serverInteractor.get()!!)
fun createChannel(
roomType: RoomType,
......@@ -35,7 +35,6 @@ class CreateChannelPresenter @Inject constructor(
view.disableUserInput()
try {
client.createChannel(roomType, channelName, usersList, readOnly)
view.prepareToShowChatList()
view.showChannelCreatedSuccessfullyMessage()
toChatList()
} catch (exception: RocketChatException) {
......
......@@ -28,12 +28,6 @@ interface CreateChannelView : LoadingView, MessageView {
*/
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.
*/
......
......@@ -9,7 +9,6 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
......@@ -19,7 +18,6 @@ import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.createchannel.presentation.CreateChannelPresenter
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.uimodel.MemberUiModel
import chat.rocket.android.util.extension.asObservable
......@@ -32,17 +30,18 @@ import com.google.android.material.chip.Chip
import dagger.android.support.AndroidSupportInjection
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_create_channel.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
internal const val TAG_CREATE_CHANNEL_FRAGMENT = "CreateChannelFragment"
fun newInstance() = CreateChannelFragment()
class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback {
@Inject
lateinit var createChannelPresenter: CreateChannelPresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
@Inject lateinit var presenter: CreateChannelPresenter
@Inject lateinit var analyticsManager: AnalyticsManager
private var actionMode: ActionMode? = null
private val adapter: MembersAdapter = MembersAdapter {
it.username?.run { processSelectedMember(this) }
......@@ -52,10 +51,6 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
private var isChannelReadOnly: Boolean = false
private var memberList = arrayListOf<String>()
companion object {
fun newInstance() = CreateChannelFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
......@@ -93,7 +88,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
override fun onActionItemClicked(mode: ActionMode, menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_create_channel -> {
createChannelPresenter.createChannel(
presenter.createChannel(
roomTypeOf(channelType),
text_channel_name.text.toString(),
memberList,
......@@ -165,17 +160,6 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
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() {
showMessage(getString(R.string.msg_channel_created_successfully))
}
......@@ -191,8 +175,14 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
}
private fun setupToolBar() {
(activity as AppCompatActivity?)?.supportActionBar?.title =
getString(R.string.title_create_channel)
with((activity as AppCompatActivity)) {
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() {
......@@ -247,7 +237,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
.filter { t -> t.isNotBlank() }
.subscribe {
if (it.length >= 3) {
createChannelPresenter.searchUser(it.toString())
presenter.searchUser(it.toString())
} else {
view_member_suggestion.isVisible = false
}
......
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.login.di.LoginFragmentProvider
import chat.rocket.android.authentication.loginoptions.di.LoginOptionsFragmentProvider
......@@ -29,13 +28,14 @@ import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.members.di.MembersFragmentProvider
import chat.rocket.android.mentions.di.MentionsFragmentProvider
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.server.di.ChangeServerModule
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.password.di.PasswordFragmentProvider
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.videoconference.di.VideoConferenceModule
import chat.rocket.android.videoconference.ui.VideoConferenceActivity
......@@ -65,11 +65,11 @@ abstract class ActivityBuilder {
@ContributesAndroidInjector(
modules = [MainModule::class,
ChatRoomsFragmentProvider::class,
ServersBottomSheetFragmentProvider::class,
SortingAndGroupingBottomSheetFragmentProvider::class,
CreateChannelProvider::class,
ProfileFragmentProvider::class,
SettingsFragmentProvider::class,
AboutFragmentProvider::class,
PreferencesFragmentProvider::class,
AdminPanelWebViewFragmentProvider::class
]
)
......
......@@ -27,23 +27,23 @@ import chat.rocket.android.push.PushManager
import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.AnalyticsTrackingInteractor
import chat.rocket.android.server.domain.AnalyticsTrackingRepository
import chat.rocket.android.server.domain.BasicAuthRepository
import chat.rocket.android.server.domain.ChatRoomsRepository
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.GetAccountInteractor
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetBasicAuthInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.JobSchedulerInteractor
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.domain.MultiServerTokenRepository
import chat.rocket.android.server.domain.PermissionsRepository
import chat.rocket.android.server.domain.SaveBasicAuthInteractor
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.SortingAndGroupingRepository
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.domain.BasicAuthRepository
import chat.rocket.android.server.domain.GetBasicAuthInteractor
import chat.rocket.android.server.domain.SaveBasicAuthInteractor
import chat.rocket.android.server.infraestructure.SharedPrefsBasicAuthRepository
import chat.rocket.android.server.infraestructure.DatabaseMessageMapper
import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl
......@@ -53,11 +53,13 @@ import chat.rocket.android.server.infraestructure.SharedPreferencesAccountsRepos
import chat.rocket.android.server.infraestructure.SharedPreferencesPermissionsRepository
import chat.rocket.android.server.infraestructure.SharedPreferencesSettingsRepository
import chat.rocket.android.server.infraestructure.SharedPrefsAnalyticsTrackingRepository
import chat.rocket.android.server.infraestructure.SharedPrefsBasicAuthRepository
import chat.rocket.android.server.infraestructure.SharedPrefsConnectingServerRepository
import chat.rocket.android.server.infraestructure.SharedPrefsCurrentServerRepository
import chat.rocket.android.server.infraestructure.SharedPrefsSortingAndGroupingRepository
import chat.rocket.android.util.AppJsonAdapterFactory
import chat.rocket.android.util.HttpLoggingInterceptor
import chat.rocket.android.util.BasicAuthenticatorInterceptor
import chat.rocket.android.util.HttpLoggingInterceptor
import chat.rocket.android.util.TimberLogger
import chat.rocket.common.internal.FallbackSealedClassJsonAdapter
import chat.rocket.common.internal.ISO8601Date
......@@ -123,7 +125,10 @@ class AppModule {
@Provides
@Singleton
fun provideOkHttpClient(logger: HttpLoggingInterceptor, basicAuthenticator: BasicAuthenticatorInterceptor): OkHttpClient {
fun provideOkHttpClient(
logger: HttpLoggingInterceptor,
basicAuthenticator: BasicAuthenticatorInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(logger)
.addInterceptor(basicAuthenticator)
......@@ -194,6 +199,12 @@ class AppModule {
return SharedPrefsAnalyticsTrackingRepository(prefs)
}
@Provides
@Singleton
fun provideSortingAndGroupingRepository(prefs: SharedPreferences): SortingAndGroupingRepository {
return SharedPrefsSortingAndGroupingRepository(prefs)
}
@Provides
@ForAuthentication
fun provideConnectingServerRepository(prefs: SharedPreferences): CurrentServerRepository {
......@@ -293,10 +304,10 @@ class AppModule {
@Provides
@Singleton
fun provideBasicAuthRepository (
fun provideBasicAuthRepository(
preferences: SharedPreferences,
moshi: Moshi
): BasicAuthRepository =
): BasicAuthRepository =
SharedPrefsBasicAuthRepository(preferences, moshi)
@Provides
......
......@@ -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>>) {
GlobalScope.launch(dbManagerContext) {
......
......@@ -5,7 +5,6 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
......@@ -23,7 +22,7 @@ class FavoriteMessagesPresenter @Inject constructor(
private val mapper: UiModelMapper,
val factory: RocketChatClientFactory
) {
private val client: RocketChatClient = factory.create(currentServer)
private val client: RocketChatClient = factory.get(currentServer)
private var offset: Int = 0
/**
......
......@@ -7,7 +7,6 @@ import chat.rocket.android.files.uimodel.FileUiModel
import chat.rocket.android.files.uimodel.FileUiModelMapper
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
......@@ -25,7 +24,7 @@ class FilesPresenter @Inject constructor(
private val mapper: FileUiModelMapper,
val factory: RocketChatClientFactory
) {
private val client: RocketChatClient = factory.create(currentServer)
private val client: RocketChatClient = factory.get(currentServer)
private var offset: Int = 0
/**
......
package chat.rocket.android.helper
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.view.ContextThemeWrapper
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
object AndroidPermissionsHelper {
const val WRITE_EXTERNAL_STORAGE_CODE = 1
const val CAMERA_CODE = 2
fun checkPermission(context: Context, permission: String): Boolean {
private fun checkPermission(context: Context, permission: String): Boolean {
return ContextCompat.checkSelfPermission(
context,
permission
) == PackageManager.PERMISSION_GRANTED
}
fun requestPermission(context: Activity, permission: String, requestCode: Int) {
private fun requestPermission(context: Activity, permission: String, requestCode: Int) {
ActivityCompat.requestPermissions(context, arrayOf(permission), requestCode)
}
fun hasCameraPermission(context: Context): Boolean {
return AndroidPermissionsHelper.checkPermission(context, Manifest.permission.CAMERA)
}
fun getCameraPermission(fragment: Fragment) {
fragment.requestPermissions(
arrayOf(Manifest.permission.CAMERA),
CAMERA_CODE
)
}
fun hasWriteExternalStoragePermission(context: Context): Boolean {
return checkPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
fun getWriteExternalStoragePermission(fragment: Fragment) {
fragment.requestPermissions(
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
WRITE_EXTERNAL_STORAGE_CODE
)
}
fun checkWritingPermission(context: Context) {
if (context is ContextThemeWrapper) {
val activity = if (context.baseContext is Activity) context.baseContext as Activity else context as Activity
AndroidPermissionsHelper.requestPermission(
activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE
)
}
}
}
\ No newline at end of file
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.Manifest
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
......@@ -9,7 +7,6 @@ import android.media.MediaScannerConnection
import android.os.Environment
import android.text.TextUtils
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
......@@ -18,6 +15,8 @@ import androidx.appcompat.widget.Toolbar
import androidx.core.net.toUri
import androidx.core.view.setPadding
import chat.rocket.android.R
import chat.rocket.android.helper.AndroidPermissionsHelper.checkWritingPermission
import chat.rocket.android.helper.AndroidPermissionsHelper.hasWriteExternalStoragePermission
import com.facebook.binaryresource.FileBinaryResource
import com.facebook.cache.common.CacheKey
import com.facebook.imageformat.ImageFormatChecker
......@@ -117,7 +116,7 @@ object ImageHelper {
}
private fun saveImage(context: Context): Boolean {
if (!canWriteToExternalStorage(context)) {
if (!hasWriteExternalStoragePermission(context)) {
checkWritingPermission(context)
return false
}
......@@ -152,22 +151,4 @@ object ImageHelper {
}
return true
}
fun canWriteToExternalStorage(context: Context): Boolean {
return AndroidPermissionsHelper.checkPermission(
context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
fun checkWritingPermission(context: Context) {
if (context is ContextThemeWrapper) {
val activity = if (context.baseContext is Activity) context.baseContext as Activity else context as Activity
AndroidPermissionsHelper.requestPermission(
activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
AndroidPermissionsHelper.WRITE_EXTERNAL_STORAGE_CODE
)
}
}
}
\ 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(
*/
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].
* 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
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerActivity
import chat.rocket.android.main.presentation.MainNavigator
import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.ui.MainActivity
import dagger.Module
import dagger.Provides
......@@ -13,16 +12,13 @@ import kotlinx.coroutines.Job
@Module
class MainModule {
@Provides
@PerActivity
fun provideJob() = Job()
@Provides
@PerActivity
fun provideMainNavigator(activity: MainActivity) = MainNavigator(activity)
@Provides
fun provideMainView(activity: MainActivity): MainView = activity
@PerActivity
fun provideJob() = Job()
@Provides
fun provideLifecycleOwner(activity: MainActivity): LifecycleOwner = activity
......
......@@ -3,51 +3,53 @@ package chat.rocket.android.main.presentation
import chat.rocket.android.R
import chat.rocket.android.authentication.ui.newServerIntent
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.createchannel.ui.CreateChannelFragment
import chat.rocket.android.createchannel.ui.TAG_CREATE_CHANNEL_FRAGMENT
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.server.ui.changeServerIntent
import chat.rocket.android.settings.ui.SettingsFragment
import chat.rocket.android.settings.ui.TAG_SETTINGS_FRAGMENT
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) {
fun toChatList(chatRoomId: String? = null) {
activity.addFragment(TAG_CHAT_ROOMS_FRAGMENT, R.id.fragment_container) {
ChatRoomsFragment.newInstance(chatRoomId)
chat.rocket.android.chatrooms.ui.newInstance(chatRoomId)
}
}
fun toCreateChannel() {
activity.addFragment(TAG_CREATE_CHANNEL_FRAGMENT, R.id.fragment_container) {
CreateChannelFragment.newInstance()
fun toSettings() {
activity.addFragmentBackStack(TAG_SETTINGS_FRAGMENT, R.id.fragment_container) {
chat.rocket.android.settings.ui.newInstance()
}
}
fun toUserProfile() {
activity.addFragment(TAG_PROFILE_FRAGMENT, R.id.fragment_container) {
ProfileFragment.newInstance()
fun toCreateChannel() {
activity.addFragmentBackStack(TAG_CREATE_CHANNEL_FRAGMENT, R.id.fragment_container) {
chat.rocket.android.createchannel.ui.newInstance()
}
}
fun toSettings() {
activity.addFragment(TAG_SETTINGS_FRAGMENT, R.id.fragment_container) {
SettingsFragment.newInstance()
fun toProfile() {
activity.addFragmentBackStack(TAG_PROFILE_FRAGMENT, R.id.fragment_container) {
chat.rocket.android.profile.ui.newInstance()
}
}
fun toAdminPanel(webPageUrl: String, userToken: String) {
activity.addFragment("AdminPanelWebViewFragment", R.id.fragment_container) {
AdminPanelWebViewFragment.newInstance(webPageUrl, userToken)
activity.addFragmentBackStack(TAG_ADMIN_PANEL_WEB_VIEW_FRAGMENT, R.id.fragment_container) {
chat.rocket.android.webview.adminpanel.ui.newInstance(webPageUrl, userToken)
}
}
fun toLicense(licenseUrl: String, licenseTitle: String) {
activity.startActivity(activity.webViewIntent(licenseUrl, licenseTitle))
}
fun toChatRoom(
chatRoomId: 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(
val factory: RocketChatClientFactory,
private val userHelper: UserHelper
) {
private val client: RocketChatClient = factory.create(currentServer)
private val client: RocketChatClient = factory.get(currentServer)
private var offset: Long = 0
/**
......
......@@ -18,7 +18,7 @@ class MentionsPresenter @Inject constructor(
private val mapper: UiModelMapper,
val factory: RocketChatClientFactory
) {
private val client = factory.create(currentServer)
private val client = factory.get(currentServer)
private var offset: Long = 0
/**
......
......@@ -5,7 +5,6 @@ import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryDB
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.roomTypeOf
import chat.rocket.common.util.ifNull
......@@ -23,7 +22,7 @@ class PinnedMessagesPresenter @Inject constructor(
private val mapper: UiModelMapper,
val factory: RocketChatClientFactory
) {
private val client: RocketChatClient = factory.create(currentServer)
private val client: RocketChatClient = factory.get(currentServer)
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
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
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.toHex
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.retryIO
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.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.setAvatar
import chat.rocket.core.internal.rest.updateProfile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import javax.inject.Inject
......@@ -56,18 +55,23 @@ class ProfilePresenter @Inject constructor(
navigator = navigator
) {
private val serverUrl = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(serverUrl)
private val client: RocketChatClient = factory.get(serverUrl)
private val user = userHelper.user()
fun loadUserProfile() {
launchUI(strategy) {
view.showLoading()
try {
val me = retryIO(description = "serverInfo", times = 5) {
client.me()
}
view.showProfile(
serverUrl.avatarUrl(user?.username ?: ""),
user?.name ?: "",
user?.username ?: "",
user?.emails?.getOrNull(0)?.address ?: ""
me.status.toString(),
serverUrl.avatarUrl(me.username ?: ""),
me.name ?: "",
me.username ?: "",
me.emails?.getOrNull(0)?.address ?: ""
)
} catch (exception: RocketChatException) {
view.showMessage(exception)
......@@ -82,9 +86,17 @@ class ProfilePresenter @Inject constructor(
view.showLoading()
try {
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.showProfile(
user.status.toString(),
serverUrl.avatarUrl(user.username ?: ""),
name,
username,
......@@ -176,26 +188,17 @@ class ProfilePresenter @Inject constructor(
}
}
fun deleteAccount(password: String) {
fun updateStatus(status: UserStatus) {
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 { client.deleteOwnAccount(password.gethash().toHex().toLowerCase()) }
setupConnectionInfo(serverUrl)
logout(null)
}
} catch (exception: Exception) {
client.setDefaultStatus(status)
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
}
}
\ No newline at end of file
......@@ -9,12 +9,19 @@ interface ProfileView : TokenView, LoadingView, MessageView {
/**
* Shows the user profile.
*
* @param status The user status.
* @param avatarUrl The user avatar URL.
* @param name The user display name.
* @param username The user username.
* @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).
......
......@@ -2,7 +2,6 @@ package chat.rocket.android.profile.ui
import DrawableHelper
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import android.content.Intent
import android.graphics.Bitmap
import android.os.Build
......@@ -12,8 +11,8 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.MenuInflater
import android.widget.EditText
import android.widget.RadioGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.net.toUri
......@@ -33,10 +32,13 @@ import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui
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 dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.avatar_profile.*
import kotlinx.android.synthetic.main.fragment_profile.*
import kotlinx.android.synthetic.main.update_avatar_options.*
......@@ -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_CAMERA = 2
fun newInstance() = ProfileFragment()
class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
@Inject
lateinit var presenter: ProfilePresenter
@Inject
lateinit var analyticsManager: AnalyticsManager
@Inject lateinit var presenter: ProfilePresenter
@Inject lateinit var analyticsManager: AnalyticsManager
private var currentStatus = ""
private var currentName = ""
private var currentUsername = ""
private var currentEmail = ""
private var actionMode: ActionMode? = null
private val editTextsDisposable = CompositeDisposable()
companion object {
fun newInstance() = ProfileFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
AndroidSupportInjection.inject(this)
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
}
......@@ -78,11 +77,12 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
setupListeners()
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
presenter.loadUserProfile()
setupListeners()
subscribeEditTexts()
analyticsManager.logScreenView(ScreenViewEvent.Profile)
......@@ -112,25 +112,21 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
super.onPrepareOptionsMenu(menu)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.profile, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_delete_account -> showDeleteAccountDialog()
}
return true
}
override fun showProfile(avatarUrl: String, name: String, username: String, email: String?) {
override fun showProfile(
status: String,
avatarUrl: String,
name: String,
username: String,
email: String?
) {
ui {
text_status.text = getString(R.string.status, status.capitalize())
image_avatar.setImageURI(avatarUrl)
text_name.textContent = name
text_username.textContent = username
text_email.textContent = email ?: ""
currentStatus = status
currentName = name
currentUsername = username
currentEmail = email ?: ""
......@@ -142,7 +138,6 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
override fun reloadUserAvatar(avatarUrl: String) {
Fresco.getImagePipeline().evictFromCache(avatarUrl.toUri())
image_avatar.setImageURI(avatarUrl)
(activity as MainActivity).setAvatar(avatarUrl)
}
override fun showProfileUpdateSuccessfullyMessage() {
......@@ -205,10 +200,19 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
}
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() {
text_status.setOnClickListener { showStatusDialog(currentStatus) }
image_avatar.setOnClickListener { showUpdateAvatarOptions() }
view_dim.setOnClickListener { hideUpdateAvatarOptions() }
......@@ -293,15 +297,38 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
}
}
fun showDeleteAccountDialog() {
context?.let {
val passwordEText = EditText(context);
val mDialogView = LayoutInflater.from(it).inflate(R.layout.item_account_delete, null)
val mBuilder = AlertDialog.Builder(it)
private fun showStatusDialog(currentStatus: String) {
val dialogLayout = layoutInflater.inflate(R.layout.dialog_status, null)
val radioGroup = dialogLayout.findViewById<RadioGroup>(R.id.radio_group_status)
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) { _, _ ->
presenter.deleteAccount(passwordEText.text.toString())
}.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }.create().show()
context?.let {
AlertDialog.Builder(it)
.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(
}
fun canSeeTheAdminPanel(): Boolean {
fun isAdministrationEnabled(): Boolean {
currentServerUrl()?.let { serverUrl ->
val viewStatistics =
permissionsRepository.get(serverUrl, VIEW_STATISTICS)
......
......@@ -20,7 +20,7 @@ class RefreshPermissionsInteractor @Inject constructor(
fun refreshAsync(server: String) {
GlobalScope.launch(Dispatchers.IO) {
try {
factory.create(server).let { client ->
factory.get(server).let { client ->
val permissions = retryIO(
description = "permissions",
times = 5,
......
......@@ -74,7 +74,7 @@ class RefreshSettingsInteractor @Inject constructor(
suspend fun refresh(server: String) {
withContext(Dispatchers.IO) {
factory.create(server).let { client ->
factory.get(server).let { client ->
val settings = retryIO(
description = "settings",
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
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.infrastructure.LocalRepository
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
......@@ -20,7 +19,7 @@ class ConnectionManagerFactory @Inject constructor(
}
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
return manager
}
......
......@@ -18,7 +18,7 @@ class RocketChatClientFactory @Inject constructor(
) {
private val cache = HashMap<String, RocketChatClient>()
fun create(url: String): RocketChatClient {
fun get(url: String): RocketChatClient {
cache[url]?.let {
Timber.d("Returning CACHED client for: $url")
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(
internal fun setupConnectionInfo(serverUrl: String) {
currentServer = serverUrl
client = factory.create(serverUrl)
client = factory.get(serverUrl)
managerFactory?.create(serverUrl)?.let {
manager = it
}
......
package chat.rocket.android.main.adapter
package chat.rocket.android.servers.adapter
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class AddAccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
\ No newline at end of file
class AddNewServerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
\ 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 androidx.core.view.isInvisible
import androidx.recyclerview.widget.RecyclerView
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) {
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_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 androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.server.domain.model.Account
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_ACCOUNT = 1
private const val VIEW_TYPE_ADD_ACCOUNT = 2
private const val VIEW_TYPE_SERVER = 0
private const val VIEW_TYPE_ADD_NEW_SERVER = 1
class AccountsAdapter(
private val accounts: List<Account>,
class ServersAdapter(
private val servers: List<Account>,
private val currentServerUrl: String,
private val selector: Selector
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_CHANGE_STATUS -> StatusViewHolder(parent.inflate(R.layout.item_change_status))
VIEW_TYPE_ACCOUNT -> AccountViewHolder(parent.inflate(R.layout.item_account))
else -> AddAccountViewHolder(parent.inflate(R.layout.item_add_account))
VIEW_TYPE_SERVER -> ServerViewHolder(
parent.inflate(R.layout.item_server), currentServerUrl
)
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 {
return when {
position == 0 -> VIEW_TYPE_CHANGE_STATUS
position <= accounts.size -> VIEW_TYPE_ACCOUNT
else -> VIEW_TYPE_ADD_ACCOUNT
position < servers.size -> VIEW_TYPE_SERVER
else -> VIEW_TYPE_ADD_NEW_SERVER
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is StatusViewHolder -> bindStatusViewHolder(holder)
is AccountViewHolder -> bindAccountViewHolder(holder, position)
is AddAccountViewHolder -> bindAddAccountViewHolder(holder)
is ServerViewHolder -> bindServerViewHolder(holder, position)
is AddNewServerViewHolder -> bindAddNewServerViewHolder(holder)
}
}
private fun bindStatusViewHolder(holder: StatusViewHolder) {
holder.bind { userStatus -> selector.onStatusSelected(userStatus) }
}
private fun bindAccountViewHolder(holder: AccountViewHolder, position: Int) {
val account = accounts[position - 1]
private fun bindServerViewHolder(holder: ServerViewHolder, position: Int) {
val account = servers[position]
holder.bind(account)
holder.itemView.setOnClickListener {
selector.onAccountSelected(account.serverUrl)
}
holder.itemView.setOnClickListener { selector.onServerSelected(account.serverUrl) }
}
private fun bindAddAccountViewHolder(holder: AddAccountViewHolder) {
holder.itemView.setOnClickListener {
selector.onAddedAccountSelected()
}
private fun bindAddNewServerViewHolder(holder: AddNewServerViewHolder) {
holder.itemView.setOnClickListener { selector.onAddNewServerSelected() }
}
}
interface Selector {
fun onStatusSelected(userStatus: UserStatus)
fun onAccountSelected(serverUrl: String)
fun onAddedAccountSelected()
fun onServerSelected(serverUrl: String)
fun onAddNewServerSelected()
}
\ 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.preferences.presentation.PreferencesView
import chat.rocket.android.preferences.ui.PreferencesFragment
import chat.rocket.android.servers.presentation.ServersView
import chat.rocket.android.servers.ui.ServersBottomSheetFragment
import dagger.Module
import dagger.Provides
@Module
class PreferencesFragmentModule {
class ServersBottomSheetFragmentModule {
@Provides
@PerFragment
fun preferencesView(frag: PreferencesFragment): PreferencesView {
return frag
}
fun serversView(frag: ServersBottomSheetFragment): ServersView = 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.preferences.ui.PreferencesFragment
import chat.rocket.android.servers.ui.ServersBottomSheetFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class PreferencesFragmentProvider {
abstract class ServersBottomSheetFragmentProvider {
@ContributesAndroidInjector(modules = [PreferencesFragmentModule::class])
@ContributesAndroidInjector(modules = [ServersBottomSheetFragmentModule::class])
@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
import androidx.lifecycle.LifecycleOwner
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.settings.presentation.SettingsView
import chat.rocket.android.settings.ui.SettingsFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.Job
@Module
class SettingsFragmentModule {
......@@ -20,13 +18,7 @@ class SettingsFragmentModule {
@Provides
@PerFragment
fun settingsLifecycleOwner(frag: SettingsFragment): LifecycleOwner {
return frag
}
@Provides
@PerFragment
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
fun settingsLifecycleOwner(fragment: SettingsFragment): LifecycleOwner {
return fragment
}
}
\ No newline at end of file
package chat.rocket.android.settings.di
import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.settings.ui.SettingsFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......@@ -8,5 +9,6 @@ import dagger.android.ContributesAndroidInjector
abstract class SettingsFragmentProvider {
@ContributesAndroidInjector(modules = [SettingsFragmentModule::class])
@PerFragment
abstract fun provideSettingsFragment(): SettingsFragment
}
\ No newline at end of file
......@@ -8,7 +8,6 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.updateProfile
import javax.inject.Inject
......@@ -22,7 +21,7 @@ class PasswordPresenter @Inject constructor(
factory: RocketChatClientFactory
) {
private val serverUrl = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(serverUrl)
private val client: RocketChatClient = factory.get(serverUrl)
fun updatePassword(password: String) {
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
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
)
}
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
......@@ -20,6 +20,7 @@ import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.ui
import com.bumptech.glide.Glide
import com.bumptech.glide.load.MultiTransformation
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
......@@ -86,6 +87,8 @@ class UserDetailsFragment : Fragment(), UserDetailsView {
isVideoCallAllowed: Boolean
) {
val requestBuilder = Glide.with(this).load(avatarUrl)
.apply(RequestOptions.skipMemoryCacheOf(true))
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.NONE))
requestBuilder.apply(
RequestOptions.bitmapTransform(MultiTransformation(BlurTransformation(), CenterCrop()))
......@@ -147,4 +150,4 @@ class UserDetailsFragment : Fragment(), UserDetailsView {
private fun setupListeners() {
image_arrow_back.setOnClickListener { activity?.onBackPressed() }
}
}
\ No newline at end of file
}
......@@ -19,7 +19,7 @@ suspend fun RocketChatClientFactory.registerPushToken(
accounts.forEach { account ->
try {
retryIO(description = "register push token: ${account.serverUrl}") {
create(account.serverUrl).registerPushToken(token)
get(account.serverUrl).registerPushToken(token)
}
} catch (ex: Exception) {
Timber.d(ex, "Error registering Push token for ${account.serverUrl}")
......
......@@ -3,14 +3,15 @@ package chat.rocket.android.videoconference.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.os.bundleOf
import chat.rocket.android.videoconference.presenter.JitsiVideoConferenceView
import chat.rocket.android.videoconference.presenter.VideoConferencePresenter
import dagger.android.AndroidInjection
import org.jitsi.meet.sdk.JitsiMeetActivity
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
import org.jitsi.meet.sdk.JitsiMeetView
import org.jitsi.meet.sdk.JitsiMeetViewListener
import timber.log.Timber
import java.net.URL
import javax.inject.Inject
fun Context.videoConferenceIntent(chatRoomId: String, chatRoomType: String): Intent =
......@@ -23,8 +24,7 @@ private const val INTENT_CHAT_ROOM_TYPE = "chat_room_type"
class VideoConferenceActivity : JitsiMeetActivity(), JitsiVideoConferenceView,
JitsiMeetViewListener {
@Inject
lateinit var presenter: VideoConferencePresenter
@Inject lateinit var presenter: VideoConferencePresenter
private lateinit var chatRoomId: String
private lateinit var chatRoomType: String
private var view: JitsiMeetView? = null
......@@ -34,9 +34,7 @@ class VideoConferenceActivity : JitsiMeetActivity(), JitsiVideoConferenceView,
super.onCreate(savedInstanceState)
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" }
chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE)
requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" }
view = JitsiMeetView(this)
view?.listener = this
......@@ -52,34 +50,24 @@ class VideoConferenceActivity : JitsiMeetActivity(), JitsiVideoConferenceView,
override fun onConferenceJoined(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Joined video conferencing", map)
override fun onConferenceWillLeave(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Leaving video conferencing", map)
override fun onConferenceLeft(map: MutableMap<String, Any>?) {
logJitsiMeetViewState("Left video conferencing", map)
override fun onConferenceTerminated(map: MutableMap<String, Any>?) {
map?.let {
if (it.containsKey("error")) {
logJitsiMeetViewState("Terminated video conferencing with error", map)
} else {
logJitsiMeetViewState("Terminated video conferencing", map)
}
}
finishJitsiVideoConference()
}
override fun onLoadConfigError(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Error loading video conference config", map)
override fun onConferenceFailed(map: MutableMap<String, Any>?) =
logJitsiMeetViewState("Video conference failed", map)
override fun startJitsiVideoConference(url: String, name: String?) {
view?.loadURLObject(
bundleOf(
"config" to bundleOf(
"startWithAudioMuted" to true,
"startWithVideoMuted" to true
),
"context" to bundleOf(
"user" to bundleOf("name" to name),
"iss" to "rocketchat-android"
),
"url" to url
)
)
JitsiMeetConferenceOptions.Builder()
.setAudioMuted(true)
.setVideoMuted(true)
.setServerURL(URL(url))
.setAudioOnly(false)
.build().let { view?.join(it) }
}
override fun finishJitsiVideoConference() {
......
......@@ -16,9 +16,17 @@ import dagger.android.support.DaggerFragment
import kotlinx.android.synthetic.main.fragment_admin_panel_web_view.*
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_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() {
private lateinit var webPageUrl: String
private lateinit var userToken: String
......@@ -30,7 +38,8 @@ class AdminPanelWebViewFragment : DaggerFragment() {
arguments?.run {
webPageUrl = getString(BUNDLE_WEB_PAGE_URL, "")
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(
......@@ -49,7 +58,7 @@ class AdminPanelWebViewFragment : DaggerFragment() {
private fun setupToolbar() {
(activity as AppCompatActivity?)?.supportActionBar?.title =
getString(R.string.title_admin_panel)
getString(R.string.title_admin_panel)
}
@SuppressLint("SetJavaScriptEnabled")
......@@ -70,13 +79,4 @@ class AdminPanelWebViewFragment : DaggerFragment() {
}
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 @@
android:width="24dp"
android:height="24dp"
android:tint="#1D74F5"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF1D74F5"
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>
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.
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