Commit e0ef6159 authored by Filipe de Lima Brito's avatar Filipe de Lima Brito

Merge branch 'develop' of github.com:RocketChat/Rocket.Chat.Android into new/video-call-support

Also enables the video conferencing from the UserDetails fragment.
parents 85e5b838 3525893c
......@@ -43,8 +43,8 @@ cd Rocket.Chat.Android/app
## Bug report & Feature request
Are you having a technical issue trying to compile the app, or setting up Push Notifications? Please use our Community Support channel for that: https://forums.rocket.chat/c/community-support. The issues are only suppose to be used for bugs, improvements and features in the native Android application.
Are you having a technical issue trying to compile the app, or setting up Push Notifications? Please use our Community Support channel for that: https://forums.rocket.chat/c/community-support. The issues are only supposed to be used for bugs, improvements, and features in the native Android application.
## Coding Style
Please follow the official [Kotlin coding convections](https://kotlinlang.org/docs/reference/coding-conventions.html) when contributing.
Please follow the official [Kotlin coding conventions](https://kotlinlang.org/docs/reference/coding-conventions.html) when contributing.
......@@ -99,87 +99,68 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':player')
implementation project(':emoji')
implementation project(':draw')
implementation project(':util')
implementation project(':core')
implementation project(':suggestions')
implementation libraries.kotlin
implementation libraries.coroutinesCore
implementation libraries.coroutinesAndroid
implementation libraries.appCompat
implementation libraries.recyclerview
implementation libraries.constraintlayout
implementation libraries.cardview
implementation libraries.browser
implementation libraries.androidKtx
implementation libraries.fragmentsKtx
implementation libraries.dagger
implementation libraries.daggerSupport
kapt libraries.daggerProcessor
kapt libraries.daggerAndroidApt
implementation libraries.flexbox
implementation libraries.material
implementation libraries.room
kapt libraries.roomProcessor
implementation libraries.lifecycleExtensions
kapt libraries.lifecycleCompiler
implementation libraries.viewmodelKtx
implementation libraries.workmanager
implementation libraries.livedataKtx
implementation libraries.rxKotlin
implementation libraries.rxAndroid
implementation libraries.moshi
implementation libraries.okhttp
implementation libraries.okhttpLogger
implementation libraries.timber
implementation libraries.threeTenABP
kapt libraries.kotshiCompiler
implementation libraries.kotshiApi
implementation libraries.fresco
api libraries.frescoOkHttp
implementation libraries.frescoAnimatedGif
implementation libraries.frescoWebP
implementation libraries.frescoAnimatedWebP
implementation libraries.frescoImageViewer
implementation libraries.markwon
implementation libraries.aVLoadingIndicatorView
implementation libraries.glide
implementation libraries.glideTransformations
implementation(libraries.jitsi) { transitive = true }
implementation 'com.google.code.findbugs:jsr305:3.0.2'
// Proprietary libraries
playImplementation libraries.fcm
playImplementation libraries.firebaseAnalytics
playImplementation libraries.playServicesAuth
playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.8@aar') { transitive = true }
playImplementation('com.crashlytics.sdk.android:answers:1.4.6@aar') { transitive = true }
testImplementation libraries.junit
testImplementation libraries.truth
androidTestImplementation libraries.espressoCore
androidTestImplementation libraries.espressoIntents
implementation files('libs/core-2af5921.jar')
implementation files('libs/common-2af5921.jar')
}
androidExtensions {
......
......@@ -34,12 +34,16 @@ class AboutFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
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(
......
import android.content.Context
import chat.rocket.android.R
import org.threeten.bp.*
import org.threeten.bp.Instant
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalTime
import org.threeten.bp.Period
import org.threeten.bp.ZoneId
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.FormatStyle
import org.threeten.bp.format.TextStyle
......@@ -53,39 +58,39 @@ object DateTimeHelper {
}
}
/**
* Returns a time from a [LocalDateTime].
*
* @param localDateTime The [LocalDateTime].
* @return The time from a [LocalDateTime].
*/
fun getTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localDateTime.toLocalTime().format(formatter).toString()
}
/**
* Returns a time from a [LocalDateTime].
*
* @param localDateTime The [LocalDateTime].
* @return The time from a [LocalDateTime].
*/
fun getTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localDateTime.toLocalTime().format(formatter).toString()
}
/**
* Returns a date time from a [LocalDateTime].
*
* @param localDateTime The [LocalDateTime].
* @return The time from a [LocalDateTime].
*/
fun getDateTime(localDateTime: LocalDateTime): String {
return formatLocalDateTime(localDateTime)
}
/**
* Returns a date time from a [LocalDateTime].
*
* @param localDateTime The [LocalDateTime].
* @return The time from a [LocalDateTime].
*/
fun getDateTime(localDateTime: LocalDateTime): String {
return formatLocalDateTime(localDateTime)
}
private fun formatLocalDateTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
return localDateTime.format(formatter).toString()
}
private fun formatLocalDateTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
return localDateTime.format(formatter).toString()
}
private fun formatLocalDate(localDate: LocalDate): String {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
return localDate.format(formatter).toString()
}
private fun formatLocalDate(localDate: LocalDate): String {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
return localDate.format(formatter).toString()
}
private fun formatLocalTime(localTime: LocalTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localTime.format(formatter).toString()
}
private fun formatLocalTime(localTime: LocalTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localTime.format(formatter).toString()
}
}
\ No newline at end of file
......@@ -8,19 +8,16 @@ import chat.rocket.android.server.domain.MultiServerTokenRepository
import com.squareup.moshi.Moshi
@PerActivity
class SharedPreferencesMultiServerTokenRepository(private val repository: LocalRepository,
private val moshi: Moshi
class SharedPreferencesMultiServerTokenRepository(
private val repository: LocalRepository,
private val moshi: Moshi
) : MultiServerTokenRepository {
override fun get(server: String): TokenModel? {
val token = repository.get("$TOKEN_KEY$server")
val adapter = moshi.adapter<TokenModel>(TokenModel::class.java)
token?.let {
return adapter.fromJson(token)
}
return null
return token?.let { adapter.fromJson(it) }
}
override fun save(server: String, token: TokenModel) {
......
......@@ -39,11 +39,9 @@ internal const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1
internal const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2
internal const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3
fun newInstance(serverName: String): Fragment {
return LoginFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_NAME, serverName)
}
fun newInstance(serverName: String): Fragment = LoginFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_NAME, serverName)
}
}
......@@ -59,9 +57,8 @@ class LoginFragment : Fragment(), LoginView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
serverName = bundle.getString(SERVER_NAME)
arguments?.run {
serverName = getString(SERVER_NAME)
}
}
......@@ -150,7 +147,7 @@ class LoginFragment : Fragment(), LoginView {
override fun showGenericErrorMessage() = showMessage(R.string.msg_generic_error)
private fun setupOnClickListener() =
ui { _ ->
ui {
button_log_in.setOnClickListener {
presenter.authenticateWithUserAndPassword(
text_username_or_email.textContent,
......@@ -160,7 +157,7 @@ class LoginFragment : Fragment(), LoginView {
}
override fun showForgotPasswordView() {
ui { _ ->
ui {
button_forgot_your_password.isVisible = true
button_forgot_your_password.setOnClickListener { presenter.forgotPassword() }
......
......@@ -88,36 +88,34 @@ fun newInstance(
isLoginFormEnabled: Boolean,
isNewAccountCreationEnabled: Boolean,
deepLinkInfo: LoginDeepLinkInfo? = null
): Fragment {
return LoginOptionsFragment().apply {
arguments = Bundle(23).apply {
putString(SERVER_NAME, serverName)
putString(STATE, state)
putString(FACEBOOK_OAUTH_URL, facebookOauthUrl)
putString(GITHUB_OAUTH_URL, githubOauthUrl)
putString(GOOGLE_OAUTH_URL, googleOauthUrl)
putString(LINKEDIN_OAUTH_URL, linkedinOauthUrl)
putString(GITLAB_OAUTH_URL, gitlabOauthUrl)
putString(WORDPRESS_OAUTH_URL, wordpressOauthUrl)
putString(CAS_LOGIN_URL, casLoginUrl)
putString(CAS_TOKEN, casToken)
putString(CAS_SERVICE_NAME, casServiceName)
putInt(CAS_SERVICE_NAME_TEXT_COLOR, casServiceNameTextColor)
putInt(CAS_SERVICE_BUTTON_COLOR, casServiceButtonColor)
putString(CUSTOM_OAUTH_URL, customOauthUrl)
putString(CUSTOM_OAUTH_SERVICE_NAME, customOauthServiceName)
putInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR, customOauthServiceNameTextColor)
putInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR, customOauthServiceButtonColor)
putString(SAML_URL, samlUrl)
putString(SAML_TOKEN, samlToken)
putString(SAML_SERVICE_NAME, samlServiceName)
putInt(SAML_SERVICE_NAME_TEXT_COLOR, samlServiceNameTextColor)
putInt(SAML_SERVICE_BUTTON_COLOR, samlServiceButtonColor)
putInt(TOTAL_SOCIAL_ACCOUNTS, totalSocialAccountsEnabled)
putBoolean(IS_LOGIN_FORM_ENABLED, isLoginFormEnabled)
putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled)
putParcelable(DEEP_LINK_INFO, deepLinkInfo)
}
): Fragment = LoginOptionsFragment().apply {
arguments = Bundle(23).apply {
putString(SERVER_NAME, serverName)
putString(STATE, state)
putString(FACEBOOK_OAUTH_URL, facebookOauthUrl)
putString(GITHUB_OAUTH_URL, githubOauthUrl)
putString(GOOGLE_OAUTH_URL, googleOauthUrl)
putString(LINKEDIN_OAUTH_URL, linkedinOauthUrl)
putString(GITLAB_OAUTH_URL, gitlabOauthUrl)
putString(WORDPRESS_OAUTH_URL, wordpressOauthUrl)
putString(CAS_LOGIN_URL, casLoginUrl)
putString(CAS_TOKEN, casToken)
putString(CAS_SERVICE_NAME, casServiceName)
putInt(CAS_SERVICE_NAME_TEXT_COLOR, casServiceNameTextColor)
putInt(CAS_SERVICE_BUTTON_COLOR, casServiceButtonColor)
putString(CUSTOM_OAUTH_URL, customOauthUrl)
putString(CUSTOM_OAUTH_SERVICE_NAME, customOauthServiceName)
putInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR, customOauthServiceNameTextColor)
putInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR, customOauthServiceButtonColor)
putString(SAML_URL, samlUrl)
putString(SAML_TOKEN, samlToken)
putString(SAML_SERVICE_NAME, samlServiceName)
putInt(SAML_SERVICE_NAME_TEXT_COLOR, samlServiceNameTextColor)
putInt(SAML_SERVICE_BUTTON_COLOR, samlServiceButtonColor)
putInt(TOTAL_SOCIAL_ACCOUNTS, totalSocialAccountsEnabled)
putBoolean(IS_LOGIN_FORM_ENABLED, isLoginFormEnabled)
putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled)
putParcelable(DEEP_LINK_INFO, deepLinkInfo)
}
}
......@@ -157,34 +155,33 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
serverName = bundle.getString(SERVER_NAME)
state = bundle.getString(STATE)
facebookOauthUrl = bundle.getString(FACEBOOK_OAUTH_URL)
githubOauthUrl = bundle.getString(GITHUB_OAUTH_URL)
googleOauthUrl = bundle.getString(GOOGLE_OAUTH_URL)
linkedinOauthUrl = bundle.getString(LINKEDIN_OAUTH_URL)
gitlabOauthUrl = bundle.getString(GITLAB_OAUTH_URL)
wordpressOauthUrl = bundle.getString(WORDPRESS_OAUTH_URL)
casLoginUrl = bundle.getString(CAS_LOGIN_URL)
casToken = bundle.getString(CAS_TOKEN)
casServiceName = bundle.getString(CAS_SERVICE_NAME)
casServiceNameTextColor = bundle.getInt(CAS_SERVICE_NAME_TEXT_COLOR)
casServiceButtonColor = bundle.getInt(CAS_SERVICE_BUTTON_COLOR)
customOauthUrl = bundle.getString(CUSTOM_OAUTH_URL)
customOauthServiceName = bundle.getString(CUSTOM_OAUTH_SERVICE_NAME)
customOauthServiceTextColor = bundle.getInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR)
customOauthServiceButtonColor = bundle.getInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR)
samlUrl = bundle.getString(SAML_URL)
samlToken = bundle.getString(SAML_TOKEN)
samlServiceName = bundle.getString(SAML_SERVICE_NAME)
samlServiceTextColor = bundle.getInt(SAML_SERVICE_NAME_TEXT_COLOR)
samlServiceButtonColor = bundle.getInt(SAML_SERVICE_BUTTON_COLOR)
totalSocialAccountsEnabled = bundle.getInt(TOTAL_SOCIAL_ACCOUNTS)
isLoginFormEnabled = bundle.getBoolean(IS_LOGIN_FORM_ENABLED)
isNewAccountCreationEnabled = bundle.getBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED)
deepLinkInfo = bundle.getParcelable(DEEP_LINK_INFO)
arguments?.run {
serverName = getString(SERVER_NAME)
state = getString(STATE)
facebookOauthUrl = getString(FACEBOOK_OAUTH_URL)
githubOauthUrl = getString(GITHUB_OAUTH_URL)
googleOauthUrl = getString(GOOGLE_OAUTH_URL)
linkedinOauthUrl = getString(LINKEDIN_OAUTH_URL)
gitlabOauthUrl = getString(GITLAB_OAUTH_URL)
wordpressOauthUrl = getString(WORDPRESS_OAUTH_URL)
casLoginUrl = getString(CAS_LOGIN_URL)
casToken = getString(CAS_TOKEN)
casServiceName = getString(CAS_SERVICE_NAME)
casServiceNameTextColor = getInt(CAS_SERVICE_NAME_TEXT_COLOR)
casServiceButtonColor = getInt(CAS_SERVICE_BUTTON_COLOR)
customOauthUrl = getString(CUSTOM_OAUTH_URL)
customOauthServiceName = getString(CUSTOM_OAUTH_SERVICE_NAME)
customOauthServiceTextColor = getInt(CUSTOM_OAUTH_SERVICE_NAME_TEXT_COLOR)
customOauthServiceButtonColor = getInt(CUSTOM_OAUTH_SERVICE_BUTTON_COLOR)
samlUrl = getString(SAML_URL)
samlToken = getString(SAML_TOKEN)
samlServiceName = getString(SAML_SERVICE_NAME)
samlServiceTextColor = getInt(SAML_SERVICE_NAME_TEXT_COLOR)
samlServiceButtonColor = getInt(SAML_SERVICE_BUTTON_COLOR)
totalSocialAccountsEnabled = getInt(TOTAL_SOCIAL_ACCOUNTS)
isLoginFormEnabled = getBoolean(IS_LOGIN_FORM_ENABLED)
isNewAccountCreationEnabled = getBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED)
deepLinkInfo = getParcelable(DEEP_LINK_INFO)
}
}
......@@ -388,7 +385,7 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
}
override fun setupExpandAccountsView() {
ui { _ ->
ui {
expand_more_accounts_container.isVisible = true
var isAccountsCollapsed = true
button_expand_collapse_accounts.setOnClickListener {
......@@ -406,14 +403,14 @@ class LoginOptionsFragment : Fragment(), LoginOptionsView {
}
override fun showLoginWithEmailButton() {
ui { _ ->
ui {
button_login_with_email.setOnClickListener { presenter.toLoginWithEmail() }
button_login_with_email.isVisible = true
}
}
override fun showCreateNewAccountButton() {
ui { _ ->
ui {
button_create_an_account.setOnClickListener { presenter.toCreateAccount() }
button_create_an_account.isVisible = true
}
......
......@@ -31,12 +31,10 @@ import javax.inject.Inject
private const val BUNDLE_USER_ID = "user_id"
private const val BUNDLE_AUTH_TOKEN = "auth_token"
fun newInstance(userId: String, authToken: String): Fragment {
return RegisterUsernameFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_USER_ID, userId)
putString(BUNDLE_AUTH_TOKEN, authToken)
}
fun newInstance(userId: String, authToken: String): Fragment = RegisterUsernameFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_USER_ID, userId)
putString(BUNDLE_AUTH_TOKEN, authToken)
}
}
......@@ -53,13 +51,10 @@ class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
userId = bundle.getString(BUNDLE_USER_ID)
authToken = bundle.getString(BUNDLE_AUTH_TOKEN)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
userId = getString(BUNDLE_USER_ID, "")
authToken = getString(BUNDLE_AUTH_TOKEN, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......
......@@ -75,7 +75,7 @@ class SignupFragment : Fragment(), SignupView {
}
private fun setupOnClickListener() =
ui { _ ->
ui {
button_register.setOnClickListener {
presenter.signup(
text_username.textContent,
......
......@@ -25,12 +25,10 @@ import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
import javax.inject.Inject
fun newInstance(username: String, password: String): Fragment {
return TwoFAFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_USERNAME, username)
putString(BUNDLE_PASSWORD, password)
}
fun newInstance(username: String, password: String): Fragment = TwoFAFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_USERNAME, username)
putString(BUNDLE_PASSWORD, password)
}
}
......@@ -50,13 +48,10 @@ class TwoFAFragment : Fragment(), TwoFAView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
username = bundle.getString(BUNDLE_USERNAME)
password = bundle.getString(BUNDLE_PASSWORD)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
username = getString(BUNDLE_USERNAME, "")
password = getString(BUNDLE_PASSWORD, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......
package chat.rocket.android.chatdetails.adapter
import android.content.Context
import DrawableHelper
import android.view.View
import android.widget.ImageView
import android.widget.TextView
......
......@@ -11,9 +11,15 @@ import chat.rocket.android.util.extensions.inflate
class ChatDetailsAdapter: RecyclerView.Adapter<OptionViewHolder>() {
private val options: MutableList<Option> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionViewHolder = OptionViewHolder(parent.inflate(R.layout.item_detail_option))
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): OptionViewHolder = OptionViewHolder(parent.inflate(R.layout.item_detail_option))
override fun onBindViewHolder(holder: OptionViewHolder, position: Int) = holder.bindViews(OptionItemHolder(options[position]))
override fun onBindViewHolder(
holder: OptionViewHolder,
position: Int
) = holder.bindViews(OptionItemHolder(options[position]))
override fun getItemCount(): Int = options.size
......
......@@ -46,7 +46,7 @@ fun newInstance(
putBoolean(BUNDLE_IS_FAVORITE, isFavorite)
putBoolean(BUNDLE_DISABLE_MENU, disableMenu)
}
}
}
}
internal const val TAG_CHAT_DETAILS_FRAGMENT = "ChatDetailsFragment"
......@@ -81,16 +81,13 @@ class ChatDetailsFragment : Fragment(), ChatDetailsView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE)
isSubscribed = bundle.getBoolean(BUNDLE_IS_SUBSCRIBED)
isFavorite = bundle.getBoolean(BUNDLE_IS_FAVORITE)
disableMenu = bundle.getBoolean(BUNDLE_DISABLE_MENU)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID)
chatRoomType = getString(BUNDLE_CHAT_ROOM_TYPE)
isSubscribed = getBoolean(BUNDLE_IS_SUBSCRIBED)
isFavorite = getBoolean(BUNDLE_IS_FAVORITE)
disableMenu = getBoolean(BUNDLE_DISABLE_MENU)
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
setHasOptionsMenu(true)
}
......
......@@ -5,9 +5,10 @@ import androidx.lifecycle.ViewModelProvider
import chat.rocket.android.db.ChatRoomDao
import javax.inject.Inject
class ChatDetailsViewModelFactory @Inject constructor(private val chatRoomDao: ChatRoomDao) : ViewModelProvider.NewInstanceFactory() {
class ChatDetailsViewModelFactory @Inject constructor(
private val chatRoomDao: ChatRoomDao
) : ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>) =
ChatDetailsViewModel(chatRoomDao) as T
override fun <T : ViewModel?> create(modelClass: Class<T>) = ChatDetailsViewModel(chatRoomDao) as T
}
\ No newline at end of file
......@@ -12,7 +12,10 @@ import com.facebook.drawee.backends.pipeline.Fresco
import kotlinx.android.synthetic.main.item_action_button.view.*
import timber.log.Timber
class ActionsListAdapter(actions: List<Action>, var actionAttachmentOnClickListener: ActionAttachmentOnClickListener) : RecyclerView.Adapter<ActionsListAdapter.ViewHolder>() {
class ActionsListAdapter(
actions: List<Action>,
var actionAttachmentOnClickListener: ActionAttachmentOnClickListener
) : RecyclerView.Adapter<ActionsListAdapter.ViewHolder>() {
var actions: List<Action> = actions
......@@ -62,9 +65,7 @@ class ActionsListAdapter(actions: List<Action>, var actionAttachmentOnClickListe
return ViewHolder(view)
}
override fun getItemCount(): Int {
return actions.size
}
override fun getItemCount(): Int = actions.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val action = actions[position]
......
......@@ -73,13 +73,9 @@ class ChatRoomAdapter(
}
}
override fun getItemViewType(position: Int): Int {
return dataSet[position].viewType
}
override fun getItemViewType(position: Int): Int = dataSet[position].viewType
override fun getItemCount(): Int {
return dataSet.size
}
override fun getItemCount(): Int = dataSet.size
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
if (holder !is MessageViewHolder) {
......@@ -171,12 +167,12 @@ class ChatRoomAdapter(
Timber.d("index: $index")
if (index > -1) {
dataSet[index] = message
dataSet.forEachIndexed { index, viewModel ->
dataSet.forEachIndexed { ind, viewModel ->
if (viewModel.messageId == message.messageId) {
if (viewModel.nextDownStreamMessage == null) {
viewModel.reactions = message.reactions
}
notifyItemChanged(index)
notifyItemChanged(ind)
}
}
// Delete message only if current is a system message update, i.e.: Message Removed
......@@ -255,10 +251,10 @@ class ChatRoomAdapter(
actionSelectListener?.editMessage(roomId, id, message.message)
}
R.id.action_message_star -> {
actionSelectListener?.toogleStar(id, !item.isChecked)
actionSelectListener?.toggleStar(id, !item.isChecked)
}
R.id.action_message_unpin -> {
actionSelectListener?.tooglePin(id, !item.isChecked)
actionSelectListener?.togglePin(id, !item.isChecked)
}
R.id.action_message_delete -> {
actionSelectListener?.deleteMessage(roomId, id)
......@@ -295,9 +291,9 @@ class ChatRoomAdapter(
fun editMessage(roomId: String, messageId: String, text: String)
fun toogleStar(id: String, star: Boolean)
fun toggleStar(id: String, star: Boolean)
fun tooglePin(id: String, pin: Boolean)
fun togglePin(id: String, pin: Boolean)
fun deleteMessage(roomId: String, id: String)
......
......@@ -24,8 +24,8 @@ class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#"
override fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) {
item as ChatRoomSuggestionUiModel
with(itemView) {
val fullname = itemView.findViewById<TextView>(R.id.text_fullname)
val name = itemView.findViewById<TextView>(R.id.text_name)
val fullname = findViewById<TextView>(R.id.text_fullname)
val name = findViewById<TextView>(R.id.text_name)
name.text = item.name
fullname.text = item.fullName
setOnClickListener {
......
......@@ -15,9 +15,7 @@ class UrlPreviewViewHolder(
) : BaseViewHolder<UrlPreviewUiModel>(itemView, listener, reactionListener) {
init {
with(itemView) {
setupActionMenu(url_preview_layout)
}
setupActionMenu(itemView.url_preview_layout)
}
override fun bindViews(data: UrlPreviewUiModel) {
......
......@@ -180,8 +180,8 @@ class ChatRoomPresenter @Inject constructor(
chatRoomId?.let {
manager.addRoomChannel(it, roomChangesChannel)
for (room in roomChangesChannel) {
dbManager.getRoom(room.id)?.let {
view.onRoomUpdated(roomMapper.map(chatRoom = it, showLastMessage = true))
dbManager.getRoom(room.id)?.let { chatRoom ->
view.onRoomUpdated(roomMapper.map(chatRoom = chatRoom, showLastMessage = true))
}
}
}
......@@ -314,16 +314,18 @@ class ChatRoomPresenter @Inject constructor(
fun sendMessage(chatRoomId: String, text: String, messageId: String?) {
launchUI(strategy) {
try {
view.disableSendMessageButton()
// ignore message for now, will receive it on the stream
if (messageId == null) {
val id = UUID.randomUUID().toString()
val username = userHelper.username()
val user = userHelper.user()
val newMessage = Message(
id = id,
roomId = chatRoomId,
message = text,
timestamp = Instant.now().toEpochMilli(),
sender = SimpleUser(null, username, username),
sender = SimpleUser(user?.id, user?.username ?: username, user?.name),
attachments = null,
avatar = currentServer.avatarUrl(username ?: ""),
channels = null,
......@@ -1052,7 +1054,7 @@ class ChatRoomPresenter @Inject constructor(
launchUI(strategy) {
try {
messagesRepository.getById(messageId)?.let { message ->
getChatRoomAsync(message.roomId)?.let { chatRoom ->
getChatRoomAsync(message.roomId)?.let {
val models = mapper.map(message)
models.firstOrNull()?.permalink?.let {
view.copyToClipboard(it)
......@@ -1273,9 +1275,7 @@ class ChatRoomPresenter @Inject constructor(
* @param unfinishedMessage The unfinished message to save.
*/
fun saveDraftMessage(unfinishedMessage: String) {
if (unfinishedMessage.isNotBlank()) {
localRepository.save(draftKey, unfinishedMessage)
}
localRepository.save(draftKey, unfinishedMessage)
}
fun clearDraftMessage() {
......
......@@ -30,18 +30,16 @@ fun Context.chatRoomIntent(
isCreator: Boolean = false,
isFavorite: Boolean = false,
chatRoomMessage: String? = null
): Intent {
return Intent(this, ChatRoomActivity::class.java).apply {
putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
putExtra(INTENT_CHAT_ROOM_NAME, chatRoomName)
putExtra(INTENT_CHAT_ROOM_TYPE, chatRoomType)
putExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, isReadOnly)
putExtra(INTENT_CHAT_ROOM_LAST_SEEN, chatRoomLastSeen)
putExtra(INTENT_CHAT_IS_SUBSCRIBED, isSubscribed)
putExtra(INTENT_CHAT_ROOM_IS_CREATOR, isCreator)
putExtra(INTENT_CHAT_ROOM_IS_FAVORITE, isFavorite)
putExtra(INTENT_CHAT_ROOM_MESSAGE, chatRoomMessage)
}
): Intent = Intent(this, ChatRoomActivity::class.java).apply {
putExtra(INTENT_CHAT_ROOM_ID, chatRoomId)
putExtra(INTENT_CHAT_ROOM_NAME, chatRoomName)
putExtra(INTENT_CHAT_ROOM_TYPE, chatRoomType)
putExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, isReadOnly)
putExtra(INTENT_CHAT_ROOM_LAST_SEEN, chatRoomLastSeen)
putExtra(INTENT_CHAT_IS_SUBSCRIBED, isSubscribed)
putExtra(INTENT_CHAT_ROOM_IS_CREATOR, isCreator)
putExtra(INTENT_CHAT_ROOM_IS_FAVORITE, isFavorite)
putExtra(INTENT_CHAT_ROOM_MESSAGE, chatRoomMessage)
}
private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
......@@ -79,42 +77,44 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
return
}
val chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" }
with(intent) {
val chatRoomId = getStringExtra(INTENT_CHAT_ROOM_ID)
requireNotNull(chatRoomId) { "no chat_room_id provided in Intent extras" }
val chatRoomName = intent.getStringExtra(INTENT_CHAT_ROOM_NAME)
requireNotNull(chatRoomName) { "no chat_room_name provided in Intent extras" }
val chatRoomName = getStringExtra(INTENT_CHAT_ROOM_NAME)
requireNotNull(chatRoomName) { "no chat_room_name provided in Intent extras" }
val chatRoomType = intent.getStringExtra(INTENT_CHAT_ROOM_TYPE)
requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" }
val chatRoomType = getStringExtra(INTENT_CHAT_ROOM_TYPE)
requireNotNull(chatRoomType) { "no chat_room_type provided in Intent extras" }
val isReadOnly = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, true)
val isReadOnly = getBooleanExtra(INTENT_CHAT_ROOM_IS_READ_ONLY, true)
val isCreator = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_CREATOR, false)
val isCreator = getBooleanExtra(INTENT_CHAT_ROOM_IS_CREATOR, false)
val isFavorite = intent.getBooleanExtra(INTENT_CHAT_ROOM_IS_FAVORITE, false)
val isFavorite = getBooleanExtra(INTENT_CHAT_ROOM_IS_FAVORITE, false)
val chatRoomLastSeen = intent.getLongExtra(INTENT_CHAT_ROOM_LAST_SEEN, -1)
val chatRoomLastSeen = getLongExtra(INTENT_CHAT_ROOM_LAST_SEEN, -1)
val isSubscribed = intent.getBooleanExtra(INTENT_CHAT_IS_SUBSCRIBED, true)
val isSubscribed = getBooleanExtra(INTENT_CHAT_IS_SUBSCRIBED, true)
val chatRoomMessage = intent.getStringExtra(INTENT_CHAT_ROOM_MESSAGE)
val chatRoomMessage = getStringExtra(INTENT_CHAT_ROOM_MESSAGE)
setupToolbar()
setupToolbar()
if (supportFragmentManager.findFragmentByTag(TAG_CHAT_ROOM_FRAGMENT) == null) {
addFragment(TAG_CHAT_ROOM_FRAGMENT, R.id.fragment_container) {
newInstance(
chatRoomId,
chatRoomName,
chatRoomType,
isReadOnly,
chatRoomLastSeen,
isSubscribed,
isCreator,
isFavorite,
chatRoomMessage
)
if (supportFragmentManager.findFragmentByTag(TAG_CHAT_ROOM_FRAGMENT) == null) {
addFragment(TAG_CHAT_ROOM_FRAGMENT, R.id.fragment_container) {
newInstance(
chatRoomId,
chatRoomName,
chatRoomType,
isReadOnly,
chatRoomLastSeen,
isSubscribed,
isCreator,
isFavorite,
chatRoomMessage
)
}
}
}
}
......
......@@ -64,4 +64,4 @@ private fun ChatRoomFragment.setupSearchViewTextListener(searchView: SearchView)
isSearchTermQueried = true
}
}
}
}
\ No newline at end of file
......@@ -416,21 +416,19 @@ class UiModelMapper @Inject constructor(
return fullUrl
}
private fun attachmentText(text: String?, attachment: Attachment?, context: Context): String? {
return if (attachment != null) {
private fun attachmentText(text: String?, attachment: Attachment?, context: Context): String? = attachment?.run {
with(context) {
when {
attachment.imageUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_photo)
attachment.videoUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_video)
attachment.audioUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_audio)
attachment.titleLink.isNotNullNorEmpty() &&
attachment.type?.contentEquals("file") == true ->
context.getString(R.string.msg_preview_file)
imageUrl.isNotNullNorEmpty() -> getString(R.string.msg_preview_photo)
videoUrl.isNotNullNorEmpty() -> getString(R.string.msg_preview_video)
audioUrl.isNotNullNorEmpty() -> getString(R.string.msg_preview_audio)
titleLink.isNotNullNorEmpty() &&
type?.contentEquals("file") == true ->
getString(R.string.msg_preview_file)
else -> text
}
} else {
text
}
}
} ?: text
private fun attachmentDescription(attachment: Attachment): String? {
return attachment.description
......@@ -464,11 +462,9 @@ class UiModelMapper @Inject constructor(
subscriptionId = chatRoom.subscriptionId)
}
private fun mapMessagePreview(message: Message): Message {
return when (message.isSystemMessage()) {
false -> stripMessageQuotes(message)
true -> message.copy(message = getSystemMessage(message).toString())
}
private fun mapMessagePreview(message: Message): Message = when (message.isSystemMessage()) {
false -> stripMessageQuotes(message)
true -> message.copy(message = getSystemMessage(message).toString())
}
private fun getReactions(message: Message): List<ReactionUiModel> {
......@@ -535,75 +531,36 @@ class UiModelMapper @Inject constructor(
private fun getTime(timestamp: Long) = DateTimeHelper.getTime(DateTimeHelper.getLocalDateTime(timestamp))
private fun getContent(message: Message): CharSequence {
return when (message.isSystemMessage()) {
true -> getSystemMessage(message)
false -> parser.render(message, currentUsername)
}
private fun getContent(message: Message): CharSequence = when (message.isSystemMessage()) {
true -> getSystemMessage(message)
false -> parser.render(message, currentUsername)
}
private fun getSystemMessage(message: Message): CharSequence {
val content = when (message.type) {
//TODO: Add implementation for Welcome type.
is MessageType.MessageRemoved -> context.getString(R.string.message_removed)
is MessageType.UserJoined -> context.getString(R.string.message_user_joined_channel)
is MessageType.UserLeft -> context.getString(R.string.message_user_left)
is MessageType.UserAdded -> context.getString(
R.string.message_user_added_by,
message.message,
message.sender?.username
)
is MessageType.RoomNameChanged -> context.getString(
R.string.message_room_name_changed, message.message, message.sender?.username
)
is MessageType.UserRemoved -> context.getString(
R.string.message_user_removed_by,
message.message,
message.sender?.username
)
is MessageType.MessagePinned -> context.getString(R.string.message_pinned)
is MessageType.UserMuted -> context.getString(
R.string.message_muted,
message.message,
message.sender?.username
)
is MessageType.UserUnMuted -> context.getString(
R.string.message_unmuted,
message.message,
message.sender?.username
)
is MessageType.SubscriptionRoleAdded -> context.getString(
R.string.message_role_add,
message.message,
message.role,
message.sender?.username
)
is MessageType.SubscriptionRoleRemoved -> context.getString(
R.string.message_role_removed,
message.message,
message.role,
message.sender?.username
)
is MessageType.RoomChangedPrivacy -> context.getString(
R.string.message_room_changed_privacy,
message.message,
message.sender?.username
)
is MessageType.JitsiCallStarted -> context.getString(
R.string.message_video_call_started, message.sender?.username
)
else -> {
throw InvalidParameterException("Invalid message type: ${message.type}")
val content = with(context) {
when (message.type) {
//TODO: Add implementation for Welcome type.
is MessageType.MessageRemoved -> getString(R.string.message_removed)
is MessageType.UserJoined -> getString(R.string.message_user_joined_channel)
is MessageType.UserLeft -> getString(R.string.message_user_left)
is MessageType.UserAdded -> getString(R.string.message_user_added_by, message.message, message.sender?.username)
is MessageType.RoomNameChanged -> getString(R.string.message_room_name_changed, message.message, message.sender?.username)
is MessageType.UserRemoved -> getString(R.string.message_user_removed_by, message.message, message.sender?.username)
is MessageType.MessagePinned -> getString(R.string.message_pinned)
is MessageType.UserMuted -> getString(R.string.message_muted, message.message, message.sender?.username)
is MessageType.UserUnMuted -> getString(R.string.message_unmuted, message.message, message.sender?.username)
is MessageType.SubscriptionRoleAdded -> getString(R.string.message_role_add, message.message, message.role, message.sender?.username)
is MessageType.SubscriptionRoleRemoved -> getString(R.string.message_role_removed, message.message, message.role, message.sender?.username)
is MessageType.RoomChangedPrivacy -> getString(R.string.message_room_changed_privacy, message.message, message.sender?.username)
is MessageType.JitsiCallStarted -> context.getString(
R.string.message_video_call_started, message.sender?.username
)
else -> throw InvalidParameterException("Invalid message type: ${message.type}")
}
}
val spannableMsg = SpannableStringBuilder(content)
spannableMsg.setSpan(
StyleSpan(Typeface.ITALIC), 0, spannableMsg.length, 0
)
spannableMsg.setSpan(
ForegroundColorSpan(Color.GRAY), 0, spannableMsg.length, 0
)
spannableMsg.setSpan(StyleSpan(Typeface.ITALIC), 0, spannableMsg.length, 0)
spannableMsg.setSpan(ForegroundColorSpan(Color.GRAY), 0, spannableMsg.length, 0)
return spannableMsg
}
}
\ No newline at end of file
......@@ -36,14 +36,29 @@ class RoomUiModelMapper(
grouped: Boolean = false,
showLastMessage: Boolean = true
): List<ItemHolder<*>> {
val list = ArrayList<ItemHolder<*>>(rooms.size + 4)
val list = ArrayList<ItemHolder<*>>(rooms.size + 5)
var lastType: String? = null
rooms.forEach { room ->
if (grouped && lastType != room.chatRoom.type) {
list.add(HeaderItemHolder(roomType(room.chatRoom.type)))
if (grouped) {
val favRooms = rooms.filter { it.chatRoom.favorite == true }
val unfavRooms = rooms.filterNot { it.chatRoom.favorite == true }
if (favRooms.isNotEmpty()) {
list.add(HeaderItemHolder(context.resources.getString(R.string.header_favorite)))
}
favRooms.forEach { room ->
list.add(RoomItemHolder(map(room, showLastMessage)))
}
unfavRooms.forEach { room ->
if (lastType != room.chatRoom.type) {
list.add(HeaderItemHolder(roomType(room.chatRoom.type)))
}
list.add(RoomItemHolder(map(room, showLastMessage)))
lastType = room.chatRoom.type
}
} else {
rooms.forEach { room ->
list.add(RoomItemHolder(map(room, showLastMessage)))
}
list.add(RoomItemHolder(map(room, showLastMessage)))
lastType = room.chatRoom.type
}
return list
......@@ -62,76 +77,71 @@ class RoomUiModelMapper(
return list
}
private fun mapUser(user: User): RoomUiModel {
return with(user) {
val name = mapName(user.username!!, user.name)
val status = user.status
val avatar = serverUrl.avatarUrl(user.username!!)
val username = user.username!!
private fun mapUser(user: User): RoomUiModel = with(user) {
val name = mapName(user.username!!, user.name)
val status = user.status
val avatar = serverUrl.avatarUrl(user.username!!)
val username = user.username!!
RoomUiModel(
RoomUiModel(
id = user.id,
name = name,
type = roomTypeOf(RoomType.DIRECT_MESSAGE),
avatar = avatar,
status = status,
username = username
)
}
)
}
private fun mapRoom(room: Room, showLastMessage: Boolean = true): RoomUiModel {
return with(room) {
RoomUiModel(
private fun mapRoom(room: Room, showLastMessage: Boolean = true): RoomUiModel = with(room) {
RoomUiModel(
id = id,
name = name!!,
type = type,
avatar = serverUrl.avatarUrl(name!!, isGroupOrChannel = true),
lastMessage = if (showLastMessage) {
mapLastMessage(
lastMessage?.sender?.id, lastMessage?.sender?.username,
lastMessage?.sender?.name, lastMessage?.message,
isDirectMessage = type is RoomType.DirectMessage
lastMessage?.sender?.id, lastMessage?.sender?.username,
lastMessage?.sender?.name, lastMessage?.message,
isDirectMessage = type is RoomType.DirectMessage
)
} else {
null
},
muted = muted.orEmpty(),
writable = isChannelWritable(muted)
)
}
)
}
fun map(chatRoom: ChatRoom, showLastMessage: Boolean = true): RoomUiModel {
return with(chatRoom.chatRoom) {
val isUnread = alert || unread > 0
val type = roomTypeOf(type)
val status = chatRoom.status?.let { userStatusOf(it) }
val roomName = mapName(name, fullname)
val timestamp = mapDate(lastMessageTimestamp ?: updatedAt)
val avatar = if (type is RoomType.DirectMessage) {
serverUrl.avatarUrl(name)
} else {
serverUrl.avatarUrl(name, isGroupOrChannel = true)
}
val unread = mapUnread(unread)
val lastMessage = if (showLastMessage) {
mapLastMessage(
fun map(chatRoom: ChatRoom, showLastMessage: Boolean = true): RoomUiModel = with(chatRoom.chatRoom) {
val isUnread = alert || unread > 0
val type = roomTypeOf(type)
val status = chatRoom.status?.let { userStatusOf(it) }
val roomName = mapName(name, fullname)
val favorite = favorite
val timestamp = mapDate(lastMessageTimestamp ?: updatedAt)
val avatar = if (type is RoomType.DirectMessage) {
serverUrl.avatarUrl(name)
} else {
serverUrl.avatarUrl(name, isGroupOrChannel = true)
}
val unread = mapUnread(unread)
val lastMessage = if (showLastMessage) {
mapLastMessage(
lastMessageUserId,
chatRoom.lastMessageUserName,
chatRoom.lastMessageUserFullName,
lastMessageText,
type is RoomType.DirectMessage
)
} else {
null
}
val hasMentions = mapMentions(userMentions, groupMentions)
val open = open
val lastMessageMarkdown =
lastMessage?.let { Markwon.markdown(context, it.toString()).toString() }
)
} else {
null
}
val hasMentions = mapMentions(userMentions, groupMentions)
val open = open
val lastMessageMarkdown = lastMessage?.let { Markwon.markdown(context, it.toString()).toString() }
RoomUiModel(
RoomUiModel(
id = id,
name = roomName,
type = type,
......@@ -140,14 +150,14 @@ class RoomUiModelMapper(
date = timestamp,
unread = unread,
mentions = hasMentions,
favorite = favorite,
alert = isUnread,
lastMessage = lastMessageMarkdown,
status = status,
username = if (type is RoomType.DirectMessage) name else null,
muted = muted.orEmpty(),
writable = isChannelWritable(muted)
)
}
)
}
private fun isChannelWritable(muted: List<String>?): Boolean {
......@@ -155,14 +165,13 @@ class RoomUiModelMapper(
return canWriteToReadOnlyChannels || !muted.orEmpty().contains(currentUser?.username)
}
private fun roomType(type: String): String {
val resources = context.resources
return when (type) {
RoomType.CHANNEL -> resources.getString(R.string.header_channel)
RoomType.PRIVATE_GROUP -> resources.getString(R.string.header_private_groups)
RoomType.DIRECT_MESSAGE -> resources.getString(R.string.header_direct_messages)
RoomType.LIVECHAT -> resources.getString(R.string.header_live_chats)
else -> resources.getString(R.string.header_unknown)
private fun roomType(type: String): String = with(context.resources) {
when (type) {
RoomType.CHANNEL -> getString(R.string.header_channel)
RoomType.PRIVATE_GROUP -> getString(R.string.header_private_groups)
RoomType.DIRECT_MESSAGE -> getString(R.string.header_direct_messages)
RoomType.LIVECHAT -> getString(R.string.header_live_chats)
else -> getString(R.string.header_unknown)
}
}
......@@ -195,13 +204,11 @@ class RoomUiModelMapper(
}
}
private fun mapUnread(unread: Long): String? {
return when (unread) {
0L -> null
in 1..99 -> unread.toString()
else -> context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
private fun mapUnread(unread: Long): String? = when (unread) {
0L -> null
in 1..99 -> unread.toString()
else -> context.getString(R.string.msg_more_than_ninety_nine_unread_messages)
}
}
private fun mapMentions(userMentions: Long?, groupMentions: Long?): Boolean {
......
......@@ -8,6 +8,7 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.util.extension.setTextViewAppearance
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.UserStatus
import kotlinx.android.synthetic.main.item_chat.view.*
......@@ -16,12 +17,12 @@ import kotlinx.android.synthetic.main.unread_messages_badge.view.*
class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit) :
ViewHolder<RoomItemHolder>(itemView) {
private val resources: Resources = itemView.resources
private val channelIcon: Drawable = resources.getDrawable(R.drawable.ic_hashtag_12dp)
private val groupIcon: Drawable = resources.getDrawable(R.drawable.ic_lock_12_dp)
private val onlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_online_12dp)
private val awayIcon: Drawable = resources.getDrawable(R.drawable.ic_status_away_12dp)
private val busyIcon: Drawable = resources.getDrawable(R.drawable.ic_status_busy_12dp)
private val offlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_invisible_12dp)
private val channelIcon: Drawable = resources.getDrawable(R.drawable.ic_hashtag_12dp, null)
private val groupIcon: Drawable = resources.getDrawable(R.drawable.ic_lock_12_dp, null)
private val onlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_online_12dp, null)
private val awayIcon: Drawable = resources.getDrawable(R.drawable.ic_status_away_12dp, null)
private val busyIcon: Drawable = resources.getDrawable(R.drawable.ic_status_busy_12dp, null)
private val offlineIcon: Drawable = resources.getDrawable(R.drawable.ic_status_invisible_12dp, null)
override fun bindViews(data: RoomItemHolder) {
val room = data.data
......@@ -53,14 +54,14 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit
if (room.unread == null) text_total_unread_messages.text = "!"
if (room.unread != null) text_total_unread_messages.text = room.unread
if (room.mentions) text_total_unread_messages.text = "@${room.unread}"
text_chat_name.setTextAppearance(context, R.style.ChatList_ChatName_Unread_TextView)
text_timestamp.setTextAppearance(context, R.style.ChatList_Timestamp_Unread_TextView)
text_last_message.setTextAppearance(context, R.style.ChatList_LastMessage_Unread_TextView)
text_chat_name.setTextViewAppearance(context, R.style.ChatList_ChatName_Unread_TextView)
text_timestamp.setTextViewAppearance(context, R.style.ChatList_Timestamp_Unread_TextView)
text_last_message.setTextViewAppearance(context, R.style.ChatList_LastMessage_Unread_TextView)
text_total_unread_messages.isVisible = true
} else {
text_chat_name.setTextAppearance(context, R.style.ChatList_ChatName_TextView)
text_timestamp.setTextAppearance(context, R.style.ChatList_Timestamp_TextView)
text_last_message.setTextAppearance(context, R.style.ChatList_LastMessage_TextView)
text_chat_name.setTextViewAppearance(context, R.style.ChatList_ChatName_TextView)
text_timestamp.setTextViewAppearance(context, R.style.ChatList_Timestamp_TextView)
text_last_message.setTextViewAppearance(context, R.style.ChatList_LastMessage_TextView)
text_total_unread_messages.isInvisible = true
}
......@@ -68,20 +69,16 @@ class RoomViewHolder(itemView: View, private val listener: (RoomUiModel) -> Unit
}
}
private fun getRoomDrawable(type: RoomType): Drawable? {
return when (type) {
is RoomType.Channel -> channelIcon
is RoomType.PrivateGroup -> groupIcon
else -> null
}
private fun getRoomDrawable(type: RoomType): Drawable? = when (type) {
is RoomType.Channel -> channelIcon
is RoomType.PrivateGroup -> groupIcon
else -> null
}
private fun getStatusDrawable(status: UserStatus): Drawable {
return when (status) {
is UserStatus.Online -> onlineIcon
is UserStatus.Away -> awayIcon
is UserStatus.Busy -> busyIcon
else -> offlineIcon
}
private fun getStatusDrawable(status: UserStatus): Drawable = when (status) {
is UserStatus.Online -> onlineIcon
is UserStatus.Away -> awayIcon
is UserStatus.Busy -> busyIcon
else -> offlineIcon
}
}
\ No newline at end of file
......@@ -19,22 +19,20 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) :
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> {
return when (viewType) {
VIEW_TYPE_ROOM -> {
val view = parent.inflate(R.layout.item_chat)
RoomViewHolder(view, listener)
}
VIEW_TYPE_HEADER -> {
val view = parent.inflate(R.layout.item_chatroom_header)
HeaderViewHolder(view)
}
VIEW_TYPE_LOADING -> {
val view = parent.inflate(R.layout.item_loading)
LoadingViewHolder(view)
}
else -> throw IllegalStateException("View type must be either Room, Header or Loading")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> = when (viewType) {
VIEW_TYPE_ROOM -> {
val view = parent.inflate(R.layout.item_chat)
RoomViewHolder(view, listener)
}
VIEW_TYPE_HEADER -> {
val view = parent.inflate(R.layout.item_chatroom_header)
HeaderViewHolder(view)
}
VIEW_TYPE_LOADING -> {
val view = parent.inflate(R.layout.item_loading)
LoadingViewHolder(view)
}
else -> throw IllegalStateException("View type must be either Room, Header or Loading")
}
override fun getItemCount() = values.size
......@@ -49,13 +47,11 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) :
}
}
override fun getItemViewType(position: Int): Int {
return when (values[position]) {
is RoomItemHolder -> VIEW_TYPE_ROOM
is HeaderItemHolder -> VIEW_TYPE_HEADER
is LoadingItemHolder -> VIEW_TYPE_LOADING
else -> throw IllegalStateException("View type must be either Room, Header or Loading")
}
override fun getItemViewType(position: Int): Int = when (values[position]) {
is RoomItemHolder -> VIEW_TYPE_ROOM
is HeaderItemHolder -> VIEW_TYPE_HEADER
is LoadingItemHolder -> VIEW_TYPE_LOADING
else -> throw IllegalStateException("View type must be either Room, Header or Loading")
}
override fun onBindViewHolder(holder: ViewHolder<*>, position: Int) {
......
......@@ -12,6 +12,7 @@ data class RoomUiModel(
val date: CharSequence? = null,
val unread: String? = null,
val alert: Boolean = false,
val favorite: Boolean? = false,
val mentions: Boolean = false,
val lastMessage: CharSequence? = null,
val status: UserStatus? = null,
......
......@@ -2,6 +2,7 @@ package chat.rocket.android.chatrooms.presentation
import chat.rocket.android.R
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.chatrooms.domain.FetchChatRoomsInteractor
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.ChatRoomEntity
......@@ -117,6 +118,7 @@ class ChatRoomsPresenter @Inject constructor(
retryIO("createDirectMessage($name)") {
withTimeout(10000) {
createDirectMessage(name)
FetchChatRoomsInteractor(client, dbManager).refreshChatRooms()
}
}
val fromTo = mutableListOf(myself.id, id).apply {
......
......@@ -36,7 +36,6 @@ import chat.rocket.android.helper.SharedPreferenceHelper
import chat.rocket.android.util.extension.onQueryTextListener
import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.ifNotNullNorEmpty
import chat.rocket.android.util.extensions.ifNotNullNotEmpty
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.showToast
......@@ -69,11 +68,9 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
private var progressDialog: ProgressDialog? = null
companion object {
fun newInstance(chatRoomId: String? = null): ChatRoomsFragment {
return ChatRoomsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
fun newInstance(chatRoomId: String? = null): ChatRoomsFragment = ChatRoomsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
}
......@@ -82,9 +79,8 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID)
chatRoomId.ifNotNullNotEmpty { roomId ->
presenter.loadChatRoom(roomId)
chatRoomId = null
......@@ -129,12 +125,14 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
)
)
recycler_view.itemAnimator = DefaultItemAnimator()
recycler_view.adapter = adapter
viewModel.getChatRooms().observe(viewLifecycleOwner, Observer { rooms ->
rooms?.let {
Timber.d("Got items: $it")
adapter.values = it
if (recycler_view.adapter != adapter) {
recycler_view.adapter = adapter
}
if (rooms.isNotEmpty()) {
text_no_data_to_display.isVisible = false
}
......@@ -320,21 +318,20 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
ui {
text_connection_status.fadeIn()
handler.removeCallbacks(dismissStatus)
when (state) {
text_connection_status.text = when (state) {
is State.Connected -> {
text_connection_status.text = getString(R.string.status_connected)
handler.postDelayed(dismissStatus, 2000)
getString(R.string.status_connected)
}
is State.Disconnected -> getString(R.string.status_disconnected)
is State.Connecting -> getString(R.string.status_connecting)
is State.Authenticating -> getString(R.string.status_authenticating)
is State.Disconnecting -> getString(R.string.status_disconnecting)
is State.Waiting -> getString(R.string.status_waiting, state.seconds)
else -> {
handler.postDelayed(dismissStatus, 500)
""
}
is State.Disconnected -> text_connection_status.text =
getString(R.string.status_disconnected)
is State.Connecting -> text_connection_status.text =
getString(R.string.status_connecting)
is State.Authenticating -> text_connection_status.text =
getString(R.string.status_authenticating)
is State.Disconnecting -> text_connection_status.text =
getString(R.string.status_disconnecting)
is State.Waiting -> text_connection_status.text =
getString(R.string.status_waiting, state.seconds)
}
}
}
......
......@@ -45,9 +45,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
lateinit var analyticsManager: AnalyticsManager
private var actionMode: ActionMode? = null
private val adapter: MembersAdapter = MembersAdapter {
if (it.username != null) {
processSelectedMember(it.username)
}
it.username?.run { processSelectedMember(this) }
}
private val compositeDisposable = CompositeDisposable()
private var channelType: String = RoomType.CHANNEL
......@@ -294,8 +292,8 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
private fun addChip(chipText: String) {
val chip = Chip(context)
chip.chipText = chipText
chip.isCloseIconEnabled = true
chip.text = chipText
chip.isCloseIconVisible = true
chip.setChipBackgroundColorResource(R.color.icon_grey)
setupChipOnCloseIconClickListener(chip)
chip_group_member.addView(chip)
......@@ -304,7 +302,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
private fun setupChipOnCloseIconClickListener(chip: Chip) {
chip.setOnCloseIconClickListener {
removeChip(it)
removeMember((it as Chip).chipText.toString())
removeMember((it as Chip).text.toString())
// whenever we remove a chip we should process the chip group visibility.
processChipGroupVisibility()
}
......
......@@ -115,12 +115,12 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity
val text = mapAttachmentText(text, attachments?.firstOrNull(), context)
val entity = AttachmentEntity(
list.add(AttachmentEntity(
_id = attachmentId,
messageId = msgId,
title = title,
type = type,
description = description,
description = description,
text = text,
titleLink = titleLink,
titleLinkDownload = titleLinkDownload.orFalse(),
......@@ -144,16 +144,14 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity
buttonAlignment = buttonAlignment,
hasActions = actions?.isNotEmpty() == true,
hasFields = fields?.isNotEmpty() == true
)
list.add(entity)
))
fields?.forEach { field ->
val entity = AttachmentFieldEntity(
list.add(AttachmentFieldEntity(
attachmentId = attachmentId,
title = field.title,
value = field.value
)
list.add(entity)
))
}
actions?.forEach { action ->
......@@ -175,18 +173,14 @@ fun Attachment.asEntity(msgId: String, context: Context): List<BaseMessageEntity
return list
}
fun mapAttachmentText(text: String?, attachment: Attachment?, context: Context): String? {
return if (attachment != null) {
when {
attachment.imageUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_photo)
attachment.videoUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_video)
attachment.audioUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_audio)
attachment.titleLink.isNotNullNorEmpty() &&
attachment.type?.contentEquals("file") == true ->
context.getString(R.string.msg_preview_file)
else -> text
}
} else {
text
fun mapAttachmentText(text: String?, attachment: Attachment?, context: Context): String? = attachment?.run {
when {
imageUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_photo)
videoUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_video)
audioUrl.isNotNullNorEmpty() -> context.getString(R.string.msg_preview_audio)
titleLink.isNotNullNorEmpty() &&
type?.contentEquals("file") == true ->
context.getString(R.string.msg_preview_file)
else -> text
}
}
} ?: text
\ No newline at end of file
......@@ -36,8 +36,7 @@ class FavoriteMessagesPresenter @Inject constructor(
try {
view.showLoading()
dbManager.getRoom(roomId)?.let {
val favoriteMessages =
client.getFavoriteMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
val favoriteMessages = client.getFavoriteMessages(roomId, roomTypeOf(it.chatRoom.type), offset)
val messageList = mapper.map(favoriteMessages.result, asNotReversed = true)
view.showFavoriteMessages(messageList)
offset += 1 * 30
......
......@@ -25,11 +25,9 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_favorite_messages.*
import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment {
return FavoriteMessagesFragment().apply {
arguments = Bundle(1).apply {
putString(INTENT_CHAT_ROOM_ID, chatRoomId)
}
fun newInstance(chatRoomId: String): Fragment = FavoriteMessagesFragment().apply {
arguments = Bundle(1).apply {
putString(INTENT_CHAT_ROOM_ID, chatRoomId)
}
}
......@@ -48,12 +46,9 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(INTENT_CHAT_ROOM_ID)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
chatRoomId = getString(INTENT_CHAT_ROOM_ID, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......
......@@ -30,11 +30,9 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_files.*
import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment {
return FilesFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
fun newInstance(chatRoomId: String): Fragment = FilesFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
......@@ -55,12 +53,9 @@ class FilesFragment : Fragment(), FilesView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......
......@@ -53,8 +53,8 @@ object ImageHelper {
)
val toolbar = Toolbar(context).also {
it.inflateMenu(R.menu.image_actions)
it.setOnMenuItemClickListener {
return@setOnMenuItemClickListener when (it.itemId) {
it.setOnMenuItemClickListener { view ->
return@setOnMenuItemClickListener when (view.itemId) {
R.id.action_save_image -> saveImage(context)
else -> true
}
......@@ -62,20 +62,24 @@ object ImageHelper {
val titleSize = context.resources
.getDimensionPixelSize(R.dimen.viewer_toolbar_title)
val titleTextView = TextView(context).also {
it.text = imageName
it.setTextColor(Color.WHITE)
it.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize.toFloat())
it.ellipsize = TextUtils.TruncateAt.END
it.setSingleLine()
it.typeface = Typeface.DEFAULT_BOLD
it.setPadding(pad)
val titleTextView = TextView(context).also { tv ->
with(tv) {
text = imageName
setTextColor(Color.WHITE)
setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize.toFloat())
ellipsize = TextUtils.TruncateAt.END
setSingleLine()
typeface = Typeface.DEFAULT_BOLD
setPadding(pad)
}
}
val backArrowView = ImageView(context).also {
it.setImageResource(R.drawable.ic_arrow_back_white_24dp)
it.setOnClickListener { imageViewer?.onDismiss() }
it.setPadding(0, pad, pad, pad)
val backArrowView = ImageView(context).also { imgView ->
with(imgView) {
setImageResource(R.drawable.ic_arrow_back_white_24dp)
setOnClickListener { imageViewer?.onDismiss() }
setPadding(0, pad, pad, pad)
}
}
val layoutParams = AppBarLayout.LayoutParams(
......@@ -88,14 +92,16 @@ object ImageHelper {
}
val appBarLayout = AppBarLayout(context).also {
it.layoutParams = lparams
it.setBackgroundColor(Color.BLACK)
it.addView(
toolbar, AppBarLayout.LayoutParams(
AppBarLayout.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
with(it) {
layoutParams = lparams
setBackgroundColor(Color.BLACK)
addView(
toolbar, AppBarLayout.LayoutParams(
AppBarLayout.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
)
)
}
}
val builder = ImageViewer.createPipelineDraweeControllerBuilder()
......
......@@ -38,6 +38,9 @@ import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.nav_header.view.*
import javax.inject.Inject
import android.app.NotificationManager
import android.content.Context
private const val CURRENT_STATE = "current_state"
......@@ -90,6 +93,9 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
presenter.toChatList(chatRoomId)
isFragmentAdded = true
}
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
as NotificationManager
notificationManager.cancelAll()
}
override fun onDestroy() {
......
......@@ -10,8 +10,9 @@ import chat.rocket.android.util.extensions.inflate
import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_member.view.*
class MembersAdapter(private val listener: (MemberUiModel) -> Unit) :
RecyclerView.Adapter<MembersAdapter.ViewHolder>() {
class MembersAdapter(
private val listener: (MemberUiModel) -> Unit
) : RecyclerView.Adapter<MembersAdapter.ViewHolder>() {
private var dataSet: List<MemberUiModel> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MembersAdapter.ViewHolder =
......@@ -43,7 +44,8 @@ class MembersAdapter(private val listener: (MemberUiModel) -> Unit) :
fun bind(memberUiModel: MemberUiModel, listener: (MemberUiModel) -> Unit) = with(itemView) {
image_avatar.setImageURI(memberUiModel.avatarUri)
text_member.content = memberUiModel.displayName
text_member.setCompoundDrawablesRelativeWithIntrinsicBounds(DrawableHelper.getUserStatusDrawable(memberUiModel.status, context), null, null, null)
text_member.setCompoundDrawablesRelativeWithIntrinsicBounds(
DrawableHelper.getUserStatusDrawable(memberUiModel.status, context), null, null, null)
setOnClickListener { listener(memberUiModel) }
}
}
......
......@@ -3,6 +3,7 @@ package chat.rocket.android.members.presentation
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.members.uimodel.MemberUiModel
import chat.rocket.android.members.uimodel.MemberUiModelMapper
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
......@@ -23,7 +24,8 @@ class MembersPresenter @Inject constructor(
@Named("currentServer") private val currentServer: String,
private val strategy: CancelStrategy,
private val mapper: MemberUiModelMapper,
val factory: RocketChatClientFactory
val factory: RocketChatClientFactory,
private val userHelper: UserHelper
) {
private val client: RocketChatClient = factory.create(currentServer)
private var offset: Long = 0
......@@ -39,7 +41,7 @@ class MembersPresenter @Inject constructor(
view.showLoading()
dbManager.getRoom(roomId)?.let {
val members =
client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60)
client.getMembers(roomId, roomTypeOf(it.chatRoom.type), offset, 60)
val memberUiModels = mapper.mapToUiModelList(members.result)
view.showMembers(memberUiModels, members.total)
offset += 1 * 60L
......@@ -59,6 +61,10 @@ class MembersPresenter @Inject constructor(
}
fun toMemberDetails(memberUiModel: MemberUiModel) {
navigator.toMemberDetails(memberUiModel.userId)
with(memberUiModel) {
if (userId != userHelper.user()?.id) {
navigator.toMemberDetails(userId)
}
}
}
}
......@@ -27,11 +27,9 @@ import kotlinx.android.synthetic.main.app_bar_chat_room.*
import kotlinx.android.synthetic.main.fragment_members.*
import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment {
return MembersFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
fun newInstance(chatRoomId: String): Fragment = MembersFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
......@@ -52,12 +50,9 @@ class MembersFragment : Fragment(), MembersView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......@@ -80,7 +75,7 @@ class MembersFragment : Fragment(), MembersView {
setupToolbar(total)
if (adapter.itemCount == 0) {
adapter.prependData(dataSet)
if (dataSet.size >= 59) { // TODO Check why the API retorns the specified count -1
if (dataSet.size >= 59) { // TODO Check why the API returns the specified count -1
recycler_view.addOnScrollListener(object :
EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(
......
......@@ -25,11 +25,9 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_mentions.*
import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment {
return MentionsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
fun newInstance(chatRoomId: String): Fragment = MentionsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
......@@ -48,12 +46,9 @@ class MentionsFragment : Fragment(), MentionsView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......
......@@ -25,11 +25,9 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_pinned_messages.*
import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment {
return PinnedMessagesFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
fun newInstance(chatRoomId: String): Fragment = PinnedMessagesFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
......@@ -48,12 +46,9 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......
......@@ -38,13 +38,17 @@ class PreferencesFragment : Fragment(), PreferencesView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
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
......
......@@ -82,7 +82,7 @@ class ProfilePresenter @Inject constructor(
view.showLoading()
try {
user?.id?.let { id ->
retryIO { client.updateProfile(id, email, name, username) }
retryIO { client.updateProfile(userId = id, email = email, name = name, username = username) }
view.showProfileUpdateSuccessfullyMessage()
view.showProfile(
serverUrl.avatarUrl(user.username ?: ""),
......@@ -115,7 +115,7 @@ class ProfilePresenter @Inject constructor(
uriInteractor.getInputStream(uri)
}
}
user?.username?.let { view.reloadUserAvatar(it) }
user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) }
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
......@@ -143,7 +143,7 @@ class ProfilePresenter @Inject constructor(
}
}
user?.username?.let { view.reloadUserAvatar(it) }
user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) }
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
......@@ -163,7 +163,7 @@ class ProfilePresenter @Inject constructor(
user?.id?.let { id ->
retryIO { client.resetAvatar(id) }
}
user?.username?.let { view.reloadUserAvatar(it) }
user?.username?.let { view.reloadUserAvatar(serverUrl.avatarUrl(it)) }
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
......@@ -198,4 +198,4 @@ class ProfilePresenter @Inject constructor(
}
}
}
}
\ No newline at end of file
}
......@@ -94,11 +94,13 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (resultData != null && resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF) {
presenter.updateAvatar(resultData.data)
} else if (requestCode == REQUEST_CODE_FOR_PERFORM_CAMERA) {
presenter.preparePhotoAndUpdateAvatar(resultData.extras["data"] as Bitmap)
resultData?.run {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF) {
data?.let { presenter.updateAvatar(it) }
} else if (requestCode == REQUEST_CODE_FOR_PERFORM_CAMERA) {
extras?.get("data")?.let { presenter.preparePhotoAndUpdateAvatar(it as Bitmap) }
}
}
}
}
......@@ -203,8 +205,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
}
private fun setupToolbar() {
(activity as AppCompatActivity?)?.supportActionBar?.title =
getString(R.string.title_profile)
(activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_profile)
}
private fun setupListeners() {
......@@ -293,19 +294,14 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
}
fun showDeleteAccountDialog() {
val passwordEditText = EditText(context)
passwordEditText.hint = getString(R.string.msg_password)
context?.let {
val builder = AlertDialog.Builder(it)
builder.setTitle(R.string.title_are_you_sure)
.setView(passwordEditText)
.setPositiveButton(R.string.action_delete_account) { _, _ ->
presenter.deleteAccount(passwordEditText.text.toString())
}
.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }
.create()
.show()
val passwordEText = EditText(context);
val mDialogView = LayoutInflater.from(it).inflate(R.layout.item_account_delete, null)
val mBuilder = AlertDialog.Builder(it)
mBuilder.setView(mDialogView).setPositiveButton(R.string.action_delete_account) { _, _ ->
presenter.deleteAccount(passwordEText.text.toString())
}.setNegativeButton(android.R.string.no) { dialog, _ -> dialog.cancel() }.create().show()
}
}
}
......@@ -12,13 +12,14 @@ import android.os.Build
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.text.Html
import android.text.Spanned
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import android.text.Html
import android.text.Spanned
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import chat.rocket.android.R
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.server.domain.GetAccountInteractor
......@@ -303,7 +304,11 @@ class PushManager @Inject constructor(
// CharSequence extensions
private fun CharSequence.fromHtml(): Spanned {
return Html.fromHtml(this as String)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(this as String, FROM_HTML_MODE_LEGACY, null, null)
} else {
Html.fromHtml(this as String)
}
}
// NotificationCompat.Builder extensions
......@@ -383,12 +388,12 @@ data class PushMessage(
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString().orEmpty(),
parcel.readString().orEmpty(),
parcel.readParcelable(PushMessage::class.java.classLoader) ?: PushInfo.EMPTY,
parcel.readString(),
parcel.readString(),
parcel.readParcelable(PushMessage::class.java.classLoader),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString().orEmpty(),
parcel.readString(),
parcel.readString())
......@@ -433,9 +438,9 @@ data class PushInfo @KotshiConstructor constructor(
}
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
roomTypeOf(parcel.readString()),
parcel.readString().orEmpty(),
parcel.readString().orEmpty(),
roomTypeOf(parcel.readString().orEmpty()),
parcel.readString(),
parcel.readParcelable(PushInfo::class.java.classLoader))
......@@ -481,7 +486,7 @@ data class PushSender @KotshiConstructor constructor(
val name: String?
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString().orEmpty(),
parcel.readString(),
parcel.readString())
......
......@@ -106,6 +106,7 @@ class ConnectionManager(
resubscribeRooms()
temporaryStatus?.let { client.setTemporaryStatus(it) }
}
is State.Waiting -> Timber.d("Connection in: ${status.seconds}")
}
......@@ -236,7 +237,7 @@ class ConnectionManager(
}
private fun resubscribeRooms() {
roomMessagesChannels.toList().map { (roomId, channel) ->
roomMessagesChannels.toList().map { (roomId, _) ->
client.subscribeRoomMessages(roomId) { _, id ->
Timber.d("Subscribed to $roomId: $id")
subscriptionIdMap[roomId] = id
......
package chat.rocket.android.server.infraestructure
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.AttachmentActionEntity
import chat.rocket.android.db.model.AttachmentEntity
import chat.rocket.android.db.model.FullMessage
import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.db.model.*
import chat.rocket.android.util.retryDB
import chat.rocket.common.model.SimpleRoom
import chat.rocket.common.model.SimpleUser
......@@ -159,38 +154,38 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
} else {
null
}
val attachment = Attachment(
title = title,
type = type,
description = description,
authorName = authorName,
text = text,
thumbUrl = thumbUrl,
color = color?.let { Color.Custom(color) },
titleLink = titleLink,
titleLinkDownload = titleLinkDownload,
imageUrl = imageUrl,
imageType = imageType,
imageSize = imageSize,
videoUrl = videoUrl,
videoType = videoType,
videoSize = videoSize,
audioUrl = audioUrl,
audioType = audioType,
audioSize = audioSize,
messageLink = messageLink,
attachments = null, // HOW TO MAP THIS
timestamp = timestamp,
authorIcon = authorIcon,
authorLink = authorLink,
fields = fields,
fallback = fallback,
buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment
?: "vertical" else null,
actions = actions
list.add(
Attachment(
title = title,
type = type,
description = description,
authorName = authorName,
text = text,
thumbUrl = thumbUrl,
color = color?.let { Color.Custom(color) },
titleLink = titleLink,
titleLinkDownload = titleLinkDownload,
imageUrl = imageUrl,
imageType = imageType,
imageSize = imageSize,
videoUrl = videoUrl,
videoType = videoType,
videoSize = videoSize,
audioUrl = audioUrl,
audioType = audioType,
audioSize = audioSize,
messageLink = messageLink,
attachments = null, // HOW TO MAP THIS
timestamp = timestamp,
authorIcon = authorIcon,
authorLink = authorLink,
fields = fields,
fallback = fallback,
buttonAlignment = if (actions != null && actions.isNotEmpty()) buttonAlignment
?: "vertical" else null,
actions = actions
)
)
list.add(attachment)
}
}
return list
......
......@@ -47,8 +47,8 @@ class DatabaseMessagesRepository(
dbManager.processMessagesBatch(listOf(message)).join()
}
override suspend fun saveAll(messages: List<Message>) {
dbManager.processMessagesBatch(messages).join()
override suspend fun saveAll(newMessages: List<Message>) {
dbManager.processMessagesBatch(newMessages).join()
}
override suspend fun removeById(id: String) {
......@@ -79,7 +79,7 @@ class DatabaseMessagesRepository(
override suspend fun getLastSyncDate(roomId: String): Long? = withContext(Dispatchers.IO) {
retryDB("getLastSync($roomId)") {
dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp }
dbManager.messageDao().getLastSync(roomId)?.timestamp
}
}
}
\ No newline at end of file
......@@ -14,13 +14,8 @@ class SharedPreferencesAccountsRepository(
private val moshi: Moshi
) : AccountsRepository {
override fun save(newAccount: Account) {
val accounts = load()
val newList = accounts.filter { account -> newAccount.serverUrl != account.serverUrl }
.toMutableList()
newList.add(0, newAccount)
save(newList)
override fun save(account: Account) {
save(load().filter { item -> item.serverUrl != item.serverUrl }.toMutableList().apply { add(0, account) })
}
override fun load(): List<Account> {
......@@ -28,15 +23,11 @@ class SharedPreferencesAccountsRepository(
val type = Types.newParameterizedType(List::class.java, Account::class.java)
val adapter = moshi.adapter<List<Account>>(type)
return adapter.fromJson(json) ?: emptyList()
return json?.let { adapter.fromJson(it) ?: emptyList() } ?: emptyList()
}
override fun remove(serverUrl: String) {
val accounts = load()
val newList = accounts.filter { account -> serverUrl != account.serverUrl }
.toMutableList()
save(newList)
save(load().filter { account -> serverUrl != account.serverUrl }.toMutableList())
}
private fun save(accounts: List<Account>) {
......
......@@ -15,8 +15,7 @@ class SharedPrefsBasicAuthRepository(
) : BasicAuthRepository {
override fun save(basicAuth: BasicAuth) {
val newList = load().filter { basicAuth -> basicAuth.host != basicAuth.host }
.toMutableList()
val newList = load().filter { auth -> auth.host != auth.host }.toMutableList()
newList.add(0, basicAuth)
save(newList)
}
......@@ -26,7 +25,7 @@ class SharedPrefsBasicAuthRepository(
val type = Types.newParameterizedType(List::class.java, BasicAuth::class.java)
val adapter = moshi.adapter<List<BasicAuth>>(type)
return adapter.fromJson(json) ?: emptyList()
return json?.let { adapter.fromJson(it) ?: emptyList() } ?: emptyList()
}
private fun save(basicAuths: List<BasicAuth>) {
......
......@@ -106,18 +106,16 @@ class PasswordFragment : Fragment(), PasswordView, ActionMode.Callback {
private fun finishActionMode() = actionMode?.finish()
private fun listenToChanges(): Disposable {
return Observables.combineLatest(
text_new_password.asObservable(),
text_confirm_password.asObservable()
).subscribe {
val newPassword = it.first.toString()
val newPasswordConfirmation = it.second.toString()
if (newPassword.length > 5 && newPassword == newPasswordConfirmation) {
startActionMode()
} else {
finishActionMode()
}
private fun listenToChanges(): Disposable = Observables.combineLatest(
text_new_password.asObservable(),
text_confirm_password.asObservable()
).subscribe {
val newPassword = it.first.toString()
val newPasswordConfirmation = it.second.toString()
if (newPassword.length > 5 && newPassword == newPasswordConfirmation) {
startActionMode()
} else {
finishActionMode()
}
}
......
......@@ -2,6 +2,7 @@ package chat.rocket.android.settings.ui
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
......@@ -114,8 +115,7 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen
}
private fun setupToolbar() {
(activity as AppCompatActivity?)?.supportActionBar?.title =
getString(R.string.title_settings)
(activity as AppCompatActivity?)?.supportActionBar?.title = getString(R.string.title_settings)
}
private fun shareApp() {
......@@ -128,11 +128,12 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen
}
private fun contactSupport() {
with(Intent(Intent.ACTION_SEND)) {
type = "message/rfc822"
putExtra(Intent.EXTRA_EMAIL, arrayOf("support@rocket.chat"))
putExtra(Intent.EXTRA_SUBJECT, getString(R.string.msg_android_app_support))
putExtra(Intent.EXTRA_TEXT, getDeviceAndAppInformation())
val uriText = "mailto:${"support@rocket.chat"}" +
"?subject=" + Uri.encode(getString(R.string.msg_android_app_support)) +
"&body=" + Uri.encode(getDeviceAndAppInformation())
with(Intent(Intent.ACTION_SENDTO)) {
data = uriText.toUri()
try {
startActivity(Intent.createChooser(this, getString(R.string.msg_send_email)))
} catch (ex: ActivityNotFoundException) {
......
......@@ -44,7 +44,7 @@ class UserDetailsPresenter @Inject constructor(
dbManager.getUser(userId)?.let {
userEntity = it
val avatarUrl =
userEntity.username?.let { currentServer.avatarUrl(avatar = it) }
userEntity.username?.let { username -> currentServer.avatarUrl(avatar = username) }
val username = userEntity.username
val name = userEntity.name
val utcOffset =
......@@ -124,6 +124,24 @@ class UserDetailsPresenter @Inject constructor(
}
}
// TODO
fun startVideoCall() {}
fun toVideoConferencing(username: String) {
launchUI(strategy) {
try {
withContext(Dispatchers.Default) {
val directMessage = retryIO("createDirectMessage($username") {
client.createDirectMessage(username)
}
navigator.toVideoConferencing(directMessage.id)
}
} catch (ex: Exception) {
Timber.e(ex)
ex.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
}
}
}
}
......@@ -29,11 +29,9 @@ import kotlinx.android.synthetic.main.app_bar_chat_room.*
import kotlinx.android.synthetic.main.fragment_user_details.*
import javax.inject.Inject
fun newInstance(userId: String): Fragment {
return UserDetailsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_USER_ID, userId)
}
fun newInstance(userId: String): Fragment = UserDetailsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_USER_ID, userId)
}
}
......@@ -52,12 +50,10 @@ class UserDetailsFragment : Fragment(), UserDetailsView {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments
if (bundle != null) {
userId = bundle.getString(BUNDLE_USER_ID)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
arguments?.run {
userId = getString(BUNDLE_USER_ID, "")
}
?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......@@ -108,7 +104,7 @@ class UserDetailsFragment : Fragment(), UserDetailsView {
if (isVideoCallAllowed) {
text_video_call.isVisible = true
text_video_call.setOnClickListener { presenter.startVideoCall() }
text_video_call.setOnClickListener { presenter.toVideoConferencing(username) }
} else {
text_video_call.isVisible = false
}
......
......@@ -191,7 +191,7 @@ class HttpLoggingInterceptor constructor(private val logger: Logger) : Intercept
val responseBody = response.body()
val contentLength = responseBody!!.contentLength()
val bodySize = if (contentLength != -1L) contentLength.toString() + "-byte" else "unknown-length"
val bodySize = if (contentLength != -1L) "$contentLength-byte" else "unknown-length"
val responseStr = if (response.message().isEmpty()) "" else " ${response.message()}"
logger.log("<-- ${response.code()}$responseStr ${response.request().url()}"
+ " (" + tookMs + "ms" + (if (!logHeaders) ", $bodySize body" else "") + ')'.toString())
......
......@@ -69,11 +69,8 @@ fun String.userId(userId: String?): String? {
return userId?.let { this.replace(it, "") }
}
fun String.lowercaseUrl(): String? {
val httpUrl = HttpUrl.parse(this)
val newScheme = httpUrl?.scheme()?.toLowerCase()
return httpUrl?.newBuilder()?.scheme(newScheme)?.build()?.toString()
fun String.lowercaseUrl(): String? = HttpUrl.parse(this)?.run {
newBuilder().scheme(scheme().toLowerCase()).build().toString()
}
fun String?.isNotNullNorEmpty(): Boolean = this != null && this.isNotEmpty()
......
......@@ -80,12 +80,10 @@ fun AppCompatActivity.toPreviousView() {
}
fun Activity.hideKeyboard() {
if (currentFocus != null) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(
currentFocus.windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN
)
currentFocus?.run {
(getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).also {
it.hideSoftInputFromWindow(windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
}
}
}
......
......@@ -51,7 +51,7 @@ fun Uri.getFileSize(context: Context): Int {
fun Uri.getMimeType(context: Context): String {
return if (scheme == ContentResolver.SCHEME_CONTENT) {
context.contentResolver.getType(this)
context.contentResolver?.getType(this) ?: ""
} else {
val fileExtension = MimeTypeMap.getFileExtensionFromUrl(toString())
if (fileExtension != null) {
......
......@@ -3,7 +3,6 @@ package chat.rocket.android.videoconferencing.presenter
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.JitsiHelper
import chat.rocket.android.server.domain.*
import chat.rocket.android.server.infraestructure.ConnectionManager
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.core.RocketChatClient
......@@ -22,8 +21,6 @@ class VideoConferencingPresenter @Inject constructor(
private val connectionManagerFactory: ConnectionManagerFactory,
private val settings: GetSettingsInteractor
) {
private lateinit var currentServerUrl: String
private lateinit var connectionManager: ConnectionManager
private lateinit var client: RocketChatClient
private lateinit var publicSettings: PublicSettings
private lateinit var chatRoomId: String
......@@ -31,9 +28,7 @@ class VideoConferencingPresenter @Inject constructor(
fun setup(chatRoomId: String) {
currentServerRepository.get()?.let {
currentServerUrl = it
connectionManager = connectionManagerFactory.create(it)
client = connectionManager.client
client = connectionManagerFactory.create(it).client
publicSettings = settings.get(it)
}
this.chatRoomId = chatRoomId
......
......@@ -27,13 +27,10 @@ class AdminPanelWebViewFragment : DaggerFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bundle = arguments
if (bundle != null) {
webPageUrl = bundle.getString(BUNDLE_WEB_PAGE_URL)
userToken = bundle.getString(BUNDLE_USER_TOKEN)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
arguments?.run {
webPageUrl = getString(BUNDLE_WEB_PAGE_URL, "")
userToken = getString(BUNDLE_USER_TOKEN, "")
} ?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
}
override fun onCreateView(
......@@ -65,7 +62,7 @@ class AdminPanelWebViewFragment : DaggerFragment() {
web_view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
ui { _ ->
ui {
view_loading.hide()
web_view.evaluateJavascript("Meteor.loginWithToken('$userToken', function() { })") {}
}
......
......@@ -8,13 +8,14 @@ import android.view.View
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.chatrooms.adapter.RoomsAdapter
/**
* Adds a default or custom divider to specific item views from the adapter's data set.
* @see RecyclerView.ItemDecoration
*/
class DividerItemDecoration() : RecyclerView.ItemDecoration() {
private lateinit var divider: Drawable
private var divider: Drawable? = null
private var boundStart = 0
private var boundEnd = 0
......@@ -39,10 +40,7 @@ class DividerItemDecoration() : RecyclerView.ItemDecoration() {
// Custom divider will be used.
constructor(context: Context, @DrawableRes drawableResId: Int) : this() {
val customDrawable = ContextCompat.getDrawable(context, drawableResId)
if (customDrawable != null) {
divider = customDrawable
}
divider = ContextCompat.getDrawable(context, drawableResId)
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
......@@ -53,16 +51,16 @@ class DividerItemDecoration() : RecyclerView.ItemDecoration() {
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
if (isLastView(child, parent))
if (isLastView(child, parent) || isViewTypeHeader(child, parent))
continue
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight
val bottom = child.bottom + params.bottomMargin
val top = bottom - (divider?.intrinsicHeight ?: 0)
divider.setBounds(left, top, right, bottom)
divider.draw(c)
divider?.setBounds(left, top, right, bottom)
divider?.draw(c)
}
}
......@@ -70,4 +68,9 @@ class DividerItemDecoration() : RecyclerView.ItemDecoration() {
val position = parent.getChildAdapterPosition(view)
return position == parent.adapter?.itemCount?.minus(1) ?: false
}
}
\ No newline at end of file
private fun isViewTypeHeader(view: View, parent: RecyclerView): Boolean {
val position = parent.getChildViewHolder(view).itemViewType
return position == RoomsAdapter.VIEW_TYPE_HEADER
}
}
<vector android:height="24dp" android:viewportHeight="100"
android:viewportWidth="100" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#ededed" android:pathData="M50,100q-24.66,0 -49.32,0c-0.57,0 -0.68,-0.11 -0.68,-0.68Q0,50 0,0.68C0,0.11 0.11,0 0.68,0Q50,0 99.32,0c0.57,0 0.68,0.11 0.68,0.68q0,49.32 0,98.64c0,0.57 -0.11,0.68 -0.68,0.68Q74.66,100 50,100Z"/>
<path android:fillColor="#afafae" android:pathData="M49.42,90C41.72,90 34,90 26.34,90a13.82,13.82 0,0 1,-8.55 -2.75A11.18,11.18 0,0 1,13.22 79a46.76,46.76 0,0 1,3 -21.44,17.06 17.06,0 0,1 6.56,-8.48 15.67,15.67 0,0 1,7.3 -2.28,4.52 4.52,0 0,1 2.84,1c1.62,1 3.18,2.14 4.86,3.05a23.24,23.24 0,0 0,20.34 1.5A25.39,25.39 0,0 0,65 48.51c2.81,-2.26 5.65,-1.76 8.61,-0.71 3.73,1.32 6.24,4 8.11,7.39A35.18,35.18 0,0 1,85.31 67,54.2 54.2,0 0,1 86,77.31a12.47,12.47 0,0 1,-4 9.28,12.65 12.65,0 0,1 -7.33,3.22c-3.9,0.4 -7.82,0.13 -11.73,0.17C58.41,90 53.92,90 49.42,90Z"/>
<path android:fillColor="#afafae" android:pathData="M69.35,30.28c0.4,10.36 -8.74,20 -19.89,19.93s-19.92,-9.58 -19.89,-20 8.81,-19.93 20,-19.88C60.64,10.35 69.77,19.87 69.35,30.28Z"/>
</vector>
......@@ -10,6 +10,7 @@
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_centerHorizontal="true"
android:background="@drawable/bg_empty_user_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
......
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="chat.rocket.android.about.ui.AboutFragment">
<ImageView
android:id="@+id/image_app_name"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:src="@drawable/ic_app_name"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="50dp"
android:layout_gravity="center">
<ImageView
android:layout_width="160dp"
android:layout_height="160dp"
android:src="@drawable/ic_launcher_foreground"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/image_app_name"
android:adjustViewBounds="true"
android:scaleX="1.5"
android:scaleY="1.5" />
<ImageView
android:id="@+id/image_app_name"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:src="@drawable/ic_app_name"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/text_version_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Version alpha2.0.1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_app_name"
android:layout_marginTop="16dp"
android:textColor="@color/colorSecondaryText"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
<ImageView
android:layout_width="160dp"
android:layout_height="160dp"
android:adjustViewBounds="true"
android:scaleX="1.5"
android:scaleY="1.5"
android:src="@drawable/ic_launcher_foreground"
app:layout_constraintBottom_toTopOf="@id/image_app_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_build_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Build # 2000"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_version_name"
android:layout_marginTop="8dp"
android:textColor="@color/colorSecondaryText" />
<TextView
android:id="@+id/text_version_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Version alpha2.0.1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_app_name"
android:layout_marginTop="16dp"
android:textColor="@color/colorSecondaryText"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
<TextView
android:id="@+id/text_build_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Build # 2000"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_version_name"
android:layout_marginTop="8dp"
android:textColor="@color/colorSecondaryText" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/screen_edge_left_and_right_margins">
<TextView
android:id="@+id/text_reset_password"
style="@style/Authentication.TextView.Headline"
android:text="@string/title_are_you_sure"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/text_delete_account_password"
style="@style/Authentication.EditText.Border"
android:layout_marginTop="16dp"
android:drawableStart="@drawable/ic_key_black_20dp"
android:hint="@string/msg_password"
android:imeOptions="actionDone"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_reset_password" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -4,24 +4,14 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<!--<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/darkGray" />-->
<TextView
android:id="@+id/text_chatroom_header"
style="@style/ChatRooms.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:padding="16dp"
android:paddingEnd="@dimen/screen_edge_left_and_right_padding"
android:paddingStart="@dimen/screen_edge_left_and_right_padding"
android:text="@string/chatroom_header" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/quoteBar" />
</LinearLayout>
\ No newline at end of file
</LinearLayout>
\ No newline at end of file
......@@ -34,8 +34,8 @@
android:id="@+id/day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textAppearance="@style/Message.DayMarker"
tools:text="Wednesday" />
......@@ -51,7 +51,7 @@
layout="@layout/avatar"
android:layout_width="40dp"
android:layout_height="40dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/message_header" />
<LinearLayout
......@@ -95,17 +95,16 @@
android:layout_marginStart="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toRightOf="@+id/layout_avatar"
app:layout_constraintStart_toEndOf="@+id/layout_avatar"
app:layout_constraintTop_toBottomOf="@+id/day_marker_layout">
<TextView
android:id="@+id/text_sender"
style="@style/Sender.Name.TextView"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:maxLength="22"
tools:text="User Name with long name" />
<TextView
......@@ -123,7 +122,7 @@
android:layout_marginStart="4dp"
android:text="@string/msg_edited"
android:textStyle="italic"
android:visibility="gone"
android:visibility="invisible"
tools:visibility="visible" />
<ImageView
......@@ -133,7 +132,7 @@
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:layout_marginTop="2dp"
android:visibility="gone"
android:visibility="invisible"
app:srcCompat="@drawable/ic_action_message_star_24dp"
tools:visibility="visible" />
......@@ -141,7 +140,7 @@
android:id="@+id/read_receipt_view"
android:layout_width="24dp"
android:layout_height="24dp"
android:visibility="gone"
android:visibility="invisible"
app:srcCompat="@drawable/ic_check_unread_24dp"
tools:visibility="visible" />
</LinearLayout>
......
......@@ -21,6 +21,7 @@
android:id="@+id/image_avatar"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/bg_empty_user_avatar"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
......
......@@ -325,6 +325,7 @@
<string name="chatroom_header">Kopf</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">Räume</string>
<string name="header_private_groups">Private Räume</string>
<string name="header_direct_messages">Direkt Nachrichten</string>
......
......@@ -316,6 +316,7 @@
<string name="chatroom_header">Cabezazo</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">Canales</string>
<string name="header_private_groups">Grupos privados</string>
<string name="header_direct_messages">Mensajes directos</string>
......
......@@ -319,6 +319,7 @@
<string name="chatroom_header">سرپیام</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">کانال‌ها</string>
<string name="header_private_groups">گروه‌های خصوصی</string>
<string name="header_direct_messages">پیام‌های خصوصی</string>
......
This diff is collapsed.
......@@ -125,7 +125,7 @@
<string name="msg_preview_file">फ़ाइल</string>
<string name="msg_unread_messages">अपठित संदेश</string>
<string name="msg_no_messages_yet">अभी तक कोई पोस्ट नहीं</string>
<string name="msg_build">Build %1$d - %2$s - %3$s</string> <!-- TODO Add translation -->
<string name="msg_build">निर्माण %1$d - %2$s - %3$s</string>
<string name="msg_update_app_version_in_order_to_continue">पुराना सर्वर वर्शन। जारी रखने के लिए सर्वर वर्शन को अद्यतन करने के लिए कृपया सर्वर व्यवस्थापक से संपर्क करें।</string>
<string name="msg_ver_not_recommended">
ऐसा लगता है कि आपका सर्वर संस्करण अनुशंसित संस्करण %1$s के नीचे है।\nआप अभी भी लॉगिन कर सकते हैं लेकिन आप अप्रत्याशित व्यवहार का अनुभव कर सकते हैं
......@@ -320,6 +320,7 @@
<string name="chatroom_header">हैडर</string>
<!--ChatRooms Headers-->
<string name="header_favorite">पसंदीदा</string>
<string name="header_channel">चैनलों</string>
<string name="header_private_groups">निजी समूहों</string>
<string name="header_direct_messages">प्रत्यक्ष संदेश</string>
......
......@@ -66,7 +66,7 @@
<string-array name="settings_actions">
<item name="item_preferences">Preferenze</item>
<item name="item_password">Cambia password</item>
<item name="change_language">Change language</item><!-- TODO Add translation -->
<item name="change_language">Cambia lingua</item>
<item name="item_share_app">Condividi app</item>
<item name="item_rate_us">Votaci</item>
<item name="item_contact_us">Contattaci</item>
......@@ -127,7 +127,7 @@
<string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">Documento</string>
<string name="msg_no_messages_yet">Nessun nuovo messaggio</string>
<string name="msg_build">Build %1$d - %2$s - %3$s</string> <!-- TODO Add translation -->
<string name="msg_build">Versione %1$d - %2$s - %3$s</string>
<string name="msg_update_app_version_in_order_to_continue">Versione server non aggiornata. Si prega di contattare l\'amministratore del server per aggiornare la versione del server per continuare.</string>
<string name="msg_ver_not_recommended">Sembra che la versione del tuo server sia inferiore alla versione consigliata %1$s.\nÈ ancora possibile accedere ma è possibile che si verifichino comportamenti imprevisti.</string>
<string name="msg_ver_not_minimum">Sembra che la versione del tuo server sia inferiore alla versione minima richiesta %1$s.\nSi prega di aggiornare il server per accedere!</string>
......@@ -316,6 +316,7 @@
<string name="chatroom_header">Intestazione</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">Canali</string>
<string name="header_private_groups">Gruppi Privati</string>
<string name="header_direct_messages">Messaggi Diretti</string>
......@@ -338,4 +339,4 @@
<string name="submit">Invia</string>
<string name="required">*necessario</string>
<string name="report_sent">Il tuo resoconto è stato inviato!</string>
</resources>
\ No newline at end of file
</resources>
......@@ -320,6 +320,7 @@
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">チャンネル</string>
<string name="header_private_groups">プライベートグループ</string>
<string name="header_direct_messages">ダイレクトメッセージ</string>
......
......@@ -318,6 +318,7 @@
<string name="chatroom_header">Cabeçalho</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favoritos</string>
<string name="header_channel">Canais</string>
<string name="header_private_groups">Grupos Privados</string>
<string name="header_direct_messages">Mensagens diretas</string>
......
This diff is collapsed.
......@@ -316,6 +316,7 @@
<string name="chatroom_header">Заголовок</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">Каналы</string>
<string name="header_private_groups">Приватные каналы</string>
<string name="header_direct_messages">Личная переписка</string>
......@@ -333,7 +334,7 @@
<string name="message_room_changed_privacy">Тип канала изменен на: %1$s пользователем %2$s</string>
<!-- User Details -->
<string name="timezone">Часовой поясe</string>
<string name="timezone">Часовой пояс</string>
<!-- Report -->
<string name="submit">Отправить</string>
......
......@@ -321,6 +321,7 @@
<string name="chatroom_header">Başlık</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">Kanallar</string>
<string name="header_private_groups">Gizli Gruplar</string>
<string name="header_direct_messages">Direkt Mesajlar</string>
......
......@@ -317,6 +317,7 @@
<string name="chatroom_header">Заголовок</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">Канали</string>
<string name="header_private_groups">Приватні канали</string>
<string name="header_direct_messages">Особисті повідомлення</string>
......
......@@ -316,6 +316,7 @@
<string name="chatroom_header">头部</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string><!-- TODO - Add proper translation -->
<string name="header_channel">频道</string>
<string name="header_private_groups">私人组</string>
<string name="header_direct_messages">直接对话</string>
......
......@@ -332,6 +332,7 @@ https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strin
<string name="chatroom_header">Header</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favorites</string>
<string name="header_channel">Channels</string>
<string name="header_private_groups">Private Groups</string>
<string name="header_direct_messages">Direct Messages</string>
......
......@@ -53,8 +53,15 @@ class DrawingActivity : DaggerAppCompatActivity(), DrawView {
}
private fun setupDrawTools() {
image_draw_eraser.setOnClickListener {
image_draw_eraser.setOnLongClickListener{
custom_draw_view.clearCanvas()
return@setOnLongClickListener true
}
image_draw_eraser.setOnClickListener {
custom_draw_view.setColor(
ResourcesCompat.getColor(resources, R.color.color_white, null)
)
toggleDrawTools(draw_tools, false)
}
......@@ -111,6 +118,13 @@ class DrawingActivity : DaggerAppCompatActivity(), DrawView {
}
private fun colorSelector() {
image_color_black.setOnClickListener {
custom_draw_view.setColor(
ResourcesCompat.getColor(resources, R.color.color_black, null)
)
scaleColorView(image_color_black)
}
image_color_red.setOnClickListener {
custom_draw_view.setColor(
ResourcesCompat.getColor(resources, R.color.color_red, null)
......
......@@ -42,7 +42,6 @@ class CustomDrawView(context: Context, attrs: AttributeSet) : View(context, attr
fun undo() {
if (mPaths.isEmpty() && mLastPaths.isNotEmpty()) {
mPaths = mLastPaths.clone() as LinkedHashMap<MyPath, PaintOptions>
mLastPaths.clear()
invalidate()
return
}
......
package chat.rocket.android.util.extension
import android.content.Context
import android.os.Build
import android.widget.TextView
import androidx.appcompat.widget.SearchView
fun SearchView.onQueryTextListener(queryListener: (String) -> Unit) {
......@@ -14,4 +17,12 @@ fun SearchView.onQueryTextListener(queryListener: (String) -> Unit) {
return true
}
})
}
fun TextView.setTextViewAppearance(context: Context, style: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setTextAppearance(style)
} else {
setTextAppearance(context, style)
}
}
\ No newline at end of file
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