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 {
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 {
fun getDateTime(localDateTime: LocalDateTime): String {
return formatLocalDateTime(localDateTime)
}
}
private fun formatLocalDateTime(localDateTime: LocalDateTime): String {
private fun formatLocalDateTime(localDateTime: LocalDateTime): String {
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
return localDateTime.format(formatter).toString()
}
}
private fun formatLocalDate(localDate: LocalDate): String {
private fun formatLocalDate(localDate: LocalDate): String {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
return localDate.format(formatter).toString()
}
}
private fun formatLocalTime(localTime: LocalTime): String {
private fun formatLocalTime(localTime: LocalTime): String {
val formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
return localTime.format(formatter).toString()
}
}
}
\ No newline at end of file
......@@ -8,7 +8,8 @@ import chat.rocket.android.server.domain.MultiServerTokenRepository
import com.squareup.moshi.Moshi
@PerActivity
class SharedPreferencesMultiServerTokenRepository(private val repository: LocalRepository,
class SharedPreferencesMultiServerTokenRepository(
private val repository: LocalRepository,
private val moshi: Moshi
) : MultiServerTokenRepository {
......@@ -16,11 +17,7 @@ class SharedPreferencesMultiServerTokenRepository(private val repository: LocalR
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,12 +39,10 @@ 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 {
fun newInstance(serverName: String): Fragment = LoginFragment().apply {
arguments = Bundle(1).apply {
putString(SERVER_NAME, serverName)
}
}
}
class LoginFragment : Fragment(), LoginView {
......@@ -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,8 +88,7 @@ fun newInstance(
isLoginFormEnabled: Boolean,
isNewAccountCreationEnabled: Boolean,
deepLinkInfo: LoginDeepLinkInfo? = null
): Fragment {
return LoginOptionsFragment().apply {
): Fragment = LoginOptionsFragment().apply {
arguments = Bundle(23).apply {
putString(SERVER_NAME, serverName)
putString(STATE, state)
......@@ -118,7 +117,6 @@ fun newInstance(
putBoolean(IS_NEW_ACCOUNT_CREATION_ENABLED, isNewAccountCreationEnabled)
putParcelable(DEEP_LINK_INFO, deepLinkInfo)
}
}
}
class LoginOptionsFragment : Fragment(), LoginOptionsView {
......@@ -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,13 +31,11 @@ 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 {
fun newInstance(userId: String, authToken: String): Fragment = RegisterUsernameFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_USER_ID, userId)
putString(BUNDLE_AUTH_TOKEN, authToken)
}
}
}
class RegisterUsernameFragment : Fragment(), RegisterUsernameView {
......@@ -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,13 +25,11 @@ 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 {
fun newInstance(username: String, password: String): Fragment = TwoFAFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_USERNAME, username)
putString(BUNDLE_PASSWORD, password)
}
}
}
private const val BUNDLE_USERNAME = "username"
......@@ -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
......
......@@ -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,10 +1275,8 @@ class ChatRoomPresenter @Inject constructor(
* @param unfinishedMessage The unfinished message to save.
*/
fun saveDraftMessage(unfinishedMessage: String) {
if (unfinishedMessage.isNotBlank()) {
localRepository.save(draftKey, unfinishedMessage)
}
}
fun clearDraftMessage() {
localRepository.clear(draftKey)
......
......@@ -30,8 +30,7 @@ fun Context.chatRoomIntent(
isCreator: Boolean = false,
isFavorite: Boolean = false,
chatRoomMessage: String? = null
): Intent {
return Intent(this, ChatRoomActivity::class.java).apply {
): 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)
......@@ -41,7 +40,6 @@ fun Context.chatRoomIntent(
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,26 +77,27 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
return
}
val chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
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)
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)
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()
......@@ -118,6 +117,7 @@ class ChatRoomActivity : AppCompatActivity(), HasSupportFragmentInjector {
}
}
}
}
override fun onBackPressed() {
finishActivity()
......
package chat.rocket.android.chatroom.ui
import android.app.Activity
import androidx.appcompat.app.AlertDialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
......@@ -12,16 +11,13 @@ import android.os.Bundle
import android.os.Handler
import android.provider.MediaStore
import android.text.SpannableStringBuilder
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider
import androidx.core.text.bold
import androidx.core.view.isVisible
......@@ -33,12 +29,7 @@ import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.chatroom.adapter.ChatRoomAdapter
import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.EmojiSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.PEOPLE
import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter
import chat.rocket.android.chatroom.adapter.*
import chat.rocket.android.chatroom.presentation.ChatRoomNavigator
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.presentation.ChatRoomView
......@@ -52,13 +43,7 @@ import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.chatrooms.adapter.model.RoomUiModel
import chat.rocket.android.draw.main.ui.DRAWING_BYTE_ARRAY_EXTRA_DATA
import chat.rocket.android.draw.main.ui.DrawingActivity
import chat.rocket.android.emoji.ComposerEditText
import chat.rocket.android.emoji.Emoji
import chat.rocket.android.emoji.EmojiKeyboardListener
import chat.rocket.android.emoji.EmojiKeyboardPopup
import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.*
import chat.rocket.android.emoji.internal.isCustom
import chat.rocket.android.helper.EndlessRecyclerViewScrollListener
import chat.rocket.android.helper.ImageHelper
......@@ -66,17 +51,7 @@ import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.MessageParser
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extension.createImageFile
import chat.rocket.android.util.extensions.circularRevealOrUnreveal
import chat.rocket.android.util.extensions.clearLightStatusBar
import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.hideKeyboard
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.isNotNullNorEmpty
import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui
import chat.rocket.android.util.extensions.*
import chat.rocket.common.model.RoomType
import chat.rocket.common.model.roomTypeOf
import chat.rocket.core.internal.realtime.socket.model.State
......@@ -110,8 +85,7 @@ fun newInstance(
isCreator: Boolean = false,
isFavorite: Boolean = false,
chatRoomMessage: String? = null
): Fragment {
return ChatRoomFragment().apply {
): Fragment = ChatRoomFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
putString(BUNDLE_CHAT_ROOM_NAME, chatRoomName)
......@@ -123,7 +97,6 @@ fun newInstance(
putBoolean(BUNDLE_CHAT_ROOM_IS_FAVORITE, isFavorite)
putString(BUNDLE_CHAT_ROOM_MESSAGE, chatRoomMessage)
}
}
}
internal const val TAG_CHAT_ROOM_FRAGMENT = "ChatRoomFragment"
......@@ -195,7 +168,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private var verticalScrollOffset = AtomicInteger(0)
private val dialogView by lazy { View.inflate(context, R.layout.file_attachments_dialog, null) }
internal val alertDialog by lazy { activity?.let { AlertDialog.Builder(it).setView(dialogView).create() } }
internal val alertDialog by lazy {
activity?.let {
AlertDialog.Builder(it).setView(dialogView).create()
}
}
internal val imagePreview by lazy { dialogView.findViewById<ImageView>(R.id.image_preview) }
internal val sendButton by lazy { dialogView.findViewById<android.widget.Button>(R.id.button_send) }
internal val cancelButton by lazy { dialogView.findViewById<android.widget.Button>(R.id.button_cancel) }
......@@ -255,7 +232,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
button_fab.hide()
newMessageCount = 0
} else {
if (dy < 0 && !button_fab.isVisible) {
if (dy < 0 && isAdded && !button_fab.isVisible) {
button_fab.show()
if (newMessageCount != 0) text_count.isVisible = true
}
......@@ -268,22 +245,27 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
AndroidSupportInjection.inject(this)
setHasOptionsMenu(true)
val bundle = arguments
if (bundle != null) {
chatRoomId = bundle.getString(BUNDLE_CHAT_ROOM_ID)
chatRoomName = bundle.getString(BUNDLE_CHAT_ROOM_NAME)
chatRoomType = bundle.getString(BUNDLE_CHAT_ROOM_TYPE)
isReadOnly = bundle.getBoolean(BUNDLE_IS_CHAT_ROOM_READ_ONLY)
isSubscribed = bundle.getBoolean(BUNDLE_CHAT_ROOM_IS_SUBSCRIBED)
chatRoomLastSeen = bundle.getLong(BUNDLE_CHAT_ROOM_LAST_SEEN)
isCreator = bundle.getBoolean(BUNDLE_CHAT_ROOM_IS_CREATOR)
isFavorite = bundle.getBoolean(BUNDLE_CHAT_ROOM_IS_FAVORITE)
chatRoomMessage = bundle.getString(BUNDLE_CHAT_ROOM_MESSAGE)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
adapter = ChatRoomAdapter(chatRoomId, chatRoomType, chatRoomName, this, reactionListener = this, navigator = navigator)
arguments?.run {
chatRoomId = getString(BUNDLE_CHAT_ROOM_ID, "")
chatRoomName = getString(BUNDLE_CHAT_ROOM_NAME, "")
chatRoomType = getString(BUNDLE_CHAT_ROOM_TYPE, "")
isReadOnly = getBoolean(BUNDLE_IS_CHAT_ROOM_READ_ONLY)
isSubscribed = getBoolean(BUNDLE_CHAT_ROOM_IS_SUBSCRIBED)
chatRoomLastSeen = getLong(BUNDLE_CHAT_ROOM_LAST_SEEN)
isCreator = getBoolean(BUNDLE_CHAT_ROOM_IS_CREATOR)
isFavorite = getBoolean(BUNDLE_CHAT_ROOM_IS_FAVORITE)
chatRoomMessage = getString(BUNDLE_CHAT_ROOM_MESSAGE)
}
?: requireNotNull(arguments) { "no arguments supplied when the fragment was instantiated" }
adapter = ChatRoomAdapter(
chatRoomId,
chatRoomType,
chatRoomName,
this,
reactionListener = this,
navigator = navigator
)
}
override fun onCreateView(
......@@ -305,7 +287,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
with(activity as ChatRoomActivity) {
setupToolbarTitle(chatRoomName)
setupExpandMoreForToolbar {
presenter.toChatDetails(chatRoomId, chatRoomType, isSubscribed, isFavorite, disableMenu)
presenter.toChatDetails(
chatRoomId,
chatRoomType,
isSubscribed,
isFavorite,
disableMenu
)
}
}
getDraftMessage()
......@@ -528,9 +516,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
text_count.text = "99+"
}
text_count.isVisible = true
}
else if (!button_fab.isVisible) {
} else if (!button_fab.isVisible) {
recycler_view.scrollToPosition(0)
}
verticalScrollOffset.set(0)
......@@ -561,7 +547,11 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
override fun showReplyingAction(username: String, replyMarkdown: String, quotedMessage: String) {
override fun showReplyingAction(
username: String,
replyMarkdown: String,
quotedMessage: String
) {
ui {
citation = replyMarkdown
actionSnackbar.title = username
......@@ -644,7 +634,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
if (cursorPosition > -1) {
context?.let {
val offset = if (!emoji.isCustom()) emoji.unicode.length else emoji.shortname.length
val parsed = if (emoji.isCustom()) emoji.shortname else EmojiParser.parse(it, emoji.shortname)
val parsed = if (emoji.isCustom()) emoji.shortname else EmojiParser.parse(
it,
emoji.shortname
)
text_message.text?.insert(cursorPosition, parsed)
text_message.setSelection(cursorPosition + offset)
}
......@@ -668,8 +661,14 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.react(messageId, emoji.shortname)
}
override fun onReactionLongClicked(shortname: String, isCustom: Boolean, url: String?, usernames: List<String>) {
val layout = LayoutInflater.from(requireContext()).inflate(R.layout.reaction_praises_list_item, null)
override fun onReactionLongClicked(
shortname: String,
isCustom: Boolean,
url: String?,
usernames: List<String>
) {
val layout =
LayoutInflater.from(requireContext()).inflate(R.layout.reaction_praises_list_item, null)
val dialog = AlertDialog.Builder(requireContext())
.setView(layout)
.setCancelable(true)
......@@ -696,11 +695,13 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
listing += if (index == usernames.size - 1) "|$username" else "$username, "
}
listing = listing.replace(", |", " ${requireContext().getString(R.string.msg_and)} ")
listing =
listing.replace(", |", " ${requireContext().getString(R.string.msg_and)} ")
}
text_view_usernames.text = requireContext().resources.getQuantityString(
R.plurals.msg_reacted_with_, usernames.size, listing, shortname)
R.plurals.msg_reacted_with_, usernames.size, listing, shortname
)
dialog.show()
}
......@@ -747,21 +748,20 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
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)
}
}
}
......@@ -835,7 +835,8 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
true
)
emojiKeyboardPopup = EmojiKeyboardPopup(activity!!, activity!!.findViewById(R.id.fragment_container))
emojiKeyboardPopup =
EmojiKeyboardPopup(activity!!, activity!!.findViewById(R.id.fragment_container))
emojiKeyboardPopup.listener = this
......@@ -1087,7 +1088,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
presenter.editMessage(roomId, messageId, text)
}
override fun toogleStar(id: String, star: Boolean) {
override fun toggleStar(id: String, star: Boolean) {
if (star) {
presenter.starMessage(id)
} else {
......@@ -1095,7 +1096,7 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
}
override fun tooglePin(id: String, pin: Boolean) {
override fun togglePin(id: String, pin: Boolean) {
if (pin) {
presenter.pinMessage(id)
} else {
......@@ -1136,8 +1137,10 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
}
override fun reportMessage(id: String) {
presenter.reportMessage(messageId = id,
description = "This message was reported by a user from the Android app")
presenter.reportMessage(
messageId = id,
description = "This message was reported by a user from the Android app"
)
}
fun openEmojiKeyboard() {
......
......@@ -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,12 +462,10 @@ class UiModelMapper @Inject constructor(
subscriptionId = chatRoom.subscriptionId)
}
private fun mapMessagePreview(message: Message): Message {
return when (message.isSystemMessage()) {
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> {
val reactions = message.reactions?.let {
......@@ -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()) {
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) {
val content = with(context) {
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.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}")
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,15 +36,30 @@ 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) {
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)))
}
}
return list
}
......@@ -62,8 +77,7 @@ class RoomUiModelMapper(
return list
}
private fun mapUser(user: User): RoomUiModel {
return with(user) {
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!!)
......@@ -78,10 +92,8 @@ class RoomUiModelMapper(
username = username
)
}
}
private fun mapRoom(room: Room, showLastMessage: Boolean = true): RoomUiModel {
return with(room) {
private fun mapRoom(room: Room, showLastMessage: Boolean = true): RoomUiModel = with(room) {
RoomUiModel(
id = id,
name = name!!,
......@@ -100,14 +112,13 @@ class RoomUiModelMapper(
writable = isChannelWritable(muted)
)
}
}
fun map(chatRoom: ChatRoom, showLastMessage: Boolean = true): RoomUiModel {
return with(chatRoom.chatRoom) {
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)
......@@ -128,8 +139,7 @@ class RoomUiModelMapper(
}
val hasMentions = mapMentions(userMentions, groupMentions)
val open = open
val lastMessageMarkdown =
lastMessage?.let { Markwon.markdown(context, it.toString()).toString() }
val lastMessageMarkdown = lastMessage?.let { Markwon.markdown(context, it.toString()).toString() }
RoomUiModel(
id = id,
......@@ -140,6 +150,7 @@ class RoomUiModelMapper(
date = timestamp,
unread = unread,
mentions = hasMentions,
favorite = favorite,
alert = isUnread,
lastMessage = lastMessageMarkdown,
status = status,
......@@ -148,21 +159,19 @@ class RoomUiModelMapper(
writable = isChannelWritable(muted)
)
}
}
private fun isChannelWritable(muted: List<String>?): Boolean {
val canWriteToReadOnlyChannels = permissions.canPostToReadOnlyChannels()
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,14 +204,12 @@ class RoomUiModelMapper(
}
}
private fun mapUnread(unread: Long): String? {
return when (unread) {
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 {
if (userMentions != null && groupMentions != null) {
......
......@@ -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) {
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) {
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,8 +19,7 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) :
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> {
return when (viewType) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<*> = when (viewType) {
VIEW_TYPE_ROOM -> {
val view = parent.inflate(R.layout.item_chat)
RoomViewHolder(view, listener)
......@@ -35,7 +34,6 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) :
}
else -> throw IllegalStateException("View type must be either Room, Header or Loading")
}
}
override fun getItemCount() = values.size
......@@ -49,14 +47,12 @@ class RoomsAdapter(private val listener: (RoomUiModel) -> Unit) :
}
}
override fun getItemViewType(position: Int): Int {
return when (values[position]) {
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) {
if (holder is RoomViewHolder) {
......
......@@ -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,22 +68,19 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
private var progressDialog: ProgressDialog? = null
companion object {
fun newInstance(chatRoomId: String? = null): ChatRoomsFragment {
return ChatRoomsFragment().apply {
fun newInstance(chatRoomId: String? = null): ChatRoomsFragment = ChatRoomsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
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()
}
......
......@@ -41,12 +41,20 @@ import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.HashSet
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.LinkedHashMap
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.system.measureTimeMillis
class DatabaseManager(val context: Application, val serverUrl: String) {
private val database: RCDatabase = androidx.room.Room.databaseBuilder(context,
RCDatabase::class.java, serverUrl.databaseName())
private val database: RCDatabase = androidx.room.Room.databaseBuilder(
context,
RCDatabase::class.java, serverUrl.databaseName()
)
.fallbackToDestructiveMigration()
.build()
private val dbContext = newSingleThreadContext("$serverUrl-db-context")
......@@ -141,10 +149,11 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
batch.forEach {
when (it.type) {
is Type.Removed -> toRemove.add(removeChatRoom(it.data))
is Type.Inserted -> insertChatRoom(it.data)?.let { toInsert.add(it) }
is Type.Inserted -> insertChatRoom(it.data)?.let { room -> toInsert.add(room) }
is Type.Updated -> {
when (it.data) {
is Subscription -> updateSubs[(it.data as Subscription).roomId] = it.data as Subscription
is Subscription -> updateSubs[(it.data as Subscription).roomId] =
it.data as Subscription
is Room -> updateRooms[(it.data as Room).id] = it.data as Room
}
}
......@@ -158,7 +167,13 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
val filteredUpdate = toUpdate.filterNot { toRemove.contains(it.id) }
val filteredInsert = toInsert.filterNot { toRemove.contains(it.id) }
sendOperation(Operation.UpdateRooms(filteredInsert, filteredUpdate, toRemove.toList()))
sendOperation(
Operation.UpdateRooms(
filteredInsert,
filteredUpdate,
toRemove.toList()
)
)
} catch (ex: Exception) {
Timber.d(ex, "Error updating chatrooms")
}
......@@ -187,17 +202,14 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
}
fun processMessagesBatch(messages: List<Message>): Job {
return GlobalScope.launch(dbManagerContext) {
fun processMessagesBatch(messages: List<Message>): Job = GlobalScope.launch(dbManagerContext) {
val list = mutableListOf<Pair<MessageEntity, List<BaseMessageEntity>>>()
messages.forEach { message ->
val pair = createMessageEntities(message)
list.add(pair)
}
sendOperation(Operation.InsertMessages(list))
}
}
private suspend fun createMessageEntities(message: Message): Pair<MessageEntity, List<BaseMessageEntity>> {
val messageEntity = message.toEntity()
......@@ -214,91 +226,86 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
return Pair(messageEntity, list)
}
private fun createReactions(message: Message): List<BaseMessageEntity>? {
if (message.reactions == null || message.reactions!!.isEmpty()) {
return null
}
val reactions = message.reactions!!
private fun createReactions(message: Message): List<BaseMessageEntity>? =
message.reactions?.run {
if (isNotEmpty()) {
val list = mutableListOf<BaseMessageEntity>()
reactions.keys.forEach { reaction ->
val users = reactions[reaction]
users?.let { users ->
list.add(ReactionEntity(reaction, message.id, users.size, users.joinToString()))
keys.forEach { reaction ->
get(reaction)?.let { reactionValue ->
list.add(
ReactionEntity(
reaction,
message.id,
size,
reactionValue.joinToString()
)
)
}
}
return list
}
private fun createUrlEntities(message: Message): List<BaseMessageEntity>? {
if (message.urls == null || message.urls!!.isEmpty()) {
return null
list
} else null
}
private fun createUrlEntities(message: Message): List<BaseMessageEntity>? = message.urls?.run {
if (isNotEmpty()) {
val list = mutableListOf<UrlEntity>()
message.urls!!.forEach { url ->
list.add(UrlEntity(message.id, url.url, url.parsedUrl?.host, url.meta?.title,
url.meta?.description, url.meta?.imageUrl))
}
return list
forEach { url ->
list.add(
UrlEntity(
message.id, url.url, url.parsedUrl?.host, url.meta?.title,
url.meta?.description, url.meta?.imageUrl
)
)
}
private fun createChannelRelations(message: Message): List<BaseMessageEntity>? {
if (message.channels == null || message.channels!!.isEmpty()) {
return null
list
} else null
}
private fun createChannelRelations(message: Message): List<BaseMessageEntity>? =
message.channels?.run {
if (isNotEmpty()) {
val list = mutableListOf<MessageChannels>()
message.channels!!.forEach { channel ->
forEach { channel ->
list.add(MessageChannels(message.id, channel.id, channel.name))
}
return list
}
private suspend fun createMentionRelations(message: Message): List<BaseMessageEntity>? {
if (message.mentions == null || message.mentions!!.isEmpty()) {
return null
list
} else null
}
private suspend fun createMentionRelations(message: Message): List<BaseMessageEntity>? =
message.mentions?.run {
if (isNotEmpty()) {
val list = mutableListOf<MessageMentionsRelation>()
message.mentions!!.filterNot { user -> user.id.isNullOrEmpty() }.forEach { mention ->
filterNot { user -> user.id.isNullOrEmpty() }.forEach { mention ->
insertUserIfMissing(mention)
list.add(MessageMentionsRelation(message.id, mention.id!!))
}
return list
}
private suspend fun createFavoriteRelations(message: Message): List<BaseMessageEntity>? {
if (message.starred == null || message.starred!!.isEmpty()) {
return null
list
} else null
}
private suspend fun createFavoriteRelations(message: Message): List<BaseMessageEntity>? =
message.starred?.run {
if (isNotEmpty()) {
val list = mutableListOf<MessageFavoritesRelation>()
message.starred!!.filterNot { user -> user.id.isNullOrEmpty() }.forEach { userId ->
filterNot { user -> user.id.isNullOrEmpty() }.forEach { userId ->
insertUserIfMissing(userId)
list.add(MessageFavoritesRelation(message.id, userId.id!!))
}
return list
}
private fun createAttachments(message: Message): List<BaseMessageEntity>? {
if (message.attachments == null || message.attachments!!.isEmpty()) {
return null
list
} else null
}
val list = ArrayList<BaseMessageEntity>(message.attachments!!.size)
message.attachments!!.forEach { attachment ->
private fun createAttachments(message: Message): List<BaseMessageEntity>? =
message.attachments?.run {
if (isNotEmpty()) {
val list = ArrayList<BaseMessageEntity>(size)
forEach { attachment ->
list.addAll(attachment.asEntity(message.id, context))
}
return list
list
} else null
}
private suspend fun createUpdates(): List<ChatRoomEntity> {
......@@ -376,15 +383,11 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
}
private fun mapLastMessageText(message: Message?): String? {
return if (message == null) {
null
private fun mapLastMessageText(message: Message?): String? = message?.run {
if (this.message.isEmpty() && attachments?.isNotEmpty() == true) {
message.attachments?.let { mapAttachmentText(it[0]) }
} else {
return if (message.message.isEmpty() && message.attachments?.isNotEmpty() == true) {
mapAttachmentText(message.attachments!![0])
} else {
message.message
}
this.message
}
}
......@@ -432,13 +435,11 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
}
}
private suspend fun insertChatRoom(data: BaseRoom): ChatRoomEntity? {
return when (data) {
private suspend fun insertChatRoom(data: BaseRoom): ChatRoomEntity? = when (data) {
is Room -> insertRoom(data)
is Subscription -> insertSubscription(data)
else -> null
}
}
private suspend fun insertRoom(data: Room): ChatRoomEntity? {
val subscription = insertSubs.remove(data.id)
......@@ -576,31 +577,18 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
is Operation.ClearStatus -> userDao().clearStatus()
is Operation.UpdateRooms -> {
Timber.d("Running ChatRooms transaction: remove: ${operation.toRemove} - insert: ${operation.toInsert} - update: ${operation.toUpdate}")
chatRoomDao().update(operation.toInsert, operation.toUpdate, operation.toRemove)
}
is Operation.InsertRooms -> {
chatRoomDao().insertOrReplace(operation.chatRooms)
}
is Operation.CleanInsertRooms -> {
chatRoomDao().cleanInsert(operation.chatRooms)
}
is Operation.InsertRooms -> chatRoomDao().insertOrReplace(operation.chatRooms)
is Operation.CleanInsertRooms -> chatRoomDao().cleanInsert(operation.chatRooms)
is Operation.InsertUsers -> {
val time = measureTimeMillis { userDao().upsert(operation.users) }
Timber.d("Upserted users batch(${operation.users.size}) in $time MS")
}
is Operation.InsertUser -> {
userDao().insert(operation.user)
}
is Operation.UpsertUser -> {
userDao().upsert(operation.user)
}
is Operation.InsertMessages -> {
messageDao().insert(operation.list)
}
is Operation.SaveLastSync -> {
messageDao().saveLastSync(operation.sync)
}
is Operation.InsertUser -> userDao().insert(operation.user)
is Operation.UpsertUser -> userDao().upsert(operation.user)
is Operation.InsertMessages -> messageDao().insert(operation.list)
is Operation.SaveLastSync -> messageDao().saveLastSync(operation.sync)
}.exhaustive
}
}
......@@ -622,7 +610,8 @@ sealed class Operation {
data class UpsertUser(val user: BaseUserEntity) : Operation()
data class InsertUser(val user: UserEntity) : Operation()
data class InsertMessages(val list: List<Pair<MessageEntity, List<BaseMessageEntity>>>) : Operation()
data class InsertMessages(val list: List<Pair<MessageEntity, List<BaseMessageEntity>>>) :
Operation()
data class SaveLastSync(val sync: MessagesSync) : Operation()
}
......
......@@ -115,7 +115,7 @@ 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,
......@@ -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) {
fun mapAttachmentText(text: String?, attachment: Attachment?, context: Context): String? = attachment?.run {
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 ->
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
}
} 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,12 +25,10 @@ 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 {
fun newInstance(chatRoomId: String): Fragment = FavoriteMessagesFragment().apply {
arguments = Bundle(1).apply {
putString(INTENT_CHAT_ROOM_ID, chatRoomId)
}
}
}
internal const val TAG_FAVORITE_MESSAGES_FRAGMENT = "FavoriteMessagesFragment"
......@@ -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,12 +30,10 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_files.*
import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment {
return FilesFragment().apply {
fun newInstance(chatRoomId: String): Fragment = FilesFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
}
internal const val TAG_FILES_FRAGMENT = "FilesFragment"
......@@ -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,15 +92,17 @@ object ImageHelper {
}
val appBarLayout = AppBarLayout(context).also {
it.layoutParams = lparams
it.setBackgroundColor(Color.BLACK)
it.addView(
with(it) {
layoutParams = lparams
setBackgroundColor(Color.BLACK)
addView(
toolbar, AppBarLayout.LayoutParams(
AppBarLayout.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
)
}
}
val builder = ImageViewer.createPipelineDraweeControllerBuilder()
.setImageRequest(request)
......
......@@ -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
......@@ -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,12 +27,10 @@ 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 {
fun newInstance(chatRoomId: String): Fragment = MembersFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
}
internal const val TAG_MEMBERS_FRAGMENT = "MembersFragment"
......@@ -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,12 +25,10 @@ import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_mentions.*
import javax.inject.Inject
fun newInstance(chatRoomId: String): Fragment {
return MentionsFragment().apply {
fun newInstance(chatRoomId: String): Fragment = MentionsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
}
internal const val TAG_MENTIONS_FRAGMENT = "MentionsFragment"
......@@ -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,12 +25,10 @@ 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 {
fun newInstance(chatRoomId: String): Fragment = PinnedMessagesFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_CHAT_ROOM_ID, chatRoomId)
}
}
}
internal const val TAG_PINNED_MESSAGES_FRAGMENT = "PinnedMessagesFragment"
......@@ -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)
......
......@@ -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) {
resultData?.run {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF) {
presenter.updateAvatar(resultData.data)
data?.let { presenter.updateAvatar(it) }
} else if (requestCode == REQUEST_CODE_FOR_PERFORM_CAMERA) {
presenter.preparePhotoAndUpdateAvatar(resultData.extras["data"] as Bitmap)
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,8 +154,8 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
} else {
null
}
val attachment = Attachment(
list.add(
Attachment(
title = title,
type = type,
description = description,
......@@ -190,7 +185,7 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
?: "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,8 +106,7 @@ class PasswordFragment : Fragment(), PasswordView, ActionMode.Callback {
private fun finishActionMode() = actionMode?.finish()
private fun listenToChanges(): Disposable {
return Observables.combineLatest(
private fun listenToChanges(): Disposable = Observables.combineLatest(
text_new_password.asObservable(),
text_confirm_password.asObservable()
).subscribe {
......@@ -119,7 +118,6 @@ class PasswordFragment : Fragment(), PasswordView, ActionMode.Callback {
finishActionMode()
}
}
}
private fun startActionMode() {
if (actionMode == null) {
......
......@@ -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,12 +29,10 @@ 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 {
fun newInstance(userId: String): Fragment = UserDetailsFragment().apply {
arguments = Bundle(1).apply {
putString(BUNDLE_USER_ID, userId)
}
}
}
internal const val TAG_USER_DETAILS_FRAGMENT = "UserDetailsFragment"
......@@ -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
}
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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="50dp"
android:layout_gravity="center">
<ImageView
android:id="@+id/image_app_name"
android:layout_width="wrap_content"
......@@ -18,13 +25,14 @@
<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" />
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_version_name"
......@@ -49,4 +57,6 @@
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>
......
......@@ -17,14 +17,14 @@
<string name="title_preferences">Préférences</string>
<string name="title_change_password">Changer le mot de passe</string>
<string name="title_rate_us">évaluez nous</string>
<string name="title_admin_panel">Administration</string>
<string name="title_admin_panel">l\' administration</string>
<string name="title_password">Changer le mot de passe</string>
<string name="title_update_profile">Mettre à jour le profil</string>
<string name="title_about">À propos</string>
<string name="title_create_channel">Créer salon</string>
<string name="title_licence">Licence</string> <!-- TODO Add translation -->
<string name="title_are_you_sure">Are you sure?</string> <!-- TODO Add translation -->
<string name="title_channel_details">Channel Details</string> <!-- TODO add translation -->
<string name="title_licence">les permis</string>
<string name="title_are_you_sure">Êtes-vous sûr?</string>
<string name="title_channel_details">Détails de la chaîne</string>
<string name="title_topic">Sujet</string>
<string name="title_announcement">Annonce</string>
<string name="title_description">La description</string>
......@@ -40,7 +40,7 @@
<string name="action_create_channel">Créer salon</string>
<string name="action_create">Créer</string>
<string name="action_logout">Se déconnecter</string>
<string name="action_attach_a_files">Attach a file</string> <!-- TODO Add translation -->
<string name="action_attach_a_files">Joindre un fichier</string>
<string name="action_confirm_password">Confirmer le mot de passe</string>
<string name="action_join_chat">Rejoignez le chat</string>
<string name="action_add_account">Ajouter un compte</string>
......@@ -53,25 +53,25 @@
<string name="action_select_photo_from_gallery">Sélectionner depuis la gallerie</string>
<string name="action_take_a_photo">Prendre une photo</string>
<string name="action_reset_avatar">Réinitialiser l\'avatar</string>
<string name="action_connect_server">Connect with a server</string> <!-- TODO Add translation -->
<string name="action_join_community">Join in the community</string> <!-- TODO Add translation -->
<string name="action_create_server">Create a new server</string> <!-- TODO Add translation -->
<string name="action_register">Register</string> <!-- TODO Add translation -->
<string name="action_confirm">Confirm</string> <!-- TODO Add translation -->
<string name="action_delete_account">Delete account</string> <!-- TODO Add translation -->
<string name="action_connect_server">Se connecter avec un serveur</string>
<string name="action_join_community">Rejoignez la communauté</string>
<string name="action_create_server">Créer un nouveau serveur</string>
<string name="action_register">registre</string>
<string name="action_confirm">Confirmer</string>
<string name="action_delete_account">Effacer le compte</string>
<string name="action_favorite">Favorite</string> <!-- TODO Add translation -->
<string name="action_remove_favorite">Remove favorite</string> <!-- TODO Add translation -->
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation -->
<item name="item_password">Change password</item> <!-- TODO Add translation -->
<item name="change_language">Change language</item> <!-- TODO Add translation -->
<item name="item_share_app">Share app</item> <!-- TODO Add translation -->
<item name="item_rate_us">Rate us</item> <!-- TODO Add translation -->
<item name="item_contact_us">Contact us</item> <!-- TODO Add translation -->
<item name="item_licence">Licence</item> <!-- TODO Add translation -->
<item name="item_about">About</item> <!-- TODO Add translation -->
<item name="item_preferences">Préférences</item>
<item name="item_password">Changer le mot de passe</item>
<item name="change_language">Changer de langue</item>
<item name="item_share_app">Partager l\'application</item>
<item name="item_rate_us">Évaluez nous</item>
<item name="item_contact_us">Contactez nous</item>
<item name="item_licence">les permis</item>
<item name="item_about">Sur</item>
</string-array>
<!-- Regular information messages -->
......@@ -111,7 +111,7 @@
<string name="msg_content_description_log_in_using_gitlab">Connectez-vous en utilisant Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Connectez-vous en utilisant WordPress</string>
<string name="msg_content_description_send_message">Envoyer message</string>
<string name="msg_content_description_show_more_login_options">Show more login options</string> <!-- TODO Translate-->
<string name="msg_content_description_show_more_login_options">montrer plus d\'options le login</string>
<string name="msg_content_description_show_attachment_options">Afficher les options de fichiers</string>
<string name="msg_you">Vous</string>
<string name="msg_unknown">Inconnu</string>
......@@ -125,7 +125,7 @@
<string name="msg_preview_photo">Photo</string>
<string name="msg_preview_file">Fichier</string>
<string name="msg_no_messages_yet">Aucun message pour le moment</string>
<string name="msg_build">Build %1$d - %2$s - %3$s</string> <!-- TODO Add translation -->
<string name="msg_build">bâtir %1$d - %2$s - %3$s</string>
<string name="msg_update_app_version_in_order_to_continue">La version du serveur est trop ancienne. Veuillez contacter l\'administateur pour mettre à jour le serveur afin de pouvoir vous y connecter.</string>
<string name="msg_ver_not_recommended">
On dirait que la version de votre serveur est en dessous de la version recommandée %1$s.\nVous pouvez toujours vous connecter mais vous pouvez rencontrer des comportements inattendus.</string>
......@@ -153,32 +153,32 @@
<string name="msg_send">envoyer</string>
<string name="msg_delete_message">Supprimer Message</string>
<string name="msg_delete_description">Êtes-vous sûr de vouloir supprimer ce message</string>
<string name="msg_welcome_to_rocket_chat">Welcome to Rocket.Chat</string> <!-- TODO Add translation -->
<string name="msg_team_communication">Team Communication</string> <!-- TODO Translate -->
<string name="msg_login_with_email">Login with <b>e-mail</b></string> <!-- TODO Add translation -->
<string name="msg_create_account">Create an account</string> <!-- TODO Add translation -->
<string name="msg_continue_with_facebook">Continue with <b>Facebook</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_github">Continue with <b>Github</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_google">Continue with <b>Google</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_linkedin">Continue with <b>Linkedin</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_gitlab">Continue with <b>GitLab</b></string> <!-- TODO Add translation -->
<string name="msg_continue_with_wordpress">Continue with <b>WordPress</b></string> <!-- TODO Add translation -->
<string name="msg_two_factor_authentication">Two-factor Authentication</string> <!-- TODO Add translation -->
<string name="msg__your_2fa_code">What’s your 2FA code?</string> <!-- TODO Add translation -->
<string name="msg_view_more">view more</string> <!-- TODO - Add proper translation -->
<string name="msg_view_less">view less</string> <!-- TODO - Add proper translation -->
<string name="msg_permalink_copied">Permalink copied</string> <!-- TODO - Add proper translation -->
<string name="msg_send_email">Send email</string> <!-- TODO - Add proper translation -->
<string name="msg_android_app_support">Android app support</string> <!-- TODO - Add proper translation -->
<string name="msg_muted_on_this_channel">You are muted on this channel</string> <!-- TODO - Add proper translation -->
<string name="msg_no_topic">No topic</string> <!-- TODO Add translation -->
<string name="msg_no_announcement">No announcement</string> <!-- TODO Add translation -->
<string name="msg_no_description">No description</string> <!-- TODO Add translation -->
<string name="msg_unable_to_update_password">Unable to update password. Error message: %1$s</string> <!-- TODO - Add proper translation -->
<string name="msg_password_updated_successfully">Password updated successfully</string> <!-- TODO - Add proper translation -->
<string name="msg_welcome_to_rocket_chat">Bienvenue à Rocket.Chat</string>
<string name="msg_team_communication">Communication d\'équipe</string>
<string name="msg_login_with_email">le login avec <b>e-mail</b></string>
<string name="msg_create_account">Créer un compte</string>
<string name="msg_continue_with_facebook">Continuer avec <b>Facebook</b></string>
<string name="msg_continue_with_github">Continuer avec <b>Github</b></string>
<string name="msg_continue_with_google">Continuer avec <b>Google</b></string>
<string name="msg_continue_with_linkedin">Continuer avec <b>Linkedin</b></string>
<string name="msg_continue_with_gitlab">Continuer avec <b>GitLab</b></string>
<string name="msg_continue_with_wordpress">Continuer avec <b>WordPress</b></string>
<string name="msg_two_factor_authentication">authentification à deux facteurs</string>
<string name="msg__your_2fa_code">Quel est votre code 2FA?</string>
<string name="msg_view_more">voir de plus</string>
<string name="msg_view_less">voir de plus</string>
<string name="msg_permalink_copied">Permalink copié</string>
<string name="msg_send_email">Envoyer email</string>
<string name="msg_android_app_support">Android app le support</string>
<string name="msg_muted_on_this_channel">Vous êtes en sourdine sur ce canal</string>
<string name="msg_no_topic">sujet non ajouté</string>
<string name="msg_no_announcement">annonce non ajoutée</string>
<string name="msg_no_description">description non ajoutée</string>
<string name="msg_unable_to_update_password">Impossible de mettre à jour le mot de passe. Message d\'erreur: %1$s</string>
<string name="msg_password_updated_successfully">Mot de passe mis à jour avec succès</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="other">%1$s reacted with %2$s</item> <!-- TODO - Add proper translation -->
<item quantity="one">%1$s a réagi avec %2$s</item>
<item quantity="other">%1$s a réagi avec %2$s</item>
</plurals>
<string name="msg_credentials_saved_successfully">Certificats sauvegardés</string>
......@@ -229,10 +229,8 @@
<string name="action_msg_share">Partager</string>
<string name="action_title_editing">Modification du message</string>
<string name="action_msg_add_reaction">Ajouter une réaction</string>
<!-- TODO - Add proper translation -->
<string name="action_msg_copy_permalink">Copy permalink</string>
<!-- TODO - Add proper translation -->
<string name="action_msg_report">Report</string>
<string name="action_msg_copy_permalink">copie permalien</string>
<string name="action_msg_report">rapport</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">L\'édition n\'est pas autorisée</string>
......@@ -319,6 +317,7 @@
<string name="chatroom_header">Entête</string>
<!--ChatRooms Headers-->
<string name="header_favorite">Favoris</string>
<string name="header_channel">Salons</string>
<string name="header_private_groups">Groupes privés</string>
<string name="header_direct_messages">Messages directs</string>
......@@ -334,14 +333,14 @@
<string name="message_information_title">Informations sur le message</string>
<string name="msg_log_out">Déconnecter…</string>
<string name="msg_sent_attachment">Envoyé un fichier</string>
<string name="message_room_changed_privacy">Room type changed to: %1$s by %2$s</string> <!--TODO - Add proper translation-->
<string name="message_room_changed_privacy">Changé de type de chambre : %1$s de %2$s</string>
<!-- User Details -->
<string name="timezone">Timezone</string> <!-- TODO - Add proper translation -->
<string name="timezone">Fuseau horaire</string>
<!-- Report -->
<string name="submit">Submit</string> <!-- TODO - Add proper translation -->
<string name="required">*required</string> <!-- TODO - Add proper translation -->
<string name="report_sent">Your report has been sent!</string> <!-- TODO - Add proper translation -->
<string name="submit">Soumettre</string>
<string name="required">*Obligatoire</string>
<string name="report_sent">Votre rapport a été envoyé!</string>
</resources>
......@@ -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>
......
......@@ -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>
......
<resources>
<!-- Titles -->
<string name="title_sign_in_your_server">Entre no seu servidor</string>
<string name="title_log_in">Entrar</string>
<string name="title_share_the_app">Partilhe a Aplicação</string>
<string name="title_register_username">Registe um utilizador</string>
<string name="title_reset_password">Reponha a palavra-passe</string>
<string name="title_sign_up">Inscreva-se</string>
<string name="title_authentication">Autenticação</string>
<string name="title_legal_terms">Termos Legais</string>
<string name="title_chats">Conversas</string>
<string name="title_profile">Pefil</string>
<string name="title_members">Membros</string>
<string name="title_counted_members">Membros (%d)</string>
<string name="title_settings">Definições</string>
<string name="title_preferences">Preferências</string>
<string name="title_change_password">Alterar palavra-passe</string>
<string name="title_rate_us">Avalie-nos</string>
<string name="title_admin_panel">Painel de Administração</string>
<string name="title_password">Alterar palavra-passe</string>
<string name="title_update_profile">Actualizar perfil</string>
<string name="title_about">Acerca</string>
<string name="title_create_channel">Criar Canal</string>
<string name="title_licence">Licença</string>
<string name="title_are_you_sure">Tem a certeza?</string>
<string name="title_channel_details">Detalhes do Canal</string>
<string name="title_topic">Tópico</string>
<string name="title_announcement">Anúncio</string>
<string name="title_description">Descrição</string>
<!-- Actions -->
<string name="action_connect">Ligar</string>
<string name="action_use_this_username">Use este nome de utilizador</string>
<string name="action_terms_of_service">Termos do Serviço</string>
<string name="action_privacy_policy">Política de Privacidade</string>
<string name="action_search">Pesquisar</string>
<string name="action_update">Actualizar</string>
<string name="action_settings">Definições</string>
<string name="action_create_channel">Criar canal</string>
<string name="action_create">Criar</string>
<string name="action_logout">Terminar sessão</string>
<string name="action_attach_a_files">Enviar um ficheiro</string>
<string name="action_confirm_password">Confirme a alteração da palavra-passe</string>
<string name="action_join_chat">Entre no chat</string>
<string name="action_add_account">Adicionar conta</string>
<string name="action_online">Online</string>
<string name="action_away">Ausente</string>
<string name="action_busy">Ocupado</string>
<string name="action_invisible">Invisível</string>
<string name="action_drawing">A desenhar</string>
<string name="action_save_to_gallery">Guardar na galeria</string>
<string name="action_select_photo_from_gallery">Selecione a foto da galeria</string>
<string name="action_take_a_photo">Tire uma foto</string>
<string name="action_reset_avatar">Repor avatar</string>
<string name="action_connect_server">Ligue-se a um servidor</string>
<string name="action_join_community">Junte-se à comunidade</string>
<string name="action_create_server">Crie um novo servidor</string>
<string name="action_register">Registar</string>
<string name="action_confirm">Confirmar</string>
<string name="action_delete_account">Apagar conta</string>
<!-- Settings List -->
<string-array name="settings_actions">
<item name="item_preferences">Preferências</item>
<item name="item_password">Alterar palavra-passe</item>
<item name="change_language">Alterar idioma</item>
<item name="item_share_app">Partilhe a aplicação</item>
<item name="item_rate_us">Avalie-nos</item>
<item name="item_contact_us">Contacte-nos</item>
<item name="item_licence">Licença</item>
<item name="item_about">Acerca</item>
</string-array>
<!-- Regular information messages -->
<string name="msg_generic_error">Lamentamos, ocorreu um erro, tente novamente</string>
<string name="msg_no_data_to_display">Sem dados para mostrar</string>
<string name="msg_check_this_out">Experimente isto</string>
<string name="msg_share_using">Partilhe usando</string>
<string name="msg_profile_update_successfully">Perfil actualizado com sucesso</string>
<string name="msg_username">utilizador</string>
<string name="msg_username_or_email">Utilizador ou e-mail</string>
<string name="msg_password">Palavra-passe</string>
<string name="msg_name">Nome</string>
<string name="msg_email">E-mail</string>
<string name="msg_avatar_url">URL do avatar</string>
<string name="msg_or_continue_using_social_accounts">Ou continue utilizando uma conta social</string>
<string name="msg_new_user">Novo utilizador? %1$s</string>
<string name="msg_forgot__your_password">Esqueceu a sua palavra-passe?</string>
<string name="msg_reset">Repor</string>
<string name="msg_check_your_email_to_reset_your_password">E-mail enviado! Verifique a sua caixa de entrada para repor a sua palavra-passe.</string>
<string name="msg_invalid_email">Por favor introduza um endereço de e-mail válido</string>
<string name="msg_new_user_agreement">Ao prosseguir, você concorda com os nossos \n%1$s e %2$s</string>
<string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string>
<string name="msg_yesterday">Ontem</string>
<string name="msg_today">Hoje</string>
<string name="msg_message">Mensagem</string>
<string name="msg_this_room_is_read_only">Esta sala é apenas de leitura</string>
<string name="msg_invalid_2fa_code">Código 2FA inválido</string>
<string name="msg_invalid_file">Ficheiro inválido</string>
<string name="msg_invalid_server_url">URL de servidor inválido</string>
<string name="msg_content_description_log_in_using_facebook">Entre utilizando Facebook</string>
<string name="msg_content_description_log_in_using_github">Entre utilizando Github</string>
<string name="msg_content_description_log_in_using_google">Entre utilizando Google</string>
<string name="msg_content_description_log_in_using_linkedin">Entre utilizando LinkedIn</string>
<string name="msg_content_description_log_in_using_meteor">Entre utilizando Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Entre utilizando Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Entre utilizando Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Entre utilizando WordPress</string>
<string name="msg_content_description_send_message">Enviar mensagem</string>
<string name="msg_content_description_show_more_login_options">Mostrar mais opções de entrada</string>
<string name="msg_content_description_show_attachment_options">Mostrar opções de ficheiro</string>
<string name="msg_you">Você</string>
<string name="msg_unknown">Desconhecido</string>
<string name="msg_email_address">Endereço de E-mail</string>
<string name="msg_utc_offset">Deslocamento UTC</string>
<string name="msg_new_password">Introduza a nova palavra-passe</string>
<string name="msg_confirm_password">Confirme a nova palavra-passe</string>
<string name="msg_channel_name">Nome do canal</string>
<string name="msg_search">Pesquisar</string>
<string name="msg_unread_messages">Mensagens não lidas</string>
<string name="msg_preview_video">Vídeo</string>
<string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">Ficheiro</string>
<string name="msg_no_messages_yet">Nenhuma mensagem ainda</string>
<string name="msg_build">Compilação %1$d - %2$s - %3$s</string>
<string name="msg_update_app_version_in_order_to_continue">Versão do servidor desactualizada. Entre em contato com o administrador do servidor para atualizar a versão do servidor para continuar.</string>
<string name="msg_ver_not_recommended">
Parece que a versão do seu servidor está abaixo da versão recomendada, %1$s.\nAinda é possível entrar, mas você pode experimentar comportamentos inesperados.</string>
<string name="msg_ver_not_minimum">
Parece que a versão do seu servidor está abaixo da versão mínima exigida, %1$s.\nPor favor, actualize o seu servidor para entrar!
</string>
<string name="msg_no_chat_title">Nenhuma mensagem de chat</string>
<string name="msg_no_chat_description">Inicie uma conversa para ver as\nsuas mensagens aqui.</string>
<string name="msg_http_insecure">Quando utiliza HTTP você liga-se a um servidor inseguro. Não lhe recomendamos que faça isso.</string>
<string name="msg_error_checking_server_version">Ocorreu um erro ao verificar a versão do seu servidor, tente novamente</string>
<string name="msg_invalid_server_protocol">O protocolo seleccionado não é aceite por este servidor, tente utilizar HTTPS</string>
<string name="msg_image_saved_successfully">Imagem guardada na galeria</string>
<string name="msg_image_saved_failed">Falha ao guardar imagem</string>
<string name="msg_edited">(editada)</string>
<string name="msg_and">\u0020e\u0020</string>
<string name="msg_is_typing">\u0020está a escrever…</string>
<string name="msg_are_typing">\u0020estão a escrever…</string>
<string name="msg_several_users_are_typing">Varios utilizadores estão a escrever…</string>
<string name="msg_no_search_found">Não foram encontrados resultados</string>
<string name="msg_log_out">A terminar sessão…</string>
<string name="msg_upload_file">Enviar ficheiro</string>
<string name="msg_file_description">Descrição do ficheiro</string>
<string name="msg_send">Enviar</string>
<string name="msg_sent_attachment">Enviou um ficheiro</string>
<string name="msg_welcome_to_rocket_chat">Bem vindo(a) ao Rocket.Chat</string>
<string name="msg_team_communication">Comunicação de Equipa</string>
<string name="msg_login_with_email">Entre com <b>e-mail</b></string>
<string name="msg_create_account">Crie uma conta</string>
<string name="msg_continue_with_facebook">Continue com <b>Facebook</b></string>
<string name="msg_continue_with_github">Continue com <b>Github</b></string>
<string name="msg_continue_with_google">Continue com <b>Google</b></string>
<string name="msg_continue_with_linkedin">Continue com <b>Linkedin</b></string>
<string name="msg_continue_with_gitlab">Continue com <b>GitLab</b></string>
<string name="msg_continue_with_wordpress">Continue com <b>WordPress</b></string>
<string name="msg_two_factor_authentication">Autenticação 2FA</string>
<string name="msg__your_2fa_code">Qual é o seu código 2FA?</string>
<string name="msg_permalink_copied">Link permanente copiado</string>
<string name="msg_no_topic">Nenhum tópico adicionado</string>
<string name="msg_no_announcement">Nenhum anúncio adicionado</string>
<string name="msg_no_description">Nenhuma descrição adicionada</string>
<string name="msg_send_email">Enviar e-mail</string>
<string name="msg_android_app_support">Suporte à aplicação Android</string>
<string name="msg_unable_to_update_password">Não é possível atualizar a palavra-passe. Erro: %1$s</string>
<string name="msg_password_updated_successfully">Palavra-passe actualizada com sucesso</string>
<plurals name="msg_reacted_with_">
<item quantity="one">%1$s reagiu com %2$s</item>
<item quantity="other">%1$s reagiram com %2$s</item>
</plurals>
<!-- Create channel messages -->
<string name="msg_private_channel">Privado</string>
<string name="msg_public_channel">Público</string>
<string name="msg_private_channel_description">Apenas você e membros convidados podem entrar neste canal</string>
<string name="msg_public_channel_description">Todos podem entrar neste canal</string>
<string name="msg_ready_only_channel">Canal apenas de leitura</string>
<string name="msg_ready_only_channel_description">Apenas administradores podem escrever novas mensagens</string>
<string name="msg_invite_members">Convidar membros para o canal</string>
<string name="msg_member_already_added">Você já seleccionou este utilizador</string>
<string name="msg_member_not_found">Membro não encontrado</string>
<string name="msg_channel_created_successfully">Canal criado com sucesso</string>
<string name="msg_message_copied">Mensagem copiada</string>
<string name="msg_delete_message">Apagar mensagem</string>
<string name="msg_delete_description">Tem a certeza que deseja apagar esta mensagem?</string>
<string name="msg_view_more">mostrar mais</string>
<string name="msg_view_less">mostrar menos</string>
<string name="msg_muted_on_this_channel">Você está silenciado neste canal</string>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">Acompanhamento de análise</string>
<string name="msg_send_analytics_tracking">Enviar estatísticas anónimas para ajudar a melhorar esta aplicação</string>
<string name="msg_do_not_send_analytics_tracking">Não enviar estatísticas anónimas para ajudar a melhorar esta aplicação</string>
<string name="msg_not_applicable_since_it_is_a_foss_version">Não aplicável, visto ser uma versão FOSS</string>
<!-- System messages -->
<string name="message_room_name_changed">Nome da sala alterado para: %1$s por %2$s</string>
<string name="message_user_added_by">Utilizador %1$s adicionado por %2$s</string>
<string name="message_user_removed_by">Utilizador %1$s removido por %2$s</string>
<string name="message_user_left">Saiu do canal.</string>
<string name="message_user_joined_channel">Entrou no canal.</string>
<string name="message_welcome">Bem vindo(a) %s</string>
<string name="message_removed">Mensagem removida</string>
<string name="message_pinned">Afixou uma mensagem:</string>
<string name="message_muted">Utilizador %1$s silenciado por %2$s</string>
<string name="message_unmuted">Utilizador %2$s removeu o silêncio de %1$s</string>
<string name="message_role_add">%3$s deu estatuto de %2$s a %1$s</string>
<string name="message_role_removed">%3$s retirou o estatuto de %2$s a %1$s</string>
<string name="message_credentials_saved_successfully">Credenciais guardadas com sucesso</string>
<!-- Message actions -->
<string name="action_msg_reply">Responder</string>
<string name="action_msg_info">Informação da mensagem</string>
<string name="action_msg_edit">Editar</string>
<string name="action_msg_copy">Copiar</string>
<string name="action_msg_quote">Citar</string>
<string name="action_msg_delete">Apagar</string>
<string name="action_msg_pin">Afixar mensagem</string>
<string name="action_msg_unpin">Desafixar mensagem</string>
<string name="action_msg_star">Dar um estrela</string>
<string name="action_msg_unstar">Remover estrela</string>
<string name="action_msg_share">Partilhar</string>
<string name="action_title_editing">A editar mensagem</string>
<string name="action_msg_add_reaction">Adicionar reacção</string>
<string name="action_msg_copy_permalink">Copiar link permanente</string>
<string name="action_msg_report">Relatar</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">Edição não é permitida</string>
<string name="permission_deleting_not_allowed">A remoção não é permitida</string>
<string name="permission_pinning_not_allowed">Afixação não é permitida</string>
<string name="permission_starring_not_allowed">Dar estrelas não é permitido</string>
<!-- Search message -->
<string name="title_search_message">Pesquisar mensagem</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Marcar chat como favorito</string>
<string name="title_unfavorite_chat">Desmarcar chat como favorito</string>
<!-- Members List -->
<string name="title_members_list">Membros</string>
<!-- Mentions -->
<string name="msg_mentions">Menções</string>
<string name="msg_no_mention">Nenhuma menção</string>
<string name="msg_all_the_mentions_appear_here">Todas as menções\naparecem aqui</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">Mensagens Afixadas</string>
<string name="no_pinned_messages">Sem mensagens afixadas</string>
<string name="no_pinned_description">Todas as mensagens afixadas\naparecem aqui</string>
<!-- Favorite Messages -->
<string name="title_favorite_messages">Mensagens Favoritas</string>
<string name="no_favorite_messages">Nenhuma mensagem favorita</string>
<string name="no_favorite_description">Todas as mensagens favoritas\naparecem aqui</string>
<!-- Files -->
<string name="title_files">Ficheiros</string>
<string name="title_files_total">Ficheiros (%d)</string>
<string name="msg_no_files">Nenhum ficheiro</string>
<string name="msg_all_files_appear_here">Todos os ficheiros aparecem aqui</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">O tamanho do ficheiro (%1$d bytes) excedeu o máximo permitido de %2$d bytes</string>
<!-- Socket status -->
<string name="status_connected">Ligado</string>
<string name="status_disconnected">Desligado</string>
<string name="status_connecting">A ligar</string>
<string name="status_authenticating">A autenticar</string>
<string name="status_disconnecting">A desligar</string>
<string name="status_waiting">A ligar dentro de %d segundos</string>
<!--Suggestions-->
<string name="suggest_all_description">Notifica todos os utilizadores nesta sala</string>
<string name="suggest_here_description">Notifica os utilizadores activos nesta sala</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">Adiciona ༼ つ ◕_◕ ༽つ antes da sua mensagem</string>
<string name="Slash_LennyFace_Description">Adiciona ( ͡° ͜ʖ ͡°) depois da sua mensagem</string>
<string name="Slash_Shrug_Description">Adiciona ¯\_(ツ)_/¯ depois da sua mensagem</string>
<string name="Slash_Tableflip_Description">Adiciona (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Adiciona ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Criar um novo canal</string>
<string name="Show_the_keyboard_shortcut_list">Mostra a lista de atalhos do teclado</string>
<string name="Invite_user_to_join_channel_all_from">Convida todos os utilizadores do [#canal] para entrar neste canal</string>
<string name="Invite_user_to_join_channel_all_to">Convida todos os utilizadores deste canal a entrar no [#canal]</string>
<string name="Archive">Arquivar</string>
<string name="Remove_someone_from_room">Remove alguém da sala</string>
<string name="Leave_the_current_channel">Sai do canal actual</string>
<string name="Displays_action_text">Escreve um texto de acção</string>
<string name="Direct_message_someone">Envia uma mensagem directa para alguém</string>
<string name="Mute_someone_in_room">Silencia alguém na sala</string>
<string name="Unmute_someone_in_room">Remove o silêncio de alguém na sala</string>
<string name="Invite_user_to_join_channel">Convida um utilizador a entrar neste canal</string>
<string name="Unarchive">Desarquivar</string>
<string name="Join_the_given_channel">Entra no canal especificado</string>
<string name="Guggy_Command_Description">Gera um GIF baseado no texto fornecido</string>
<string name="Slash_Topic_Description">Define o tópico</string>
<!-- Emoji message-->
<string name="msg_no_recent_emoji">Nenhum emojis recente</string>
<string name="alert_title_default_skin_tone">Tom de pele padrão</string>
<!-- Sorting and grouping-->
<string name="msg_sort">Ordenar</string>
<string name="dialog_sort_title">Ordenar por</string>
<string name="dialog_sort_by_alphabet">Alfabeticamente</string>
<string name="dialog_sort_by_activity">Actividade</string>
<string name="dialog_group_by_type">Agrupar por tipo</string>
<string name="dialog_group_favourites">Agrupar favoritos</string>
<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 Directas</string>
<string name="header_live_chats">Chats ao Vivo</string>
<string name="header_unknown">Desconhecido</string>
<!--Notifications-->
<string name="share_label">Editar mensagem partilhada</string>
<string name="notif_action_reply_hint">RESPONDER</string>
<string name="notif_error_sending">A resposta falhou. Tente novamente.</string>
<string name="notif_success_sending">Mensagem enviada para %1$s!</string>
<string name="read_by">Lida por</string>
<string name="message_information_title">Informação da Mensagem</string>
<string name="message_room_changed_privacy">Tipo de sala alterado para: %1$s por %2$s</string>
<string name="foss" translatable="false">(FOSS)</string>
<!-- User Details -->
<string name="timezone">Fuso Horário</string>
<string name="status">Estado</string>
<!-- Report -->
<string name="submit">Enviar</string>
<string name="required">*requerido</string>
<string name="report_sent">O seu relatório foi enviado!</string>
</resources>
......@@ -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) {
......@@ -15,3 +18,11 @@ fun SearchView.onQueryTextListener(queryListener: (String) -> Unit) {
}
})
}
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