Unverified Commit 7f75dc2a authored by Divyanshu Bhargava's avatar Divyanshu Bhargava Committed by GitHub

Merge pull request #55 from RocketChat/develop

merge
parents 7113d93f cb57e76e
...@@ -6,6 +6,7 @@ if (isPlay) { apply plugin: 'io.fabric' } ...@@ -6,6 +6,7 @@ if (isPlay) { apply plugin: 'io.fabric' }
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: "com.github.ben-manes.versions"
android { android {
compileSdkVersion versions.compileSdk compileSdkVersion versions.compileSdk
...@@ -15,8 +16,8 @@ android { ...@@ -15,8 +16,8 @@ android {
applicationId "chat.rocket.android" applicationId "chat.rocket.android"
minSdkVersion versions.minSdk minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk targetSdkVersion versions.targetSdk
versionCode 2036 versionCode 2042
versionName "2.5.1" versionName "2.6.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
...@@ -92,6 +93,7 @@ dependencies { ...@@ -92,6 +93,7 @@ dependencies {
implementation project(':draw') implementation project(':draw')
implementation project(':util') implementation project(':util')
implementation project(':core') implementation project(':core')
implementation project(':suggestions')
implementation libraries.kotlin implementation libraries.kotlin
implementation libraries.coroutines implementation libraries.coroutines
...@@ -104,6 +106,7 @@ dependencies { ...@@ -104,6 +106,7 @@ dependencies {
implementation libraries.browser implementation libraries.browser
implementation libraries.androidKtx implementation libraries.androidKtx
implementation libraries.fragmentsKtx
implementation libraries.dagger implementation libraries.dagger
implementation libraries.daggerSupport implementation libraries.daggerSupport
...@@ -117,6 +120,8 @@ dependencies { ...@@ -117,6 +120,8 @@ dependencies {
kapt libraries.roomProcessor kapt libraries.roomProcessor
implementation libraries.lifecycleExtensions implementation libraries.lifecycleExtensions
kapt libraries.lifecycleCompiler kapt libraries.lifecycleCompiler
implementation libraries.viewmodelKtx
implementation libraries.workmanager
implementation libraries.rxKotlin implementation libraries.rxKotlin
implementation libraries.rxAndroid implementation libraries.rxAndroid
...@@ -145,14 +150,14 @@ dependencies { ...@@ -145,14 +150,14 @@ dependencies {
implementation libraries.aVLoadingIndicatorView implementation libraries.aVLoadingIndicatorView
implementation "com.github.luciofm:livedata-ktx:b1e8bbc25a" implementation libraries.livedataKtx
// Proprietary libraries // Proprietary libraries
playImplementation libraries.fcm playImplementation libraries.fcm
playImplementation libraries.firebaseAnalytics playImplementation libraries.firebaseAnalytics
playImplementation libraries.playServicesAuth playImplementation libraries.playServicesAuth
playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.4@aar') { transitive = true } playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.5@aar') { transitive = true }
playImplementation('com.crashlytics.sdk.android:answers:1.4.2@aar') { transitive = true } playImplementation('com.crashlytics.sdk.android:answers:1.4.3@aar') { transitive = true }
testImplementation libraries.junit testImplementation libraries.junit
testImplementation libraries.truth testImplementation libraries.truth
......
package chat.rocket.android.push package chat.rocket.android.push
class FirebaseTokenService { fun refreshPushToken() {
}
}
\ No newline at end of file
package chat.rocket.android.util package chat.rocket.android.util
import chat.rocket.android.main.presentation.MainPresenter
fun refreshFCMToken(presenter: MainPresenter) {
//Do absolutely nothing
}
fun invalidateFirebaseToken(token: String) { fun invalidateFirebaseToken(token: String) {
//Do absolutely nothing //Do absolutely nothing
} }
\ No newline at end of file
app/src/main/ic_launcher-web.png

39.1 KB | W: | H:

app/src/main/ic_launcher-web.png

63.8 KB | W: | H:

app/src/main/ic_launcher-web.png
app/src/main/ic_launcher-web.png
app/src/main/ic_launcher-web.png
app/src/main/ic_launcher-web.png
  • 2-up
  • Swipe
  • Onion skin
package chat.rocket.android.about.di
import chat.rocket.android.about.ui.AboutFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module
abstract class AboutFragmentProvider {
@ContributesAndroidInjector()
abstract fun provideAboutFragment(): AboutFragment
}
...@@ -10,6 +10,7 @@ import chat.rocket.android.R ...@@ -10,6 +10,7 @@ import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.ScreenViewEvent import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.main.ui.MainActivity import chat.rocket.android.main.ui.MainActivity
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.fragment_about.* import kotlinx.android.synthetic.main.fragment_about.*
import javax.inject.Inject import javax.inject.Inject
...@@ -20,6 +21,11 @@ class AboutFragment : Fragment() { ...@@ -20,6 +21,11 @@ class AboutFragment : Fragment() {
@Inject @Inject
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
...@@ -36,8 +42,10 @@ class AboutFragment : Fragment() { ...@@ -36,8 +42,10 @@ class AboutFragment : Fragment() {
private fun setupViews() { private fun setupViews() {
text_version_name.text = BuildConfig.VERSION_NAME text_version_name.text = BuildConfig.VERSION_NAME
text_build_number.text = getString(R.string.msg_build, BuildConfig.VERSION_CODE, text_build_number.text = getString(
BuildConfig.GIT_SHA, BuildConfig.FLAVOR) R.string.msg_build, BuildConfig.VERSION_CODE,
BuildConfig.GIT_SHA, BuildConfig.FLAVOR
)
} }
private fun setupToolbar() { private fun setupToolbar() {
......
...@@ -8,9 +8,15 @@ import android.content.Context ...@@ -8,9 +8,15 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.Worker
import chat.rocket.android.BuildConfig import chat.rocket.android.BuildConfig
import chat.rocket.android.dagger.DaggerAppComponent import chat.rocket.android.dagger.DaggerAppComponent
import chat.rocket.android.dagger.injector.HasWorkerInjector
import chat.rocket.android.dagger.qualifier.ForMessages import chat.rocket.android.dagger.qualifier.ForMessages
import chat.rocket.android.emoji.Emoji
import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.emoji.Fitzpatrick
import chat.rocket.android.emoji.internal.EmojiCategory
import chat.rocket.android.helper.CrashlyticsTree import chat.rocket.android.helper.CrashlyticsTree
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.AccountsRepository import chat.rocket.android.server.domain.AccountsRepository
...@@ -18,22 +24,26 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor ...@@ -18,22 +24,26 @@ import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.SITE_URL import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.TokenRepository import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.android.util.setupFabric import chat.rocket.android.util.setupFabric
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.getCustomEmojis
import com.facebook.drawee.backends.pipeline.DraweeConfig import com.facebook.drawee.backends.pipeline.DraweeConfig
import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector import dagger.android.HasActivityInjector
import dagger.android.HasBroadcastReceiverInjector import dagger.android.HasBroadcastReceiverInjector
import dagger.android.HasServiceInjector import dagger.android.HasServiceInjector
import kotlinx.coroutines.experimental.launch
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import javax.inject.Inject import javax.inject.Inject
class RocketChatApplication : Application(), HasActivityInjector, HasServiceInjector, class RocketChatApplication : Application(), HasActivityInjector, HasServiceInjector,
HasBroadcastReceiverInjector { HasBroadcastReceiverInjector, HasWorkerInjector {
@Inject @Inject
lateinit var appLifecycleObserver: AppLifecycleObserver lateinit var appLifecycleObserver: AppLifecycleObserver
...@@ -47,6 +57,9 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -47,6 +57,9 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
@Inject @Inject
lateinit var broadcastReceiverInjector: DispatchingAndroidInjector<BroadcastReceiver> lateinit var broadcastReceiverInjector: DispatchingAndroidInjector<BroadcastReceiver>
@Inject
lateinit var workerInjector: DispatchingAndroidInjector<Worker>
@Inject @Inject
lateinit var imagePipelineConfig: ImagePipelineConfig lateinit var imagePipelineConfig: ImagePipelineConfig
@Inject @Inject
...@@ -63,6 +76,8 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -63,6 +76,8 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
lateinit var localRepository: LocalRepository lateinit var localRepository: LocalRepository
@Inject @Inject
lateinit var accountRepository: AccountsRepository lateinit var accountRepository: AccountsRepository
@Inject
lateinit var factory: RocketChatClientFactory
@Inject @Inject
@field:ForMessages @field:ForMessages
...@@ -98,6 +113,9 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -98,6 +113,9 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
// TODO - remove REALM files. // TODO - remove REALM files.
// TODO - remove this // TODO - remove this
checkCurrentServer() checkCurrentServer()
// TODO - FIXME - we need to proper inject the EmojiRepository and initialize it properly
loadEmojis()
} }
private fun checkCurrentServer() { private fun checkCurrentServer() {
...@@ -132,17 +150,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -132,17 +150,13 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
} }
} }
override fun activityInjector(): AndroidInjector<Activity> { override fun activityInjector() = activityDispatchingAndroidInjector
return activityDispatchingAndroidInjector
}
override fun serviceInjector(): AndroidInjector<Service> { override fun serviceInjector() = serviceDispatchingAndroidInjector
return serviceDispatchingAndroidInjector
}
override fun broadcastReceiverInjector(): AndroidInjector<BroadcastReceiver> { override fun broadcastReceiverInjector() = broadcastReceiverInjector
return broadcastReceiverInjector
} override fun workerInjector() = workerInjector
companion object { companion object {
var context: WeakReference<Context>? = null var context: WeakReference<Context>? = null
...@@ -150,6 +164,44 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje ...@@ -150,6 +164,44 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
return context?.get() return context?.get()
} }
} }
// TODO - FIXME - This is a big Workaround
/**
* Load all emojis for the current server. Simple emojis are always the same for every server,
* but custom emojis vary according to the its url.
*/
fun loadEmojis() {
EmojiRepository.init(this)
val currentServer = getCurrentServerInteractor.get()
currentServer?.let { server ->
launch {
val client = factory.create(server)
EmojiRepository.setCurrentServerUrl(server)
val customEmojiList = mutableListOf<Emoji>()
try {
for (customEmoji in retryIO("getCustomEmojis()") { client.getCustomEmojis() }) {
customEmojiList.add(Emoji(
shortname = ":${customEmoji.name}:",
category = EmojiCategory.CUSTOM.name,
url = "$currentServer/emoji-custom/${customEmoji.name}.${customEmoji.extension}",
count = 0,
fitzpatrick = Fitzpatrick.Default.type,
keywords = customEmoji.aliases,
shortnameAlternates = customEmoji.aliases,
siblings = mutableListOf(),
unicode = "",
isDefault = true
))
}
EmojiRepository.load(this@RocketChatApplication, customEmojis = customEmojiList)
} catch (ex: RocketChatException) {
Timber.e(ex)
EmojiRepository.load(this@RocketChatApplication as Context)
}
}
}
}
} }
private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true) private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true)
......
...@@ -7,8 +7,29 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator ...@@ -7,8 +7,29 @@ import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.OauthHelper import chat.rocket.android.helper.OauthHelper
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.* import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SaveAccountInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.casLoginUrl
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.gitlabUrl
import chat.rocket.android.server.domain.isCasAuthenticationEnabled
import chat.rocket.android.server.domain.isFacebookAuthenticationEnabled
import chat.rocket.android.server.domain.isGithubAuthenticationEnabled
import chat.rocket.android.server.domain.isGitlabAuthenticationEnabled
import chat.rocket.android.server.domain.isGoogleAuthenticationEnabled
import chat.rocket.android.server.domain.isLdapAuthenticationEnabled
import chat.rocket.android.server.domain.isLinkedinAuthenticationEnabled
import chat.rocket.android.server.domain.isLoginFormEnabled
import chat.rocket.android.server.domain.isPasswordResetEnabled
import chat.rocket.android.server.domain.isRegistrationEnabledForNewUsers
import chat.rocket.android.server.domain.isWordpressAuthenticationEnabled
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.domain.wordpressUrl
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
...@@ -17,7 +38,6 @@ import chat.rocket.android.util.extensions.encodeToBase64 ...@@ -17,7 +38,6 @@ import chat.rocket.android.util.extensions.encodeToBase64
import chat.rocket.android.util.extensions.generateRandomString import chat.rocket.android.util.extensions.generateRandomString
import chat.rocket.android.util.extensions.isEmail import chat.rocket.android.util.extensions.isEmail
import chat.rocket.android.util.extensions.parseColor import chat.rocket.android.util.extensions.parseColor
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.samlUrl import chat.rocket.android.util.extensions.samlUrl
import chat.rocket.android.util.extensions.serverLogoUrl import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
...@@ -60,7 +80,6 @@ class LoginPresenter @Inject constructor( ...@@ -60,7 +80,6 @@ class LoginPresenter @Inject constructor(
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val localRepository: LocalRepository, private val localRepository: LocalRepository,
private val getAccountsInteractor: GetAccountsInteractor,
private val settingsInteractor: GetSettingsInteractor, private val settingsInteractor: GetSettingsInteractor,
private val analyticsManager: AnalyticsManager, private val analyticsManager: AnalyticsManager,
serverInteractor: GetConnectingServerInteractor, serverInteractor: GetConnectingServerInteractor,
...@@ -454,9 +473,9 @@ class LoginPresenter @Inject constructor( ...@@ -454,9 +473,9 @@ class LoginPresenter @Inject constructor(
) )
localRepository.saveCurrentUser(currentServer, user) localRepository.saveCurrentUser(currentServer, user)
saveCurrentServer.save(currentServer) saveCurrentServer.save(currentServer)
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, myself.username)
saveAccount(myself.username!!) saveAccount(myself.username!!)
saveToken(token) saveToken(token)
registerPushToken()
analyticsManager.logLogin(loginMethod, true) analyticsManager.logLogin(loginMethod, true)
if (loginType == TYPE_LOGIN_USER_EMAIL) { if (loginType == TYPE_LOGIN_USER_EMAIL) {
view.saveSmartLockCredentials(usernameOrEmail, password) view.saveSmartLockCredentials(usernameOrEmail, password)
...@@ -610,12 +629,4 @@ class LoginPresenter @Inject constructor( ...@@ -610,12 +629,4 @@ class LoginPresenter @Inject constructor(
private fun saveToken(token: Token) { private fun saveToken(token: Token) {
tokenRepository.save(currentServer, token) tokenRepository.save(currentServer, token)
} }
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it, getAccountsInteractor.get(), factory)
}
// TODO: When the push token is null, at some point we should receive it with
// onTokenRefresh() on FirebaseTokenService, we need to confirm it.
}
} }
\ No newline at end of file
...@@ -4,8 +4,6 @@ import chat.rocket.android.analytics.AnalyticsManager ...@@ -4,8 +4,6 @@ import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.AuthenticationEvent import chat.rocket.android.analytics.event.AuthenticationEvent
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetConnectingServerInteractor import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
...@@ -18,7 +16,6 @@ import chat.rocket.android.server.domain.wideTile ...@@ -18,7 +16,6 @@ import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.serverLogoUrl import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
...@@ -33,10 +30,8 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -33,10 +30,8 @@ class RegisterUsernamePresenter @Inject constructor(
private val strategy: CancelStrategy, private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator, private val navigator: AuthenticationNavigator,
private val tokenRepository: TokenRepository, private val tokenRepository: TokenRepository,
private val localRepository: LocalRepository, factory: RocketChatClientFactory,
private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor,
private val analyticsManager: AnalyticsManager, private val analyticsManager: AnalyticsManager,
serverInteractor: GetConnectingServerInteractor, serverInteractor: GetConnectingServerInteractor,
private val saveCurrentServer: SaveCurrentServerInteractor, private val saveCurrentServer: SaveCurrentServerInteractor,
...@@ -61,7 +56,6 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -61,7 +56,6 @@ class RegisterUsernamePresenter @Inject constructor(
saveAccount(registeredUsername) saveAccount(registeredUsername)
saveCurrentServer.save(currentServer) saveCurrentServer.save(currentServer)
tokenRepository.save(currentServer, Token(userId, authToken)) tokenRepository.save(currentServer, Token(userId, authToken))
registerPushToken()
analyticsManager.logSignUp( analyticsManager.logSignUp(
AuthenticationEvent.AuthenticationWithOauth, AuthenticationEvent.AuthenticationWithOauth,
true true
...@@ -82,14 +76,6 @@ class RegisterUsernamePresenter @Inject constructor( ...@@ -82,14 +76,6 @@ class RegisterUsernamePresenter @Inject constructor(
} }
} }
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it, getAccountsInteractor.get(), factory)
}
// TODO: When the push token is null, at some point we should receive it with
// onTokenRefresh() on FirebaseTokenService, we need to confirm it.
}
private suspend fun saveAccount(username: String) { private suspend fun saveAccount(username: String) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
......
...@@ -5,7 +5,6 @@ import chat.rocket.android.analytics.event.AuthenticationEvent ...@@ -5,7 +5,6 @@ import chat.rocket.android.analytics.event.AuthenticationEvent
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetConnectingServerInteractor import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
...@@ -18,13 +17,11 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory ...@@ -18,13 +17,11 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.privacyPolicyUrl import chat.rocket.android.util.extensions.privacyPolicyUrl
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.serverLogoUrl import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.extensions.termsOfServiceUrl import chat.rocket.android.util.extensions.termsOfServiceUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.signup import chat.rocket.core.internal.rest.signup
...@@ -41,11 +38,9 @@ class SignupPresenter @Inject constructor( ...@@ -41,11 +38,9 @@ class SignupPresenter @Inject constructor(
private val analyticsManager: AnalyticsManager, private val analyticsManager: AnalyticsManager,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor,
settingsInteractor: GetSettingsInteractor settingsInteractor: GetSettingsInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
fun signup(name: String, username: String, password: String, email: String) { fun signup(name: String, username: String, password: String, email: String) {
...@@ -79,7 +74,6 @@ class SignupPresenter @Inject constructor( ...@@ -79,7 +74,6 @@ class SignupPresenter @Inject constructor(
saveCurrentServerInteractor.save(currentServer) saveCurrentServerInteractor.save(currentServer)
localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username) localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
saveAccount(me) saveAccount(me)
registerPushToken()
analyticsManager.logSignUp( analyticsManager.logSignUp(
AuthenticationEvent.AuthenticationWithUserAndPassword, AuthenticationEvent.AuthenticationWithUserAndPassword,
true true
...@@ -117,14 +111,6 @@ class SignupPresenter @Inject constructor( ...@@ -117,14 +111,6 @@ class SignupPresenter @Inject constructor(
} }
} }
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it, getAccountsInteractor.get(), factory)
}
// TODO: When the push token is null, at some point we should receive it with
// onTokenRefresh() on FirebaseTokenService, we need to confirm it.
}
private suspend fun saveAccount(me: Myself) { private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
......
...@@ -5,7 +5,6 @@ import chat.rocket.android.analytics.event.AuthenticationEvent ...@@ -5,7 +5,6 @@ import chat.rocket.android.analytics.event.AuthenticationEvent
import chat.rocket.android.authentication.presentation.AuthenticationNavigator import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetConnectingServerInteractor import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings import chat.rocket.android.server.domain.PublicSettings
...@@ -18,13 +17,11 @@ import chat.rocket.android.server.domain.wideTile ...@@ -18,13 +17,11 @@ import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infraestructure.RocketChatClientFactory import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.serverLogoUrl import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.me import chat.rocket.core.internal.rest.me
import chat.rocket.core.model.Myself import chat.rocket.core.model.Myself
...@@ -41,11 +38,9 @@ class TwoFAPresenter @Inject constructor( ...@@ -41,11 +38,9 @@ class TwoFAPresenter @Inject constructor(
private val analyticsManager: AnalyticsManager, private val analyticsManager: AnalyticsManager,
private val factory: RocketChatClientFactory, private val factory: RocketChatClientFactory,
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor,
settingsInteractor: GetSettingsInteractor settingsInteractor: GetSettingsInteractor
) { ) {
private val currentServer = serverInteractor.get()!! private val currentServer = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!) private var settings: PublicSettings = settingsInteractor.get(serverInteractor.get()!!)
// TODO: If the usernameOrEmail and password was informed by the user on the previous screen, then we should pass only the pin, like this: fun authenticate(pin: EditText) // TODO: If the usernameOrEmail and password was informed by the user on the previous screen, then we should pass only the pin, like this: fun authenticate(pin: EditText)
...@@ -75,7 +70,7 @@ class TwoFAPresenter @Inject constructor( ...@@ -75,7 +70,7 @@ class TwoFAPresenter @Inject constructor(
saveAccount(me) saveAccount(me)
saveCurrentServerInteractor.save(currentServer) saveCurrentServerInteractor.save(currentServer)
tokenRepository.save(server, token) tokenRepository.save(server, token)
registerPushToken() localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, me.username)
analyticsManager.logLogin( analyticsManager.logLogin(
AuthenticationEvent.AuthenticationWithUserAndPassword, AuthenticationEvent.AuthenticationWithUserAndPassword,
true true
...@@ -105,14 +100,6 @@ class TwoFAPresenter @Inject constructor( ...@@ -105,14 +100,6 @@ class TwoFAPresenter @Inject constructor(
fun signup() = navigator.toSignUp() fun signup() = navigator.toSignUp()
private suspend fun registerPushToken() {
localRepository.get(LocalRepository.KEY_PUSH_TOKEN)?.let {
client.registerPushToken(it, getAccountsInteractor.get(), factory)
}
// TODO: When the push token is null, at some point we should receive it with
// onTokenRefresh() on FirebaseTokenService, we need to confirm it.
}
private suspend fun saveAccount(me: Myself) { private suspend fun saveAccount(me: Myself) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
......
...@@ -65,7 +65,9 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>( ...@@ -65,7 +65,9 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
val manager = FlexboxLayoutManager(context, FlexDirection.ROW) val manager = FlexboxLayoutManager(context, FlexDirection.ROW)
recyclerView.layoutManager = manager recyclerView.layoutManager = manager
recyclerView.adapter = adapter recyclerView.adapter = adapter
adapter.addReactions(it.reactions.filterNot { it.unicode.startsWith(":") }) adapter.addReactions(it.reactions.filterNot { reactionUiModel ->
reactionUiModel.unicode.startsWith(":") && reactionUiModel.url.isNullOrEmpty()
})
} }
} }
} }
......
...@@ -158,23 +158,36 @@ class ChatRoomAdapter( ...@@ -158,23 +158,36 @@ class ChatRoomAdapter(
} }
fun prependData(dataSet: List<BaseUiModel<*>>) { fun prependData(dataSet: List<BaseUiModel<*>>) {
val item = dataSet.indexOfFirst { newItem -> //---At first we will update all already saved elements with received updated ones
this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } > -1 val filteredDataSet = dataSet.filter { newItem ->
} val matchedIndex = this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType }
if (item == -1) { if (matchedIndex > -1) {
this.dataSet.addAll(0, dataSet) this.dataSet[matchedIndex] = newItem
notifyItemRangeInserted(0, dataSet.size) notifyItemChanged(matchedIndex)
} else {
dataSet.forEach { item ->
val index = this.dataSet.indexOfFirst {
item.messageId == it.messageId && item.viewType == it.viewType
}
if (index > -1) {
this.dataSet[index] = item
notifyItemChanged(index)
}
} }
return@filter (matchedIndex < 0)
}
val minAdditionDate = filteredDataSet.minBy { it.message.timestamp } ?: return
//---In the most cases we will just add new elements to the top of messages heap
if (this.dataSet.isEmpty() || minAdditionDate.message.timestamp > this.dataSet[0].message.timestamp) {
this.dataSet.addAll(0, filteredDataSet)
notifyItemRangeInserted(0, filteredDataSet.size)
return
} }
//---Else branch: merging messages---
//---We are inserting new received elements into set. Sort them by time+type and show
if (filteredDataSet.isEmpty()) return
this.dataSet.addAll(0, filteredDataSet)
val tmp = this.dataSet.sortedWith(Comparator { t, t2 ->
val timeComparison = t.message.timestamp.compareTo(t2.message.timestamp)
if (timeComparison == 0) {
return@Comparator t.viewType.compareTo(t2.viewType)
}
timeComparison
}).reversed()
this.dataSet.clear()
this.dataSet.addAll(tmp)
notifyDataSetChanged()
} }
fun updateItem(message: BaseUiModel<*>) { fun updateItem(message: BaseUiModel<*>) {
......
...@@ -7,9 +7,9 @@ import android.widget.TextView ...@@ -7,9 +7,9 @@ import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter.CommandSuggestionsViewHolder import chat.rocket.android.chatroom.adapter.CommandSuggestionsAdapter.CommandSuggestionsViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.CommandSuggestionUiModel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter import chat.rocket.android.suggestions.ui.SuggestionsAdapter
class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolder>(token = "/", class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolder>(token = "/",
constraint = CONSTRAINT_BOUND_TO_START, threshold = RESULT_COUNT_UNLIMITED) { constraint = CONSTRAINT_BOUND_TO_START, threshold = RESULT_COUNT_UNLIMITED) {
......
...@@ -5,6 +5,7 @@ import android.view.View ...@@ -5,6 +5,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.uimodel.ReactionUiModel import chat.rocket.android.chatroom.uimodel.ReactionUiModel
...@@ -13,15 +14,13 @@ import chat.rocket.android.emoji.Emoji ...@@ -13,15 +14,13 @@ import chat.rocket.android.emoji.Emoji
import chat.rocket.android.emoji.EmojiKeyboardListener import chat.rocket.android.emoji.EmojiKeyboardListener
import chat.rocket.android.emoji.EmojiPickerPopup import chat.rocket.android.emoji.EmojiPickerPopup
import chat.rocket.android.emoji.EmojiReactionListener import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.infrastructure.LocalRepository import chat.rocket.android.infrastructure.LocalRepository
import kotlinx.android.synthetic.main.item_reaction.view.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject import javax.inject.Inject
class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val REACTION_VIEW_TYPE = 0
private const val ADD_REACTION_VIEW_TYPE = 1
}
private val reactions = CopyOnWriteArrayList<ReactionUiModel>() private val reactions = CopyOnWriteArrayList<ReactionUiModel>()
var listener: EmojiReactionListener? = null var listener: EmojiReactionListener? = null
...@@ -74,9 +73,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -74,9 +73,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
fun contains(reactionShortname: String) = fun contains(reactionShortname: String) =
reactions.firstOrNull { it.shortname == reactionShortname } != null reactions.firstOrNull { it.shortname == reactionShortname } != null
class SingleReactionViewHolder(view: View, class SingleReactionViewHolder(
private val listener: EmojiReactionListener?) view: View,
: RecyclerView.ViewHolder(view), View.OnClickListener { private val listener: EmojiReactionListener?
) : RecyclerView.ViewHolder(view), View.OnClickListener {
@Inject @Inject
lateinit var localRepository: LocalRepository lateinit var localRepository: LocalRepository
@Volatile @Volatile
...@@ -95,23 +96,33 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -95,23 +96,33 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
clickHandled = false clickHandled = false
this.reaction = reaction this.reaction = reaction
with(itemView) { with(itemView) {
val emojiTextView = findViewById<TextView>(R.id.text_emoji) if (reaction.url.isNullOrEmpty()) {
val countTextView = findViewById<TextView>(R.id.text_count) text_emoji.text = reaction.unicode
emojiTextView.text = reaction.unicode view_flipper_reaction.displayedChild = 0
countTextView.text = reaction.count.toString() } else {
view_flipper_reaction.displayedChild = 1
val glideRequest = if (reaction.url!!.endsWith("gif", true)) {
GlideApp.with(context).asGif()
} else {
GlideApp.with(context).asBitmap()
}
glideRequest.load(reaction.url).into(image_emoji)
}
text_count.text = reaction.count.toString()
val myself = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY) val myself = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY)
if (reaction.usernames.contains(myself)) { if (reaction.usernames.contains(myself)) {
val context = itemView.context val context = itemView.context
val resources = context.resources text_count.setTextColor(ContextCompat.getColor(context, R.color.colorAccent))
countTextView.setTextColor(resources.getColor(R.color.colorAccent))
} }
emojiTextView.setOnClickListener(this@SingleReactionViewHolder) view_flipper_reaction.setOnClickListener(this@SingleReactionViewHolder)
countTextView.setOnClickListener(this@SingleReactionViewHolder) text_count.setOnClickListener(this@SingleReactionViewHolder)
} }
} }
override fun onClick(v: View?) { override fun onClick(v: View) {
synchronized(this) { synchronized(this) {
if (!clickHandled) { if (!clickHandled) {
clickHandled = true clickHandled = true
...@@ -121,8 +132,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -121,8 +132,11 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
} }
} }
class AddReactionViewHolder(view: View, class AddReactionViewHolder(
private val listener: EmojiReactionListener?) : RecyclerView.ViewHolder(view) { view: View,
private val listener: EmojiReactionListener?
) : RecyclerView.ViewHolder(view) {
fun bind(messageId: String) { fun bind(messageId: String) {
itemView as ImageView itemView as ImageView
itemView.setOnClickListener { itemView.setOnClickListener {
...@@ -136,4 +150,9 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() ...@@ -136,4 +150,9 @@ class MessageReactionsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>()
} }
} }
} }
}
\ No newline at end of file companion object {
private const val REACTION_VIEW_TYPE = 0
private const val ADD_REACTION_VIEW_TYPE = 1
}
}
...@@ -11,9 +11,9 @@ import chat.rocket.android.R ...@@ -11,9 +11,9 @@ import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter.PeopleSuggestionViewHolder import chat.rocket.android.chatroom.adapter.PeopleSuggestionsAdapter.PeopleSuggestionViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.PeopleSuggestionUiModel
import chat.rocket.android.util.extensions.setVisible import chat.rocket.android.util.extensions.setVisible
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter import chat.rocket.android.suggestions.ui.SuggestionsAdapter
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
class PeopleSuggestionsAdapter(context: Context) : SuggestionsAdapter<PeopleSuggestionViewHolder>("@") { class PeopleSuggestionsAdapter(context: Context) : SuggestionsAdapter<PeopleSuggestionViewHolder>("@") {
......
...@@ -7,9 +7,9 @@ import android.widget.TextView ...@@ -7,9 +7,9 @@ import android.widget.TextView
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter.RoomSuggestionsViewHolder import chat.rocket.android.chatroom.adapter.RoomSuggestionsAdapter.RoomSuggestionsViewHolder
import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel import chat.rocket.android.chatroom.uimodel.suggestion.ChatRoomSuggestionUiModel
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder import chat.rocket.android.suggestions.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter import chat.rocket.android.suggestions.ui.SuggestionsAdapter
class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#") { class RoomSuggestionsAdapter : SuggestionsAdapter<RoomSuggestionsViewHolder>("#") {
......
package chat.rocket.android.chatroom.presentation package chat.rocket.android.chatroom.presentation
import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.analytics.AnalyticsManager import chat.rocket.android.analytics.AnalyticsManager
...@@ -32,6 +33,7 @@ import chat.rocket.android.server.domain.uploadMimeTypeFilter ...@@ -32,6 +33,7 @@ import chat.rocket.android.server.domain.uploadMimeTypeFilter
import chat.rocket.android.server.domain.useRealName import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.state import chat.rocket.android.server.infraestructure.state
import chat.rocket.android.util.extension.compressImageAndGetByteArray
import chat.rocket.android.util.extension.compressImageAndGetInputStream import chat.rocket.android.util.extension.compressImageAndGetInputStream
import chat.rocket.android.util.extension.launchUI import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl import chat.rocket.android.util.extensions.avatarUrl
...@@ -80,6 +82,7 @@ import org.threeten.bp.Instant ...@@ -80,6 +82,7 @@ import org.threeten.bp.Instant
import timber.log.Timber import timber.log.Timber
import java.io.InputStream import java.io.InputStream
import java.util.* import java.util.*
import java.util.zip.DeflaterInputStream
import javax.inject.Inject import javax.inject.Inject
class ChatRoomPresenter @Inject constructor( class ChatRoomPresenter @Inject constructor(
...@@ -182,7 +185,8 @@ class ChatRoomPresenter @Inject constructor( ...@@ -182,7 +185,8 @@ class ChatRoomPresenter @Inject constructor(
isBroadcast = chatIsBroadcast, isRoom = true isBroadcast = chatIsBroadcast, isRoom = true
) )
) )
if (oldMessages.isNotEmpty()) { val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
if (oldMessages.isNotEmpty() && lastSyncDate != null) {
view.showMessages(oldMessages, clearDataSet) view.showMessages(oldMessages, clearDataSet)
loadMissingMessages() loadMissingMessages()
} else { } else {
...@@ -224,6 +228,19 @@ class ChatRoomPresenter @Inject constructor( ...@@ -224,6 +228,19 @@ class ChatRoomPresenter @Inject constructor(
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
} }
messagesRepository.saveAll(messages) messagesRepository.saveAll(messages)
//we are saving last sync date of latest synced chat room message
if (offset == 0L) {
//if success - saving last synced time
if (messages.isEmpty()) {
//chat history is empty - just saving current date
messagesRepository.saveLastSyncDate(chatRoomId, System.currentTimeMillis())
} else {
//assume that BE returns ordered messages, the first message is the latest one
messagesRepository.saveLastSyncDate(chatRoomId, messages.first().timestamp)
}
}
view.showMessages( view.showMessages(
mapper.map( mapper.map(
messages, messages,
...@@ -273,7 +290,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -273,7 +290,7 @@ class ChatRoomPresenter @Inject constructor(
timestamp = Instant.now().toEpochMilli(), timestamp = Instant.now().toEpochMilli(),
sender = SimpleUser(null, username, username), sender = SimpleUser(null, username, username),
attachments = null, attachments = null,
avatar = currentServer.avatarUrl(username!!), avatar = currentServer.avatarUrl(username ?: ""),
channels = null, channels = null,
editedAt = null, editedAt = null,
editedBy = null, editedBy = null,
...@@ -331,14 +348,15 @@ class ChatRoomPresenter @Inject constructor( ...@@ -331,14 +348,15 @@ class ChatRoomPresenter @Inject constructor(
view.showFileSelection(settings.uploadMimeTypeFilter()) view.showFileSelection(settings.uploadMimeTypeFilter())
} }
fun uploadFile(roomId: String, uri: Uri, msg: String) { fun uploadFile(roomId: String, uri: Uri, msg: String, bitmap: Bitmap? = null) {
launchUI(strategy) { launchUI(strategy) {
view.showLoading() view.showLoading()
try { try {
withContext(DefaultDispatcher) { withContext(DefaultDispatcher) {
val fileName = uriInteractor.getFileName(uri) ?: uri.toString() val fileName = uriInteractor.getFileName(uri) ?: uri.toString()
val fileSize = uriInteractor.getFileSize(uri)
val mimeType = uriInteractor.getMimeType(uri) val mimeType = uriInteractor.getMimeType(uri)
val byteArray = bitmap?.compressImageAndGetByteArray(mimeType)
val fileSize = byteArray?.size ?: uriInteractor.getFileSize(uri)
val maxFileSizeAllowed = settings.uploadMaxFileSize() val maxFileSizeAllowed = settings.uploadMaxFileSize()
when { when {
...@@ -346,16 +364,6 @@ class ChatRoomPresenter @Inject constructor( ...@@ -346,16 +364,6 @@ class ChatRoomPresenter @Inject constructor(
fileSize > maxFileSizeAllowed && maxFileSizeAllowed !in -1..0 -> fileSize > maxFileSizeAllowed && maxFileSizeAllowed !in -1..0 ->
view.showInvalidFileSize(fileSize, maxFileSizeAllowed) view.showInvalidFileSize(fileSize, maxFileSizeAllowed)
else -> { else -> {
var inputStream: InputStream? = uriInteractor.getInputStream(uri)
if (mimeType.contains("image")) {
uriInteractor.getBitmap(uri)?.let {
it.compressImageAndGetInputStream(mimeType)?.let {
inputStream = it
}
}
}
retryIO("uploadFile($roomId, $fileName, $mimeType") { retryIO("uploadFile($roomId, $fileName, $mimeType") {
client.uploadFile( client.uploadFile(
roomId, roomId,
...@@ -364,7 +372,7 @@ class ChatRoomPresenter @Inject constructor( ...@@ -364,7 +372,7 @@ class ChatRoomPresenter @Inject constructor(
msg, msg,
description = fileName description = fileName
) { ) {
inputStream byteArray?.inputStream() ?: uriInteractor.getInputStream(uri)
} }
} }
logMediaUploaded(mimeType) logMediaUploaded(mimeType)
...@@ -485,45 +493,49 @@ class ChatRoomPresenter @Inject constructor( ...@@ -485,45 +493,49 @@ class ChatRoomPresenter @Inject constructor(
private fun loadMissingMessages() { private fun loadMissingMessages() {
launch(parent = strategy.jobs) { launch(parent = strategy.jobs) {
if (chatRoomId != null && chatRoomType != null) { chatRoomId?.let { chatRoomId ->
val roomType = roomTypeOf(chatRoomType!!) val roomType = roomTypeOf(chatRoomType)
messagesRepository.getByRoomId(chatRoomId!!) val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
.sortedByDescending { it.timestamp }.firstOrNull()?.let { lastMessage -> // lastSyncDate or 0. LastSyncDate could be in case when we sent some messages offline(and saved them locally),
val instant = Instant.ofEpochMilli(lastMessage.timestamp).toString() // but never has obtained chatMessages(or history) from remote. In this case we should sync all chat history from beginning
try { val instant = Instant.ofEpochMilli(lastSyncDate ?: 0).toString()
val messages = //
retryIO(description = "history($chatRoomId, $roomType, $instant)") { try {
client.history( val messages =
chatRoomId!!, roomType, count = 50, retryIO(description = "history($chatRoomId, $roomType, $instant)") {
oldest = instant client.history(
) chatRoomId!!, roomType, count = 50,
} oldest = instant
Timber.d("History: $messages") )
}
if (messages.result.isNotEmpty()) { Timber.d("History: $messages")
val models = mapper.map(messages.result, RoomUiModel(
roles = chatRoles,
isBroadcast = chatIsBroadcast,
// FIXME: Why are we fixing isRoom attribute to true here?
isRoom = true
))
messagesRepository.saveAll(messages.result)
launchUI(strategy) {
view.showNewMessage(models, true)
}
if (messages.result.size == 50) { if (messages.result.isNotEmpty()) {
// we loaded at least count messages, try one more to fetch more messages val models = mapper.map(messages.result, RoomUiModel(
loadMissingMessages() roles = chatRoles,
} isBroadcast = chatIsBroadcast,
} // FIXME: Why are we fixing isRoom attribute to true here?
} catch (ex: Exception) { isRoom = true
// TODO - we need to better treat connection problems here, but no let gaps ))
// on the messages list messagesRepository.saveAll(messages.result)
Timber.d(ex, "Error fetching channel history") //if success - saving last synced time
//assume that BE returns ordered messages, the first message is the latest one
messagesRepository.saveLastSyncDate(chatRoomId, messages.result.first().timestamp)
launchUI(strategy) {
view.showNewMessage(models, true)
}
if (messages.result.size == 50) {
// we loaded at least count messages, try one more to fetch more messages
loadMissingMessages()
} }
} }
} catch (ex: Exception) {
// TODO - we need to better treat connection problems here, but no let gaps
// on the messages list
Timber.d(ex, "Error fetching channel history")
}
} }
} }
} }
......
...@@ -5,7 +5,6 @@ import android.app.job.JobService ...@@ -5,7 +5,6 @@ import android.app.job.JobService
import chat.rocket.android.server.domain.CurrentServerRepository import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.sendMessage import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
......
...@@ -334,7 +334,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -334,7 +334,6 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
val currentDayMarkerText = msgModel.currentDayMarkerText val currentDayMarkerText = msgModel.currentDayMarkerText
val previousDayMarkerText = prevMsgModel.currentDayMarkerText val previousDayMarkerText = prevMsgModel.currentDayMarkerText
println("$previousDayMarkerText then $currentDayMarkerText")
if (previousDayMarkerText != currentDayMarkerText) { if (previousDayMarkerText != currentDayMarkerText) {
prevMsgModel.showDayMarker = true prevMsgModel.showDayMarker = true
} }
......
package chat.rocket.android.chatroom.ui package chat.rocket.android.chatroom.ui
import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import androidx.core.view.isVisible import androidx.core.view.isVisible
import chat.rocket.android.emoji.internal.GlideApp
import chat.rocket.android.util.extensions.getFileName import chat.rocket.android.util.extensions.getFileName
import chat.rocket.android.util.extensions.getMimeType import chat.rocket.android.util.extensions.getMimeType
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
fun ChatRoomFragment.showFileAttachmentDialog(uri: Uri) { fun ChatRoomFragment.showFileAttachmentDialog(uri: Uri) {
activity?.let { fragmentActivity -> imagePreview.isVisible = false
uri.getMimeType(fragmentActivity).let { mimeType -> audioVideoAttachment.isVisible = false
textFile.isVisible = false
var bitmap: Bitmap? = null
activity?.let { context ->
uri.getMimeType(context).let { mimeType ->
description.text.clear() description.text.clear()
when { when {
mimeType.startsWith("image") -> { mimeType.startsWith("image") -> {
imagePreview.isVisible = true GlideApp
imagePreview.setImageURI(uri) .with(context)
} .asBitmap()
mimeType.startsWith("video") -> { .load(uri)
audioVideoAttachment.isVisible = true .override(imagePreview.width, imagePreview.height)
.fitCenter()
.into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap>?
) {
bitmap = resource
imagePreview.setImageBitmap(resource)
imagePreview.isVisible = true
}
})
} }
mimeType.startsWith("video") -> audioVideoAttachment.isVisible = true
else -> { else -> {
textFile.isVisible = true textFile.isVisible = true
textFile.text = uri.getFileName(fragmentActivity) textFile.text = uri.getFileName(context)
} }
} }
} }
} }
sendButton.setOnClickListener { sendButton.setOnClickListener {
presenter.uploadFile(chatRoomId, uri, (citation ?: "") + description.text.toString()) presenter.uploadFile(
chatRoomId,
uri,
(citation ?: "") + description.text.toString(),
bitmap
)
alertDialog.dismiss() alertDialog.dismiss()
} }
cancelButton.setOnClickListener { alertDialog.dismiss() } cancelButton.setOnClickListener { alertDialog.dismiss() }
......
...@@ -5,5 +5,6 @@ data class ReactionUiModel( ...@@ -5,5 +5,6 @@ data class ReactionUiModel(
val shortname: String, val shortname: String,
val unicode: CharSequence, val unicode: CharSequence,
val count: Int, val count: Int,
val usernames: List<String> = emptyList() val usernames: List<String> = emptyList(),
var url: String? = null
) )
\ No newline at end of file
...@@ -18,6 +18,7 @@ import chat.rocket.android.chatroom.domain.MessageReply ...@@ -18,6 +18,7 @@ import chat.rocket.android.chatroom.domain.MessageReply
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.db.DatabaseManager import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.emoji.EmojiParser import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.helper.MessageHelper import chat.rocket.android.helper.MessageHelper
import chat.rocket.android.helper.MessageParser import chat.rocket.android.helper.MessageParser
import chat.rocket.android.helper.UserHelper import chat.rocket.android.helper.UserHelper
...@@ -504,15 +505,18 @@ class UiModelMapper @Inject constructor( ...@@ -504,15 +505,18 @@ class UiModelMapper @Inject constructor(
private fun getReactions(message: Message): List<ReactionUiModel> { private fun getReactions(message: Message): List<ReactionUiModel> {
val reactions = message.reactions?.let { val reactions = message.reactions?.let {
val list = mutableListOf<ReactionUiModel>() val list = mutableListOf<ReactionUiModel>()
val customEmojis = EmojiRepository.getCustomEmojis()
it.getShortNames().forEach { shortname -> it.getShortNames().forEach { shortname ->
val usernames = it.getUsernames(shortname) ?: emptyList() val usernames = it.getUsernames(shortname) ?: emptyList()
val count = usernames.size val count = usernames.size
val custom = customEmojis.firstOrNull { emoji -> emoji.shortname == shortname }
list.add( list.add(
ReactionUiModel(messageId = message.id, ReactionUiModel(messageId = message.id,
shortname = shortname, shortname = shortname,
unicode = EmojiParser.parse(context, shortname), unicode = EmojiParser.parse(context, shortname),
count = count, count = count,
usernames = usernames) usernames = usernames,
url = custom?.url)
) )
} }
list list
......
package chat.rocket.android.chatroom.uimodel.suggestion package chat.rocket.android.chatroom.uimodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
class ChatRoomSuggestionUiModel(text: String, class ChatRoomSuggestionUiModel(text: String,
val fullName: String, val fullName: String,
......
package chat.rocket.android.chatroom.uimodel.suggestion package chat.rocket.android.chatroom.uimodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
class CommandSuggestionUiModel(text: String, class CommandSuggestionUiModel(text: String,
val description: String, val description: String,
......
package chat.rocket.android.chatroom.uimodel.suggestion package chat.rocket.android.chatroom.uimodel.suggestion
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
class PeopleSuggestionUiModel(val imageUri: String?, class PeopleSuggestionUiModel(val imageUri: String?,
......
...@@ -17,15 +17,15 @@ import chat.rocket.common.util.ifNull ...@@ -17,15 +17,15 @@ import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.realtime.socket.model.State import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.rest.spotlight import chat.rocket.core.internal.rest.spotlight
import chat.rocket.core.model.SpotlightResult import chat.rocket.core.model.SpotlightResult
import com.shopify.livedataktx.distinct
import com.shopify.livedataktx.map
import com.shopify.livedataktx.nonNull
import kotlinx.coroutines.experimental.android.UI import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.isActive import kotlinx.coroutines.experimental.isActive
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
import me.henrytao.livedataktx.distinct
import me.henrytao.livedataktx.map
import me.henrytao.livedataktx.nonNull
import timber.log.Timber import timber.log.Timber
import java.security.InvalidParameterException import java.security.InvalidParameterException
import kotlin.coroutines.experimental.coroutineContext import kotlin.coroutines.experimental.coroutineContext
......
package chat.rocket.android.dagger package chat.rocket.android.dagger
import android.app.Application import android.app.Application
import chat.rocket.android.app.AppLifecycleObserver
import chat.rocket.android.app.RocketChatApplication import chat.rocket.android.app.RocketChatApplication
import chat.rocket.android.chatroom.service.MessageService import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.module.ActivityBuilder import chat.rocket.android.dagger.module.ActivityBuilder
import chat.rocket.android.dagger.module.AndroidWorkerInjectionModule
import chat.rocket.android.dagger.module.AppModule import chat.rocket.android.dagger.module.AppModule
import chat.rocket.android.dagger.module.ReceiverBuilder import chat.rocket.android.dagger.module.ReceiverBuilder
import chat.rocket.android.dagger.module.ServiceBuilder import chat.rocket.android.dagger.module.ServiceBuilder
import chat.rocket.android.push.FirebaseTokenService
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Component import dagger.Component
import dagger.android.support.AndroidSupportInjectionModule import dagger.android.support.AndroidSupportInjectionModule
...@@ -16,7 +15,8 @@ import javax.inject.Singleton ...@@ -16,7 +15,8 @@ import javax.inject.Singleton
@Singleton @Singleton
@Component(modules = [AndroidSupportInjectionModule::class, @Component(modules = [AndroidSupportInjectionModule::class,
AppModule::class, ActivityBuilder::class, ServiceBuilder::class, ReceiverBuilder::class]) AppModule::class, ActivityBuilder::class, ServiceBuilder::class, ReceiverBuilder::class,
AndroidWorkerInjectionModule::class])
interface AppComponent { interface AppComponent {
@Component.Builder @Component.Builder
...@@ -29,10 +29,5 @@ interface AppComponent { ...@@ -29,10 +29,5 @@ interface AppComponent {
fun inject(app: RocketChatApplication) fun inject(app: RocketChatApplication)
fun inject(service: FirebaseTokenService)
fun inject(service: MessageService) fun inject(service: MessageService)
/*@Component.Builder
abstract class Builder : AndroidInjector.Builder<RocketChatApplication>()*/
} }
package chat.rocket.android.dagger.injector
import androidx.work.Worker
object AndroidWorkerInjection {
fun inject(worker: Worker) {
val application = worker.applicationContext
if (application !is HasWorkerInjector) {
throw RuntimeException("${application.javaClass.canonicalName} does not implement ${HasWorkerInjector::class.java.canonicalName}")
}
val workerInjector = (application as HasWorkerInjector).workerInjector()
checkNotNull(workerInjector) { "${application.javaClass}.workerInjector() return null" }
workerInjector.inject(worker)
}
}
\ No newline at end of file
package chat.rocket.android.dagger.injector
import androidx.work.Worker
import dagger.android.AndroidInjector
interface HasWorkerInjector {
fun workerInjector(): AndroidInjector<Worker>
}
\ No newline at end of file
package chat.rocket.android.dagger.module package chat.rocket.android.dagger.module
import chat.rocket.android.about.di.AboutFragmentProvider
import chat.rocket.android.authentication.di.AuthenticationModule import chat.rocket.android.authentication.di.AuthenticationModule
import chat.rocket.android.authentication.login.di.LoginFragmentProvider import chat.rocket.android.authentication.login.di.LoginFragmentProvider
import chat.rocket.android.authentication.registerusername.di.RegisterUsernameFragmentProvider import chat.rocket.android.authentication.registerusername.di.RegisterUsernameFragmentProvider
...@@ -58,6 +59,7 @@ abstract class ActivityBuilder { ...@@ -58,6 +59,7 @@ abstract class ActivityBuilder {
CreateChannelProvider::class, CreateChannelProvider::class,
ProfileFragmentProvider::class, ProfileFragmentProvider::class,
SettingsFragmentProvider::class, SettingsFragmentProvider::class,
AboutFragmentProvider::class,
PreferencesFragmentProvider::class PreferencesFragmentProvider::class
] ]
) )
......
package chat.rocket.android.dagger.module
import androidx.work.Worker
import dagger.Module
import dagger.android.AndroidInjector
import dagger.multibindings.Multibinds
@Module
abstract class AndroidWorkerInjectionModule {
@Multibinds
abstract fun workerInjectorFactories(): Map<Class<out Worker>, AndroidInjector.Factory<out Worker>>
}
\ No newline at end of file
package chat.rocket.android.dagger.qualifier
import androidx.work.Worker
import dagger.MapKey
import kotlin.reflect.KClass
@MapKey
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class WorkerKey(val value: KClass<out Worker>)
...@@ -78,25 +78,26 @@ class FilesFragment : Fragment(), FilesView { ...@@ -78,25 +78,26 @@ class FilesFragment : Fragment(), FilesView {
} }
override fun showFiles(dataSet: List<FileUiModel>, total: Long) { override fun showFiles(dataSet: List<FileUiModel>, total: Long) {
setupToolbar(total) ui {
if (adapter.itemCount == 0) { setupToolbar(total)
adapter.prependData(dataSet) if (adapter.itemCount == 0) {
if (dataSet.size >= 30) { adapter.prependData(dataSet)
recycler_view.addOnScrollListener(object : if (dataSet.size >= 30) {
EndlessRecyclerViewScrollListener(linearLayoutManager) { recycler_view.addOnScrollListener(object :
override fun onLoadMore( EndlessRecyclerViewScrollListener(linearLayoutManager) {
page: Int, override fun onLoadMore(
totalItemsCount: Int, page: Int,
recyclerView: RecyclerView totalItemsCount: Int,
) { recyclerView: RecyclerView
presenter.loadFiles(chatRoomId) ) {
} presenter.loadFiles(chatRoomId)
}) }
})
}
group_no_file.isVisible = dataSet.isEmpty()
} else {
adapter.appendData(dataSet)
} }
group_no_file.isVisible = dataSet.isEmpty()
} else {
adapter.appendData(dataSet)
} }
} }
......
...@@ -34,8 +34,8 @@ class FileUiModel( ...@@ -34,8 +34,8 @@ class FileUiModel(
} }
private fun getUserDisplayName(): String { private fun getUserDisplayName(): String {
val username = "@${genericAttachment.user?.username}" val username = "@${genericAttachment.user.username}"
val realName = genericAttachment.user?.name val realName = genericAttachment.user.name
val uploaderName = if (settings.useRealName()) realName else username val uploaderName = if (settings.useRealName()) realName else username
return uploaderName ?: username return uploaderName ?: username
} }
......
...@@ -131,7 +131,7 @@ class MessageParser @Inject constructor( ...@@ -131,7 +131,7 @@ class MessageParser @Inject constructor(
private val builder: SpannableBuilder private val builder: SpannableBuilder
) : SpannableMarkdownVisitor(configuration, builder) { ) : SpannableMarkdownVisitor(configuration, builder) {
private val emojiSize = context.resources.getDimensionPixelSize(R.dimen.radius_mention) private val emojiSize = context.resources.getDimensionPixelSize(R.dimen.custom_emoji_small)
override fun visit(document: Document) { override fun visit(document: Document) {
val spannable = EmojiParser.parse(context, builder.text()) val spannable = EmojiParser.parse(context, builder.text())
......
...@@ -59,7 +59,7 @@ class MainPresenter @Inject constructor( ...@@ -59,7 +59,7 @@ class MainPresenter @Inject constructor(
private val saveAccountInteractor: SaveAccountInteractor, private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor, private val getAccountsInteractor: GetAccountsInteractor,
private val removeAccountInteractor: RemoveAccountInteractor, private val removeAccountInteractor: RemoveAccountInteractor,
private val factory: RocketChatClientFactory, factory: RocketChatClientFactory,
private val groupedPush: GroupedPush, private val groupedPush: GroupedPush,
dbManagerFactory: DatabaseManagerFactory, dbManagerFactory: DatabaseManagerFactory,
getSettingsInteractor: GetSettingsInteractor, getSettingsInteractor: GetSettingsInteractor,
...@@ -233,13 +233,6 @@ class MainPresenter @Inject constructor( ...@@ -233,13 +233,6 @@ class MainPresenter @Inject constructor(
} }
} }
suspend fun refreshToken(token: String?) {
token?.let {
localRepository.save(LocalRepository.KEY_PUSH_TOKEN, token)
client.registerPushToken(token, getAccountsInteractor.get(), factory)
}
}
private suspend fun saveAccount(uiModel: NavHeaderUiModel) { private suspend fun saveAccount(uiModel: NavHeaderUiModel) {
val icon = settings.favicon()?.let { val icon = settings.favicon()?.let {
currentServer.serverLogoUrl(it) currentServer.serverLogoUrl(it)
......
...@@ -18,6 +18,7 @@ import chat.rocket.android.main.adapter.Selector ...@@ -18,6 +18,7 @@ import chat.rocket.android.main.adapter.Selector
import chat.rocket.android.main.presentation.MainPresenter import chat.rocket.android.main.presentation.MainPresenter
import chat.rocket.android.main.presentation.MainView import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.uimodel.NavHeaderUiModel import chat.rocket.android.main.uimodel.NavHeaderUiModel
import chat.rocket.android.push.refreshPushToken
import chat.rocket.android.server.domain.PermissionsInteractor import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.model.Account import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.ui.INTENT_CHAT_ROOM_ID import chat.rocket.android.server.ui.INTENT_CHAT_ROOM_ID
...@@ -26,7 +27,6 @@ import chat.rocket.android.util.extensions.fadeOut ...@@ -26,7 +27,6 @@ import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.rotateBy import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.invalidateFirebaseToken import chat.rocket.android.util.invalidateFirebaseToken
import chat.rocket.android.util.refreshFCMToken
import chat.rocket.common.model.UserStatus import chat.rocket.common.model.UserStatus
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
...@@ -36,9 +36,6 @@ import dagger.android.support.HasSupportFragmentInjector ...@@ -36,9 +36,6 @@ import dagger.android.support.HasSupportFragmentInjector
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar.* import kotlinx.android.synthetic.main.app_bar.*
import kotlinx.android.synthetic.main.nav_header.view.* import kotlinx.android.synthetic.main.nav_header.view.*
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
private const val CURRENT_STATE = "current_state" private const val CURRENT_STATE = "current_state"
...@@ -64,9 +61,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector, ...@@ -64,9 +61,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
launch(CommonPool) { refreshPushToken()
refreshFCMToken(presenter)
}
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID) chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
......
...@@ -2,6 +2,7 @@ package chat.rocket.android.members.di ...@@ -2,6 +2,7 @@ package chat.rocket.android.members.di
import chat.rocket.android.members.ui.MembersFragment import chat.rocket.android.members.ui.MembersFragment
import chat.rocket.android.dagger.scope.PerFragment import chat.rocket.android.dagger.scope.PerFragment
import chat.rocket.android.members.ui.MemberBottomSheetFragment
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
...@@ -11,4 +12,9 @@ abstract class MembersFragmentProvider { ...@@ -11,4 +12,9 @@ abstract class MembersFragmentProvider {
@ContributesAndroidInjector(modules = [MembersFragmentModule::class]) @ContributesAndroidInjector(modules = [MembersFragmentModule::class])
@PerFragment @PerFragment
abstract fun provideMembersFragment(): MembersFragment abstract fun provideMembersFragment(): MembersFragment
@ContributesAndroidInjector()
@PerFragment
abstract fun provideMemberBottomSheetFragment(): MemberBottomSheetFragment
} }
\ No newline at end of file
...@@ -11,6 +11,7 @@ import chat.rocket.android.analytics.event.ScreenViewEvent ...@@ -11,6 +11,7 @@ import chat.rocket.android.analytics.event.ScreenViewEvent
import chat.rocket.android.util.extensions.content import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.textContent import chat.rocket.android.util.extensions.textContent
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_member_bottom_sheet.* import kotlinx.android.synthetic.main.fragment_member_bottom_sheet.*
import javax.inject.Inject import javax.inject.Inject
...@@ -51,6 +52,7 @@ class MemberBottomSheetFragment : BottomSheetDialogFragment() { ...@@ -51,6 +52,7 @@ class MemberBottomSheetFragment : BottomSheetDialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this)
val bundle = arguments val bundle = arguments
if (bundle != null) { if (bundle != null) {
......
...@@ -4,7 +4,6 @@ import android.app.NotificationManager ...@@ -4,7 +4,6 @@ import android.app.NotificationManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput import androidx.core.app.RemoteInput
import android.widget.Toast import android.widget.Toast
import chat.rocket.android.R import chat.rocket.android.R
......
...@@ -72,4 +72,22 @@ interface MessagesRepository { ...@@ -72,4 +72,22 @@ interface MessagesRepository {
suspend fun getAllUnsent(): List<Message> suspend fun getAllUnsent(): List<Message>
suspend fun getUnsentByRoomId(roomId: String): List<Message> suspend fun getUnsentByRoomId(roomId: String): List<Message>
/**
* Save time of the latest room messages sync.
* Call this fun only when the latest messages list being received via /history or /messages network calls
*
* @param rid The id of the room the messages are.
* @param timeMillis time of room messages sync or the latest room message timestamp(which came with /history request)
*/
suspend fun saveLastSyncDate(rid: String, timeMillis: Long)
/**
* Get time when the room chat history has been loaded last time.
*
* @param rid The id of the room the messages are.
*
* @return Last Sync time or Null.
*/
suspend fun getLastSyncDate(rid: String): Long?
} }
\ No newline at end of file
...@@ -7,8 +7,16 @@ import kotlinx.coroutines.experimental.withContext ...@@ -7,8 +7,16 @@ import kotlinx.coroutines.experimental.withContext
class MemoryMessagesRepository : MessagesRepository { class MemoryMessagesRepository : MessagesRepository {
private var lastSyncDates: HashMap<String, Long> = HashMap()
private val messages: HashMap<String, Message> = HashMap() private val messages: HashMap<String, Message> = HashMap()
override suspend fun saveLastSyncDate(rid: String, timeMillis: Long) {
lastSyncDates[rid] = timeMillis
}
override suspend fun getLastSyncDate(rid: String) = lastSyncDates[rid]
override suspend fun getById(id: String): Message? = withContext(CommonPool) { override suspend fun getById(id: String): Message? = withContext(CommonPool) {
return@withContext messages[id] return@withContext messages[id]
} }
......
...@@ -13,6 +13,27 @@ class SharedPreferencesMessagesRepository( ...@@ -13,6 +13,27 @@ class SharedPreferencesMessagesRepository(
private val moshi: Moshi, private val moshi: Moshi,
private val currentServerInteractor: GetCurrentServerInteractor private val currentServerInteractor: GetCurrentServerInteractor
) : MessagesRepository { ) : MessagesRepository {
private val KEY_LAST_SYNC_DATE = "KEY_LAST_SYNC_DATE"
override suspend fun saveLastSyncDate(rid: String, timeMillis: Long) {
withContext(CommonPool) {
currentServerInteractor.get()?.let {
prefs.edit().putLong(getSyncDateKey(it, rid), timeMillis).apply()
}
}
}
override suspend fun getLastSyncDate(rid: String): Long? = withContext(CommonPool) {
currentServerInteractor.get()?.also { server ->
if (!prefs.contains(getSyncDateKey(server, rid)))
return@withContext null
val time = prefs.getLong(getSyncDateKey(server, rid), -1)
return@withContext if (time == -1L) null else time
}
return@withContext null
}
private fun getSyncDateKey(server: String, rid: String) = "${KEY_LAST_SYNC_DATE}_$server $rid"
override suspend fun getById(id: String): Message? = withContext(CommonPool) { override suspend fun getById(id: String): Message? = withContext(CommonPool) {
currentServerInteractor.get()?.also { server -> currentServerInteractor.get()?.also { server ->
......
...@@ -27,7 +27,7 @@ class PasswordPresenter @Inject constructor( ...@@ -27,7 +27,7 @@ class PasswordPresenter @Inject constructor(
val me = retryIO("me") { client.me() } val me = retryIO("me") { client.me() }
retryIO("updateProfile(${me.id})") { retryIO("updateProfile(${me.id})") {
client.updateProfile(me.id!!, null, null, password, null) client.updateProfile(me.id, null, null, password, null)
} }
view.showPasswordSuccessfullyUpdatedMessage() view.showPasswordSuccessfullyUpdatedMessage()
......
...@@ -31,7 +31,7 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen ...@@ -31,7 +31,7 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen
@Inject @Inject
lateinit var analyticsManager: AnalyticsManager lateinit var analyticsManager: AnalyticsManager
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AndroidSupportInjection.inject(this) AndroidSupportInjection.inject(this)
} }
......
...@@ -7,18 +7,18 @@ import chat.rocket.core.RocketChatClient ...@@ -7,18 +7,18 @@ import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.registerPushToken import chat.rocket.core.internal.rest.registerPushToken
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import timber.log.Timber import timber.log.Timber
suspend fun RocketChatClient.registerPushToken( suspend fun RocketChatClientFactory.registerPushToken(
token: String, token: String,
accounts: List<Account>, accounts: List<Account>
factory: RocketChatClientFactory
) { ) {
launch(CommonPool) { withContext(CommonPool) {
accounts.forEach { account -> accounts.forEach { account ->
try { try {
retryIO(description = "register push token: ${account.serverUrl}") { retryIO(description = "register push token: ${account.serverUrl}") {
factory.create(account.serverUrl).registerPushToken(token) create(account.serverUrl).registerPushToken(token)
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.d(ex, "Error registering Push token for ${account.serverUrl}") Timber.d(ex, "Error registering Push token for ${account.serverUrl}")
......
...@@ -10,15 +10,19 @@ import android.provider.DocumentsContract ...@@ -10,15 +10,19 @@ import android.provider.DocumentsContract
import android.provider.MediaStore import android.provider.MediaStore
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import java.io.* import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
fun Uri.getFileName(context: Context): String? { fun Uri.getFileName(context: Context): String? {
val cursor = context.contentResolver.query(this, null, null, null, null, null) val cursor = context.contentResolver.query(this, null, null, null, null, null)
var fileName: String? = null var fileName: String? = null
cursor.use { cursor -> cursor?.use {
if (cursor != null && cursor.moveToFirst()) { if (it.moveToFirst()) {
fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) fileName = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
} }
} }
return fileName return fileName
...@@ -29,7 +33,7 @@ fun Uri.getFileSize(context: Context): Int { ...@@ -29,7 +33,7 @@ fun Uri.getFileSize(context: Context): Int {
if (scheme == ContentResolver.SCHEME_CONTENT) { if (scheme == ContentResolver.SCHEME_CONTENT) {
try { try {
val fileInputStream = context.contentResolver.openInputStream(this) val fileInputStream = context.contentResolver.openInputStream(this)
fileSize = fileInputStream.available().toString() fileSize = fileInputStream?.available().toString()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
...@@ -67,14 +71,17 @@ fun Uri.isVirtualFile(context: Context): Boolean { ...@@ -67,14 +71,17 @@ fun Uri.isVirtualFile(context: Context): Boolean {
val cursor = context.contentResolver.query( val cursor = context.contentResolver.query(
this, this,
arrayOf(DocumentsContract.Document.COLUMN_FLAGS), arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
null, null, null null,
null,
null
) )
var flags = 0 var flags = 0
if (cursor.moveToFirst()) { cursor?.use {
flags = cursor.getInt(0) if (it.moveToFirst()) {
flags = it.getInt(0)
}
} }
cursor.close()
return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0 return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0
} }
...@@ -104,4 +111,4 @@ fun Uri.getInputStream(context: Context): InputStream? { ...@@ -104,4 +111,4 @@ fun Uri.getInputStream(context: Context): InputStream? {
fun Uri.getBitmpap(context: Context): Bitmap? { fun Uri.getBitmpap(context: Context): Bitmap? {
return MediaStore.Images.Media.getBitmap(context.contentResolver, this) return MediaStore.Images.Media.getBitmap(context.contentResolver, this)
} }
\ No newline at end of file
...@@ -4,11 +4,8 @@ import androidx.lifecycle.LiveData ...@@ -4,11 +4,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.CoroutineScope
import kotlinx.coroutines.experimental.Job import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import kotlin.coroutines.experimental.CoroutineContext import kotlin.coroutines.experimental.CoroutineContext
class WrappedLiveData<Source, Output>( class WrappedLiveData<Source, Output>(
......
...@@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatActivity ...@@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import chat.rocket.android.R import chat.rocket.android.R
import chat.rocket.android.util.extensions.inflate import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.ui
import kotlinx.android.synthetic.main.fragment_admin_panel_web_view.* import kotlinx.android.synthetic.main.fragment_admin_panel_web_view.*
private const val BUNDLE_WEB_PAGE_URL = "web_page_url" private const val BUNDLE_WEB_PAGE_URL = "web_page_url"
...@@ -58,8 +59,10 @@ class AdminPanelWebViewFragment : Fragment() { ...@@ -58,8 +59,10 @@ class AdminPanelWebViewFragment : Fragment() {
web_view.webViewClient = object : WebViewClient() { web_view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url) super.onPageFinished(view, url)
view_loading.hide() ui { _ ->
web_view.evaluateJavascript("Meteor.loginWithToken('$userToken', function() { })") {} view_loading.hide()
web_view.evaluateJavascript("Meteor.loginWithToken('$userToken', function() { })") {}
}
} }
} }
web_view.loadUrl(webPageUrl) web_view.loadUrl(webPageUrl)
......
...@@ -8,32 +8,19 @@ ...@@ -8,32 +8,19 @@
android:translateX="281.28394" android:translateX="281.28394"
android:translateY="271.51514"> android:translateY="271.51514">
<path <path
android:fillColor="#CC3333" android:fillColor="#FFDB2323"
android:pathData="M491.3,255.3c0,-24.1 -7.2,-47.2 -21.4,-68.7c-12.8,-19.3 -30.7,-36.4 -53.2,-50.7c-43.5,-27.8 -100.6,-43.1 -160.9,-43.1c-20.1,0 -40,1.7 -59.2,5.1c-11.9,-11.2 -25.9,-21.2 -40.7,-29.2c-79,-38.3 -144.6,-0.9 -144.6,-0.9s60.9,50.1 51,93.9c-27.3,27 -42,59.6 -42,93.6c0,0.1 0,0.2 0,0.3c0,0.1 0,0.2 0,0.3c0,33.9 14.8,66.6 42,93.6c9.9,43.9 -51,93.9 -51,93.9s65.5,37.4 144.6,-0.9c14.8,-8 28.8,-18 40.7,-29.2c19.2,3.4 39.1,5.1 59.2,5.1c60.3,0 117.4,-15.3 160.9,-43.1c22.5,-14.4 40.4,-31.5 53.2,-50.7c14.2,-21.5 21.4,-44.6 21.4,-68.7c0,-0.1 0,-0.2 0,-0.3C491.3,255.6 491.3,255.4 491.3,255.3z" /> android:pathData="M491.3,255.3c0,-24.1 -7.2,-47.2 -21.4,-68.7c-12.8,-19.3 -30.7,-36.4 -53.2,-50.7c-43.5,-27.8 -100.6,-43.1 -160.9,-43.1c-20.1,0 -40,1.7 -59.2,5.1c-11.9,-11.2 -25.9,-21.2 -40.7,-29.2c-79,-38.3 -144.6,-0.9 -144.6,-0.9s60.9,50.1 51,93.9c-27.3,27 -42,59.6 -42,93.6c0,0.1 0,0.2 0,0.3c0,0.1 0,0.2 0,0.3c0,33.9 14.8,66.6 42,93.6c9.9,43.9 -51,93.9 -51,93.9s65.5,37.4 144.6,-0.9c14.8,-8 28.8,-18 40.7,-29.2c19.2,3.4 39.1,5.1 59.2,5.1c60.3,0 117.4,-15.3 160.9,-43.1c22.5,-14.4 40.4,-31.5 53.2,-50.7c14.2,-21.5 21.4,-44.6 21.4,-68.7c0,-0.1 0,-0.2 0,-0.3C491.3,255.6 491.3,255.4 491.3,255.3z" />
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:pathData="M255.9,124.2c113.9,0 206.3,59 206.3,131.8c0,72.8 -92.4,131.8 -206.3,131.8c-25.4,0 -49.7,-2.9 -72.1,-8.3c-22.8,27.4 -73,65.6 -121.7,53.3c15.9,-17 39.4,-45.8 34.3,-93.2c-29.2,-22.7 -46.8,-51.8 -46.8,-83.5C49.6,183.2 142,124.2 255.9,124.2" /> android:pathData="M255.9,124.2c113.9,0 206.3,59 206.3,131.8c0,72.8 -92.4,131.8 -206.3,131.8c-25.4,0 -49.7,-2.9 -72.1,-8.3c-22.8,27.4 -73,65.6 -121.7,53.3c15.9,-17 39.4,-45.8 34.3,-93.2c-29.2,-22.7 -46.8,-51.8 -46.8,-83.5C49.6,183.2 142,124.2 255.9,124.2" />
<path <path
android:fillColor="#CC3333" android:fillColor="#FFDB2323"
android:pathData="M255.9,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" /> android:pathData="M255.9,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" />
<path <path
android:fillColor="#CC3333" android:fillColor="#FFDB2323"
android:pathData="M351.2,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" /> android:pathData="M351.2,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" />
<path <path
android:fillColor="#CC3333" android:fillColor="#FFDB2323"
android:pathData="M160.6,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" /> android:pathData="M160.6,256m-27.4,0a27.4,27.4 0,1 1,54.8 0a27.4,27.4 0,1 1,-54.8 0" />
<path
android:fillColor="#CCCCCC"
android:pathData="M255.8,372.8c-25.4,0 -56.2,-4.9 -78.7,-9.5c-20.1,21 -53.7,52.7 -99.6,50.3c-5.7,8.6 -10.2,13.5 -15.5,19.2c48.7,12.3 98.9,-25.8 121.7,-53.3c22.4,5.4 46.7,8.3 72.1,8.3c113,0 204.8,-58.1 206.3,-130C460.7,320.1 368.8,372.8 255.8,372.8z" />
<path
android:fillColor="#00000000"
android:pathData="M172,350.9"
android:strokeColor="#000000"
android:strokeWidth="1" />
<path
android:fillColor="#00000000"
android:pathData="M200.4,422.9"
android:strokeColor="#000000"
android:strokeWidth="1" />
</group> </group>
</vector> </vector>
\ No newline at end of file
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
android:width="24dp" android:width="24dp"
android:height="24dp" /> android:height="24dp" />
<solid android:color="#efeeee" /> <solid android:color="#efeeee" />
<corners android:radius="4dp"/> <corners android:radius="4dp" />
</shape> </shape>
\ No newline at end of file
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" /> tools:visibility="visible" />
<chat.rocket.android.widget.autocompletion.ui.SuggestionsView <chat.rocket.android.suggestions.ui.SuggestionsView
android:id="@+id/suggestions_view" android:id="@+id/suggestions_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="2dp" android:layout_marginEnd="2dp"
android:layout_marginRight="2dp" android:background="@drawable/rounded_background">
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:descendantFocusability="beforeDescendants"
android:background="@drawable/rounded_background"
android:orientation="horizontal">
<TextView <ViewFlipper
android:id="@+id/text_emoji" android:id="@+id/view_flipper_reaction"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" app:layout_constraintBottom_toBottomOf="parent"
android:maxLines="1" app:layout_constraintEnd_toStartOf="@+id/text_count"
android:paddingLeft="4dp" app:layout_constraintStart_toStartOf="parent"
android:paddingStart="4dp" app:layout_constraintTop_toTopOf="parent">
android:textColor="#868585"
android:textSize="16sp" <TextView
tools:text=":)" /> android:id="@+id/text_emoji"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:textColor="#868585"
android:textSize="16sp"
tools:text=":)" />
<ImageView
android:id="@+id/image_emoji"
android:layout_width="@dimen/custom_emoji_small"
android:layout_height="@dimen/custom_emoji_small"
android:layout_gravity="center"
tools:src="@tools:sample/avatars" />
</ViewFlipper>
<TextView <TextView
android:id="@+id/text_count" android:id="@+id/text_count"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:paddingBottom="4dp"
android:paddingEnd="4dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:paddingStart="4dp" android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp" android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:paddingBottom="4dp"
android:textColor="#868585" android:textColor="#868585"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/view_flipper_reaction"
app:layout_constraintTop_toTopOf="parent"
tools:text="12" /> tools:text="12" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<resources> <resources>
<!-- Titles --> <!-- Titles -->
<string name="title_sign_in_your_server">Anmelden am Server</string> <string name="title_sign_in_your_server">Anmelden am Server</string>
<string name="title_log_in">Anmelden</string> <string name="title_log_in">Anmelden</string>
...@@ -70,7 +71,6 @@ ...@@ -70,7 +71,6 @@
<string name="msg_invalid_email">Bitte eine korrekte E-Mail Adresse eingeben</string> <string name="msg_invalid_email">Bitte eine korrekte E-Mail Adresse eingeben</string>
<string name="msg_new_user_agreement">Beim weitergehen akzeptieren Sie usere\n%1$s und %2$s</string> <string name="msg_new_user_agreement">Beim weitergehen akzeptieren Sie usere\n%1$s und %2$s</string>
<string name="msg_2fa_code">2FA Code</string> <string name="msg_2fa_code">2FA Code</string>
<!-- <string name="msg_more_than_ninety_nine_unread_messages" translatable="false">99+</string> -->
<string name="msg_yesterday">Gestern</string> <string name="msg_yesterday">Gestern</string>
<string name="msg_today">Heute</string> <string name="msg_today">Heute</string>
<string name="msg_message">Nachricht</string> <string name="msg_message">Nachricht</string>
...@@ -104,11 +104,8 @@ ...@@ -104,11 +104,8 @@
<string name="msg_no_messages_yet">Noch keine Nachrichten</string> <string name="msg_no_messages_yet">Noch keine Nachrichten</string>
<string name="msg_ok">OK</string> <string name="msg_ok">OK</string>
<string name="msg_update_app_version_in_order_to_continue">Server Version veraltet. Bitte kontaktieren Sie ihren Server Administrator.</string> <string name="msg_update_app_version_in_order_to_continue">Server Version veraltet. Bitte kontaktieren Sie ihren Server Administrator.</string>
<string name="msg_ver_not_recommended"> <string name="msg_ver_not_recommended">Die Server Version scheint älter als die empfolene Version %1$s zu sein.\nSie können sich trotzdem einloggen, aber es kann zu einem unerwartetem Verhalten kommen.</string>
Die Server Version scheint älter als die empfolene Version %1$s zu sein.\nSie können sich trotzdem einloggen, aber es kann zu einem unerwartetem Verhalten kommen.</string> <string name="msg_ver_not_minimum">Die Server Version scheint älter als die minimale Version %1$s zu sein.\nBitte updaten Sie Ihren Server um sich einloggen zu können!</string>
<string name="msg_ver_not_minimum">
Die Server Version scheint älter als die minimale Version %1$s zu sein.\nBitte updaten Sie Ihren Server um sich einloggen zu können!
</string>
<string name="msg_no_chat_title">Keine Chat Nachrichten</string> <string name="msg_no_chat_title">Keine Chat Nachrichten</string>
<string name="msg_no_chat_description">Starte die Konversation um Ihre \nNachrichten hier zu sehen.</string> <string name="msg_no_chat_description">Starte die Konversation um Ihre \nNachrichten hier zu sehen.</string>
<string name="msg_proceed">WEITER</string> <string name="msg_proceed">WEITER</string>
......
<resources> <resources>
<!-- Titles --> <!-- Titles -->
<string name="title_sign_in_your_server">Connectez-vous sur votre serveur</string> <string name="title_sign_in_your_server">Connectez-vous sur votre serveur</string>
<string name="title_log_in">S\'identifier</string> <string name="title_log_in">S\'identifier</string>
<string name="title_register_username">Enregistrer le nom d\'utilisateur</string> <string name="title_register_username">Enregistrer le nom d\'utilisateur</string>
// TODO: Add proper translation. <string name="title_reset_password">Réinitialiser mot de passe</string>
<string name="title_reset_password">Reset password</string>
<string name="title_sign_up">S\'inscrire</string> <string name="title_sign_up">S\'inscrire</string>
<string name="title_authentication">Authentification</string> <string name="title_authentication">Authentification</string>
<string name="title_legal_terms">Termes légaux</string> <string name="title_legal_terms">Termes juridiques</string>
<string name="title_chats">Chats</string> <string name="title_chats">Chats</string>
<string name="title_profile">Profil</string> <string name="title_profile">Profil</string>
<string name="title_members">Membres (%d)</string> <string name="title_members">Membres (%d)</string>
<string name="title_settings">Paramètres</string> <string name="title_settings">Paramètres</string>
<string name="title_preferences">Preferences</string> <!-- TODO Add translation --> <string name="title_preferences">Préférences</string>
<string name="title_change_password">Changer le mot de passe</string> <string name="title_change_password">Changer le mot de passe</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation --> <string name="title_admin_panel">Administration</string>
<string name="title_password">Changer le mot de passe</string> <string name="title_password">Changer le mot de passe</string>
<string name="title_update_profile">Update profile</string> <string name="title_update_profile">Mettre à jour le profil</string>
<string name="title_about">Sur</string> <string name="title_about">À propos</string>
// TODO: Add proper translation. <string name="title_create_channel">Créer salon</string>
<string name="title_create_channel">Create Channel</string>
<!-- Actions --> <!-- Actions -->
<string name="action_connect">Se connecter</string> <string name="action_connect">Se connecter</string>
...@@ -30,8 +29,8 @@ ...@@ -30,8 +29,8 @@
<string name="action_search">Chercher</string> <string name="action_search">Chercher</string>
<string name="action_update">Mettre à jour</string> <string name="action_update">Mettre à jour</string>
<string name="action_settings">Paramètres</string> <string name="action_settings">Paramètres</string>
<string name="action_create_channel">Create channel</string> <!-- TODO Add translation --> <string name="action_create_channel">Créer salon</string>
<string name="action_create">Create</string> <string name="action_create">Créer</string>
<string name="action_logout">Se déconnecter</string> <string name="action_logout">Se déconnecter</string>
<string name="action_files">Fichiers</string> <string name="action_files">Fichiers</string>
<string name="action_confirm_password">Confirmer le mot de passe</string> <string name="action_confirm_password">Confirmer le mot de passe</string>
...@@ -42,16 +41,16 @@ ...@@ -42,16 +41,16 @@
<string name="action_busy">Occupé</string> <string name="action_busy">Occupé</string>
<string name="action_invisible">Invisible</string> <string name="action_invisible">Invisible</string>
<string name="action_drawing">Dessin</string> <string name="action_drawing">Dessin</string>
<string name="action_save_to_gallery">Save to gallery</string> <!-- TODO Add translation --> <string name="action_save_to_gallery">Sauvegarder vers la gallerie</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation --> <string name="action_select_photo_from_gallery">Sélectionner depuis la gallerie</string>
<string name="action_take_photo">Select photo from gallery</string> <!-- TODO Add translation --> <string name="action_take_photo">Prendre une photo</string>
<string name="action_reset_avatar">Reset avatar</string> <!-- TODO Add translation --> <string name="action_reset_avatar">Réinitialiser l\'avatar</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation --> <item name="item_preferences">Préférences</item>
<item name="item_password">Changer le mot de passe</item> <item name="item_password">Changer le mot de passe</item>
<item name="item_password">Sur</item> <item name="item_password">À propos</item>
</string-array> </string-array>
<!-- Regular information messages --> <!-- Regular information messages -->
...@@ -66,20 +65,16 @@ ...@@ -66,20 +65,16 @@
<string name="msg_avatar_url">URL de l\'avatar</string> <string name="msg_avatar_url">URL de l\'avatar</string>
<string name="msg_or_continue_using_social_accounts">Ou continuer en utilisant les comptes sociaux</string> <string name="msg_or_continue_using_social_accounts">Ou continuer en utilisant les comptes sociaux</string>
<string name="msg_new_user">Nouvel utilisateur? %1$s</string> <string name="msg_new_user">Nouvel utilisateur? %1$s</string>
// TODO: Add proper translation. <string name="msg_forgot_password">Mot de passe oublié ? %1$s</string>
<string name="msg_forgot_password">Forgot password? %1$s</string> <string name="msg_reset">Réinitialiser</string>
// TODO: Add proper translation. <string name="msg_check_your_email_to_reset_your_password">Email envoyé ! Consultez votre boîte mail pour réinitialiser votre mot de passe.</string>
<string name="msg_reset">Reset</string> <string name="msg_invalid_email">Veuillez entrer une adresse valide</string>
// TODO: Add proper translation.
<string name="msg_check_your_email_to_reset_your_password">Email sent! Check your inbox to reset your password.</string>
// TODO: Add proper translation.
<string name="msg_invalid_email">Please type a valid e-mail</string>
<string name="msg_new_user_agreement">En procédant, vous acceptez notre\n%1$s et %2$s</string> <string name="msg_new_user_agreement">En procédant, vous acceptez notre\n%1$s et %2$s</string>
<string name="msg_2fa_code">Code 2FA</string> <string name="msg_2fa_code">Code 2FA</string>
<string name="msg_yesterday">Hier</string> <string name="msg_yesterday">Hier</string>
<string name="msg_today">Aujourd\'hui</string> <string name="msg_today">Aujourd\'hui</string>
<string name="msg_message">Message</string> <string name="msg_message">Message</string>
<string name="msg_this_room_is_read_only">Cette salle est seulement de lecture</string> <string name="msg_this_room_is_read_only">Ce salon est en lecture seule</string>
<string name="msg_invalid_2fa_code">Code 2FA non valide</string> <string name="msg_invalid_2fa_code">Code 2FA non valide</string>
<string name="msg_invalid_file">Fichier non valide</string> <string name="msg_invalid_file">Fichier non valide</string>
<string name="msg_invalid_server_url">URL de serveur non valide</string> <string name="msg_invalid_server_url">URL de serveur non valide</string>
...@@ -90,10 +85,10 @@ ...@@ -90,10 +85,10 @@
<string name="msg_content_description_log_in_using_meteor">Connectez-vous en utilisant Meteor</string> <string name="msg_content_description_log_in_using_meteor">Connectez-vous en utilisant Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Connectez-vous en utilisant Twitter</string> <string name="msg_content_description_log_in_using_twitter">Connectez-vous en utilisant Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Connectez-vous en utilisant Gitlab</string> <string name="msg_content_description_log_in_using_gitlab">Connectez-vous en utilisant Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Login using WordPress</string> <!-- TODO Translate--> <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_send_message">Envoyer message</string>
<string name="msg_content_description_show_attachment_options">Afficher les options de fichiers</string> <string name="msg_content_description_show_attachment_options">Afficher les options de fichiers</string>
<string name="msg_you">Toi</string> <string name="msg_you">Vous</string>
<string name="msg_unknown">Inconnu</string> <string name="msg_unknown">Inconnu</string>
<string name="msg_email_address">Adresse e-mail</string> <string name="msg_email_address">Adresse e-mail</string>
<string name="msg_utc_offset">Décalage UTC</string> <string name="msg_utc_offset">Décalage UTC</string>
...@@ -103,11 +98,10 @@ ...@@ -103,11 +98,10 @@
<string name="msg_preview_video">Vidéo</string> <string name="msg_preview_video">Vidéo</string>
<string name="msg_preview_audio">Audio</string> <string name="msg_preview_audio">Audio</string>
<string name="msg_preview_photo">Photo</string> <string name="msg_preview_photo">Photo</string>
<string name="msg_preview_file">File</string> <string name="msg_preview_file">Fichier</string>
<string name="msg_no_messages_yet">Aucun message pour le moment</string> <string name="msg_no_messages_yet">Aucun message pour le moment</string>
<string name="msg_ok">OK</string> <string name="msg_ok">OK</string>
// TODO: Add proper translation. <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_update_app_version_in_order_to_continue">Out to date server version. Please contact the server admin to update the server version in order to continue.</string>
<string name="msg_ver_not_recommended"> <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> 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>
<string name="msg_ver_not_minimum"> <string name="msg_ver_not_minimum">
...@@ -124,62 +118,53 @@ ...@@ -124,62 +118,53 @@
<string name="msg_no_chat_title">Aucun message de discussion</string> <string name="msg_no_chat_title">Aucun message de discussion</string>
<string name="msg_no_chat_description">Commencez à converser pour voir\nvos messages ici.</string> <string name="msg_no_chat_description">Commencez à converser pour voir\nvos messages ici.</string>
<string name="msg_edited">(édité)</string> <string name="msg_edited">(édité)</string>
// TODO: Add proper translation. <string name="msg_and">\u0020et\u0020</string>
<string name="msg_and">\u0020and\u0020</string> <string name="msg_is_typing">\u0020écrit…</string>
// TODO: Add proper translation. <string name="msg_are_typing">\u0020écrivent…</string>
<string name="msg_is_typing">\u0020is typing…</string> <string name="msg_several_users_are_typing">Plusieurs utilisateurs écrivent…</string>
// TODO: Add proper translation.
<string name="msg_are_typing">\u0020are typing…</string>
// TODO: Add proper translation.
<string name="msg_several_users_are_typing">Several users are typing…</string>
<string name="msg_no_search_found">Aucun résultat trouvé</string> <string name="msg_no_search_found">Aucun résultat trouvé</string>
// TODO: Add proper translation. <string name="msg_channel_name">Nom du salon</string>
<string name="msg_channel_name">Channel name</string> <string name="msg_search">Rechercher</string>
// TODO: Add proper translation.
<string name="msg_search">Search</string>
<string name="msg_message_copied">Message copié</string> <string name="msg_message_copied">Message copié</string>
<string name="msg_upload_file">Téléverser un fichier</string> <string name="msg_upload_file">Téléverser un fichier</string>
<string name="msg_file_description">Description du fichier</string> <string name="msg_file_description">Description du fichier</string>
<string name="msg_send">envoyer</string> <string name="msg_send">envoyer</string>
// TODO: Add proper translation. <string name="msg_delete_message">Supprimer Message</string>
<string name="msg_delete_message">Delete Message</string> <string name="msg_delete_description">Êtes-vous sûr de vouloir supprimer ce message</string>
<string name="msg_delete_description">Are you sure you want to delete this message</string>
<!-- Create channel messages --> <!-- Create channel messages -->
// TODO: Add proper translation. <string name="msg_private_channel">Privé</string>
<string name="msg_private_channel">Private</string>
<string name="msg_public_channel">Public</string> <string name="msg_public_channel">Public</string>
<string name="msg_private_channel_description">Only you and invited members can access this channel</string> <string name="msg_private_channel_description">Seul vous et les membres invités peuvent accéder à ce salon</string>
<string name="msg_public_channel_description">Everyone can access this channel</string> <string name="msg_public_channel_description">Tout le monde peut accéder à ce salon</string>
<string name="msg_ready_only_channel">Read only channel</string> <string name="msg_ready_only_channel">Salon en lecture seule</string>
<string name="msg_ready_only_channel_description">Only admin can write new messages</string> <string name="msg_ready_only_channel_description">Seuls les administrateurs peuvent écrire de nouveaux messages</string>
<string name="msg_invite_members">Invite members to channel</string> <string name="msg_invite_members">Inviter des membres à rejoindre le salon</string>
<string name="msg_member_already_added">You have already selected this user</string> <string name="msg_member_already_added">Cet utilisateur est déjà sélectionné</string>
<string name="msg_member_not_found">Member not found</string> <string name="msg_member_not_found">Membre non trouvé</string>
<string name="msg_channel_created_successfully">Channel created successfully</string> <string name="msg_channel_created_successfully">Salon créé</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation --> <string name="msg_analytics_tracking">Pistage à des fins d\'analyses</string>
<string name="msg_send_analytics_tracking">Send anonymous statics to help improving this app</string> <!-- TODO Add translation --> <string name="msg_send_analytics_tracking">Envoyer des statistiques anonymes pour aider à l\'amélioration de l\'application</string>
<string name="msg_do_not_send_analytics_tracking">Do not send anonymous statics to help improving this app</string> <!-- TODO Add translation --> <string name="msg_do_not_send_analytics_tracking">Ne pas envoyer des statistiques anonymes pour aider à l\'amélioration de l\'application</string>
<string name="msg_not_applicable_since_it_is_a_foss_version">Not applicable since it is a FOSS version</string> <!-- TODO Add translation --> <string name="msg_not_applicable_since_it_is_a_foss_version">Ne s\'applique pas étant donné qu\'il s\'agit d\'une version FOSS</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">Le nom de le salle a changé à: %1$s par %2$s</string> <string name="message_room_name_changed">Le nom du salon %1$s a été changé en %2$s</string>
<string name="message_user_added_by">Utilisateur %1$s ajouté par %2$s</string> <string name="message_user_added_by">Utilisateur %1$s ajouté par %2$s</string>
<string name="message_user_removed_by">Utilisateur %1$s enlevé par %2$s</string> <string name="message_user_removed_by">Utilisateur %1$s retiré par %2$s</string>
<string name="message_user_left">A quitté de la salle.</string> <string name="message_user_left">A quitté le salon.</string>
<string name="message_user_joined_channel">A rejoint la salle.</string> <string name="message_user_joined_channel">A rejoint le salon.</string>
<string name="message_welcome">Bienvenue %s</string> <string name="message_welcome">Bienvenue %s</string>
<string name="message_removed">Message supprimé</string> <string name="message_removed">Message supprimé</string>
<string name="message_pinned">Épinglé un message:</string> <string name="message_pinned">Message épinglé:</string>
<string name="message_muted">Utilisateur %1$s mis en sourdine par %2$s</string> <string name="message_muted">Utilisateur %1$s mis en sourdine par %2$s</string>
<string name="message_unmuted">Utilisateur %1$s non muté par %2$s</string> <string name="message_unmuted">Utilisateur %1$s a retrouvé la parole grâce à %2$s</string>
<string name="message_role_add">%1$s a été défini %2$s par %3$s</string> <string name="message_role_add">%1$s a été défini %2$s par %3$s</string>
<string name="message_role_removed">%1$s is no longer %2$s par %3$s</string> <string name="message_role_removed">%1$s n\'est plus %2$s par %3$s</string>
// TODO:Add proper translation. <string name="message_credentials_saved_successfully">Certificats sauvegardés</string>
<string name="message_credentials_saved_successfully">Credentials saved successfully</string>
<!-- Message actions --> <!-- Message actions -->
<string name="action_msg_reply">Répondre</string> <string name="action_msg_reply">Répondre</string>
...@@ -188,8 +173,8 @@ ...@@ -188,8 +173,8 @@
<string name="action_msg_copy">Copier</string> <string name="action_msg_copy">Copier</string>
<string name="action_msg_quote">Citation</string> <string name="action_msg_quote">Citation</string>
<string name="action_msg_delete">Effacer</string> <string name="action_msg_delete">Effacer</string>
<string name="action_msg_pin">Épingle message</string> <string name="action_msg_pin">Épingler message</string>
<string name="action_msg_unpin">Enlever message</string> <string name="action_msg_unpin">Ne plus épingler message</string>
<string name="action_msg_star">Ajouter aux Favoris</string> <string name="action_msg_star">Ajouter aux Favoris</string>
<string name="action_msg_unstar">Retirer des favoris</string> <string name="action_msg_unstar">Retirer des favoris</string>
<string name="action_msg_share">Partager</string> <string name="action_msg_share">Partager</string>
...@@ -200,26 +185,22 @@ ...@@ -200,26 +185,22 @@
<string name="permission_editing_not_allowed">L\'édition n\'est pas autorisée</string> <string name="permission_editing_not_allowed">L\'édition n\'est pas autorisée</string>
<string name="permission_deleting_not_allowed">La suppression n\'est pas autorisée</string> <string name="permission_deleting_not_allowed">La suppression n\'est pas autorisée</string>
<string name="permission_pinning_not_allowed">L\'épinglage n\'est pas autorisé</string> <string name="permission_pinning_not_allowed">L\'épinglage n\'est pas autorisé</string>
// TODO: Add proper translation. <string name="permission_starring_not_allowed">La mise en favori n\'est pas autorisée</string>
<string name="permission_starring_not_allowed">Starring is not allowed</string>
<!-- Search message --> <!-- Search message -->
<!-- TODO Add proper translation--> <string name="title_search_message">Rechercher message</string>
<string name="title_search_message">Search message</string>
<!-- Favorite/Unfavorite chat room --> <!-- Favorite/Unfavorite chat room -->
<!-- TODO Add proper translation--> <string name="title_favorite_chat">Mettre chat en favori</string>
<string name="title_favorite_chat">Favorite chat</string> <string name="title_unfavorite_chat">Retirer chat des favoris</string>
<string name="title_unfavorite_chat">Unfavorite chat</string>
<!-- Members List --> <!-- Members List -->
<string name="title_members_list">Membres</string> <string name="title_members_list">Membres</string>
<!-- Mentions --> <!-- Mentions -->
<!-- TODO Add proper translation-->
<string name="msg_mentions">Mentions</string> <string name="msg_mentions">Mentions</string>
<string name="msg_no_mention">No mention</string> <string name="msg_no_mention">Aucune mention</string>
<string name="msg_all_the_mentions_appear_here">All the mentions\nappear here</string> <string name="msg_all_the_mentions_appear_here">Toutes les mentions\napparaissent ici</string>
<!-- Pinned Messages --> <!-- Pinned Messages -->
<string name="title_pinned_messages">Messages épinglés</string> <string name="title_pinned_messages">Messages épinglés</string>
...@@ -227,32 +208,30 @@ ...@@ -227,32 +208,30 @@
<string name="no_pinned_description">Tous les messages épinglés\napparaissent ici</string> <string name="no_pinned_description">Tous les messages épinglés\napparaissent ici</string>
<!-- Favorite Messages --> <!-- Favorite Messages -->
<!-- TODO Add proper translation--> <string name="title_favorite_messages">Messages favoris</string>
<string name="title_favorite_messages">Favorite Messages</string> <string name="no_favorite_messages">Aucun message favori</string>
<string name="no_favorite_messages">No favorite messages</string> <string name="no_favorite_description">Tous les messages favoris\napparaissent ici</string>
<string name="no_favorite_description">All the favorite messages\nappear here</string>
<!-- Files --> <!-- Files -->
<!-- TODO Add proper translation--> <string name="title_files">Fichiers</string>
<string name="title_files">Files</string> <string name="title_files_total">Fichiers (%d)</string>
<string name="title_files_total">Files (%d)</string> <string name="msg_no_files">Aucun fichier</string>
<string name="msg_no_files">No files</string> <string name="msg_all_files_appear_here">Tous les fichiers apparaissent ici</string>
<string name="msg_all_files_appear_here">All the files appear here</string>
<!-- Upload Messages --> <!-- Upload Messages -->
<string name="max_file_size_exceeded">Taille du fichier (%1$d bytes) dépassé la taille de téléchargement maximale de %2$d bytes</string> <string name="max_file_size_exceeded">Taille du fichier (%1$d bytes) dépasse la taille de téléchargement maximale de %2$d bytes</string>
<!-- Socket status --> <!-- Socket status -->
<string name="status_connected">Connecté</string> <string name="status_connected">Connecté</string>
<string name="status_disconnected">taché</string> <string name="status_disconnected">connecté</string>
<string name="status_connecting">Connexion</string> <string name="status_connecting">Connexion</string>
<string name="status_authenticating">Authentification</string> <string name="status_authenticating">Authentification</string>
<string name="status_disconnecting">Déconnexion</string> <string name="status_disconnecting">Déconnexion</string>
<string name="status_waiting">Connexion en %d secondes</string> <string name="status_waiting">Connexion dans %d secondes</string>
<!--Suggestions--> <!--Suggestions-->
<string name="suggest_all_description">Notifier tout dans cette salle</string> <string name="suggest_all_description">Notifier tous les utilisateurs dans ce salon</string>
<string name="suggest_here_description">Notifier les utilisateurs actifs dans cette salle</string> <string name="suggest_here_description">Notifier les utilisateurs actifs dans ce salon</string>
<!-- Slash Commands --> <!-- Slash Commands -->
<string name="Slash_Gimme_Description">Affiche ༼ つ ◕_◕ ༽つ avant votre message</string> <string name="Slash_Gimme_Description">Affiche ༼ つ ◕_◕ ༽つ avant votre message</string>
...@@ -260,20 +239,20 @@ ...@@ -260,20 +239,20 @@
<string name="Slash_Shrug_Description">Affiche ¯\_(ツ)_/¯ après votre message</string> <string name="Slash_Shrug_Description">Affiche ¯\_(ツ)_/¯ après votre message</string>
<string name="Slash_Tableflip_Description">Affiche (╯°□°)╯︵ ┻━┻</string> <string name="Slash_Tableflip_Description">Affiche (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Affiche ┬─┬ ノ( ゜-゜ノ)</string> <string name="Slash_TableUnflip_Description">Affiche ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Créer une nouvelle salle</string> <string name="Create_A_New_Channel">Créer un nouveau salon</string>
<string name="Show_the_keyboard_shortcut_list">Afficher la liste des raccourcis clavier</string> <string name="Show_the_keyboard_shortcut_list">Afficher la liste des raccourcis clavier</string>
<string name="Invite_user_to_join_channel_all_from">Inviter tous les utilisateurs de [#salle] à rejoindre cette salle</string> <string name="Invite_user_to_join_channel_all_from">Inviter tous les utilisateurs de [#salle] à rejoindre ce salon</string>
<string name="Invite_user_to_join_channel_all_to">Inviter tous les utilisateurs de cette salle à rejoindre [#salle]</string> <string name="Invite_user_to_join_channel_all_to">Inviter tous les utilisateurs de ce salon à rejoindre [#salle]</string>
<string name="Archive">Archiver</string> <string name="Archive">Archiver</string>
<string name="Remove_someone_from_room">Retirer quelqu\'un de la salle</string> <string name="Remove_someone_from_room">Retirer quelqu\'un du salon</string>
<string name="Leave_the_current_channel">Sortir de la salle actuelle</string> <string name="Leave_the_current_channel">Sortir du salon actuel</string>
<string name="Displays_action_text">Affiche le texte d\'action</string> <string name="Displays_action_text">Affiche le texte d\'action</string>
<string name="Direct_message_someone">Message direct avec quelqu\'un</string> <string name="Direct_message_someone">Message direct avec quelqu\'un</string>
<string name="Mute_someone_in_room">Mettre en sourdine une personne dans la salle</string> <string name="Mute_someone_in_room">Mettre en sourdine une personne dans le salon</string>
<string name="Unmute_someone_in_room">Retirer la sourdine d\'une personne dans la salle</string> <string name="Unmute_someone_in_room">Retirer la sourdine d\'une personne dans le salon</string>
<string name="Invite_user_to_join_channel">Inviter un utilisateur à rejoindre cette salle</string> <string name="Invite_user_to_join_channel">Inviter un utilisateur à rejoindre ce salon</string>
<string name="Unarchive">Désarchiver</string> <string name="Unarchive">Désarchiver</string>
<string name="Join_the_given_channel">Rejoignez la salle fourni</string> <string name="Join_the_given_channel">Rejoignez la salon fourni</string>
<string name="Guggy_Command_Description">Génère un gif basé sur le texte fourni</string> <string name="Guggy_Command_Description">Génère un gif basé sur le texte fourni</string>
<string name="Slash_Topic_Description">Définir le sujet</string> <string name="Slash_Topic_Description">Définir le sujet</string>
...@@ -288,11 +267,11 @@ ...@@ -288,11 +267,11 @@
<string name="dialog_sort_by_activity">Activité</string> <string name="dialog_sort_by_activity">Activité</string>
<string name="dialog_group_by_type">Grouper par type</string> <string name="dialog_group_by_type">Grouper par type</string>
<string name="dialog_group_favourites">Grouper favoris</string> <string name="dialog_group_favourites">Grouper favoris</string>
<string name="dialog_button_done">Done</string><!-- TODO Add translation --> <string name="dialog_button_done">Ok</string>
<string name="chatroom_header">Entête</string> <string name="chatroom_header">Entête</string>
<!--ChatRooms Headers--> <!--ChatRooms Headers-->
<string name="header_channel">Salles</string> <string name="header_channel">Salons</string>
<string name="header_private_groups">Groupes privés</string> <string name="header_private_groups">Groupes privés</string>
<string name="header_direct_messages">Messages directs</string> <string name="header_direct_messages">Messages directs</string>
<string name="header_live_chats">Chats en direct</string> <string name="header_live_chats">Chats en direct</string>
...@@ -307,4 +286,4 @@ ...@@ -307,4 +286,4 @@
<string name="message_information_title">Informations sur le message</string> <string name="message_information_title">Informations sur le message</string>
<string name="msg_log_out">Déconnecter…</string> <string name="msg_log_out">Déconnecter…</string>
<string name="msg_sent_attachment">Envoyé un fichier</string> <string name="msg_sent_attachment">Envoyé un fichier</string>
</resources> </resources>
\ No newline at end of file
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
<string name="title_profile">प्रोफाइल</string> <string name="title_profile">प्रोफाइल</string>
<string name="title_members">सदस्य (%d)</string> <string name="title_members">सदस्य (%d)</string>
<string name="title_settings">सेटिंग्स</string> <string name="title_settings">सेटिंग्स</string>
<string name="title_preferences">Preferences</string> <!-- TODO Add translation --> <string name="title_preferences">प्राथमिकताएँ</string>
<string name="title_change_password">पासवर्ड बदलें</string> <string name="title_change_password">पासवर्ड बदलें</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation --> <string name="title_admin_panel">एडमिन पैनल</string>
<string name="title_password">पासवर्ड बदलें</string> <string name="title_password">पासवर्ड बदलें</string>
<string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string> <string name="title_update_profile">प्रोफ़ाइल अपडेट करें</string>
<string name="title_about">परिचय</string> <string name="title_about">परिचय</string>
...@@ -42,13 +42,13 @@ ...@@ -42,13 +42,13 @@
<string name="action_invisible">अदृश्य</string> <string name="action_invisible">अदृश्य</string>
<string name="action_save_to_gallery">गैलरी में सहेजें</string> <string name="action_save_to_gallery">गैलरी में सहेजें</string>
<string name="action_drawing">चित्रकारी</string> <string name="action_drawing">चित्रकारी</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation --> <string name="action_select_photo_from_gallery">गैलरी से फोटो का चयन करें</string>
<string name="action_take_photo">Select photo from gallery</string> <!-- TODO Add translation --> <string name="action_take_photo">फोटो खेचिये</string>
<string name="action_reset_avatar">Reset avatar</string> <!-- TODO Add translation --> <string name="action_reset_avatar">अवतार रीसेट करें</string>
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation --> <item name="item_preferences">प्राथमिकताएँ</item>
<item name="item_password">पासवर्ड बदलें</item> <item name="item_password">पासवर्ड बदलें</item>
<item name="item_password">परिचय</item> <item name="item_password">परिचय</item>
</string-array> </string-array>
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
<string name="msg_content_description_log_in_using_meteor">Meteor द्वारा लॉगिन करें</string> <string name="msg_content_description_log_in_using_meteor">Meteor द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_twitter">Twitter द्वारा लॉगिन करें</string> <string name="msg_content_description_log_in_using_twitter">Twitter द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_gitlab">Gitlab द्वारा लॉगिन करें</string> <string name="msg_content_description_log_in_using_gitlab">Gitlab द्वारा लॉगिन करें</string>
<string name="msg_content_description_log_in_using_wordpress">Login using WordPress</string> <!-- TODO Translate--> <string name="msg_content_description_log_in_using_wordpress">WordPress द्वारा लॉगिन करें</string>
<string name="msg_content_description_send_message">मेसेज भेजें</string> <string name="msg_content_description_send_message">मेसेज भेजें</string>
<string name="msg_content_description_show_attachment_options">अटैचमेंट विकल्प दिखाएं</string> <string name="msg_content_description_show_attachment_options">अटैचमेंट विकल्प दिखाएं</string>
<string name="msg_you">आप</string> <string name="msg_you">आप</string>
...@@ -147,10 +147,10 @@ ...@@ -147,10 +147,10 @@
<string name="msg_channel_created_successfully">चैनल सफलतापूर्वक बनाया गया</string> <string name="msg_channel_created_successfully">चैनल सफलतापूर्वक बनाया गया</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation --> <string name="msg_analytics_tracking">एनालिटिक्स ट्रैकिंग</string>
<string name="msg_send_analytics_tracking">Send anonymous statics to help improving this app</string> <!-- TODO Add translation --> <string name="msg_send_analytics_tracking">इस ऐप को बेहतर बनाने में मदद के लिए अज्ञात स्टेटिक्स भेजें</string>
<string name="msg_do_not_send_analytics_tracking">Do not send anonymous statics to help improving this app</string> <!-- TODO Add translation --> <string name="msg_do_not_send_analytics_tracking">इस ऐप को बेहतर बनाने में मदद के लिए अनाम स्टेटिक न भेजें</string>
<string name="msg_not_applicable_since_it_is_a_foss_version">Not applicable since it is a FOSS version</string> <!-- TODO Add translation --> <string name="msg_not_applicable_since_it_is_a_foss_version">लागू नहीं है क्योंकि यह एक FOSS संस्करण है</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string> <string name="message_room_name_changed">%2$s ने रूम का नाम बदलकर %1$s किया</string>
...@@ -190,8 +190,7 @@ ...@@ -190,8 +190,7 @@
<string name="permission_starring_not_allowed">तारांकित की अनुमति नहीं है</string> <string name="permission_starring_not_allowed">तारांकित की अनुमति नहीं है</string>
<!-- Search message --> <!-- Search message -->
<!-- TODO Add proper translation--> <string name="title_search_message">संदेश खोजें</string>
<string name="title_search_message">Search message</string>
<!-- Favorite/Unfavorite chat room --> <!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">पसंदीदा चैट</string> <string name="title_favorite_chat">पसंदीदा चैट</string>
...@@ -270,7 +269,7 @@ ...@@ -270,7 +269,7 @@
<string name="dialog_sort_by_activity">गतिविधि</string> <string name="dialog_sort_by_activity">गतिविधि</string>
<string name="dialog_group_by_type">प्रकार के आधार पर समूह</string> <string name="dialog_group_by_type">प्रकार के आधार पर समूह</string>
<string name="dialog_group_favourites">पसंदीदा समूह</string> <string name="dialog_group_favourites">पसंदीदा समूह</string>
<string name="dialog_button_done">Done</string><!-- TODO Add translation --> <string name="dialog_button_done">पूर्ण</string>
<string name="chatroom_header">हैडर</string> <string name="chatroom_header">हैडर</string>
<!--ChatRooms Headers--> <!--ChatRooms Headers-->
......
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<string name="app_name" translatable="false">Rocket.Chat</string>
<!-- Titles -->
<string name="title_sign_in_your_server">サーバーに接続</string>
<string name="title_log_in">ログイン</string>
<string name="title_register_username">ユーザー名を登録する</string>
<string name="title_reset_password">パスワードリセット</string>
<string name="title_sign_up">サインアップ</string>
<string name="title_authentication">認証</string>
<string name="title_legal_terms">Legal Terms</string>
<string name="title_chats">チャット</string>
<string name="title_profile">プロフィール</string>
<string name="title_members">メンバー (%d)</string>
<string name="title_settings">設定</string>
<string name="title_preferences">Preferences</string> <!-- TODO Add translation -->
<string name="title_change_password">Change Password</string> <!-- TODO Add translation -->
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation -->
<string name="title_password">パスワードの変更</string>
<string name="title_update_profile">プロフィールの更新</string>
<string name="title_about">About</string>
<string name="title_create_channel">新しいチャネルを作成します</string>
<!-- Actions -->
<string name="action_connect">接続</string>
<string name="action_use_this_username">このユーザー名を使用する</string>
<string name="action_login_or_sign_up">ログインまたはアカウントを作成するにはこのボタンを押してください</string>
<string name="action_terms_of_service">サービス利用規約</string>
<string name="action_privacy_policy">プライバシーポリシー</string>
<string name="action_search">検索</string>
<string name="action_update">更新</string>
<string name="action_settings">設定</string>
<string name="action_create_channel">チャンネル作成</string>
<string name="action_create">作ります</string>
<string name="action_logout">ログアウト</string>
<string name="action_files">ファイル</string>
<string name="action_confirm_password">変更したパスワードの確認</string>
<string name="action_join_chat">チャットに参加</string>
<string name="action_add_account">サーバーの追加</string>
<string name="action_online">オンライン</string>
<string name="action_away">離席中</string>
<string name="action_busy">取り込み中</string>
<string name="action_invisible">状態を隠す</string>
<string name="action_drawing">絵を描く</string>
<string name="action_save_to_gallery">ギャラリーに保存</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_photo">Take photo</string> <!-- TODO Add translation -->
<string name="action_reset_avatar">Reset avatar</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="item_password">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
<string name="msg_generic_error">エラーが発生しました。もう一度お試しください。</string>
<string name="msg_no_data_to_display">表示するデータがありません</string>
<string name="msg_profile_update_successfully">プロフィールの更新に成功しました</string>
<string name="msg_username">ユーザー名</string>
<string name="msg_username_or_email">メールアドレスまたはユーザー名</string>
<string name="msg_password">パスワード</string>
<string name="msg_name">名前</string>
<string name="msg_email">メール</string>
<string name="msg_avatar_url">アバター URL</string>
<string name="msg_or_continue_using_social_accounts">またはソーシャルアカウントを使用する</string>
<string name="msg_new_user">新規ユーザー? %1$s</string>
<string name="msg_forgot_password">パスワードをお忘れですか? %1$s</string>
<string name="msg_reset">リセット</string>
<string name="msg_check_your_email_to_reset_your_password">メールが送信されました! パスワードを変更するためには、受信ボックスを確認してください。</string>
<string name="msg_invalid_email">有効なメールアドレスを入力してください</string>
<string name="msg_new_user_agreement">サインアップすることで、\n%1$s と %2$s に同意したとみなします。</string>
<string name="msg_2fa_code">2FA コード</string>
<string name="msg_today">Today</string> <!-- TODO Add translation -->
<string name="msg_yesterday">昨日</string>
<string name="msg_message">メッセージ</string>
<string name="msg_this_room_is_read_only">この部屋は読み取り専用です</string>
<string name="msg_invalid_2fa_code">無効な 2FA コード</string>
<string name="msg_invalid_file">無効なファイル</string>
<string name="msg_invalid_server_url">無効なサーバー URL</string>
<string name="msg_content_description_log_in_using_facebook">Login using Facebook</string>
<string name="msg_content_description_log_in_using_github">Login using Github</string>
<string name="msg_content_description_log_in_using_google">Login using Google</string>
<string name="msg_content_description_log_in_using_linkedin">Login using Linkedin</string>
<string name="msg_content_description_log_in_using_meteor">Login using Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login using Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login using Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Login using WordPress</string>
<string name="msg_content_description_send_message">メッセージを送信</string> <!-- TODO Add translation -->
<string name="msg_content_description_show_attachment_options">添付ファイルオプションを表示する</string>
<string name="msg_you">あなた</string>
<string name="msg_unknown">不明</string>
<string name="msg_email_address">電子メールアドレス</string>
<string name="msg_utc_offset">UTC offset</string>
<string name="msg_new_password">新しいパスワード</string>
<string name="msg_confirm_password">新しいパスワードの確認</string>
<string name="msg_channel_name">チャンネル名</string>
<string name="msg_search">検索</string>
<string name="msg_unread_messages">未読メッセージ</string>
<string name="msg_preview_video">ビデオ</string>
<string name="msg_preview_audio">音声</string>
<string name="msg_preview_photo">写真</string>
<string name="msg_preview_file">ファイル</string>
<string name="msg_no_messages_yet">メッセージはまだありません</string>
<string name="msg_ok">OK</string>
<string name="msg_update_app_version_in_order_to_continue">古いバージョンのサーバーを使用しています。続行するには、管理者にサーバーのバージョンを更新するよう連絡してください。</string>
<string name="msg_ver_not_recommended">
ご使用のサーバーのバージョンは推薦バージョン %1$s よりも古いものです。\nログインは可能ですが、予期しない動作が発生する可能性があります。</string>
<string name="msg_ver_not_minimum">
サーバーのバージョンが最低限必要なバージョン %1$s よりも古いものです。\nサーバーのバージョンを更新してからログインしてください!
</string>
<string name="msg_no_chat_title">メッセージがまだありません</string>
<string name="msg_no_chat_description">ここから会話を始めましょう</string>
<string name="msg_proceed">PROCEED</string>
<string name="msg_cancel">キャンセル</string>
<string name="msg_warning">警告</string>
<string name="msg_http_insecure">HTTPを使用している場合、安全ではないサーバーに接続します。安全なサーバーに接続することをお勧めします。</string>
<string name="msg_error_checking_server_version">サーバーのバージョンを確認中にエラーが発生しました。もう一度お試しください。</string>
<string name="msg_invalid_server_protocol">選択したプロトコルはこのサーバーでは使用できません。HTTPSを選択してください。</string>
<string name="msg_image_saved_successfully">画像をギャラリーに保存しました</string>
<string name="msg_image_saved_failed">画像を保存できませんでした</string>
<string name="msg_edited">(編集済)</string>
<string name="msg_and">\u0020and\u0020</string>
<string name="msg_is_typing">\u0020is 入力中…</string>
<string name="msg_are_typing">\u0020are 入力中…</string>
<string name="msg_several_users_are_typing">複数のユーザーが入力中…</string>
<string name="msg_no_search_found">結果が見つかりません</string>
<string name="msg_log_out">ログアウト…</string>
<string name="msg_upload_file">ファイルをアップロード</string>
<string name="msg_file_description">ファイルの説明</string>
<string name="msg_send">送信</string>
<string name="msg_sent_attachment">添付ファイルを送信しました</string>
<!-- Create channel messages -->
<string name="msg_private_channel">プライベート</string>
<string name="msg_public_channel">パブリック</string>
<string name="msg_private_channel_description">招待されたユーザーだけがアクセスできます</string>
<string name="msg_public_channel_description">誰もがこのチャンネルにアクセスできます</string>
<string name="msg_ready_only_channel">読み取り専用チャネル</string>
<string name="msg_ready_only_channel_description">許可されたユーザーのみ新しいメッセージを書けます</string>
<string name="msg_invite_members">メンバーを招待する</string>
<string name="msg_member_already_added">あなたは既にこのユーザーを選択しています</string>
<string name="msg_member_not_found">メンバーが見つかりません</string>
<string name="msg_channel_created_successfully">チャンネルが正常に作成されました</string>
<string name="msg_message_copied">メッセージをコピー</string>
<string name="msg_delete_message">メッセージを削除</string>
<string name="msg_delete_description">このメッセージを削除してもよろしいですか?</string>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation -->
<string name="msg_send_analytics_tracking">Send anonymous statics to help improving this app</string> <!-- TODO Add translation -->
<string name="msg_do_not_send_analytics_tracking">Do not send anonymous statics to help improving this app</string> <!-- TODO Add translation -->
<string name="msg_not_applicable_since_it_is_a_foss_version">Not applicable since it is a FOSS version</string> <!-- TODO Add translation -->
<!-- System messages -->
<string name="message_room_name_changed">ルーム名を %1$s から %2$s へ変更しました。</string>
<string name="message_user_added_by">%1$s がユーザー %2$s を追加しました。</string>
<string name="message_user_removed_by">%1$s がユーザー %2$s を削除しました。</string>
<string name="message_user_left">チャンネルを退去しました。</string>
<string name="message_user_joined_channel">チャンネルへ参加しました。</string>
<string name="message_welcome">ようこそ %s</string>
<string name="message_removed">メッセージを削除しました。</string>
<string name="message_pinned">メッセージをピン留めしました:</string>
<string name="message_muted">%2$sでミュートされたユーザー %1$s</string>
<string name="message_unmuted">ユーザー %1$s は %2$s によってミュートされていません</string>
<string name="message_role_add">%1$s は %3$s によって %2$s に設定されました</string>
<string name="message_role_removed">%1$s は %3$s で、もう %2$s ではありません</string>
<string name="message_credentials_saved_successfully">資格情報を正常に保存しました</string>
<!-- Message actions -->
<string name="action_msg_reply">返信</string>
<string name="action_msg_info">メッセージ情報</string>
<string name="action_msg_edit">編集</string>
<string name="action_msg_copy">コピー</string>
<string name="action_msg_quote">引用</string>
<string name="action_msg_delete">削除</string>
<string name="action_msg_pin">ピン留めする</string>
<string name="action_msg_unpin">ピン留を外す</string>
<string name="action_msg_star">スターをつける</string>
<string name="action_msg_unstar">スターを外す</string>
<string name="action_msg_share">Share</string>
<string name="action_title_editing">メッセージの編集</string>
<string name="action_msg_add_reaction">リアクションする</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">編集は許可されていません</string>
<string name="permission_deleting_not_allowed">削除は許可されていません</string>
<string name="permission_pinning_not_allowed">ピン留めは許可されていません</string>
<string name="permission_starring_not_allowed">閲覧は許可されていません</string>
<!-- Search message -->
<string name="title_search_message">メッセージを検索</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">お気に入り</string>
<string name="title_unfavorite_chat">お気に入り解除</string>
<!-- Members List -->
<string name="title_members_list">メンバー</string>
<!-- Mentions -->
<string name="msg_mentions">メンション</string>
<string name="msg_no_mention">メンションされたメッセージはありません</string>
<string name="msg_all_the_mentions_appear_here">全てのメンションされたメッセージが\ここに表示されます</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">ピン留めされたメッセージ</string>
<string name="no_pinned_messages">ピン留めされたメッセージはありません</string>
<string name="no_pinned_description">ピン留めされたすべてのメッセージが\ここに表示されます</string>
<!-- Favorite Messages -->
<string name="title_favorite_messages">お気に入りのメッセージ</string>
<string name="no_favorite_messages">お気に入りのメッセージはありません</string>
<string name="no_favorite_description">全てのお気に入りメッセージが\ここに表示されます</string>
<!-- Files -->
<string name="title_files">ファイル</string>
<string name="title_files_total">ファイル (%d)</string>
<string name="msg_no_files">ファイルがありません</string>
<string name="msg_all_files_appear_here">全てのファイルがここに表示されます</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">ファイルサイズ %1$d バイトが最大アップロードサイズ %2$d バイトとを超えました</string>
<!-- Socket status -->
<string name="status_connected">接続済み</string>
<string name="status_disconnected">接続切断</string>
<string name="status_connecting">接続中</string>
<string name="status_authenticating">認証中</string>
<string name="status_disconnecting">切断</string>
<string name="status_waiting">%d 秒間接続中</string>
<!--Suggestions-->
<string name="suggest_all_description">このルームの全員に通知</string>
<string name="suggest_here_description">この部屋のアクティブユーザーに通知する</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">あなたのメッセージの前に表示(つ◕_◕)つ</string>
<string name="Slash_LennyFace_Description">あなたのメッセージの後に表示(͡°͜ʖ͡°)</string>
<string name="Slash_Shrug_Description">あなたのメッセージの後に表示¯\\ _(ツ)_ /¯</string>
<string name="Slash_Tableflip_Description">表示(╯°□°)╯(┻━┻</string>
<string name="Slash_TableUnflip_Description">表示┬─┬ノ(゜ - ゜ノ)</string>
<string name="Create_A_New_Channel">新しいチャネルを作成します</string>
<string name="Show_the_keyboard_shortcut_list">キーボードショートカットリストを表示する</string>
<string name="Invite_user_to_join_channel_all_from">[#channel]のすべてのユーザーをこのチャンネルに招待する</string>
<string name="Invite_user_to_join_channel_all_to">このチャンネルのすべてのユーザーを[#channel]に招待する</string>
<string name="Archive">アーカイブ</string>
<string name="Remove_someone_from_room">誰かをルームから削除</string>
<string name="Leave_the_current_channel">現在のチャンネルを残す</string>
<string name="Displays_action_text">操作テキストを表示</string>
<string name="Direct_message_someone">誰かへダイレクトメッセージ</string>
<string name="Mute_someone_in_room">誰かをルームでミュートにする</string>
<string name="Unmute_someone_in_room">誰かをルームでミュートを解除されました</string>
<string name="Invite_user_to_join_channel">ユーザーをこのチャンネルへ招待します</string>
<string name="Unarchive">アーカイブを解除</string>
<string name="Join_the_given_channel">チャンネルへ参加する</string>
<string name="Guggy_Command_Description">提供されたテキストに基づいてgifを生成する</string>
<string name="Slash_Topic_Description">設定トピック</string>
<!-- Emoji message-->
<string name="msg_no_recent_emoji">最近の絵文字はありません</string>
<string name="alert_title_default_skin_tone">デフォルトスキントークン</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">ソート</string>
<string name="dialog_sort_title">ソート</string>
<string name="dialog_sort_by_alphabet">アルファベット順</string>
<string name="dialog_sort_by_activity">アクティビティで並べ替える</string>
<string name="dialog_group_by_type">グループ別</string>
<string name="dialog_group_favourites">お気に入りのグループ</string>
<string name="chatroom_header">ヘッダ</string>
<string name="dialog_button_done">完了</string>
<!--ChatRooms Headers-->
<string name="header_channel">チャンネル</string>
<string name="header_private_groups">プライベートグループ</string>
<string name="header_direct_messages">ダイレクトメッセージ</string>
<string name="header_live_chats">ライブチャット</string>
<string name="header_unknown">不明</string>
<!--Notifications-->
<string name="share_label">Edit shared message</string> <!-- TODO Add translation -->
<string name="notif_action_reply_hint">返信</string>
<string name="notif_error_sending">送信に失敗しました。もう一度お試しください。</string>
<string name="notif_success_sending">メッセージは %1$s に送信されました!</string>
<string name="read_by">読者</string>
<string name="message_information_title">メッセージ情報</string>
</resources>
\ No newline at end of file
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
<string name="title_profile">Профиль</string> <string name="title_profile">Профиль</string>
<string name="title_members">Пользователи (%d)</string> <string name="title_members">Пользователи (%d)</string>
<string name="title_settings">Настройки</string> <string name="title_settings">Настройки</string>
<string name="title_preferences">Preferences</string> <!-- TODO Add translation --> <string name="title_preferences">Персональные</string>
<string name="title_change_password">Изменить пароль</string> <string name="title_change_password">Изменить пароль</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation --> <string name="title_admin_panel">Панель админа</string>
<string name="title_password">Изменить пароль</string> <string name="title_password">Изменить пароль</string>
<string name="title_update_profile">Обновить профиль</string> <string name="title_update_profile">Обновить профиль</string>
<string name="title_about">О программе</string> <string name="title_about">О программе</string>
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
<!-- Settings List --> <!-- Settings List -->
<string-array name="settings_actions"> <string-array name="settings_actions">
<item name="item_preferences">Preferences</item> <!-- TODO Add translation --> <item name="item_preferences">Персональные</item>
<item name="item_password">Изменить пароль</item> <item name="item_password">Изменить пароль</item>
<item name="item_password">О программе</item> <item name="item_password">О программе</item>
</string-array> </string-array>
...@@ -144,10 +144,10 @@ ...@@ -144,10 +144,10 @@
<string name="msg_channel_created_successfully">Канал создан успешно</string> <string name="msg_channel_created_successfully">Канал создан успешно</string>
<!-- Preferences messages --> <!-- Preferences messages -->
<string name="msg_analytics_tracking">Analytics tracking</string> <!-- TODO Add translation --> <string name="msg_analytics_tracking">Отслеживание Analytics</string>
<string name="msg_send_analytics_tracking">Send anonymous statics to help improving this app</string> <!-- TODO Add translation --> <string name="msg_send_analytics_tracking">Отправлять анонимную статистику для улучшения приложения.</string>
<string name="msg_do_not_send_analytics_tracking">Do not send anonymous statics to help improving this app</string> <!-- TODO Add translation --> <string name="msg_do_not_send_analytics_tracking">Не отправлять анонимную статистику для улучшения приложения</string>
<string name="msg_not_applicable_since_it_is_a_foss_version">Not applicable since it is a FOSS version</string> <!-- TODO Add translation --> <string name="msg_not_applicable_since_it_is_a_foss_version">Не применимо, так как это FOSS версия</string>
<!-- System messages --> <!-- System messages -->
<string name="message_room_name_changed">%2$s изменил название канала на %1$s</string> <string name="message_room_name_changed">%2$s изменил название канала на %1$s</string>
......
<resources>
<!-- Titles -->
<string name="title_sign_in_your_server">Ваш сервер</string>
<string name="title_log_in">Увійти</string>
<string name="title_register_username">Зареєструвати ім\'я</string>
<string name="title_reset_password">Відновлення паролю</string>
<string name="title_sign_up">Зареєструватися</string>
<string name="title_authentication">Аутентифікація</string>
<string name="title_legal_terms">Юридичні умови</string>
<string name="title_chats">Чати</string>
<string name="title_profile">Профіль</string>
<string name="title_members">Користувачі (%d)</string>
<string name="title_settings">Налаштування</string>
<string name="title_preferences">Персональні</string>
<string name="title_change_password">Змінити пароль</string>
<string name="title_admin_panel">Панель адміністратора</string>
<string name="title_password">Змінити пароль</string>
<string name="title_update_profile">Оновити профіль</string>
<string name="title_about">"Про програму"</string>
<string name="title_create_channel">Створити новий канал</string>
<!-- Actions -->
<string name="action_connect">Підключитися</string>
<string name="action_use_this_username">Використати це ім\'я</string>
<string name="action_login_or_sign_up">Натисніть цю кнопку, щоб увійти до системи або створити аккаунт</string>
<string name="action_terms_of_service">Умови використання</string>
<string name="action_privacy_policy">Політика конфіденційності</string>
<string name="action_search">Пошук</string>
<string name="action_update">Оновити</string>
<string name="action_settings">Налаштування</string>
<string name="action_create_channel">Створити канал</string>
<string name="action_create">Створити</string>
<string name="action_logout">Вийти</string>
<string name="action_files">Файли</string>
<string name="action_confirm_password">Підтвердження зміни пароля</string>
<string name="action_join_chat">Приєднатися до чату</string>
<string name="action_add_account">Додати аккаунт</string>
<string name="action_online">Онлайн</string>
<string name="action_away">Відійшов</string>
<string name="action_busy">Зайнятий</string>
<string name="action_invisible">Невидимий</string>
<string name="action_drawing">Малюнок</string>
<string name="action_save_to_gallery">Зберегти до галереї</string>
<string name="action_select_photo_from_gallery">Вибрати з галереї</string>
<string name="action_take_photo">Зробити знімок</string>
<string name="action_reset_avatar">Відновити аватар</string>
<!-- 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="item_password">About</item> <!-- TODO Add translation -->
</string-array>
<!-- Regular information messages -->
<string name="msg_generic_error">Сталася помилка, спробуйте ще раз.</string>
<string name="msg_no_data_to_display">Немає даних для відображення</string>
<string name="msg_profile_update_successfully">"Профіль оновлено успішно "</string>
<string name="msg_username">Ім\'я користувача</string>
<string name="msg_username_or_email">Ім\'я користувача або e-mail</string>
<string name="msg_password">пароль</string>
<string name="msg_name">ім\'я</string>
<string name="msg_email">e-mail</string>
<string name="msg_avatar_url">URL аватара</string>
<string name="msg_or_continue_using_social_accounts">Або продовжити, за допомогою акаунта у соціальних мережах</string>
<string name="msg_new_user">Новий користувач? %1$s</string>
<string name="msg_forgot_password">Забули пароль? %1$s</string>
<string name="msg_reset">Скинути</string>
<string name="msg_check_your_email_to_reset_your_password">Лист було відправлено! Перевірте свою поштову скриньку, щоб скинути пароль.</string>
<string name="msg_invalid_email">Введіть діючий e-mail</string>
<string name="msg_new_user_agreement">Продовжуючи, ви погоджуєтеся з %1$s і %2$s</string>
<string name="msg_2fa_code">Код 2FA</string>
<string name="msg_yesterday">Вчора</string>
<string name="msg_today">Сьогодні</string>
<string name="msg_message">"Повідомлення "</string>
<string name="msg_this_room_is_read_only">Канал тільки для читання</string>
<string name="msg_invalid_2fa_code">Неправильний код 2FA</string>
<string name="msg_invalid_file">Неправильний файл</string>
<string name="msg_invalid_server_url">Неправильний URL-адреса сервера.</string>
<string name="msg_content_description_log_in_using_facebook">Увійти за допомогою Facebook</string>
<string name="msg_content_description_log_in_using_github">Увійти за допомогою Github</string>
<string name="msg_content_description_log_in_using_google">Увійти за допомогою Google</string>
<string name="msg_content_description_log_in_using_linkedin">Увійти за допомогою Linkedin</string>
<string name="msg_content_description_log_in_using_meteor">Увійти за допомогою Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Увійти за допомогою Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Увійти за допомогою Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Увійти за допомогою Wordpress</string>
<string name="msg_content_description_send_message">Надіслати повідомлення</string>
<string name="msg_content_description_show_attachment_options">Показати параметри долучення</string>
<string name="msg_you">Ви</string>
<string name="msg_unknown">Невідомий</string>
<string name="msg_email_address">E-mail адреса</string>
<string name="msg_utc_offset">UTC зсув</string>
<string name="msg_new_password">Введіть новий пароль</string>
<string name="msg_confirm_password">Підтвердіть новий пароль</string>
<string name="msg_unread_messages">Непрочитані повідомлення</string>
<string name="msg_preview_video">Відео</string>
<string name="msg_preview_audio">Аудіо</string>
<string name="msg_preview_photo">Фото</string>
<string name="msg_preview_file">Файл</string>
<string name="msg_no_messages_yet">"Немає повідомлень "</string>
<string name="msg_build">Збірка %1$d - %2$s - %3$s</string>
<string name="msg_ok">ОК</string>
<string name="msg_update_app_version_in_order_to_continue">Версія сервера застаріла. Будь ласка, зв\'яжіться з адміністратором, щоб оновити сервер.</string>
<string name="msg_ver_not_recommended">Здається, версія сервера менше ніж рекомендована %1$s. \nМожна увійти, але можуть виникнути ті чи інші проблеми під час роботи.</string>
<string name="msg_ver_not_minimum">Здається, версія сервера менше ніж мінімально необхідна %1$s.\nДля роботи потрібно оновити сервер!</string>
<string name="msg_proceed">ПРОДОВЖИТИ</string>
<string name="msg_cancel">ВІДМІНА</string>
<string name="msg_warning">ПОПЕРЕДЖЕННЯ</string>
<string name="msg_http_insecure">При використанні HTTP ви підключаєтеся до потенційно небезпечного сервера. Ми не радимо вам це робити.</string>
<string name="msg_error_checking_server_version">Під час перевірки версії вашого сервера сталася помилка, спробуйте ще раз.</string>
<string name="msg_invalid_server_protocol">Використання обраного протоколу не дозволено на цьому сервері, спробуйте використати HTTPS</string>
<string name="msg_image_saved_successfully">Зображення збережено до галереї</string>
<string name="msg_image_saved_failed">Не вдалося зберегти зображення</string>
<string name="msg_no_chat_title">Повідомлення відсутні</string>
<string name="msg_no_chat_description">Почніть спілкуватися, щоб побачити тут повідомлення.</string>
<string name="msg_edited">(змінено)</string>
<string name="msg_and">\\u0020і\\u0020</string>
<string name="msg_is_typing">\\u0020друкує…</string>
<string name="msg_are_typing">\\u0020друкують…</string>
<string name="msg_several_users_are_typing">Декілька користувачів друкують…</string>
<string name="msg_no_search_found">Результатів не знайдено</string>
<string name="msg_channel_name">Назва каналу</string>
<string name="msg_search">Пошук</string>
<string name="msg_message_copied">Повідомлення було скопійовано</string>
<string name="msg_upload_file">Завантажити файл</string>
<string name="msg_file_description">Опис файлу</string>
<string name="msg_send">Надіслати</string>
<string name="msg_delete_message">Видалити повідомлення</string>
<string name="msg_delete_description">Ви впевнені, що хочете видалити це повідомлення?</string>
<!-- Create channel messages -->
<string name="msg_private_channel">Приватний</string>
<string name="msg_public_channel">Публічний</string>
<string name="msg_private_channel_description">Тільки ви та запрошені користувачі можуть отримати доступ до цього каналу</string>
<string name="msg_public_channel_description">Кожен може отримати доступ до цього каналу</string>
<string name="msg_ready_only_channel">Канал тільки для читання</string>
<string name="msg_ready_only_channel_description">Тільки адміністратор може писати нові повідомлення</string>
<string name="msg_invite_members">Запросити користувачів до каналу</string>
<string name="msg_member_already_added">Ви вже обрали цього користувача</string>
<string name="msg_member_not_found">"Користувача не знайдено "</string>
<string name="msg_channel_created_successfully">Канал був створений успішно</string>
<!-- Preferences messages -->
<string name="msg_analytics_tracking">Збір аналітики</string>
<string name="msg_send_analytics_tracking">Надсилати анонімну статистику для покращення роботи програми</string>
<string name="msg_do_not_send_analytics_tracking">Не надсилати анонімну статистику для покращення роботи програми</string>
<string name="msg_not_applicable_since_it_is_a_foss_version">Недоступно у FOSS версії</string>
<!-- System messages -->
<string name="message_room_name_changed">%2$s змінив назву каналу на %1$s</string>
<string name="message_user_added_by">Користувач %2$s додав користувача %1$s</string>
<string name="message_user_removed_by">Користувач %2$s видалив користувача %1$s</string>
<string name="message_user_left">Залишив канал.</string>
<string name="message_user_joined_channel">Приєднався до каналу.</string>
<string name="message_welcome">Привіт, %s</string>
<string name="message_removed">"Повідомлення видалено "</string>
<string name="message_pinned">Зафіксоване повідомлення:</string>
<string name="message_muted">Користувач %2$s позбавив %1$s дару мови</string>
<string name="message_unmuted">Користувачу і%1$s повернули дар мови за рішенням %2$s</string>
<string name="message_role_add">%1$s був призначений як %2$s користувачем %3$s</string>
<string name="message_role_removed">%1$s більше не %2$s за рішенням %3$s</string>
<string name="message_credentials_saved_successfully">Облікові дані було успішно збережено</string>
<!-- Message actions -->
<string name="action_msg_reply">Відповісти</string>
<string name="action_msg_info">Інформація про прочитання</string>
<string name="action_msg_edit">Відредагувати</string>
<string name="action_msg_copy">Скопіювати</string>
<string name="action_msg_quote">Цитувати</string>
<string name="action_msg_delete">Видалити</string>
<string name="action_msg_pin">Зафіксувати повідомлення</string>
<string name="action_msg_unpin">Відмінити фіксацію повідомлення</string>
<string name="action_msg_star">До обраного</string>
<string name="action_msg_unstar">Видалити з обраного</string>
<string name="action_msg_share">Поділитися</string>
<string name="action_title_editing">Редагування повідомлення</string>
<string name="action_msg_add_reaction">Відреагувати</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">Редагування заборонено</string>
<string name="permission_deleting_not_allowed">Видалення заборонено</string>
<string name="permission_pinning_not_allowed">Неможливо зафіксувати</string>
<string name="permission_starring_not_allowed">Неможливо додати до обраного</string>
<!-- Search message -->
<string name="title_search_message">Пошук повідомлення</string>
<!-- Favorite/Unfavorite chat room -->
<string name="title_favorite_chat">Додати до обраного</string>
<string name="title_unfavorite_chat">Видалити з обраного</string>
<!-- Members List -->
<string name="title_members_list">Користувачі</string>
<!-- Mentions -->
<string name="msg_mentions">Згадування</string>
<string name="msg_no_mention">Згадування відсутні</string>
<string name="msg_all_the_mentions_appear_here">Згадування відображаються тут</string>
<!-- Pinned Messages -->
<string name="title_pinned_messages">Зафіксовані повідомлення</string>
<string name="no_pinned_messages">Немає зафіксованих повідомлень</string>
<string name="no_pinned_description">Зафіксовані повідомлення відображаються тут</string>
<!-- Favorite Messages -->
<string name="title_favorite_messages">Обрані повідомлення</string>
<string name="no_favorite_messages">Немає обраних повідомлень</string>
<string name="no_favorite_description">Обрані повідомлення відображаються тут</string>
<!-- Files -->
<string name="title_files">Файли</string>
<string name="title_files_total">Файли (%d)</string>
<string name="msg_no_files">Файли відсутні</string>
<string name="msg_all_files_appear_here">Усі файли відображаються тут</string>
<!-- Upload Messages -->
<string name="max_file_size_exceeded">Розмір файлу %1$d перевищив максимально допустимий %2$d байт</string>
<!-- Socket status -->
<string name="status_connected">Підключено</string>
<string name="status_disconnected">Відключено</string>
<string name="status_connecting">Підключаємося…</string>
<string name="status_authenticating">Аутентифікація</string>
<string name="status_disconnecting">Відключаємося…</string>
<string name="status_waiting">З\'єднання через %d секунд</string>
<!--Suggestions-->
<string name="suggest_all_description">Повідомити всіх у цьому чаті</string>
<string name="suggest_here_description">Повідомити всіх активних користувачів у цьому чаті</string>
<!-- Slash Commands -->
<string name="Slash_Gimme_Description">Показує ༼ つ ◕_◕ ༽つ перед вашим повідомленням</string>
<string name="Slash_LennyFace_Description">Показує ( ͡° ͜ʖ ͡°) після Вашого повідомлення</string>
<string name="Slash_Shrug_Description">Показує ¯_(ツ)_/¯ після Вашого повідомлення</string>
<string name="Slash_Tableflip_Description">Показує (╯°□°)╯︵ ┻━┻</string>
<string name="Slash_TableUnflip_Description">Показує ┬─┬ ノ( ゜-゜ノ)</string>
<string name="Create_A_New_Channel">Створити новий канал</string>
<string name="Show_the_keyboard_shortcut_list">Показати список гарячих клавіш</string>
<string name="Invite_user_to_join_channel_all_from">Запросити всіх користувачів з [#channel] приєднатися до цього каналу</string>
<string name="Invite_user_to_join_channel_all_to">Запросити всіх користувачів приєднатися до [#channel]</string>
<string name="Archive">Архівувати</string>
<string name="Remove_someone_from_room">Видалити когось з каналу</string>
<string name="Leave_the_current_channel">Покинути цей канал</string>
<string name="Direct_message_someone">Особисте повідомлення</string>
<string name="Displays_action_text">Відображає текст дії</string>
<string name="Mute_someone_in_room">Позбавити користувача дару мови на каналі</string>
<string name="Unmute_someone_in_room">Повернути користувачу дар мови на каналі</string>
<string name="Invite_user_to_join_channel">Запросити користувача на канал</string>
<string name="Unarchive">Розархівувати</string>
<string name="Join_the_given_channel">Приєднатися до цього каналу</string>
<string name="Guggy_Command_Description">Створює gif використовуючи наданий текст</string>
<string name="Slash_Topic_Description">Встановити тему</string>
<!-- Emoji message-->
<string name="msg_no_recent_emoji">Пусто</string>
<string name="alert_title_default_skin_tone">Тон шкіри за замовчуванням</string>
<!-- Sorting and grouping-->
<string name="menu_chatroom_sort">Сортувати</string>
<string name="dialog_sort_title">Сортувати за</string>
<string name="dialog_sort_by_alphabet">За алфавітом</string>
<string name="dialog_sort_by_activity">За активністю</string>
<string name="dialog_group_by_type">Групувати за типом</string>
<string name="dialog_group_favourites">Групувати обране</string>
<string name="dialog_button_done">Ок</string>
<string name="chatroom_header">Заголовок</string>
<!--ChatRooms Headers-->
<string name="header_channel">Канали</string>
<string name="header_private_groups">Приватні канали</string>
<string name="header_direct_messages">Особисті повідомлення</string>
<string name="header_live_chats">Живі чати</string>
<string name="header_unknown">Невідомі</string>
<!--Notifications-->
<string name="share_label">Редагувати повідомлення</string>
<string name="notif_action_reply_hint">ВІДПОВІСТИ</string>
<string name="notif_error_sending">Не вдалося відповісти. Будь ласка, спробуйте ще раз.</string>
<string name="notif_success_sending">Повідомлення надіслано %1$s!</string>
<string name="read_by">Прочитано</string>
<string name="message_information_title">Інформація про прочитання</string>
<string name="msg_log_out">Виходимо…</string>
<string name="msg_sent_attachment">Долучення відправлено</string>
</resources>
...@@ -41,10 +41,6 @@ ...@@ -41,10 +41,6 @@
<dimen name="padding_mention">4dp</dimen> <dimen name="padding_mention">4dp</dimen>
<dimen name="radius_mention">6dp</dimen> <dimen name="radius_mention">6dp</dimen>
<!-- Autocomplete Popup -->
<dimen name="popup_max_height">150dp</dimen>
<dimen name="suggestions_box_max_height">250dp</dimen>
<dimen name="viewer_toolbar_padding">16dp</dimen> <dimen name="viewer_toolbar_padding">16dp</dimen>
<dimen name="viewer_toolbar_title">16sp</dimen> <dimen name="viewer_toolbar_title">16sp</dimen>
......
<!--
REMARK:
When adding a new string in this file do not forget to add a proper translation for the
corresponding supported language - if applicable.
Please, follow the rules as shown here (by adding a new string in the correct section).
Coding style:
https://github.com/RocketChat/java-code-styles/blob/master/CODING_STYLE.md#strings
-->
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" translatable="false">Rocket.Chat</string> <string name="app_name" translatable="false">Rocket.Chat</string>
......
...@@ -2,14 +2,6 @@ ...@@ -2,14 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android"> package="chat.rocket.android">
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<application <application
android:name=".app.RocketChatApplication" android:name=".app.RocketChatApplication"
android:allowBackup="true" android:allowBackup="true"
...@@ -20,25 +12,6 @@ ...@@ -20,25 +12,6 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"> android:supportsRtl="true">
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
<service
android:name=".push.FirebaseTokenService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<service <service
android:name=".push.FirebaseMessagingService" android:name=".push.FirebaseMessagingService"
android:enabled="true" android:enabled="true"
......
package chat.rocket.android.dagger.module package chat.rocket.android.dagger.module
import androidx.work.Worker
import chat.rocket.android.chatroom.di.MessageServiceProvider import chat.rocket.android.chatroom.di.MessageServiceProvider
import chat.rocket.android.chatroom.service.MessageService import chat.rocket.android.chatroom.service.MessageService
import chat.rocket.android.dagger.qualifier.WorkerKey
import chat.rocket.android.push.FirebaseMessagingService import chat.rocket.android.push.FirebaseMessagingService
import chat.rocket.android.push.FirebaseTokenService
import chat.rocket.android.push.di.FirebaseMessagingServiceProvider import chat.rocket.android.push.di.FirebaseMessagingServiceProvider
import chat.rocket.android.push.di.FirebaseTokenServiceProvider import chat.rocket.android.push.di.TokenRegistrationSubComponent
import chat.rocket.android.push.worker.TokenRegistrationWorker
import dagger.Binds
import dagger.Module import dagger.Module
import dagger.android.AndroidInjector
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoMap
@Module abstract class ServiceBuilder { @Module(subcomponents = [TokenRegistrationSubComponent::class])
abstract class ServiceBuilder {
@ContributesAndroidInjector(modules = [FirebaseTokenServiceProvider::class])
abstract fun bindFirebaseTokenService(): FirebaseTokenService
@ContributesAndroidInjector(modules = [FirebaseMessagingServiceProvider::class]) @ContributesAndroidInjector(modules = [FirebaseMessagingServiceProvider::class])
abstract fun bindGcmListenerService(): FirebaseMessagingService abstract fun bindGcmListenerService(): FirebaseMessagingService
@ContributesAndroidInjector(modules = [MessageServiceProvider::class]) @ContributesAndroidInjector(modules = [MessageServiceProvider::class])
abstract fun bindMessageService(): MessageService abstract fun bindMessageService(): MessageService
@Binds
@IntoMap
@WorkerKey(TokenRegistrationWorker::class)
abstract fun bindTokenRegistrationWorkerFactory(
builder: TokenRegistrationSubComponent.Builder
): AndroidInjector.Factory<out Worker>
} }
\ No newline at end of file
package chat.rocket.android.extensions
import com.google.android.gms.tasks.Task
import kotlin.coroutines.experimental.suspendCoroutine
@JvmName("awaitVoid")
suspend fun Task<Void>.await() = suspendCoroutine<Unit> { continuation ->
addOnSuccessListener { continuation.resume(Unit) }
addOnFailureListener { continuation.resumeWithException(it) }
}
suspend fun <TResult> Task<TResult>.await() = suspendCoroutine<TResult> { continuation ->
addOnSuccessListener { continuation.resume(it) }
addOnFailureListener { continuation.resumeWithException(it) }
}
\ No newline at end of file
package chat.rocket.android.push package chat.rocket.android.push
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import chat.rocket.android.push.worker.TokenRegistrationWorker
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import dagger.android.AndroidInjection import dagger.android.AndroidInjection
...@@ -17,8 +23,22 @@ class FirebaseMessagingService : FirebaseMessagingService() { ...@@ -17,8 +23,22 @@ class FirebaseMessagingService : FirebaseMessagingService() {
} }
override fun onMessageReceived(message: RemoteMessage) { override fun onMessageReceived(message: RemoteMessage) {
// XXX - for now this is ok, if we start to do network calls, use a Worker instead
message.data?.let { message.data?.let {
pushManager.handle(bundleOf(*(it.map { Pair(it.key, it.value) }).toTypedArray())) pushManager.handle(bundleOf(*(it.map { Pair(it.key, it.value) }).toTypedArray()))
} }
} }
override fun onNewToken(token: String) {
val data = workDataOf("token" to token)
val constraint =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val work = OneTimeWorkRequestBuilder<TokenRegistrationWorker>()
.setInputData(data)
.setConstraints(constraint)
.build()
// Schedule a job since we are using network...
WorkManager.getInstance().enqueue(work)
}
} }
\ No newline at end of file
package chat.rocket.android.push
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.registerPushToken
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.iid.FirebaseInstanceIdService
import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject
class FirebaseTokenService : FirebaseInstanceIdService() {
@Inject
lateinit var factory: RocketChatClientFactory
@Inject
lateinit var getCurrentServerInteractor: GetCurrentServerInteractor
@Inject
lateinit var localRepository: LocalRepository
override fun onCreate() {
super.onCreate()
AndroidInjection.inject(this)
}
override fun onTokenRefresh() {
try {
val fcmToken = FirebaseInstanceId.getInstance().token
val currentServer = getCurrentServerInteractor.get()
val client = currentServer?.let { factory.create(currentServer) }
fcmToken?.let {
localRepository.save(LocalRepository.KEY_PUSH_TOKEN, fcmToken)
client?.let {
launch {
try {
Timber.d("Registering push token: $fcmToken for ${client.url}")
retryIO("register push token") { client.registerPushToken(fcmToken) }
} catch (ex: RocketChatException) {
Timber.e(ex, "Error registering push token")
}
}
}
}
} catch (ex: Exception) {
Timber.e(ex, "Error refreshing Firebase TOKEN")
}
}
}
\ No newline at end of file
package chat.rocket.android.push
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import chat.rocket.android.push.worker.TokenRegistrationWorker
fun refreshPushToken() {
val constraint =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val work = OneTimeWorkRequestBuilder<TokenRegistrationWorker>()
.setConstraints(constraint)
.build()
// Schedule a job since we are using network...
WorkManager.getInstance().enqueue(work)
}
\ No newline at end of file
package chat.rocket.android.push.di
import chat.rocket.android.dagger.module.AppModule
import chat.rocket.android.push.FirebaseTokenService
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class FirebaseTokenServiceProvider {
@ContributesAndroidInjector(modules = [AppModule::class])
abstract fun provideFirebaseTokenService(): FirebaseTokenService
}
\ No newline at end of file
package chat.rocket.android.push.di
import chat.rocket.android.push.worker.TokenRegistrationWorker
import dagger.Subcomponent
import dagger.android.AndroidInjector
@Subcomponent
interface TokenRegistrationSubComponent : AndroidInjector<TokenRegistrationWorker> {
@Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<TokenRegistrationWorker>()
}
\ No newline at end of file
package chat.rocket.android.push.worker
import androidx.work.Worker
import chat.rocket.android.dagger.injector.AndroidWorkerInjection
import chat.rocket.android.extensions.await
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.common.util.ifNull
import com.google.firebase.iid.FirebaseInstanceId
import kotlinx.coroutines.experimental.runBlocking
import timber.log.Timber
import javax.inject.Inject
class TokenRegistrationWorker : Worker() {
@Inject
lateinit var factory: RocketChatClientFactory
@Inject
lateinit var getAccountsInteractor: GetAccountsInteractor
@Inject
lateinit var localRepository: LocalRepository
override fun doWork(): Result {
AndroidWorkerInjection.inject(this)
runBlocking {
val token = inputData.getString("token") ?: refreshToken()
token?.let { fcmToken ->
localRepository.save(LocalRepository.KEY_PUSH_TOKEN, fcmToken)
factory.registerPushToken(fcmToken, getAccountsInteractor.get())
}.ifNull {
Timber.d("Unavailable FCM Token...")
}
}
return Result.SUCCESS
}
private fun refreshToken(): String? {
return runBlocking {
try {
FirebaseInstanceId.getInstance().instanceId.await().token
} catch (ex: Exception) {
Timber.e(ex, "Error refreshing Firebase TOKEN")
null
}
}
}
}
\ No newline at end of file
package chat.rocket.android.util package chat.rocket.android.util
import chat.rocket.android.main.presentation.MainPresenter
import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import timber.log.Timber
suspend fun refreshFCMToken(presenter: MainPresenter) {
try {
val token = FirebaseInstanceId.getInstance().token
Timber.d("FCM token: $token")
presenter.refreshToken(token)
} catch (ex: Exception) {
Timber.d(ex, "Missing play services...")
}
}
fun invalidateFirebaseToken(token: String) { fun invalidateFirebaseToken(token: String) {
FirebaseInstanceId.getInstance().deleteToken(token, FirebaseMessaging.INSTANCE_ID_SCOPE) FirebaseInstanceId.getInstance().deleteToken(token, FirebaseMessaging.INSTANCE_ID_SCOPE)
......
...@@ -10,12 +10,14 @@ buildscript { ...@@ -10,12 +10,14 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.0-rc02' classpath 'com.android.tools.build:gradle:3.2.0-rc03'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}" classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:4.0.2' classpath 'com.google.gms:google-services:4.0.2'
classpath 'io.fabric.tools:gradle:1.25.4' classpath 'io.fabric.tools:gradle:1.25.4'
classpath "com.github.ben-manes:gradle-versions-plugin:0.20.0"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }
......
...@@ -10,28 +10,31 @@ ext { ...@@ -10,28 +10,31 @@ ext {
// For app // For app
kotlin : '1.2.61', kotlin : '1.2.61',
coroutine : '0.24.0', coroutine : '0.25.0',
appCompat : '1.0.0-beta01', appCompat : '1.0.0-rc02',
recyclerview : '1.0.0-beta01', recyclerview : '1.0.0-rc02',
constraintLayout : '2.0.0-alpha1', constraintLayout : '2.0.0-alpha2',
cardview : '1.0.0-beta01', cardview : '1.0.0-rc02',
browser : '1.0.0-beta01', browser : '1.0.0-rc02',
androidKtx : '1.0.0-beta01', androidKtx : '1.0.0-rc02',
workmanager : '1.0.0-alpha08',
dagger : '2.16', dagger : '2.16',
firebaseCloudMessage : '17.1.0', firebaseCloudMessage : '17.3.0',
firebaseAnalytics : '16.0.3', firebaseAnalytics : '16.0.3',
playServices : '15.0.1', playServices : '16.0.0',
exoPlayer : '2.8.2', exoPlayer : '2.8.2',
flexbox : '1.0.0', flexbox : '1.0.0',
material : '1.0.0-beta01', material : '1.0.0-beta01',
room : '2.0.0-beta01', room : '2.0.0-rc01',
lifecycle : '2.0.0-beta01', lifecycle : '2.0.0-rc01',
rxKotlin : '2.2.0', livedataKtx : '2.0.1',
rxAndroid : '2.0.2',
rxKotlin : '2.3.0',
rxAndroid : '2.1.0',
moshi : '1.6.0', moshi : '1.6.0',
okhttp : '3.11.0', okhttp : '3.11.0',
...@@ -45,9 +48,9 @@ ext { ...@@ -45,9 +48,9 @@ ext {
kotshi : '1.0.4', kotshi : '1.0.4',
frescoImageViewer : '0.5.1', frescoImageViewer : '0.5.1',
markwon : '1.1.0', markwon : '1.1.1',
aVLoadingIndicatorView: '2.1.3', aVLoadingIndicatorView: '2.1.3',
glide : '4.8.0-SNAPSHOT', glide : '4.8.0',
// For wearable // For wearable
wear : '2.3.0', wear : '2.3.0',
...@@ -71,6 +74,9 @@ ext { ...@@ -71,6 +74,9 @@ ext {
cardview : "androidx.cardview:cardview:${versions.cardview}", cardview : "androidx.cardview:cardview:${versions.cardview}",
browser : "androidx.browser:browser:${versions.browser}", browser : "androidx.browser:browser:${versions.browser}",
androidKtx : "androidx.core:core-ktx:${versions.androidKtx}", androidKtx : "androidx.core:core-ktx:${versions.androidKtx}",
fragmentsKtx : "androidx.fragment:fragment-ktx:${versions.androidKtx}",
workmanager : "android.arch.work:work-runtime-ktx:${versions.workmanager}",
workmanagerFirebase : "android.arch.work:work-firebase:${versions.workmanager}",
dagger : "com.google.dagger:dagger:${versions.dagger}", dagger : "com.google.dagger:dagger:${versions.dagger}",
daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}", daggerSupport : "com.google.dagger:dagger-android-support:${versions.dagger}",
...@@ -84,6 +90,9 @@ ext { ...@@ -84,6 +90,9 @@ ext {
roomProcessor : "androidx.room:room-compiler:${versions.room}", roomProcessor : "androidx.room:room-compiler:${versions.room}",
lifecycleExtensions : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycle}", lifecycleExtensions : "androidx.lifecycle:lifecycle-extensions:${versions.lifecycle}",
lifecycleCompiler : "androidx.lifecycle:lifecycle-compiler:${versions.lifecycle}", lifecycleCompiler : "androidx.lifecycle:lifecycle-compiler:${versions.lifecycle}",
viewmodelKtx : "androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.lifecycle}",
livedataKtx : "com.shopify:livedata-ktx:${versions.livedataKtx}",
rxKotlin : "io.reactivex.rxjava2:rxkotlin:${versions.rxKotlin}", rxKotlin : "io.reactivex.rxjava2:rxkotlin:${versions.rxKotlin}",
rxAndroid : "io.reactivex.rxjava2:rxandroid:${versions.rxAndroid}", rxAndroid : "io.reactivex.rxjava2:rxandroid:${versions.rxAndroid}",
......
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "5d591429a85289a5fe608ab2c4d77b0b",
"entities": [
{
"tableName": "Emoji",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`shortname` TEXT NOT NULL, `shortnameAlternates` TEXT NOT NULL, `unicode` TEXT NOT NULL, `category` TEXT NOT NULL, `count` INTEGER NOT NULL, `siblings` TEXT NOT NULL, `fitzpatrick` TEXT NOT NULL, `url` TEXT, `isDefault` INTEGER NOT NULL, PRIMARY KEY(`shortname`))",
"fields": [
{
"fieldPath": "shortname",
"columnName": "shortname",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "shortnameAlternates",
"columnName": "shortnameAlternates",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unicode",
"columnName": "unicode",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "category",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "count",
"columnName": "count",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "siblings",
"columnName": "siblings",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "fitzpatrick",
"columnName": "fitzpatrick",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isDefault",
"columnName": "isDefault",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"shortname"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"5d591429a85289a5fe608ab2c4d77b0b\")"
]
}
}
\ No newline at end of file
...@@ -139,9 +139,11 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow ...@@ -139,9 +139,11 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
@ColorInt @ColorInt
private fun getFitzpatrickColor(tone: Fitzpatrick): Int { private fun getFitzpatrickColor(tone: Fitzpatrick): Int {
val sharedPreferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE) val sharedPreferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE)
sharedPreferences.edit { sharedPreferences.edit {
putString(PREF_EMOJI_SKIN_TONE, tone.type) putString(PREF_EMOJI_SKIN_TONE, tone.type)
} }
return when (tone) { return when (tone) {
Fitzpatrick.Default -> ContextCompat.getColor(context, R.color.tone_default) Fitzpatrick.Default -> ContextCompat.getColor(context, R.color.tone_default)
Fitzpatrick.LightTone -> ContextCompat.getColor(context, R.color.tone_light) Fitzpatrick.LightTone -> ContextCompat.getColor(context, R.color.tone_light)
...@@ -154,21 +156,24 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow ...@@ -154,21 +156,24 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
private suspend fun setupViewPager() { private suspend fun setupViewPager() {
context.let { context.let {
val callback = when (it) { val callback: EmojiKeyboardListener? = when (it) {
is EmojiKeyboardListener -> it is EmojiKeyboardListener -> it
else -> { else -> {
val fragments = (it as AppCompatActivity).supportFragmentManager.fragments val fragments = (it as AppCompatActivity).supportFragmentManager.fragments
if (fragments.size == 0 || !(fragments[0] is EmojiKeyboardListener)) { if (fragments.size == 0 || !(fragments[0] is EmojiKeyboardListener)) {
throw IllegalStateException("activity/fragment should implement Listener interface") // Since the app can arrive in an inconsistent state at this point, do not throw
// throw IllegalStateException("activity/fragment should implement Listener interface")
null
} else {
fragments[0] as EmojiKeyboardListener
} }
fragments[0] as EmojiKeyboardListener
} }
} }
adapter = EmojiPagerAdapter(object : EmojiKeyboardListener { adapter = EmojiPagerAdapter(object : EmojiKeyboardListener {
override fun onEmojiAdded(emoji: Emoji) { override fun onEmojiAdded(emoji: Emoji) {
EmojiRepository.addToRecents(emoji) EmojiRepository.addToRecents(emoji)
callback.onEmojiAdded(emoji) callback?.onEmojiAdded(emoji)
} }
}) })
...@@ -194,6 +199,7 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow ...@@ -194,6 +199,7 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
} }
class EmojiTextWatcher(private val editor: EditText) : TextWatcher { class EmojiTextWatcher(private val editor: EditText) : TextWatcher {
@Volatile @Volatile
private var emojiToRemove = mutableListOf<EmojiTypefaceSpan>() private var emojiToRemove = mutableListOf<EmojiTypefaceSpan>()
...@@ -237,4 +243,4 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow ...@@ -237,4 +243,4 @@ class EmojiKeyboardPopup(context: Context, view: View) : OverKeyboardPopupWindow
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
} }
} }
} }
\ No newline at end of file
...@@ -79,10 +79,10 @@ class EmojiParser { ...@@ -79,10 +79,10 @@ class EmojiParser {
val px = context.resources.getDimensionPixelSize(R.dimen.custom_emoji_small) val px = context.resources.getDimensionPixelSize(R.dimen.custom_emoji_small)
return spannable.also { return spannable.also { sp ->
regex.findAll(spannable).iterator().forEach { match -> regex.findAll(spannable).iterator().forEach { match ->
customEmojis.find { it.shortname.toLowerCase() == match.value.toLowerCase() }?.let { customEmojis.find { it.shortname.toLowerCase() == match.value.toLowerCase() }?.let { emoji ->
it.url?.let { url -> emoji.url?.let { url ->
try { try {
val glideRequest = if (url.endsWith("gif", true)) { val glideRequest = if (url.endsWith("gif", true)) {
GlideApp.with(context).asGif() GlideApp.with(context).asGif()
......
...@@ -37,10 +37,12 @@ class EmojiPickerPopup(context: Context) : Dialog(context) { ...@@ -37,10 +37,12 @@ class EmojiPickerPopup(context: Context) : Dialog(context) {
private fun setSize() { private fun setSize() {
val lp = WindowManager.LayoutParams() val lp = WindowManager.LayoutParams()
lp.copyFrom(window.attributes) window?.let {
val dialogWidth = lp.width lp.copyFrom(it.attributes)
val dialogHeight = context.resources.getDimensionPixelSize(R.dimen.picker_popup_height) val dialogWidth = lp.width
window.setLayout(dialogWidth, dialogHeight) val dialogHeight = context.resources.getDimensionPixelSize(R.dimen.picker_popup_height)
it.setLayout(dialogWidth, dialogHeight)
}
} }
private suspend fun setupViewPager() { private suspend fun setupViewPager() {
......
...@@ -3,11 +3,13 @@ package chat.rocket.android.emoji ...@@ -3,11 +3,13 @@ package chat.rocket.android.emoji
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Typeface import android.graphics.Typeface
import android.util.Log
import chat.rocket.android.emoji.internal.EmojiCategory import chat.rocket.android.emoji.internal.EmojiCategory
import chat.rocket.android.emoji.internal.PREF_EMOJI_RECENTS import chat.rocket.android.emoji.internal.PREF_EMOJI_RECENTS
import chat.rocket.android.emoji.internal.db.EmojiDatabase import chat.rocket.android.emoji.internal.db.EmojiDatabase
import chat.rocket.android.emoji.internal.isCustom import chat.rocket.android.emoji.internal.isCustom
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.GlideException
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
...@@ -46,7 +48,11 @@ object EmojiRepository { ...@@ -46,7 +48,11 @@ object EmojiRepository {
this@EmojiRepository.customEmojis = customEmojis this@EmojiRepository.customEmojis = customEmojis
val allEmojis = mutableListOf<Emoji>() val allEmojis = mutableListOf<Emoji>()
db = EmojiDatabase.getInstance(context) db = EmojiDatabase.getInstance(context)
cachedTypeface = Typeface.createFromAsset(context.assets, "fonts/emojione-android.ttf")
if (!::cachedTypeface.isInitialized) {
cachedTypeface = Typeface.createFromAsset(context.assets, "fonts/emojione-android.ttf")
}
preferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE) preferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE)
val stream = context.assets.open(path) val stream = context.assets.open(path)
// Load emojis from emojione ttf file temporarily here. We still need to work on them. // Load emojis from emojione ttf file temporarily here. We still need to work on them.
...@@ -110,10 +116,17 @@ object EmojiRepository { ...@@ -110,10 +116,17 @@ object EmojiRepository {
val px = context.resources.getDimensionPixelSize(R.dimen.custom_emoji_large) val px = context.resources.getDimensionPixelSize(R.dimen.custom_emoji_large)
customEmojis.forEach { customEmojis.forEach {
val future = Glide.with(context) try {
.load(it.url) val future = Glide.with(context)
.submit(px, px) .load(it.url)
future.get() .submit(px, px)
future.get()
} catch (ex: Exception) {
Log.d("EmojiRepository", "Error fetching custom emoji ${it.shortname}", ex)
if (ex is GlideException) {
ex.logRootCauses("EmojiRepository")
}
}
} }
} }
} }
...@@ -197,7 +210,7 @@ object EmojiRepository { ...@@ -197,7 +210,7 @@ object EmojiRepository {
} }
} }
internal fun getCustomEmojis(): List<Emoji> = customEmojis fun getCustomEmojis(): List<Emoji> = customEmojis
/** /**
* Get all recently used emojis ordered by usage count. * Get all recently used emojis ordered by usage count.
...@@ -300,4 +313,12 @@ object EmojiRepository { ...@@ -300,4 +313,12 @@ object EmojiRepository {
val s2: Int = ((scalar - 0x10000) % 0x400) + 0xDC00 val s2: Int = ((scalar - 0x10000) % 0x400) + 0xDC00
return Pair(s1, s2) return Pair(s1, s2)
} }
fun init(context: Context) {
launch {
db = EmojiDatabase.getInstance(context)
preferences = context.getSharedPreferences("emoji", Context.MODE_PRIVATE)
cachedTypeface = Typeface.createFromAsset(context.assets, "fonts/emojione-android.ttf")
}
}
} }
\ No newline at end of file
...@@ -57,9 +57,6 @@ abstract class OverKeyboardPopupWindow( ...@@ -57,9 +57,6 @@ abstract class OverKeyboardPopupWindow(
init { init {
setBackgroundDrawable(null) setBackgroundDrawable(null)
if (BuildConfig.VERSION_CODE >= Build.VERSION_CODES.LOLLIPOP) {
elevation = 0f
}
val view = onCreateView(LayoutInflater.from(context)) val view = onCreateView(LayoutInflater.from(context))
onViewCreated(view) onViewCreated(view)
contentView = view contentView = view
...@@ -89,7 +86,7 @@ abstract class OverKeyboardPopupWindow( ...@@ -89,7 +86,7 @@ abstract class OverKeyboardPopupWindow(
/** /**
* Call this function to resize the emoji popup according to your soft keyboard size * Call this function to resize the emoji popup according to your soft keyboard size
*/ */
fun setSizeForSoftKeyboard() { private fun setSizeForSoftKeyboard() {
val viewTreeObserver = rootView.viewTreeObserver val viewTreeObserver = rootView.viewTreeObserver
viewTreeObserver.addOnGlobalLayoutListener(this) viewTreeObserver.addOnGlobalLayoutListener(this)
} }
......
...@@ -147,10 +147,11 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) : ...@@ -147,10 +147,11 @@ internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) :
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiRowViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiRowViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = if (viewType == CUSTOM) { val view = if (viewType == CUSTOM) {
LayoutInflater.from(parent.context).inflate(R.layout.emoji_image_row_item, parent, false) inflater.inflate(R.layout.emoji_image_row_item, parent, false)
} else { } else {
LayoutInflater.from(parent.context).inflate(R.layout.emoji_row_item, parent, false) inflater.inflate(R.layout.emoji_row_item, parent, false)
} }
return EmojiRowViewHolder(view, listener) return EmojiRowViewHolder(view, listener)
} }
......
...@@ -6,18 +6,16 @@ ...@@ -6,18 +6,16 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/emoji_recycler_view" android:id="@+id/emoji_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp" />
<TextView <TextView
android:id="@+id/text_no_recent_emoji" android:id="@+id/text_no_recent_emoji"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/msg_no_recent_emoji"
android:layout_gravity="center" android:layout_gravity="center"
android:elevation="10dp" android:elevation="10dp"
android:text="@string/msg_no_recent_emoji"
android:textSize="16sp" android:textSize="16sp"
android:visibility="gone"/> android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
...@@ -20,8 +20,6 @@ ...@@ -20,8 +20,6 @@
android:id="@+id/pager_categories" android:id="@+id/pager_categories"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:background="@color/colorWhite" /> android:background="@color/colorWhite" />
</LinearLayout> </LinearLayout>
\ No newline at end of file
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/emoji_view" android:id="@+id/emoji_view"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:foreground="?selectableItemBackground" android:foreground="?selectableItemBackground"
android:layout_gravity="center"
android:gravity="center" android:gravity="center"
android:textColor="#000000" android:textColor="#000000"
android:textSize="26sp" android:textSize="24sp"
tools:text="😀" /> tools:text="😀" />
include ':app', ':player', ':emoji', ':draw', ':util', ':core' //, ':wear' include ':app', ':player', ':emoji', ':draw', ':util', ':core', ':suggestions' //, ':wear'
\ No newline at end of file \ No newline at end of file
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion versions.compileSdk
buildToolsVersion versions.buildTools
defaultConfig {
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation libraries.kotlin
implementation libraries.recyclerview
implementation libraries.appCompat
implementation libraries.material
}
androidExtensions {
experimental = true
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package yampsample.leonardoaramaki.github.com.suggestions;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("yampsample.leonardoaramaki.github.com.suggestions.test", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android.suggestions" />
package chat.rocket.android.widget.autocompletion.model package chat.rocket.android.suggestions.model
abstract class SuggestionModel(val text: String, // This is the text key for searches, must be unique. abstract class SuggestionModel(val text: String, // This is the text key for searches, must be unique.
val searchList: List<String> = emptyList(), // Where to search for matches. val searchList: List<String> = emptyList(), // Where to search for matches.
...@@ -15,4 +15,4 @@ abstract class SuggestionModel(val text: String, // This is the text key for sea ...@@ -15,4 +15,4 @@ abstract class SuggestionModel(val text: String, // This is the text key for sea
override fun hashCode(): Int { override fun hashCode(): Int {
return text.hashCode() return text.hashCode()
} }
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.repository package chat.rocket.android.suggestions.repository
interface LocalSuggestionProvider { interface LocalSuggestionProvider {
fun find(prefix: String) fun find(prefix: String)
......
package chat.rocket.android.widget.autocompletion.strategy package chat.rocket.android.suggestions.strategy
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
interface CompletionStrategy { interface CompletionStrategy {
fun getItem(prefix: String, position: Int): SuggestionModel fun getItem(prefix: String, position: Int): SuggestionModel
...@@ -8,4 +8,4 @@ interface CompletionStrategy { ...@@ -8,4 +8,4 @@ interface CompletionStrategy {
fun addAll(list: List<SuggestionModel>) fun addAll(list: List<SuggestionModel>)
fun addPinned(list: List<SuggestionModel>) fun addPinned(list: List<SuggestionModel>)
fun size(): Int fun size(): Int
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.strategy.regex package chat.rocket.android.suggestions.strategy.regex
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter.Companion.RESULT_COUNT_UNLIMITED import chat.rocket.android.suggestions.ui.SuggestionsAdapter.Companion.RESULT_COUNT_UNLIMITED
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
internal class StringMatchingCompletionStrategy(private val threshold: Int = RESULT_COUNT_UNLIMITED) : CompletionStrategy { internal class StringMatchingCompletionStrategy(private val threshold: Int = RESULT_COUNT_UNLIMITED) : CompletionStrategy {
...@@ -46,4 +46,4 @@ internal class StringMatchingCompletionStrategy(private val threshold: Int = RES ...@@ -46,4 +46,4 @@ internal class StringMatchingCompletionStrategy(private val threshold: Int = RES
override fun size(): Int { override fun size(): Int {
return list.size return list.size
} }
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.strategy.trie package chat.rocket.android.suggestions.strategy.trie
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.strategy.trie.data.Trie import chat.rocket.android.suggestions.strategy.trie.data.Trie
class TrieCompletionStrategy : CompletionStrategy { class TrieCompletionStrategy : CompletionStrategy {
private val items = mutableListOf<SuggestionModel>() private val items = mutableListOf<SuggestionModel>()
...@@ -28,8 +28,7 @@ class TrieCompletionStrategy : CompletionStrategy { ...@@ -28,8 +28,7 @@ class TrieCompletionStrategy : CompletionStrategy {
} }
override fun addPinned(list: List<SuggestionModel>) { override fun addPinned(list: List<SuggestionModel>) {
} }
override fun size() = items.size override fun size() = items.size
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.strategy.trie.data package chat.rocket.android.suggestions.strategy.trie.data
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
internal class Trie { internal class Trie {
private val root = TrieNode(' ') private val root = TrieNode(' ')
...@@ -67,4 +67,4 @@ internal class Trie { ...@@ -67,4 +67,4 @@ internal class Trie {
} }
fun getCount() = count fun getCount() = count
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.strategy.trie.data package chat.rocket.android.suggestions.strategy.trie.data
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
internal class TrieNode(internal var data: Char, internal class TrieNode(internal var data: Char,
internal var parent: TrieNode? = null, internal var parent: TrieNode? = null,
...@@ -44,4 +44,4 @@ internal class TrieNode(internal var data: Char, ...@@ -44,4 +44,4 @@ internal class TrieNode(internal var data: Char,
} }
override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data" override fun toString(): String = if (parent == null) "" else "${parent.toString()}$data"
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.ui package chat.rocket.android.suggestions.ui
import androidx.recyclerview.widget.RecyclerView
import android.view.View import android.view.View
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.suggestions.model.SuggestionModel
abstract class BaseSuggestionViewHolder(view: View) : RecyclerView.ViewHolder(view) { abstract class BaseSuggestionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?) abstract fun bind(item: SuggestionModel, itemClickListener: SuggestionsAdapter.ItemClickListener?)
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.ui package chat.rocket.android.suggestions.ui
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.WindowManager import android.view.WindowManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R import chat.rocket.android.suggestions.R
internal class PopupRecyclerView : RecyclerView { internal class PopupRecyclerView : RecyclerView {
private var displayWidth: Int = 0 private var displayWidth: Int = 0
...@@ -38,4 +38,4 @@ internal class PopupRecyclerView : RecyclerView { ...@@ -38,4 +38,4 @@ internal class PopupRecyclerView : RecyclerView {
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l + 40, t, r - 40, b) super.onLayout(changed, l + 40, t, r - 40, b)
} }
} }
\ No newline at end of file
package chat.rocket.android.widget.autocompletion.ui package chat.rocket.android.suggestions.ui
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy import chat.rocket.android.suggestions.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCompletionStrategy import chat.rocket.android.suggestions.strategy.regex.StringMatchingCompletionStrategy
import java.lang.reflect.Type import java.lang.reflect.Type
import kotlin.properties.Delegates import kotlin.properties.Delegates
...@@ -32,10 +32,10 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>( ...@@ -32,10 +32,10 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(
// The strategy used for suggesting completions. // The strategy used for suggesting completions.
private val strategy: CompletionStrategy = StringMatchingCompletionStrategy(resultsThreshold) private val strategy: CompletionStrategy = StringMatchingCompletionStrategy(resultsThreshold)
// Current input term to look up for suggestions. // Current input term to look up for suggestions.
private var currentTerm: String by Delegates.observable("", { _, _, newTerm -> private var currentTerm: String by Delegates.observable("") { _, _, newTerm ->
val items = strategy.autocompleteItems(newTerm) val items = strategy.autocompleteItems(newTerm)
notifyDataSetChanged() notifyDataSetChanged()
}) }
init { init {
setHasStableIds(true) setHasStableIds(true)
......
package chat.rocket.android.widget.autocompletion.ui package chat.rocket.android.suggestions.ui
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import androidx.transition.Slide
import androidx.transition.TransitionManager
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.text.Editable import android.text.Editable
import android.text.InputType import android.text.InputType
import android.text.TextWatcher import android.text.TextWatcher
import android.transition.Slide
import android.transition.TransitionManager
import android.util.AttributeSet import android.util.AttributeSet
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.FrameLayout import android.widget.FrameLayout
import chat.rocket.android.R import androidx.annotation.DrawableRes
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import androidx.core.content.ContextCompat
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter.Companion.CONSTRAINT_BOUND_TO_START import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.suggestions.R
import chat.rocket.android.suggestions.model.SuggestionModel
import chat.rocket.android.suggestions.ui.SuggestionsAdapter.Companion.CONSTRAINT_BOUND_TO_START
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
...@@ -103,7 +103,8 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -103,7 +103,8 @@ class SuggestionsView : FrameLayout, TextWatcher {
val prefixEndIndex = this.editor?.get()?.selectionStart ?: NO_STATE_INDEX val prefixEndIndex = this.editor?.get()?.selectionStart ?: NO_STATE_INDEX
if (prefixEndIndex == NO_STATE_INDEX || prefixEndIndex < completionOffset.get()) return if (prefixEndIndex == NO_STATE_INDEX || prefixEndIndex < completionOffset.get()) return
val prefix = s.subSequence(completionOffset.get(), this.editor?.get()?.selectionStart ?: completionOffset.get()).toString() val prefix = s.subSequence(completionOffset.get(), this.editor?.get()?.selectionStart
?: completionOffset.get()).toString()
recyclerView.adapter?.let { recyclerView.adapter?.let {
it as SuggestionsAdapter it as SuggestionsAdapter
// we need to look up only after the '@' // we need to look up only after the '@'
...@@ -156,7 +157,7 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -156,7 +157,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
val adapter = adapter(token) val adapter = adapter(token)
localProvidersByToken.getOrPut(token, { hashMapOf() }) localProvidersByToken.getOrPut(token, { hashMapOf() })
.put(adapter.term(), list) .put(adapter.term(), list)
if (completionOffset.get() > NO_STATE_INDEX && adapter.itemCount == 0) expand() if (completionOffset.get() > NO_STATE_INDEX && adapter.itemCount == 0) expand()
adapter.addItems(list) adapter.addItems(list)
} }
...@@ -192,7 +193,8 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -192,7 +193,8 @@ class SuggestionsView : FrameLayout, TextWatcher {
} }
private fun adapter(token: String): SuggestionsAdapter<*> { private fun adapter(token: String): SuggestionsAdapter<*> {
return adaptersByToken[token] ?: throw IllegalStateException("no adapter binds to token \"$token\"") return adaptersByToken[token]
?: throw IllegalStateException("no adapter binds to token \"$token\"")
} }
private fun cancelSuggestions(haltCompletion: Boolean) { private fun cancelSuggestions(haltCompletion: Boolean) {
......
...@@ -8,4 +8,4 @@ ...@@ -8,4 +8,4 @@
<size android:height="2dp" /> <size android:height="2dp" />
</shape> </shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="popup_max_height">150dp</dimen>
<dimen name="suggestions_box_max_height">250dp</dimen>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">suggestions</string>
</resources>
package yampsample.leonardoaramaki.github.com.suggestions;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ 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