Unverified Commit 00aa5de8 authored by Shailesh Baldaniya's avatar Shailesh Baldaniya Committed by GitHub

Merge branch 'develop' into upstrem-button-action

parents 611b0086 7301cde4
......@@ -64,7 +64,7 @@ jobs:
- store_artifacts:
path: app/build/reports/
destination: reports
build-apk:
build-play-apk:
docker:
- image: circleci/android:api-27-alpha
environment:
......@@ -93,7 +93,40 @@ jobs:
- run:
name: Build APK
command: |
./gradlew assembleRelease --info --console=plain --stacktrace
./gradlew assemblePlayRelease --info --console=plain --stacktrace
- store_artifacts:
path: app/build/outputs/apk
destination: apks
build-foss-apk:
docker:
- image: circleci/android:api-27-alpha
environment:
JVM_OPTS: -Xmx3200m
steps:
- checkout
- run:
name: restore files from ENV
command: |
echo $ROCKET_JKS_BASE64 | base64 --decode > Rocket.jks
echo $ROCKET_PLAY_JSON | base64 --decode > app/rocket-chat.json
- run:
name: checkout Rocket.Chat.Kotlin.SDK
command: git clone https://github.com/RocketChat/Rocket.Chat.Kotlin.SDK.git ../Rocket.Chat.Kotlin.SDK
- restore_cache:
key: kotlin-sdk-{{ .Revision }}
- restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}-{{ checksum "player/build.gradle" }}
- run:
name: Download Dependencies
command: ./gradlew androidDependencies --quiet --console=plain
- save_cache:
paths:
- ~/.gradle
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}-{{ checksum "player/build.gradle" }}
- run:
name: Build APK
command: |
./gradlew assembleFossRelease --info --console=plain --stacktrace
- store_artifacts:
path: app/build/outputs/apk
destination: apks
......@@ -112,6 +145,9 @@ workflows:
- develop
- develop-2.x
- master
- build-apk:
- build-play-apk:
requires:
- build-kotlin-sdk
- build-foss-apk:
requires:
- build-kotlin-sdk
#!/bin/bash
CURRENT_DIR=$(pwd)
# The SDK dir should be 2 directories up in the tree, so we use dirname 2 times
# to get the common parent dir of the SDK and the app
GIT=$(which git)
cd ../..
tmp=$(pwd)
SDK_DIR="$tmp/Rocket.Chat.Kotlin.SDK"
cd "${CURRENT_DIR}"
if [ "$#" -eq 1 ] && [ ! -z "$1" ]; then
# if in an argument is given this is the (relative) path to SDK_DIR
SDK_DIR=$(readlink -f $1)
else
# The SDK dir should be 2 directories up in the tree, so we use dirname 2 times
# to get the common parent dir of the SDK and the app
cd ../..
tmp=$(pwd)
SDK_DIR="$tmp/Rocket.Chat.Kotlin.SDK"
cd "${CURRENT_DIR}"
fi
echo "CURRENT DIR: $CURRENT_DIR"
echo "SDK DIR: $SDK_DIR"
......@@ -99,4 +105,4 @@ cp -v "${SDK_DIR}"/core/build/libs/core-0.1-SNAPSHOT.jar "${CURRENT_DIR}"/libs/c
echo "$SHA" > "${SDK_DIR}"/.last_commit_hash
exit 0
\ No newline at end of file
exit 0
def taskRequests = getGradle().getStartParameter().getTaskRequests().toString()
def isPlay = !(taskRequests.contains("Foss") || taskRequests.contains("foss"))
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'
if (isPlay) { apply plugin: 'io.fabric' }
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
......@@ -12,11 +15,15 @@ android {
applicationId "chat.rocket.android"
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
versionCode 2034
versionName "2.5.0"
versionCode 2036
versionName "2.5.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
def gitSha = 'git rev-parse --short HEAD'.execute([], project.rootDir).text.trim()
def buildTime = new GregorianCalendar().format("MM-dd-yyyy' 'h:mm:ss a z")
buildConfigField "String", "GIT_SHA", "\"${gitSha}\""
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
......@@ -57,6 +64,20 @@ android {
}
}
flavorDimensions "type"
productFlavors {
// includes proprietary libs
play {
dimension "type"
}
// only foss
foss {
dimension "type"
}
}
packagingOptions {
exclude 'META-INF/core.kotlin_module'
exclude 'META-INF/main.kotlin_module'
......@@ -91,9 +112,6 @@ dependencies {
kapt libraries.daggerProcessor
kapt libraries.daggerAndroidApt
implementation libraries.fcm
implementation libraries.playServicesAuth
implementation libraries.room
kapt libraries.roomProcessor
implementation libraries.lifecycleExtensions
......@@ -126,7 +144,10 @@ dependencies {
implementation "com.github.luciofm:livedata-ktx:b1e8bbc25a"
implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') {
playImplementation libraries.fcm
playImplementation libraries.playServicesAuth
playImplementation('com.crashlytics.sdk.android:crashlytics:2.9.4@aar') {
transitive = true
}
......@@ -148,13 +169,16 @@ androidExtensions {
// FIXME - build and install the sdk into the app/libs directory
// We were having some issues with the kapt generated files from the sdk when importing as a module
def sdk_location=project.properties['sdk_location'] ?: ""
task compileSdk(type:Exec) {
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
commandLine 'cmd', '/c', 'build-sdk.sh'
commandLine 'cmd', '/c', 'build-sdk.sh', sdk_location
} else {
commandLine './build-sdk.sh'
commandLine './build-sdk.sh', sdk_location
}
}
preBuild.dependsOn compileSdk
apply plugin: 'com.google.gms.google-services'
if (isPlay) {
apply plugin: 'com.google.gms.google-services'
}
#Thu Feb 15 15:50:42 BRST 2018
#Wed Aug 01 21:56:00 EDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
package chat.rocket.android.dagger.module
import chat.rocket.android.chatroom.di.MessageServiceProvider
import chat.rocket.android.chatroom.service.MessageService
import dagger.Module
import dagger.android.ContributesAndroidInjector
@Module abstract class ServiceBuilder {
@ContributesAndroidInjector(modules = [MessageServiceProvider::class])
abstract fun bindMessageService(): MessageService
}
package chat.rocket.android.helper
import android.app.Activity
import android.content.Intent
import androidx.fragment.app.FragmentActivity
fun FragmentActivity.saveCredentials(id: String, password: String) {
}
fun Activity.requestStoredCredentials(): Pair<String, String>? = null
fun getCredentials(data: Intent): Pair<String, String>? = null
fun hasCredentialsSupport() = false
\ No newline at end of file
package chat.rocket.android.helper
import timber.log.Timber
import android.util.Log
class CrashlyticsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) {
Log.println(priority, tag, message)
if (throwable != null) {
Log.e(tag,throwable.toString())
}
}
}
package chat.rocket.android.push
class FirebaseTokenService {
}
\ No newline at end of file
package chat.rocket.android.util
import android.content.Context
fun setupCrashlytics(context: Context) {
//Do absolutely nothing
}
package chat.rocket.android.util
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.main.presentation.MainPresenter
fun refreshFCMToken(presenter: MainPresenter) {
//Do absolutely nothing
}
fun invalidateFirebaseToken(token: String) {
//Do absolutely nothing
}
\ No newline at end of file
......@@ -95,23 +95,6 @@
</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
android:name=".push.FirebaseMessagingService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service
android:name=".chatroom.service.MessageService"
android:exported="true"
......@@ -122,4 +105,4 @@
android:value="12ac6e94f850aaffcdff52001af77ca415d06a43" />
</application>
</manifest>
\ No newline at end of file
</manifest>
......@@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import chat.rocket.android.BuildConfig
import chat.rocket.android.R
......@@ -29,8 +30,9 @@ class AboutFragment : Fragment() {
}
private fun setupViews() {
text_version_name.text = getString(R.string.msg_version, BuildConfig.VERSION_NAME)
text_build_number.text = getString(R.string.msg_build, BuildConfig.VERSION_CODE)
text_version_name.text = BuildConfig.VERSION_NAME
text_build_number.text = getString(R.string.msg_build, BuildConfig.VERSION_CODE,
BuildConfig.GIT_SHA, BuildConfig.FLAVOR)
}
private fun setupToolbar() {
......
......@@ -22,6 +22,9 @@ class AppLifecycleObserver @Inject constructor(
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onEnterForeground() {
changeTemporaryStatus(UserStatus.Online())
serverInteractor.get()?.let { currentServer ->
factory.create(currentServer).resetReconnectionTimer()
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
......
......@@ -13,15 +13,13 @@ import chat.rocket.android.dagger.DaggerAppComponent
import chat.rocket.android.dagger.qualifier.ForMessages
import chat.rocket.android.helper.CrashlyticsTree
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.infrastructure.installCrashlyticsWrapper
import chat.rocket.android.server.domain.AccountsRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.SITE_URL
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.emoji.EmojiRepository
import com.crashlytics.android.Crashlytics
import com.crashlytics.android.core.CrashlyticsCore
import chat.rocket.android.util.setupCrashlytics
import com.facebook.drawee.backends.pipeline.DraweeConfig
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
......@@ -31,7 +29,6 @@ import dagger.android.DispatchingAndroidInjector
import dagger.android.HasActivityInjector
import dagger.android.HasBroadcastReceiverInjector
import dagger.android.HasServiceInjector
import io.fabric.sdk.android.Fabric
import timber.log.Timber
import java.lang.ref.WeakReference
import javax.inject.Inject
......@@ -89,7 +86,7 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
AndroidThreeTen.init(this)
EmojiRepository.load(this)
setupCrashlytics()
setupCrashlytics(this)
setupFresco()
setupTimber()
......@@ -125,15 +122,6 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
}
}
private fun setupCrashlytics() {
val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()
Fabric.with(this, Crashlytics.Builder().core(core).build())
installCrashlyticsWrapper(this@RocketChatApplication,
getCurrentServerInteractor, settingsInteractor,
accountRepository, localRepository)
}
private fun setupFresco() {
Fresco.initialize(this, imagePipelineConfig, draweeConfig)
}
......@@ -169,4 +157,4 @@ class RocketChatApplication : Application(), HasActivityInjector, HasServiceInje
private fun LocalRepository.needOldMessagesCleanUp() = getBoolean(CLEANUP_OLD_MESSAGES_NEEDED, true)
private fun LocalRepository.setOldMessagesCleanedUp() = save(CLEANUP_OLD_MESSAGES_NEEDED, false)
private const val CLEANUP_OLD_MESSAGES_NEEDED = "CLEANUP_OLD_MESSAGES_NEEDED"
\ No newline at end of file
private const val CLEANUP_OLD_MESSAGES_NEEDED = "CLEANUP_OLD_MESSAGES_NEEDED"
......@@ -11,8 +11,8 @@ import timber.log.Timber
@Parcelize
data class LoginDeepLinkInfo(
val url: String,
val userId: String,
val token: String
val userId: String?,
val token: String?
) : Parcelable
fun Intent.getLoginDeepLinkInfo(): LoginDeepLinkInfo? {
......
......@@ -2,7 +2,6 @@ package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import com.google.android.gms.auth.api.credentials.Credential
interface LoginView : LoadingView, MessageView {
......@@ -190,6 +189,21 @@ interface LoginView : LoadingView, MessageView {
*/
fun setupGitlabButtonListener(gitlabUrl: String, state: String)
/**
* Shows the "login by WordPress" view if it is enable by the server settings.
*
* REMARK: We must set up the Gitlab button listener before enabling it [setupWordpressButtonListener].
*/
fun enableLoginByWordpress()
/**
* Setups the WordPress button when tapped.
*
* @param wordpressUrl The WordPress OAuth URL to authenticate with.
* @param state A random string generated by the app, which you'll verify later (to protect against forgery attacks).
*/
fun setupWordpressButtonListener(wordpressUrl: String, state: String)
/**
* Adds a custom OAuth button in the oauth view.
*
......
......@@ -30,11 +30,13 @@ import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_SECRET
import chat.rocket.android.webview.oauth.ui.INTENT_OAUTH_CREDENTIAL_TOKEN
import chat.rocket.android.webview.oauth.ui.oauthWebViewIntent
import chat.rocket.common.util.ifNull
import com.google.android.gms.auth.api.credentials.*
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import javax.inject.Inject
internal const val REQUEST_CODE_FOR_SIGN_IN_REQUIRED = 1
internal const val REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION = 2
internal const val REQUEST_CODE_FOR_SAVE_RESOLUTION = 3
internal const val REQUEST_CODE_FOR_CAS = 4
internal const val REQUEST_CODE_FOR_SAML = 5
internal const val REQUEST_CODE_FOR_OAUTH = 6
......@@ -48,7 +50,6 @@ class LoginFragment : Fragment(), LoginView {
}
private var isGlobalLayoutListenerSetUp = false
private var deepLinkInfo: LoginDeepLinkInfo? = null
private val credentialsClient by lazy { Credentials.getClient(requireActivity()) }
companion object {
private const val DEEP_LINK_INFO = "DeepLinkInfo"
......@@ -85,6 +86,10 @@ class LoginFragment : Fragment(), LoginView {
}.ifNull {
presenter.setupView()
}
if (!hasCredentialsSupport()) {
image_key.isVisible = false
}
}
override fun onDestroyView() {
......@@ -100,13 +105,15 @@ class LoginFragment : Fragment(), LoginView {
if (data != null) {
when (requestCode) {
REQUEST_CODE_FOR_MULTIPLE_ACCOUNTS_RESOLUTION -> {
onCredentialRetrieved(data.getParcelableExtra(Credential.EXTRA_KEY))
getCredentials(data)?.let {
onCredentialRetrieved(it.first, it.second)
}
}
REQUEST_CODE_FOR_SIGN_IN_REQUIRED -> {
//use the hints to autofill sign in forms to reduce the info to be filled.
val credential: Credential = data.getParcelableExtra(Credential.EXTRA_KEY)
text_username_or_email.setText(credential.id)
text_password.setText(credential.password)
getCredentials(data)?.let { credential ->
text_username_or_email.setText(credential.first)
text_password.setText(credential.second)
}
}
REQUEST_CODE_FOR_SAVE_RESOLUTION -> {
showMessage(getString(R.string.message_credentials_saved_successfully))
......@@ -154,19 +161,19 @@ class LoginFragment : Fragment(), LoginView {
private fun requestStoredCredentials() {
activity?.let {
SmartLockHelper.requestStoredCredentials(credentialsClient, it)?.let {
onCredentialRetrieved(it)
it.requestStoredCredentials()?.let { credentials ->
onCredentialRetrieved(credentials.first, credentials.second)
}
}
}
private fun onCredentialRetrieved(credential: Credential) {
presenter.authenticateWithUserAndPassword(credential.id, credential.password.toString())
private fun onCredentialRetrieved(id: String, password: String) {
presenter.authenticateWithUserAndPassword(id, password)
}
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.let {
SmartLockHelper.save(credentialsClient, it, id, password)
it.saveCredentials(id, password)
}
}
......@@ -443,6 +450,24 @@ class LoginFragment : Fragment(), LoginView {
}
}
override fun enableLoginByWordpress() {
ui {
button_wordpress.isClickable = true
}
}
override fun setupWordpressButtonListener(wordpressUrl: String, state: String) {
ui { activity ->
button_wordpress.setOnClickListener {
startActivityForResult(
activity.oauthWebViewIntent(wordpressUrl, state),
REQUEST_CODE_FOR_OAUTH
)
activity.overridePendingTransition(R.anim.slide_up, R.anim.hold)
}
}
}
override fun addCustomOauthServiceButton(
customOauthUrl: String,
state: String,
......@@ -488,11 +513,11 @@ class LoginFragment : Fragment(), LoginView {
override fun setupFabListener() {
ui {
button_fab.isVisible = true
button_fab.setOnClickListener({
button_fab.setOnClickListener {
button_fab.hide()
showRemainingSocialAccountsView()
scrollToBottom()
})
}
}
}
......
......@@ -104,6 +104,8 @@ class ServerFragment : Fragment(), ServerView {
override fun onDestroyView() {
super.onDestroyView()
// reset deep link info, so user can come back and log to another server...
deepLinkInfo = null
relative_layout.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
}
......@@ -144,9 +146,9 @@ class ServerFragment : Fragment(), ServerView {
hideLoading()
AlertDialog.Builder(it)
.setMessage(getString(R.string.msg_ver_not_recommended, BuildConfig.RECOMMENDED_SERVER_VERSION))
.setPositiveButton(R.string.msg_ok, { _, _ ->
.setPositiveButton(R.string.msg_ok) { _, _ ->
performConnect()
})
}
.create()
.show()
}
......
......@@ -2,7 +2,6 @@ package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.core.behaviours.LoadingView
import chat.rocket.android.core.behaviours.MessageView
import com.google.android.gms.auth.api.credentials.Credential
interface SignupView : LoadingView, MessageView {
......
......@@ -16,10 +16,9 @@ import chat.rocket.android.R.string.message_credentials_saved_successfully
import chat.rocket.android.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.helper.KeyboardHelper
import chat.rocket.android.helper.SmartLockHelper
import chat.rocket.android.helper.TextHelper
import chat.rocket.android.helper.saveCredentials
import chat.rocket.android.util.extensions.*
import com.google.android.gms.auth.api.credentials.Credentials
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
import javax.inject.Inject
......@@ -156,7 +155,7 @@ class SignupFragment : Fragment(), SignupView {
override fun saveSmartLockCredentials(id: String, password: String) {
activity?.let {
SmartLockHelper.save(Credentials.getClient(it), it, id, password)
it.saveCredentials(id, password)
}
}
......
......@@ -16,7 +16,7 @@ import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
import dagger.android.support.HasSupportFragmentInjector
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import chat.rocket.android.util.temp.UI
import kotlinx.coroutines.experimental.launch
import javax.inject.Inject
......@@ -56,9 +56,7 @@ class AuthenticationActivity : AppCompatActivity(), HasSupportFragmentInjector {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
if (currentFragment != null) {
currentFragment.onActivityResult(requestCode, resultCode, data)
}
currentFragment?.onActivityResult(requestCode, resultCode, data)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
......
......@@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.RecyclerView
import chat.rocket.android.R
import chat.rocket.android.chatroom.ui.bottomsheet.MessageActionsBottomSheet
......@@ -94,9 +95,11 @@ abstract class BaseViewHolder<T : BaseUiModel<*>>(
view.context?.let {
if (it is ContextThemeWrapper && it.baseContext is AppCompatActivity) {
with(it.baseContext as AppCompatActivity) {
val actionsBottomSheet = MessageActionsBottomSheet()
actionsBottomSheet.addItems(menuItems, this@BaseViewHolder)
actionsBottomSheet.show(supportFragmentManager, null)
if (this.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
val actionsBottomSheet = MessageActionsBottomSheet()
actionsBottomSheet.addItems(menuItems, this@BaseViewHolder)
actionsBottomSheet.show(supportFragmentManager, null)
}
}
}
}
......
......@@ -24,10 +24,9 @@ class ChatRoomAdapter(
private val roomId: String? = null,
private val roomType: String? = null,
private val roomName: String? = null,
private val presenter: ChatRoomPresenter? = null,
private val actionSelectListener: OnActionSelected? = null,
private val enableActions: Boolean = true,
private val reactionListener: EmojiReactionListener? = null,
private val context: Context? = null
private val reactionListener: EmojiReactionListener? = null
) : RecyclerView.Adapter<BaseViewHolder<*>>() {
private val dataSet = ArrayList<BaseUiModel<*>>()
......@@ -76,7 +75,7 @@ class ChatRoomAdapter(
BaseUiModel.ViewType.MESSAGE_REPLY -> {
val view = parent.inflate(R.layout.item_message_reply)
MessageReplyViewHolder(view, actionsListener, reactionListener) { roomName, permalink ->
presenter?.openDirectMessage(roomName, permalink)
actionSelectListener?.openDirectMessage(roomName, permalink)
}
}
BaseUiModel.ViewType.ACTIONS_ATTACHMENT -> {
......@@ -251,52 +250,53 @@ class ChatRoomAdapter(
message.apply {
when (item.itemId) {
R.id.action_message_info -> {
presenter?.messageInfo(id)
actionSelectListener?.showMessageInfo(id)
}
R.id.action_message_reply -> {
if (roomName != null && roomType != null) {
presenter?.citeMessage(roomName, roomType, id, true)
actionSelectListener?.citeMessage(roomName, roomType, id, true)
}
}
R.id.action_message_quote -> {
if (roomName != null && roomType != null) {
presenter?.citeMessage(roomName, roomType, id, false)
actionSelectListener?.citeMessage(roomName, roomType, id, false)
}
}
R.id.action_message_copy -> {
presenter?.copyMessage(id)
actionSelectListener?.copyMessage(id)
}
R.id.action_message_edit -> {
presenter?.editMessage(roomId, id, message.message)
actionSelectListener?.editMessage(roomId, id, message.message)
}
R.id.action_message_star -> {
if (!item.isChecked) {
presenter?.starMessage(id)
} else {
presenter?.unstarMessage(id)
}
actionSelectListener?.toogleStar(id, !item.isChecked)
}
R.id.action_message_unpin -> {
if (!item.isChecked) {
presenter?.pinMessage(id)
} else {
presenter?.unpinMessage(id)
}
actionSelectListener?.tooglePin(id, !item.isChecked)
}
R.id.action_message_delete -> {
context?.let {
val builder = AlertDialog.Builder(it)
builder.setTitle(it.getString(R.string.msg_delete_message))
.setMessage(it.getString(R.string.msg_delete_description))
.setPositiveButton(it.getString(R.string.msg_ok)) { _, _ -> presenter?.deleteMessage(roomId, id) }
.setNegativeButton(it.getString(R.string.msg_cancel)) { _, _ -> }
.show()
}
actionSelectListener?.deleteMessage(roomId, id)
}
R.id.action_menu_msg_react -> {
actionSelectListener?.showReactions(id)
}
else -> {
TODO("Not implemented")
}
R.id.action_menu_msg_react -> presenter?.showReactions(id)
else -> TODO("Not implemented")
}
}
}
}
interface OnActionSelected {
fun showMessageInfo(id: String)
fun citeMessage(roomName: String, roomType: String, messageId: String, mentionAuthor: Boolean)
fun copyMessage(id: String)
fun editMessage(roomId: String, messageId: String, text: String)
fun toogleStar(id: String, star: Boolean)
fun tooglePin(id: String, pin: Boolean)
fun deleteMessage(roomId: String, id: String)
fun showReactions(id: String)
fun openDirectMessage(roomName: String, message: String)
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.adapter
import android.net.Uri
import android.view.View
import androidx.core.view.isVisible
import chat.rocket.android.chatroom.uimodel.UrlPreviewUiModel
import chat.rocket.android.util.extensions.openTabbedUrl
import chat.rocket.android.emoji.EmojiReactionListener
import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.openTabbedUrl
import kotlinx.android.synthetic.main.message_url_preview.view.*
class UrlPreviewViewHolder(itemView: View,
......@@ -42,7 +41,7 @@ class UrlPreviewViewHolder(itemView: View,
private val onClickListener = { view: View ->
if (data != null) {
view.openTabbedUrl(Uri.parse(data!!.rawData.url))
view.openTabbedUrl(data!!.rawData.url)
}
}
}
\ No newline at end of file
......@@ -70,7 +70,7 @@ import chat.rocket.core.model.Command
import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.DefaultDispatcher
import kotlinx.coroutines.experimental.android.UI
import chat.rocket.android.util.temp.UI
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
......@@ -338,12 +338,9 @@ class ChatRoomPresenter @Inject constructor(
val maxFileSizeAllowed = settings.uploadMaxFileSize()
when {
fileName.isEmpty() -> {
view.showInvalidFileMessage()
}
fileSize > maxFileSizeAllowed -> {
fileName.isEmpty() -> view.showInvalidFileMessage()
fileSize > maxFileSizeAllowed && maxFileSizeAllowed !in -1..0 ->
view.showInvalidFileSize(fileSize, maxFileSizeAllowed)
}
else -> {
var inputStream: InputStream? = uriInteractor.getInputStream(uri)
......@@ -392,9 +389,8 @@ class ChatRoomPresenter @Inject constructor(
val maxFileSizeAllowed = settings.uploadMaxFileSize()
when {
fileSize > maxFileSizeAllowed -> {
fileSize > maxFileSizeAllowed && maxFileSizeAllowed !in -1..0 ->
view.showInvalidFileSize(fileSize, maxFileSizeAllowed)
}
else -> {
retryIO("uploadFile($roomId, $fileName, $mimeType") {
client.uploadFile(
......
......@@ -126,7 +126,8 @@ internal const val MENU_ACTION_PINNED_MESSAGES = 4
internal const val MENU_ACTION_FAVORITE_MESSAGES = 5
internal const val MENU_ACTION_FILES = 6
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener {
class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiReactionListener,
ChatRoomAdapter.OnActionSelected {
@Inject
lateinit var presenter: ChatRoomPresenter
@Inject
......@@ -200,6 +201,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
adapter = ChatRoomAdapter(chatRoomType, chatRoomName, this,
reactionListener = this)
}
override fun onCreateView(
......@@ -968,4 +972,55 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupToolbar(toolbarTitle: String) {
(activity as ChatRoomActivity).showToolbarTitle(toolbarTitle)
}
override fun showMessageInfo(id: String) {
presenter.messageInfo(id)
}
override fun citeMessage(roomName: String, roomType: String, messageId: String, mentionAuthor: Boolean) {
presenter.citeMessage(roomName, roomType, messageId, mentionAuthor)
}
override fun copyMessage(id: String) {
presenter.copyMessage(id)
}
override fun editMessage(roomId: String, messageId: String, text: String) {
presenter.editMessage(roomId, messageId, text)
}
override fun toogleStar(id: String, star: Boolean) {
if (star) {
presenter.starMessage(id)
} else {
presenter.unstarMessage(id)
}
}
override fun tooglePin(id: String, pin: Boolean) {
if (pin) {
presenter.pinMessage(id)
} else {
presenter.unpinMessage(id)
}
}
override fun deleteMessage(roomId: String, id: String) {
ui {
val builder = AlertDialog.Builder(it)
builder.setTitle(it.getString(R.string.msg_delete_message))
.setMessage(it.getString(R.string.msg_delete_description))
.setPositiveButton(it.getString(R.string.msg_ok)) { _, _ -> presenter.deleteMessage(roomId, id) }
.setNegativeButton(it.getString(R.string.msg_cancel)) { _, _ -> }
.show()
}
}
override fun showReactions(id: String) {
presenter.showReactions(id)
}
override fun openDirectMessage(roomName: String, message: String) {
presenter.openDirectMessage(roomName, message)
}
}
......@@ -216,7 +216,7 @@ class ChatRoomsFragment : Fragment(), ChatRoomsView {
AlertDialog.Builder(context)
.setTitle(R.string.dialog_sort_title)
.setView(dialogLayout)
.setPositiveButton("Done") { dialog, _ ->
.setPositiveButton(R.string.dialog_button_done) { dialog, _ ->
invalidateQueryOnSearch()
updateSort()
dialog.dismiss()
......
......@@ -17,7 +17,7 @@ import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.realtime.socket.model.State
import chat.rocket.core.internal.rest.spotlight
import chat.rocket.core.model.SpotlightResult
import kotlinx.coroutines.experimental.android.UI
import chat.rocket.android.util.temp.UI
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.isActive
import kotlinx.coroutines.experimental.launch
......
......@@ -161,7 +161,7 @@ class CreateChannelFragment : Fragment(), CreateChannelView, ActionMode.Callback
override fun prepareToShowChatList() {
with(activity as MainActivity) {
setCheckedNavDrawerItem(R.id.action_chat_rooms)
setCheckedNavDrawerItem(R.id.menu_action_chats)
openDrawer()
getDrawerLayout().postDelayed(1000) {
closeDrawer()
......
......@@ -330,7 +330,7 @@ class DatabaseManager(val context: Application,
id = room.id,
subscriptionId = subscription.id,
type = room.type.toString(),
name = room.name ?: subscription.name ?: throw NullPointerException(),// this should be filtered on the SDK
name = room.name ?: subscription.name ?: throw NullPointerException(), // this should be filtered on the SDK
fullname = subscription.fullName ?: room.fullName,
userId = userId,
ownerId = room.user?.id,
......
......@@ -35,7 +35,7 @@ private const val INTENT_CHAT_ROOM_ID = "chat_room_id"
class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
private lateinit var chatRoomId: String
private lateinit var adapter: ChatRoomAdapter
private val adapter = ChatRoomAdapter(enableActions = false)
@Inject
lateinit var presenter: FavoriteMessagesPresenter
......@@ -66,7 +66,6 @@ class FavoriteMessagesFragment : Fragment(), FavoriteMessagesView {
override fun showFavoriteMessages(favoriteMessages: List<BaseUiModel<*>>) {
ui {
if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false)
recycler_view.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context)
recycler_view.layoutManager = linearLayoutManager
......
......@@ -5,21 +5,19 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.net.Uri
import androidx.core.content.res.ResourcesCompat
import android.text.Spanned
import android.text.style.ClickableSpan
import android.text.style.ReplacementSpan
import android.text.style.StyleSpan
import android.util.Patterns
import android.view.View
import androidx.core.content.res.ResourcesCompat
import chat.rocket.android.R
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.util.extensions.openTabbedUrl
import chat.rocket.android.emoji.EmojiParser
import chat.rocket.android.emoji.EmojiRepository
import chat.rocket.android.emoji.EmojiTypefaceSpan
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.useRealName
import chat.rocket.android.util.extensions.openTabbedUrl
import chat.rocket.common.model.SimpleUser
import chat.rocket.core.model.Message
import org.commonmark.node.AbstractVisitor
......@@ -159,7 +157,7 @@ class MessageParser @Inject constructor(
if (node is ListItem) {
newLine()
builder.append("$number$delimiter ")
super.visit(node.firstChild as Paragraph)
super.visitChildren(node)
newLine()
}
number++
......@@ -187,7 +185,7 @@ class MessageParser @Inject constructor(
if (!link.startsWith("@") && link !in consumed) {
builder.setSpan(object : ClickableSpan() {
override fun onClick(view: View) {
view.openTabbedUrl(getUri(link))
view.openTabbedUrl(link)
}
}, matcher.start(0), matcher.end(0))
consumed.add(link)
......@@ -195,14 +193,6 @@ class MessageParser @Inject constructor(
}
visitChildren(text)
}
private fun getUri(link: String): Uri {
val uri = Uri.parse(link)
if (uri.scheme == null) {
return Uri.parse("http://$link")
}
return uri
}
}
class MentionSpan(
......
......@@ -92,6 +92,59 @@ object OauthHelper {
"&scope=email"
}
/**
* Returns the WordPress-Com Oauth URL.
*
* @param clientId The WordPress-Com client ID.
* @param serverUrl The server URL.
* @param state An unguessable random string used to protect against forgery attacks.
* @return The WordPress-Com Oauth URL.
*/
fun getWordpressComOauthUrl(clientId: String, serverUrl: String, state: String): String {
return "https://public-api.wordpress.com/oauth2/authorize" +
"?client_id=$clientId" +
"&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/wordpress?close" +
"&state=$state" +
"&response_type=code" +
"&scope=auth"
}
/**
* Returns the WordPress custom Oauth URL.
*
* @param host The WordPress custom OAuth host.
* @param authorizePath The WordPress custom OAuth authorization path.
* @param clientId The WordPress custom OAuth client ID.
* @param serverUrl The server URL.
* @param serviceName The service name.
* @param state An unguessable random string used to protect against forgery attacks.
* @param scope The WordPress custom OAuth scope.
* @return The WordPress custom Oauth URL.
*/
fun getWordpressCustomOauthUrl(
host: String,
authorizePath: String,
clientId: String,
serverUrl: String,
serviceName: String,
state: String,
scope: String
): String {
(authorizePath +
"?client_id=$clientId" +
"&redirect_uri=${serverUrl.removeTrailingSlash()}/_oauth/$serviceName?close" +
"&state=$state" +
"&scope=$scope" +
"&response_type=code"
).let {
return if (it.contains(host)) {
it
} else {
host + it
}
}
}
/**
* Returns the Custom Oauth URL.
*
......
......@@ -2,7 +2,6 @@ package chat.rocket.android.helper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.useRealName
import chat.rocket.common.model.SimpleUser
......@@ -12,43 +11,13 @@ import javax.inject.Inject
class UserHelper @Inject constructor(
private val localRepository: LocalRepository,
private val getCurrentServerInteractor: GetCurrentServerInteractor,
settingsRepository: SettingsRepository
private val settingsRepository: SettingsRepository
) {
private val settings: PublicSettings = settingsRepository.get(getCurrentServerInteractor.get()!!)
/**
* Return the display name for the given [user].
* If setting 'Use_Real_Name' is true then the real name will be given, or else
* the username without the '@' is yielded. The fallback for any case is the username, which
* could be null.
*/
fun displayName(user: User): String? {
return if (settings.useRealName()) user.name ?: user.username else user.username
}
fun displayName(user: SimpleUser): String {
return if (settings.useRealName()) user.name ?: user.username ?: "" else user.username ?: ""
}
/**
* Return current logged user's display name.
*
* @see displayName
*/
fun displayName(): String? {
user()?.let {
return displayName(it)
}
return null
}
/**
* Return current logged [User].
*/
fun user(): User? {
return localRepository.getCurrentUser(serverUrl())
}
fun user(): User? = getCurrentServerInteractor.get()?.let { localRepository.getCurrentUser(it) }
/**
* Return the username for the current logged [User].
......@@ -56,13 +25,20 @@ class UserHelper @Inject constructor(
fun username(): String? = localRepository.get(LocalRepository.CURRENT_USERNAME_KEY, null)
/**
* Whether current [User] is admin on the current server.
* Return the display name for the given [user].
* If setting 'Use_Real_Name' is true then the real name will be given, otherwise the username
* without the '@' is yielded.
*/
fun isAdmin(): Boolean {
return user()?.roles?.find { it.equals("admin", ignoreCase = true) } != null
}
fun displayName(user: SimpleUser) = getCurrentServerInteractor.get()?.let {
if (settingsRepository.get(it).useRealName()) {
user.name
} else {
user.username
}
}.orEmpty()
private fun serverUrl(): String {
return getCurrentServerInteractor.get()!!
}
}
\ No newline at end of file
/**
* Whether current [User] is admin on the current server.
*/
fun isAdmin(): Boolean = user()?.roles?.find { it.equals("admin", true) } != null
}
......@@ -10,6 +10,7 @@ import chat.rocket.android.profile.ui.ProfileFragment
import chat.rocket.android.server.ui.changeServerIntent
import chat.rocket.android.settings.ui.SettingsFragment
import chat.rocket.android.util.extensions.addFragment
import chat.rocket.android.webview.adminpanel.ui.AdminPanelWebViewFragment
class MainNavigator(internal val activity: MainActivity) {
......@@ -37,6 +38,12 @@ class MainNavigator(internal val activity: MainActivity) {
}
}
fun toAdminPanel(webPageUrl: String, userToken: String) {
activity.addFragment("AdminPanelWebViewFragment", R.id.fragment_container) {
AdminPanelWebViewFragment.newInstance(webPageUrl, userToken)
}
}
fun toChatRoom(
chatRoomId: String,
chatRoomName: String,
......
......@@ -5,11 +5,13 @@ import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.main.uimodel.NavHeaderUiModel
import chat.rocket.android.main.uimodel.NavHeaderUiModelMapper
import chat.rocket.android.push.GroupedPush
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.RefreshSettingsInteractor
import chat.rocket.android.server.domain.RefreshPermissionsInteractor
import chat.rocket.android.server.domain.RemoveAccountInteractor
import chat.rocket.android.server.domain.SaveAccountInteractor
import chat.rocket.android.server.domain.TokenRepository
......@@ -19,6 +21,7 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.server.presentation.CheckServerPresenter
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.adminPanelUrl
import chat.rocket.android.util.extensions.registerPushToken
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO
......@@ -27,14 +30,12 @@ import chat.rocket.common.RocketChatException
import chat.rocket.common.model.UserStatus
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.realtime.setDefaultStatus
import chat.rocket.core.internal.rest.logout
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.unregisterPushToken
import chat.rocket.core.model.Myself
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import timber.log.Timber
import javax.inject.Inject
......@@ -46,12 +47,14 @@ class MainPresenter @Inject constructor(
private val tokenRepository: TokenRepository,
private val serverInteractor: GetCurrentServerInteractor,
private val refreshSettingsInteractor: RefreshSettingsInteractor,
private val refreshPermissionsInteractor: RefreshPermissionsInteractor,
private val localRepository: LocalRepository,
private val navHeaderMapper: NavHeaderUiModelMapper,
private val saveAccountInteractor: SaveAccountInteractor,
private val getAccountsInteractor: GetAccountsInteractor,
private val removeAccountInteractor: RemoveAccountInteractor,
private val factory: RocketChatClientFactory,
private val groupedPush: GroupedPush,
dbManagerFactory: DatabaseManagerFactory,
getSettingsInteractor: GetSettingsInteractor,
managerFactory: ConnectionManagerFactory
......@@ -61,7 +64,6 @@ class MainPresenter @Inject constructor(
private val dbManager = dbManagerFactory.create(currentServer)
private val client: RocketChatClient = factory.create(currentServer)
private var settings: PublicSettings = getSettingsInteractor.get(serverInteractor.get()!!)
private val userDataChannel = Channel<Myself>()
fun toChatList(chatRoomId: String? = null) = navigator.toChatList(chatRoomId)
......@@ -70,6 +72,10 @@ class MainPresenter @Inject constructor(
fun toSettings() = navigator.toSettings()
fun toAdminPanel() = tokenRepository.get(currentServer)?.let {
navigator.toAdminPanel(currentServer.adminPanelUrl(), it.authToken)
}
fun toCreateChannel() = navigator.toCreateChannel()
fun loadServerAccounts() {
......@@ -153,6 +159,7 @@ class MainPresenter @Inject constructor(
fun connect() {
refreshSettingsInteractor.refreshAsync(currentServer)
refreshPermissionsInteractor.refreshAsync(currentServer)
manager.connect()
}
......@@ -232,4 +239,12 @@ class MainPresenter @Inject constructor(
private fun updateMyself(myself: Myself) =
view.setupUserAccountInfo(navHeaderMapper.mapToUiModel(myself))
}
\ No newline at end of file
fun clearNotificationsForChatroom(chatRoomId: String?) {
if (chatRoomId == null) return
groupedPush.hostToPushMessageList[currentServer]?.let { list ->
list.removeAll { it.info.roomId == chatRoomId }
}
}
}
......@@ -5,13 +5,12 @@ import android.app.Activity
import android.app.AlertDialog
import android.app.ProgressDialog
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Gravity
import android.view.MenuItem
import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import chat.rocket.android.BuildConfig
import chat.rocket.android.R
import chat.rocket.android.main.adapter.AccountsAdapter
......@@ -19,15 +18,16 @@ import chat.rocket.android.main.adapter.Selector
import chat.rocket.android.main.presentation.MainPresenter
import chat.rocket.android.main.presentation.MainView
import chat.rocket.android.main.uimodel.NavHeaderUiModel
import chat.rocket.android.server.domain.PermissionsInteractor
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.ui.INTENT_CHAT_ROOM_ID
import chat.rocket.android.util.extensions.fadeIn
import chat.rocket.android.util.extensions.fadeOut
import chat.rocket.android.util.extensions.rotateBy
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.invalidateFirebaseToken
import chat.rocket.android.util.refreshFCMToken
import chat.rocket.common.model.UserStatus
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.messaging.FirebaseMessaging
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
import dagger.android.DispatchingAndroidInjector
......@@ -51,11 +51,13 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
@Inject
lateinit var presenter: MainPresenter
@Inject
lateinit var permissions: PermissionsInteractor
private var isFragmentAdded: Boolean = false
private var expanded = false
private val headerLayout by lazy { view_navigation.getHeaderView(0) }
private var chatRoomId: String? = null
private var progressDialog : ProgressDialog? = null
private var progressDialog: ProgressDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
......@@ -63,17 +65,14 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
setContentView(R.layout.activity_main)
launch(CommonPool) {
try {
val token = FirebaseInstanceId.getInstance().token
Timber.d("FCM token: $token")
presenter.refreshToken(token)
} catch (ex: Exception) {
Timber.d(ex, "Missing play services...")
}
refreshFCMToken(presenter)
}
chatRoomId = intent.getStringExtra(INTENT_CHAT_ROOM_ID)
println("ChatRoomId: $chatRoomId")
presenter.clearNotificationsForChatroom(chatRoomId)
presenter.connect()
presenter.loadServerAccounts()
presenter.loadCurrentInfo()
......@@ -132,7 +131,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
text_user_name.text = userDisplayName
}
if (userAvatar != null) {
image_avatar.setImageURI(userAvatar)
setAvatar(userAvatar)
}
if (serverLogo != null) {
server_logo.setImageURI(serverLogo)
......@@ -169,9 +168,9 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
}
headerLayout.image_avatar.setOnClickListener {
view_navigation.menu.findItem(R.id.action_profile).isChecked = true
view_navigation.menu.findItem(R.id.menu_action_profile).isChecked = true
presenter.toUserProfile()
drawer_layout.closeDrawer(Gravity.START)
drawer_layout.closeDrawer(GravityCompat.START)
}
}
......@@ -208,7 +207,7 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
}
override fun invalidateToken(token: String) =
FirebaseInstanceId.getInstance().deleteToken(token, FirebaseMessaging.INSTANCE_ID_SCOPE)
invalidateFirebaseToken(token)
override fun showMessage(resId: Int) = showToast(resId)
......@@ -221,44 +220,31 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
}
fun setupNavigationView() {
view_navigation.setNavigationItemSelectedListener { menuItem ->
menuItem.isChecked = true
with (view_navigation.menu) {
clear()
setupMenu(this)
}
view_navigation.setNavigationItemSelectedListener {
it.isChecked = true
closeDrawer()
onNavDrawerItemSelected(menuItem)
onNavDrawerItemSelected(it)
true
}
toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp)
toolbar.setNavigationOnClickListener {
openDrawer()
}
toolbar.setNavigationOnClickListener { openDrawer() }
}
private fun onNavDrawerItemSelected(menuItem: MenuItem) {
when (menuItem.itemId) {
R.id.action_chat_rooms -> {
presenter.toChatList()
}
R.id.action_profile -> {
presenter.toUserProfile()
}
R.id.action_channel -> {
presenter.toCreateChannel()
}
R.id.action_settings -> {
presenter.toSettings()
}
R.id.action_logout -> {
presenter.logout()
}
}
fun setAvatar(avatarUrl: String) {
headerLayout.image_avatar.setImageURI(avatarUrl)
}
fun getDrawerLayout(): DrawerLayout = drawer_layout
fun openDrawer() = drawer_layout.openDrawer(Gravity.START)
fun openDrawer() = drawer_layout.openDrawer(GravityCompat.START)
fun closeDrawer() = drawer_layout.closeDrawer(Gravity.START)
fun closeDrawer() = drawer_layout.closeDrawer(GravityCompat.START)
fun setCheckedNavDrawerItem(@IdRes item: Int) = view_navigation.setCheckedItem(item)
......@@ -270,4 +256,4 @@ class MainActivity : AppCompatActivity(), MainView, HasActivityInjector,
progressDialog?.dismiss()
progressDialog = null
}
}
\ No newline at end of file
}
package chat.rocket.android.main.ui
import android.view.Menu
import android.view.MenuItem
import chat.rocket.android.R
internal fun MainActivity.setupMenu(menu: Menu) {
with(menu) {
add(
R.id.menu_section_one,
R.id.menu_action_chats,
Menu.NONE,
R.string.title_chats
).setIcon(R.drawable.ic_chat_bubble_black_24dp)
.isChecked = true
add(
R.id.menu_section_one,
R.id.menu_action_create_channel,
Menu.NONE,
R.string.action_create_channel
).setIcon(R.drawable.ic_create_black_24dp)
add(
R.id.menu_section_two,
R.id.menu_action_profile,
Menu.NONE,
R.string.title_profile
).setIcon(R.drawable.ic_person_black_24dp)
add(
R.id.menu_section_two,
R.id.menu_action_settings,
Menu.NONE,
R.string.title_settings
).setIcon(R.drawable.ic_settings_black_24dp)
if (permissions.canSeeTheAdminPanel()) {
add(
R.id.menu_section_two,
R.id.menu_action_admin_panel,
Menu.NONE,
R.string.title_admin_panel
).setIcon(R.drawable.ic_settings_black_24dp)
}
add(
R.id.menu_section_three,
R.id.menu_action_logout,
Menu.NONE,
R.string.action_logout
).setIcon(R.drawable.ic_logout_black_24dp)
setGroupCheckable(R.id.menu_section_one, true, true)
setGroupCheckable(R.id.menu_section_two, true, true)
setGroupCheckable(R.id.menu_section_three, true, true)
}
}
internal fun MainActivity.onNavDrawerItemSelected(menuItem: MenuItem) {
when (menuItem.itemId) {
R.id.menu_action_chats-> presenter.toChatList()
R.id.menu_action_create_channel -> presenter.toCreateChannel()
R.id.menu_action_profile -> presenter.toUserProfile()
R.id.menu_action_settings -> presenter.toSettings()
R.id.menu_action_admin_panel -> presenter.toAdminPanel()
R.id.menu_action_logout -> presenter.logout()
}
}
......@@ -36,7 +36,7 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
class MentionsFragment : Fragment(), MentionsView {
private lateinit var chatRoomId: String
private lateinit var adapter: ChatRoomAdapter
private val adapter = ChatRoomAdapter(enableActions = false)
@Inject
lateinit var presenter: MentionsPresenter
......@@ -68,7 +68,6 @@ class MentionsFragment : Fragment(), MentionsView {
override fun showMentions(mentions: List<BaseUiModel<*>>) {
ui {
if (recycler_view.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false)
recycler_view.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context)
......
......@@ -36,7 +36,7 @@ private const val BUNDLE_CHAT_ROOM_ID = "chat_room_id"
class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
private lateinit var chatRoomId: String
private lateinit var adapter: ChatRoomAdapter
private val adapter = ChatRoomAdapter(enableActions = false)
@Inject
lateinit var presenter: PinnedMessagesPresenter
......@@ -68,7 +68,6 @@ class PinnedMessagesFragment : Fragment(), PinnedMessagesView {
override fun showPinnedMessages(pinnedMessages: List<BaseUiModel<*>>) {
ui {
if (recycler_view_pinned.adapter == null) {
adapter = ChatRoomAdapter(enableActions = false)
recycler_view_pinned.adapter = adapter
val linearLayoutManager = LinearLayoutManager(context)
......
package chat.rocket.android.profile.presentation
import android.graphics.Bitmap
import android.net.Uri
import chat.rocket.android.chatroom.domain.UriInteractor
import chat.rocket.android.core.behaviours.showMessage
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.server.domain.GetCurrentServerInteractor
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.extension.compressImageAndGetByteArray
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.me
import chat.rocket.core.internal.rest.resetAvatar
import chat.rocket.core.internal.rest.setAvatar
import chat.rocket.core.internal.rest.updateProfile
import java.util.*
import javax.inject.Inject
class ProfilePresenter @Inject constructor(
private val view: ProfileView,
private val strategy: CancelStrategy,
private val uriInteractor: UriInteractor,
val userHelper: UserHelper,
serverInteractor: GetCurrentServerInteractor,
factory: RocketChatClientFactory
) {
private val serverUrl = serverInteractor.get()!!
private val client: RocketChatClient = factory.create(serverUrl)
private lateinit var myselfId: String
private val myselfId = userHelper.user()?.id ?: ""
private var myselfName = userHelper.user()?.name ?: ""
private var myselfUsername = userHelper.username() ?: ""
private var myselfEmailAddress = userHelper.user()?.emails?.getOrNull(0)?.address ?: ""
fun loadUserProfile() {
launchUI(strategy) {
view.showLoading()
try {
val myself = retryIO("me") { client.me() }
val id = myself.id
val username = myself.username
if (id == null || username == null) {
view.showProfile(
serverUrl.avatarUrl(myselfUsername),
myselfName,
myselfUsername,
myselfEmailAddress
)
} catch (exception: RocketChatException) {
view.showMessage(exception)
} finally {
view.hideLoading()
}
}
}
fun updateUserProfile(email: String, name: String, username: String) {
launchUI(strategy) {
view.showLoading()
try {
retryIO { client.updateProfile(myselfId, email, name, username) }
myselfEmailAddress = email
myselfName = name
myselfUsername = username
view.showProfileUpdateSuccessfullyMessage()
loadUserProfile()
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
} else {
myselfId = id
val avatarUrl = serverUrl.avatarUrl(username)
val email = myself.emails?.getOrNull(0)?.address
view.showProfile(
avatarUrl,
myself.name ?: "",
myself.username ?: "",
email
)
}
} finally {
view.hideLoading()
}
}
}
fun updateAvatar(uri: Uri) {
launchUI(strategy) {
view.showLoading()
try {
retryIO {
client.setAvatar(
uriInteractor.getFileName(uri) ?: uri.toString(),
uriInteractor.getMimeType(uri)
) {
uriInteractor.getInputStream(uri)
}
}
view.reloadUserAvatar(serverUrl.avatarUrl(myselfUsername))
} catch (exception: RocketChatException) {
view.showMessage(exception)
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
} finally {
view.hideLoading()
}
}
}
fun updateUserProfile(email: String, name: String, username: String, avatarUrl: String = "") {
fun preparePhotoAndUpdateAvatar(bitmap: Bitmap) {
launchUI(strategy) {
view.showLoading()
try {
if (avatarUrl != "") {
retryIO { client.setAvatar(avatarUrl) }
val byteArray = bitmap.compressImageAndGetByteArray("image/png")
retryIO {
client.setAvatar(
UUID.randomUUID().toString() + ".png",
"image/png"
) {
byteArray?.inputStream()
}
}
val user = retryIO {
client.updateProfile(
userId = myselfId, email = email, name = name, username = username
)
view.reloadUserAvatar(serverUrl.avatarUrl(myselfUsername))
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
}.ifNull {
view.showGenericErrorMessage()
}
view.showProfileUpdateSuccessfullyMessage()
loadUserProfile()
} finally {
view.hideLoading()
}
}
}
fun resetAvatar() {
launchUI(strategy) {
view.showLoading()
try {
retryIO { client.resetAvatar(myselfId) }
view.reloadUserAvatar(serverUrl.avatarUrl(myselfUsername))
} catch (exception: RocketChatException) {
exception.message?.let {
view.showMessage(it)
......
......@@ -15,6 +15,13 @@ interface ProfileView : LoadingView, MessageView {
*/
fun showProfile(avatarUrl: String, name: String, username: String, email: String?)
/**
* Reloads the user avatar (after successfully updating it).
*
* @param avatarUrl The user avatar URL.
*/
fun reloadUserAvatar(avatarUrl: String)
/**
* Shows a profile update successfully message
*/
......
package chat.rocket.android.profile.ui
import DrawableHelper
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.os.Build
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.appcompat.view.ActionMode
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.profile.presentation.ProfilePresenter
import chat.rocket.android.profile.presentation.ProfileView
import chat.rocket.android.util.extension.asObservable
import chat.rocket.android.util.extensions.*
import chat.rocket.android.util.extension.dispatchImageSelection
import chat.rocket.android.util.extension.dispatchTakePicture
import chat.rocket.android.util.extensions.inflate
import chat.rocket.android.util.extensions.showToast
import chat.rocket.android.util.extensions.textContent
import chat.rocket.android.util.extensions.ui
import com.facebook.drawee.backends.pipeline.Fresco
import dagger.android.support.AndroidSupportInjection
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.rxkotlin.Observables
import kotlinx.android.synthetic.main.avatar_profile.*
import kotlinx.android.synthetic.main.fragment_profile.*
import kotlinx.android.synthetic.main.update_avatar_options.*
import javax.inject.Inject
private const val REQUEST_CODE_FOR_PERFORM_SAF = 1
private const val REQUEST_CODE_FOR_PERFORM_CAMERA = 2
class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
@Inject
lateinit var presenter: ProfilePresenter
private lateinit var currentName: String
private lateinit var currentUsername: String
private lateinit var currentEmail: String
private lateinit var currentAvatar: String
private var currentName = ""
private var currentUsername = ""
private var currentEmail = ""
private var actionMode: ActionMode? = null
private val editTextsDisposable = CompositeDisposable()
......@@ -50,11 +63,12 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
setupListeners()
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
tintEditTextDrawableStart()
}
presenter.loadUserProfile()
subscribeEditTexts()
}
override fun onDestroyView() {
......@@ -62,56 +76,61 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
unsubscribeEditTexts()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
if (resultData != null && resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_CODE_FOR_PERFORM_SAF) {
presenter.updateAvatar(resultData.data)
} else if (requestCode == REQUEST_CODE_FOR_PERFORM_CAMERA) {
presenter.preparePhotoAndUpdateAvatar(resultData.extras["data"] as Bitmap)
}
}
}
override fun showProfile(avatarUrl: String, name: String, username: String, email: String?) {
ui {
image_avatar.setImageURI(avatarUrl)
text_name.textContent = name
text_username.textContent = username
text_email.textContent = email ?: ""
text_avatar_url.textContent = ""
currentName = name
currentUsername = username
currentEmail = email ?: ""
currentAvatar = avatarUrl
profile_container.setVisible(true)
subscribeEditTexts()
profile_container.isVisible = true
}
}
override fun reloadUserAvatar(avatarUrl: String) {
Fresco.getImagePipeline().evictFromCache(avatarUrl.toUri())
image_avatar.setImageURI(avatarUrl)
(activity as MainActivity).setAvatar(avatarUrl)
}
override fun showProfileUpdateSuccessfullyMessage() {
showMessage(getString(R.string.msg_profile_update_successfully))
}
override fun showLoading() {
enableUserInput(false)
ui {
view_loading.setVisible(true)
}
ui { view_loading.isVisible = true }
}
override fun hideLoading() {
ui {
if (view_loading != null) {
view_loading.setVisible(false)
view_loading.isVisible = false
}
}
enableUserInput(true)
}
override fun showMessage(resId: Int) {
ui {
showToast(resId)
}
ui { showToast(resId) }
}
override fun showMessage(message: String) {
ui {
showToast(message)
}
ui { showToast(message) }
}
override fun showGenericErrorMessage() = showMessage(getString(R.string.msg_generic_error))
......@@ -130,8 +149,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
presenter.updateUserProfile(
text_email.textContent,
text_name.textContent,
text_username.textContent,
text_avatar_url.textContent
text_username.textContent
)
mode.finish()
true
......@@ -151,6 +169,37 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
getString(R.string.title_profile)
}
private fun setupListeners() {
image_avatar.setOnClickListener { showUpdateAvatarOptions() }
view_dim.setOnClickListener { hideUpdateAvatarOptions() }
button_open_gallery.setOnClickListener {
dispatchImageSelection(REQUEST_CODE_FOR_PERFORM_SAF)
hideUpdateAvatarOptions()
}
button_take_photo.setOnClickListener {
dispatchTakePicture(REQUEST_CODE_FOR_PERFORM_CAMERA)
hideUpdateAvatarOptions()
}
button_reset_avatar.setOnClickListener {
hideUpdateAvatarOptions()
presenter.resetAvatar()
}
}
private fun showUpdateAvatarOptions() {
view_dim.isVisible = true
layout_update_avatar_options.isVisible = true
}
private fun hideUpdateAvatarOptions() {
layout_update_avatar_options.isVisible = false
view_dim.isVisible = false
}
private fun tintEditTextDrawableStart() {
(activity as MainActivity).apply {
val personDrawable =
......@@ -158,14 +207,12 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
val atDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_at_black_24dp, this)
val emailDrawable =
DrawableHelper.getDrawableFromId(R.drawable.ic_email_black_24dp, this)
val linkDrawable = DrawableHelper.getDrawableFromId(R.drawable.ic_link_black_24dp, this)
val drawables = arrayOf(personDrawable, atDrawable, emailDrawable, linkDrawable)
val drawables = arrayOf(personDrawable, atDrawable, emailDrawable)
DrawableHelper.wrapDrawables(drawables)
DrawableHelper.tintDrawables(drawables, this, R.color.colorDrawableTintGrey)
DrawableHelper.compoundDrawables(
arrayOf(text_name, text_username, text_email, text_avatar_url),
drawables
arrayOf(text_name, text_username, text_email), drawables
)
}
}
......@@ -174,13 +221,11 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
editTextsDisposable.add(Observables.combineLatest(
text_name.asObservable(),
text_username.asObservable(),
text_email.asObservable(),
text_avatar_url.asObservable()
) { text_name, text_username, text_email, text_avatar_url ->
text_email.asObservable()
) { text_name, text_username, text_email ->
return@combineLatest (text_name.toString() != currentName ||
text_username.toString() != currentUsername ||
text_email.toString() != currentEmail ||
(text_avatar_url.toString() != "" && text_avatar_url.toString() != currentAvatar))
text_email.toString() != currentEmail)
}.subscribe { isValid ->
if (isValid) {
startActionMode()
......@@ -190,9 +235,7 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
})
}
private fun unsubscribeEditTexts() {
editTextsDisposable.clear()
}
private fun unsubscribeEditTexts() = editTextsDisposable.clear()
private fun startActionMode() {
if (actionMode == null) {
......@@ -207,7 +250,6 @@ class ProfileFragment : Fragment(), ProfileView, ActionMode.Callback {
text_username.isEnabled = value
text_username.isEnabled = value
text_email.isEnabled = value
text_avatar_url.isEnabled = value
}
}
}
......@@ -4,7 +4,6 @@ import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import android.widget.Toast
import chat.rocket.android.R
......@@ -12,7 +11,7 @@ import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.common.RocketChatException
import chat.rocket.core.internal.rest.sendMessage
import dagger.android.AndroidInjection
import kotlinx.coroutines.experimental.android.UI
import chat.rocket.android.util.temp.UI
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import java.util.*
......
......@@ -12,4 +12,4 @@ class GroupedPush {
// Map a hostname to a list of push messages that pertain to it.
val hostToPushMessageList = HashMap<String, MutableList<PushMessage>>()
}
\ No newline at end of file
}
......@@ -18,6 +18,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import android.text.Html
import android.text.Spanned
import androidx.core.content.ContextCompat
import chat.rocket.android.R
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.server.domain.GetAccountInteractor
......@@ -36,10 +37,6 @@ import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
/**
* Refer to: https://github.com/RocketChat/Rocket.Chat.Android/blob/9e846b7fde8fe0c74b9e0117c37ce49293308db5/app/src/main/java/chat/rocket/android/push/PushManager.kt
* for old source code.
*/
class PushManager @Inject constructor(
private val groupedPushes: GroupedPush,
private val manager: NotificationManager,
......@@ -80,7 +77,7 @@ class PushManager @Inject constructor(
showNotification(pushMessage)
} catch (ex: Exception) {
Timber.d(ex, "Error parsing PUSH message: $data")
Timber.e(ex, "Error parsing PUSH message: $data")
ex.printStackTrace()
}
}
......@@ -101,7 +98,7 @@ class PushManager @Inject constructor(
val groupTuple = getGroupForHost(host)
groupTuple.second.incrementAndGet()
val notIdListForHostname: MutableList<PushMessage>? = groupedPushes.hostToPushMessageList.get(host)
val notIdListForHostname: MutableList<PushMessage>? = groupedPushes.hostToPushMessageList[host]
if (notIdListForHostname == null) {
groupedPushes.hostToPushMessageList[host] = arrayListOf(pushMessage)
} else {
......@@ -365,14 +362,14 @@ class PushManager @Inject constructor(
val res = context.resources
val smallIcon = res.getIdentifier(
"rocket_chat_notification", "drawable", context.packageName)
with(this, {
with(this) {
setAutoCancel(true)
setShowWhen(true)
color = context.resources.getColor(R.color.colorPrimary)
color = ContextCompat.getColor(context, R.color.colorPrimary)
setDefaults(Notification.DEFAULT_ALL)
setSmallIcon(smallIcon)
setSound(alarmSound)
})
}
return this
}
}
......
package chat.rocket.android.server.domain
import chat.rocket.android.helper.UserHelper
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.core.model.Permission
import javax.inject.Inject
// Creating rooms
const val CREATE_PUBLIC_CHANNELS = "create-c"
const val CREATE_DIRECT_MESSAGES = "create-d"
const val CREATE_PRIVATE_CHANNELS = "create-p"
private const val CREATE_PUBLIC_CHANNELS = "create-c"
private const val CREATE_DIRECT_MESSAGES = "create-d"
private const val CREATE_PRIVATE_CHANNELS = "create-p"
// Messages
const val DELETE_MESSAGE = "delete-message"
const val FORCE_DELETE_MESSAGE = "force-delete-message"
const val EDIT_MESSAGE = "edit-message"
const val PIN_MESSAGE = "pin-message"
const val POST_READONLY = "post-readonly"
private const val DELETE_MESSAGE = "delete-message"
private const val FORCE_DELETE_MESSAGE = "force-delete-message"
private const val EDIT_MESSAGE = "edit-message"
private const val PIN_MESSAGE = "pin-message"
private const val POST_READONLY = "post-readonly"
private const val VIEW_STATISTICS = "view-statistics"
private const val VIEW_ROOM_ADMINISTRATION = "view-room-administration"
private const val VIEW_USER_ADMINISTRATION = "view-user-administration"
private const val VIEW_PRIVILEGED_SETTING = "view-privileged-setting"
class PermissionsInteractor @Inject constructor(
private val settingsRepository: SettingsRepository,
......@@ -23,14 +26,8 @@ class PermissionsInteractor @Inject constructor(
private val getCurrentServerInteractor: GetCurrentServerInteractor,
private val userHelper: UserHelper
) {
private fun publicSettings(): PublicSettings? = settingsRepository.get(currentServerUrl()!!)
fun saveAll(permissions: List<Permission>) {
val url = currentServerUrl()!!
permissions.forEach { permissionsRepository.save(url, it) }
}
/**
* Check whether the user is allowed to delete a message.
*/
......@@ -71,6 +68,28 @@ class PermissionsInteractor @Inject constructor(
} == true || userHelper.isAdmin()
}
fun canSeeTheAdminPanel(): Boolean {
currentServerUrl()?.let { serverUrl ->
val viewStatistics =
permissionsRepository.get(serverUrl, VIEW_STATISTICS)
val viewRoomAdministration =
permissionsRepository.get(serverUrl, VIEW_ROOM_ADMINISTRATION)
val viewUserAdministration =
permissionsRepository.get(serverUrl, VIEW_USER_ADMINISTRATION)
val viewPrivilegedSetting =
permissionsRepository.get(serverUrl, VIEW_PRIVILEGED_SETTING)
userHelper.user()?.roles?.let { userRolesList ->
return viewStatistics?.roles?.any { userRolesList.contains(it) } == true ||
viewRoomAdministration?.roles?.any { userRolesList.contains(it) } == true ||
viewUserAdministration?.roles?.any { userRolesList.contains(it) } == true ||
viewPrivilegedSetting?.roles?.any { userRolesList.contains(it) } == true
}
}
return false
}
private fun currentServerUrl(): String? {
return getCurrentServerInteractor.get()
}
......
......@@ -5,20 +5,20 @@ import chat.rocket.core.model.Permission
interface PermissionsRepository {
/**
* Store [permission] locally.
* Stores a list of [Permission] locally.
*
* @param url The server url from where we're interest to store the permission.
* @param permission The permission to store.
* @param url The server url to store the permission.
* @param permissionList The permission list to store.
*/
fun save(url: String, permission: Permission)
fun save(url: String, permissionList: List<Permission>)
/**
* Get permission given by the [permissionId] and for the server [url].
* Gets permission given by the [permissionId] and for the server [url].
*
* @param url The server url from where we're interested on getting the permissions.
* @param permissionId the id of the permission to get.
* @param url The server url to get the permissions from.
* @param permissionId the ID of the permission to get.
*
* @return The interested [Permission] or null if not found.
* @return The [Permission] or null if not found.
*/
fun get(url: String, permissionId: String): Permission?
}
\ No newline at end of file
package chat.rocket.android.server.domain
import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.permissions
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.launch
import timber.log.Timber
import javax.inject.Inject
/**
* This class reloads the current logged server permission whenever its used.
*/
class RefreshPermissionsInteractor @Inject constructor(
private val factory: RocketChatClientFactory,
private val repository: PermissionsRepository
) {
fun refreshAsync(server: String) {
launch(CommonPool) {
try {
factory.create(server).let { client ->
val permissions = retryIO(
description = "permissions",
times = 5,
maxDelay = 5000,
initialDelay = 300
) {
client.permissions()
}
repository.save(server, permissions)
}
} catch (ex: Exception) {
Timber.e(ex, "Error refreshing permissions for: $server")
}
}
}
}
\ No newline at end of file
......@@ -4,36 +4,75 @@ import chat.rocket.android.server.infraestructure.RocketChatClientFactory
import chat.rocket.android.util.retryIO
import chat.rocket.core.internal.rest.settings
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import timber.log.Timber
import javax.inject.Inject
/**
* This class reloads the current logged server settings whenever needed.
*/
class RefreshSettingsInteractor @Inject constructor(
private val factory: RocketChatClientFactory,
private val repository: SettingsRepository
) {
private var settingsFilter = arrayOf(
LDAP_ENABLE, CAS_ENABLE, CAS_LOGIN_URL,
LDAP_ENABLE,
CAS_ENABLE,
CAS_LOGIN_URL,
ACCOUNT_REGISTRATION, ACCOUNT_LOGIN_FORM, ACCOUNT_PASSWORD_RESET, ACCOUNT_CUSTOM_FIELDS,
ACCOUNT_GOOGLE, ACCOUNT_FACEBOOK, ACCOUNT_GITHUB, ACCOUNT_LINKEDIN, ACCOUNT_METEOR,
ACCOUNT_TWITTER, ACCOUNT_WORDPRESS, ACCOUNT_GITLAB, ACCOUNT_GITLAB_URL,
ACCOUNT_REGISTRATION,
ACCOUNT_LOGIN_FORM,
ACCOUNT_PASSWORD_RESET,
ACCOUNT_CUSTOM_FIELDS,
ACCOUNT_GOOGLE,
ACCOUNT_FACEBOOK,
ACCOUNT_GITHUB,
ACCOUNT_LINKEDIN,
ACCOUNT_METEOR,
ACCOUNT_TWITTER,
ACCOUNT_GITLAB,
ACCOUNT_GITLAB_URL,
ACCOUNT_WORDPRESS,
ACCOUNT_WORDPRESS_URL,
SITE_URL, SITE_NAME, FAVICON_512, FAVICON_196, USE_REALNAME, ALLOW_ROOM_NAME_SPECIAL_CHARS,
FAVORITE_ROOMS, UPLOAD_STORAGE_TYPE, UPLOAD_MAX_FILE_SIZE, UPLOAD_WHITELIST_MIMETYPES,
HIDE_USER_JOIN, HIDE_USER_LEAVE,
HIDE_TYPE_AU, HIDE_MUTE_UNMUTE, HIDE_TYPE_RU, ALLOW_MESSAGE_DELETING,
ALLOW_MESSAGE_EDITING, ALLOW_MESSAGE_PINNING, ALLOW_MESSAGE_STARRING, SHOW_DELETED_STATUS, SHOW_EDITED_STATUS,
WIDE_TILE_310, STORE_LAST_MESSAGE, MESSAGE_READ_RECEIPT_ENABLED, MESSAGE_READ_RECEIPT_STORE_USERS)
SITE_URL,
SITE_NAME,
FAVICON_512,
FAVICON_196,
USE_REALNAME,
ALLOW_ROOM_NAME_SPECIAL_CHARS,
FAVORITE_ROOMS,
UPLOAD_STORAGE_TYPE,
UPLOAD_MAX_FILE_SIZE,
UPLOAD_WHITELIST_MIMETYPES,
HIDE_USER_JOIN,
HIDE_USER_LEAVE,
HIDE_TYPE_AU,
HIDE_MUTE_UNMUTE,
HIDE_TYPE_RU,
ALLOW_MESSAGE_DELETING,
ALLOW_MESSAGE_EDITING,
ALLOW_MESSAGE_PINNING,
ALLOW_MESSAGE_STARRING,
SHOW_DELETED_STATUS,
SHOW_EDITED_STATUS,
WIDE_TILE_310,
STORE_LAST_MESSAGE,
MESSAGE_READ_RECEIPT_ENABLED,
MESSAGE_READ_RECEIPT_STORE_USERS
)
suspend fun refresh(server: String) {
withContext(CommonPool) {
factory.create(server).let { client ->
val settings = retryIO(description = "settings", times = 5,
maxDelay = 5000, initialDelay = 300) {
val settings = retryIO(
description = "settings",
times = 5,
maxDelay = 5000,
initialDelay = 300
) {
client.settings(*settingsFilter)
}
repository.save(server, settings)
......
package chat.rocket.android.server.domain
import chat.rocket.core.model.Value
import javax.inject.Inject
class SaveSettingsInteractor @Inject constructor(private val repository: SettingsRepository) {
fun save(url: String, settings: Map<String, Value<Any>>) = repository.save(url, settings)
}
\ No newline at end of file
......@@ -19,9 +19,10 @@ const val ACCOUNT_GITHUB = "Accounts_OAuth_Github"
const val ACCOUNT_LINKEDIN = "Accounts_OAuth_Linkedin"
const val ACCOUNT_METEOR = "Accounts_OAuth_Meteor"
const val ACCOUNT_TWITTER = "Accounts_OAuth_Twitter"
const val ACCOUNT_WORDPRESS = "Accounts_OAuth_Wordpress"
const val ACCOUNT_GITLAB = "Accounts_OAuth_Gitlab"
const val ACCOUNT_GITLAB_URL = "API_Gitlab_URL"
const val ACCOUNT_WORDPRESS = "Accounts_OAuth_Wordpress"
const val ACCOUNT_WORDPRESS_URL = "API_Wordpress_URL"
const val SITE_URL = "Site_Url"
const val SITE_NAME = "Site_Name"
......@@ -71,6 +72,7 @@ fun PublicSettings.isTwitterAuthenticationEnabled(): Boolean = this[ACCOUNT_TWIT
fun PublicSettings.isGitlabAuthenticationEnabled(): Boolean = this[ACCOUNT_GITLAB]?.value == true
fun PublicSettings.gitlabUrl(): String? = this[ACCOUNT_GITLAB_URL]?.value as String?
fun PublicSettings.isWordpressAuthenticationEnabled(): Boolean = this[ACCOUNT_WORDPRESS]?.value == true
fun PublicSettings.wordpressUrl(): String? = this[ACCOUNT_WORDPRESS_URL]?.value as String?
fun PublicSettings.useRealName(): Boolean = this[USE_REALNAME]?.value == true
fun PublicSettings.useSpecialCharsOnRoom(): Boolean = this[ALLOW_ROOM_NAME_SPECIAL_CHARS]?.value == true
......
......@@ -2,7 +2,6 @@ package chat.rocket.android.server.infraestructure
import androidx.lifecycle.MutableLiveData
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.User
import chat.rocket.common.model.UserStatus
......@@ -194,6 +193,14 @@ class ConnectionManager(
client.setTemporaryStatus(userStatus)
}
fun resetReconnectionTimer() {
// if we are waiting to reconnect, immediately try to reconnect
// and reset the reconnection counter
if (client.state is State.Waiting) {
client.connect(resetCounter = true)
}
}
private fun resubscribeRooms() {
roomMessagesChannels.toList().map { (roomId, channel) ->
client.subscribeRoomMessages(roomId) { _, id ->
......
......@@ -9,11 +9,12 @@ class SharedPreferencesPermissionsRepository(
private val localRepository: LocalRepository,
moshi: Moshi
) : PermissionsRepository {
private val adapter = moshi.adapter(Permission::class.java)
override fun save(url: String, permission: Permission) {
localRepository.save(getPermissionKey(url, permission.id), adapter.toJson(permission))
override fun save(url: String, permissionList: List<Permission>) {
for (permission in permissionList) {
localRepository.save(getPermissionKey(url, permission.id), adapter.toJson(permission))
}
}
override fun get(url: String, permissionId: String): Permission? {
......
......@@ -19,9 +19,6 @@ import kotlinx.android.synthetic.main.fragment_settings.*
import kotlin.reflect.KClass
class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListener {
companion object {
fun newInstance() = SettingsFragment()
}
override fun onCreateView(
inflater: LayoutInflater,
......@@ -69,4 +66,8 @@ class SettingsFragment : Fragment(), SettingsView, AdapterView.OnItemClickListen
startActivity(Intent(activity, classType.java))
activity?.overridePendingTransition(R.anim.open_enter, R.anim.open_exit)
}
companion object {
fun newInstance() = SettingsFragment()
}
}
......@@ -4,7 +4,7 @@ import android.os.Looper
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import chat.rocket.android.util.temp.UI
import kotlinx.coroutines.experimental.launch
inline fun Fragment.ui(crossinline block: (activity: FragmentActivity) -> Unit): Job? {
......
......@@ -3,6 +3,7 @@ package chat.rocket.android.util.extensions
import android.graphics.Color
import android.util.Patterns
import chat.rocket.common.model.Token
import okhttp3.HttpUrl
import timber.log.Timber
fun String.removeTrailingSlash(): String {
......@@ -50,6 +51,8 @@ fun String.termsOfServiceUrl() = "${removeTrailingSlash()}/terms-of-service"
fun String.privacyPolicyUrl() = "${removeTrailingSlash()}/privacy-policy"
fun String.adminPanelUrl() = "${removeTrailingSlash()}/admin/info?layout=embedded"
fun String.isValidUrl(): Boolean = Patterns.WEB_URL.matcher(this).matches()
fun String.parseColor(): Int {
......@@ -64,4 +67,11 @@ fun String.parseColor(): Int {
fun String.userId(userId: String?): String? {
return userId?.let { this.replace(it, "") }
}
fun String.lowercaseUrl(): String? {
val httpUrl = HttpUrl.parse(this)
val newScheme = httpUrl?.scheme()?.toLowerCase()
return httpUrl?.newBuilder()?.scheme(newScheme)?.build()?.toString()
}
\ No newline at end of file
......@@ -7,15 +7,27 @@ import android.view.View
import chat.rocket.android.R
import timber.log.Timber
fun View.openTabbedUrl(url: Uri) {
fun View.openTabbedUrl(url: String) {
with(this) {
val uri = url.ensureScheme()
val tabsbuilder = CustomTabsIntent.Builder()
tabsbuilder.setToolbarColor(ResourcesCompat.getColor(context.resources, R.color.colorPrimary, context.theme))
val customTabsIntent = tabsbuilder.build()
try {
customTabsIntent.launchUrl(context, url)
customTabsIntent.launchUrl(context, uri)
} catch (ex: Exception) {
Timber.d(ex, "Unable to launch URL")
}
}
}
private fun String.ensureScheme(): Uri? {
// check if the URL starts with a http(s) scheme
val url = if (!this.matches(Regex("^([h|H][t|T][t|T][p|P]).*"))) {
"http://$this"
} else {
this
}
return Uri.parse(url.lowercaseUrl())
}
\ No newline at end of file
......@@ -4,12 +4,11 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import chat.rocket.android.util.temp.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import kotlin.coroutines.experimental.CoroutineContext
class TransformedLiveData<Source, Output>(
private val runContext: CoroutineContext = CommonPool,
private val source: LiveData<Source>,
......
package chat.rocket.android.webview.adminpanel.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import chat.rocket.android.R
import chat.rocket.android.util.extensions.inflate
import kotlinx.android.synthetic.main.fragment_admin_panel_web_view.*
private const val BUNDLE_WEB_PAGE_URL = "web_page_url"
private const val BUNDLE_USER_TOKEN = "user_token"
class AdminPanelWebViewFragment : Fragment() {
private lateinit var webPageUrl: String
private lateinit var userToken: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bundle = arguments
if (bundle != null) {
webPageUrl = bundle.getString(BUNDLE_WEB_PAGE_URL)
userToken = bundle.getString(BUNDLE_USER_TOKEN)
} else {
requireNotNull(bundle) { "no arguments supplied when the fragment was instantiated" }
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = container?.inflate(R.layout.fragment_admin_panel_web_view)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar()
setupWebView()
}
private fun setupToolbar() {
(activity as AppCompatActivity?)?.supportActionBar?.title =
getString(R.string.title_admin_panel)
}
@SuppressLint("SetJavaScriptEnabled")
private fun setupWebView() {
with(web_view.settings) {
javaScriptEnabled = true
domStorageEnabled = true
}
web_view.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
view_loading.hide()
web_view.evaluateJavascript("Meteor.loginWithToken('$userToken', function() { })") {}
}
}
web_view.loadUrl(webPageUrl)
}
companion object {
fun newInstance(webPageUrl: String, userToken: String) = AdminPanelWebViewFragment().apply {
arguments = Bundle(2).apply {
putString(BUNDLE_WEB_PAGE_URL, webPageUrl)
putString(BUNDLE_USER_TOKEN, userToken)
}
}
}
}
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>
\ No newline at end of file
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
<path
android:fillColor="#FF000000"
android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="290dp"
android:height="40dp"
android:viewportWidth="290"
android:viewportHeight="40">
<path
android:fillColor="#428BBA"
android:fillType="evenOdd"
android:pathData="M2,0L288,0A2,2 0,0 1,290 2L290,38A2,2 0,0 1,288 40L2,40A2,2 0,0 1,0 38L0,2A2,2 0,0 1,2 0z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="#FFFFFFFF"
android:fillType="evenOdd"
android:pathData="M134.878,23.188L136.628,14.625L138.589,14.625L135.964,26L134.073,26L131.909,17.695L129.698,26L127.8,26L125.175,14.625L127.136,14.625L128.901,23.172L131.073,14.625L132.729,14.625L134.878,23.188ZM139.824,21.695C139.824,20.867 139.988,20.121 140.316,19.457C140.645,18.793 141.105,18.283 141.699,17.926C142.293,17.569 142.975,17.391 143.746,17.391C144.887,17.391 145.813,17.758 146.523,18.492C147.234,19.227 147.618,20.201 147.676,21.414L147.684,21.859C147.684,22.693 147.523,23.437 147.203,24.094C146.883,24.75 146.424,25.258 145.828,25.617C145.232,25.977 144.543,26.156 143.762,26.156C142.569,26.156 141.615,25.759 140.898,24.965C140.182,24.171 139.824,23.112 139.824,21.789L139.824,21.695ZM141.723,21.859C141.723,22.729 141.902,23.41 142.262,23.902C142.621,24.395 143.121,24.641 143.762,24.641C144.402,24.641 144.901,24.391 145.258,23.891C145.615,23.391 145.793,22.659 145.793,21.695C145.793,20.841 145.609,20.164 145.242,19.664C144.875,19.164 144.376,18.914 143.746,18.914C143.126,18.914 142.634,19.16 142.27,19.652C141.905,20.145 141.723,20.88 141.723,21.859ZM154.286,19.281C154.036,19.24 153.778,19.219 153.513,19.219C152.643,19.219 152.057,19.552 151.755,20.219L151.755,26L149.857,26L149.857,17.547L151.669,17.547L151.716,18.492C152.174,17.758 152.81,17.391 153.622,17.391C153.893,17.391 154.117,17.427 154.294,17.5L154.286,19.281ZM155.553,21.711C155.553,20.409 155.855,19.363 156.459,18.574C157.063,17.785 157.873,17.391 158.889,17.391C159.785,17.391 160.509,17.703 161.061,18.328L161.061,14L162.959,14L162.959,26L161.241,26L161.147,25.125C160.579,25.813 159.821,26.156 158.873,26.156C157.884,26.156 157.083,25.758 156.471,24.961C155.859,24.164 155.553,23.081 155.553,21.711ZM157.451,21.875C157.451,22.734 157.617,23.405 157.948,23.887C158.278,24.368 158.748,24.609 159.358,24.609C160.134,24.609 160.701,24.263 161.061,23.57L161.061,19.961C160.712,19.284 160.149,18.945 159.373,18.945C158.759,18.945 158.285,19.189 157.951,19.676C157.618,20.163 157.451,20.896 157.451,21.875ZM167.671,21.773L167.671,26L165.695,26L165.695,14.625L170.046,14.625C171.317,14.625 172.326,14.956 173.074,15.617C173.821,16.279 174.195,17.154 174.195,18.242C174.195,19.357 173.829,20.224 173.097,20.844C172.365,21.464 171.341,21.773 170.023,21.773L167.671,21.773ZM167.671,20.188L170.046,20.188C170.749,20.188 171.286,20.022 171.656,19.691C172.025,19.361 172.21,18.883 172.21,18.258C172.21,17.643 172.023,17.152 171.648,16.785C171.273,16.418 170.757,16.229 170.101,16.219L167.671,16.219L167.671,20.188ZM180.735,19.281C180.485,19.24 180.227,19.219 179.962,19.219C179.092,19.219 178.506,19.552 178.204,20.219L178.204,26L176.305,26L176.305,17.547L178.118,17.547L178.165,18.492C178.623,17.758 179.258,17.391 180.071,17.391C180.342,17.391 180.566,17.427 180.743,17.5L180.735,19.281ZM186.08,26.156C184.877,26.156 183.901,25.777 183.154,25.02C182.407,24.262 182.033,23.253 182.033,21.992L182.033,21.758C182.033,20.914 182.196,20.16 182.521,19.496C182.847,18.832 183.304,18.315 183.892,17.945C184.481,17.576 185.137,17.391 185.861,17.391C187.012,17.391 187.901,17.758 188.529,18.492C189.157,19.227 189.47,20.266 189.47,21.609L189.47,22.375L183.947,22.375C184.004,23.073 184.237,23.625 184.646,24.031C185.055,24.438 185.569,24.641 186.189,24.641C187.059,24.641 187.767,24.289 188.314,23.586L189.338,24.563C188.999,25.068 188.547,25.46 187.982,25.738C187.417,26.017 186.783,26.156 186.08,26.156ZM185.853,18.914C185.332,18.914 184.912,19.096 184.592,19.461C184.271,19.826 184.067,20.333 183.978,20.984L187.595,20.984L187.595,20.844C187.554,20.208 187.384,19.728 187.088,19.402C186.791,19.077 186.379,18.914 185.853,18.914ZM196.253,23.703C196.253,23.365 196.113,23.107 195.835,22.93C195.556,22.753 195.094,22.596 194.448,22.461C193.802,22.326 193.263,22.154 192.831,21.945C191.883,21.487 191.409,20.823 191.409,19.953C191.409,19.224 191.716,18.615 192.331,18.125C192.945,17.635 193.727,17.391 194.675,17.391C195.685,17.391 196.501,17.641 197.124,18.141C197.746,18.641 198.057,19.289 198.057,20.086L196.159,20.086C196.159,19.721 196.024,19.418 195.753,19.176C195.482,18.934 195.123,18.813 194.675,18.813C194.258,18.813 193.918,18.909 193.655,19.102C193.392,19.294 193.261,19.552 193.261,19.875C193.261,20.167 193.383,20.393 193.628,20.555C193.873,20.716 194.367,20.879 195.112,21.043C195.857,21.207 196.442,21.402 196.866,21.629C197.291,21.855 197.606,22.128 197.811,22.445C198.017,22.763 198.12,23.148 198.12,23.602C198.12,24.362 197.805,24.978 197.175,25.449C196.544,25.921 195.719,26.156 194.698,26.156C194.005,26.156 193.388,26.031 192.847,25.781C192.305,25.531 191.883,25.188 191.581,24.75C191.279,24.312 191.128,23.841 191.128,23.336L192.972,23.336C192.998,23.784 193.167,24.129 193.479,24.371C193.792,24.613 194.206,24.734 194.722,24.734C195.222,24.734 195.602,24.639 195.862,24.449C196.123,24.259 196.253,24.01 196.253,23.703ZM205.082,23.703C205.082,23.365 204.943,23.107 204.664,22.93C204.385,22.753 203.923,22.596 203.277,22.461C202.632,22.326 202.092,22.154 201.66,21.945C200.712,21.487 200.238,20.823 200.238,19.953C200.238,19.224 200.546,18.615 201.16,18.125C201.775,17.635 202.556,17.391 203.504,17.391C204.514,17.391 205.331,17.641 205.953,18.141C206.576,18.641 206.887,19.289 206.887,20.086L204.988,20.086C204.988,19.721 204.853,19.418 204.582,19.176C204.311,18.934 203.952,18.813 203.504,18.813C203.087,18.813 202.747,18.909 202.484,19.102C202.221,19.294 202.09,19.552 202.09,19.875C202.09,20.167 202.212,20.393 202.457,20.555C202.702,20.716 203.197,20.879 203.941,21.043C204.686,21.207 205.271,21.402 205.695,21.629C206.12,21.855 206.435,22.128 206.641,22.445C206.846,22.763 206.949,23.148 206.949,23.602C206.949,24.362 206.634,24.978 206.004,25.449C205.374,25.921 204.548,26.156 203.527,26.156C202.835,26.156 202.217,26.031 201.676,25.781C201.134,25.531 200.712,25.188 200.41,24.75C200.108,24.312 199.957,23.841 199.957,23.336L201.801,23.336C201.827,23.784 201.996,24.129 202.309,24.371C202.621,24.613 203.035,24.734 203.551,24.734C204.051,24.734 204.431,24.639 204.691,24.449C204.952,24.259 205.082,24.01 205.082,23.703Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="#FFFFFFFF"
android:fillType="nonZero"
android:pathData="M104.26,11C99.146,11 95,15.146 95,20.26C95,25.375 99.146,29.521 104.26,29.521C109.375,29.521 113.521,25.375 113.521,20.26C113.521,15.146 109.375,11 104.26,11M104.26,11.556C105.995,11.552 107.69,12.07 109.127,13.042C110.054,13.669 110.852,14.467 111.479,15.394C112.451,16.83 112.969,18.526 112.965,20.26C112.969,21.995 112.451,23.69 111.479,25.127C110.852,26.054 110.054,26.852 109.127,27.479C107.69,28.451 105.995,28.969 104.26,28.965C102.526,28.969 100.83,28.451 99.394,27.479C98.467,26.852 97.669,26.054 97.042,25.127C96.07,23.69 95.552,21.995 95.556,20.26C95.552,18.526 96.07,16.83 97.042,15.394C97.669,14.467 98.467,13.669 99.393,13.042C100.83,12.07 102.526,11.552 104.26,11.556"
android:strokeWidth="1"
android:strokeColor="#00000000" />
<path
android:fillColor="#FFFFFFFF"
android:fillType="nonZero"
android:pathData="M111.032,16.558C111.066,16.804 111.084,17.068 111.084,17.351C111.084,18.134 110.937,19.014 110.497,20.115L108.14,26.93C110.516,25.549 111.978,23.008 111.977,20.26C111.979,18.966 111.654,17.693 111.032,16.559L111.032,16.558ZM104.396,20.935L102.08,27.663C103.635,28.121 105.294,28.078 106.823,27.54C106.802,27.506 106.783,27.471 106.768,27.434L104.396,20.935ZM109.47,19.871C109.47,18.917 109.128,18.257 108.834,17.743C108.443,17.106 108.076,16.569 108.076,15.933C108.076,15.223 108.613,14.563 109.372,14.563C109.406,14.563 109.438,14.567 109.472,14.569C108.05,13.264 106.19,12.541 104.26,12.544C101.662,12.543 99.238,13.851 97.813,16.022C97.994,16.028 98.165,16.032 98.309,16.032C99.116,16.032 100.366,15.934 100.366,15.934C100.782,15.909 100.831,16.52 100.415,16.57C100.415,16.57 99.997,16.618 99.532,16.643L102.343,25.002L104.031,19.937L102.829,16.643C102.414,16.618 102.02,16.57 102.02,16.57C101.604,16.545 101.652,15.909 102.068,15.934C102.068,15.934 103.342,16.032 104.101,16.032C104.908,16.032 106.158,15.934 106.158,15.934C106.574,15.909 106.623,16.52 106.207,16.57C106.207,16.57 105.788,16.618 105.324,16.643L108.113,24.938L108.908,22.415C109.263,21.313 109.47,20.531 109.47,19.872L109.47,19.871ZM96.544,20.26C96.544,23.217 98.233,25.915 100.893,27.205L97.211,17.12C96.77,18.108 96.543,19.178 96.544,20.26Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
......@@ -33,8 +33,7 @@
android:id="@+id/view_navigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:headerLayout="@layout/nav_header"
app:menu="@menu/navigation" />
app:headerLayout="@layout/nav_header" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/accounts_list"
......
......@@ -35,7 +35,8 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_app_name"
android:layout_marginTop="16dp"
android:textColor="@color/colorSecondaryText" />
android:textColor="@color/colorSecondaryText"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
<TextView
android:id="@+id/text_build_number"
......
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".webview.adminpanel.ui.AdminPanelWebViewFragment">
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.wang.avi.AVLoadingIndicatorView
android:id="@+id/view_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:indicatorColor="@color/colorBlack"
app:indicatorName="BallPulseIndicator"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -40,6 +40,7 @@
android:src="@drawable/ic_vpn_key_black_24dp"
android:tint="@color/colorDrawableTintGrey"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/text_username_or_email"
app:layout_constraintEnd_toEndOf="@+id/text_username_or_email"
app:layout_constraintTop_toTopOf="@+id/text_username_or_email" />
......@@ -218,6 +219,18 @@
android:src="@drawable/ic_gitlab"
android:visibility="gone"
tools:visibility="gone" />
<ImageButton
android:id="@+id/button_wordpress"
android:layout_width="290dp"
android:layout_height="40dp"
android:layout_marginTop="16dp"
android:clickable="false"
android:contentDescription="@string/msg_content_description_log_in_using_gitlab"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_wordpress"
android:visibility="gone"
tools:visibility="gone" />
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
......
......@@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
app:layout_behavior=" com.google.android.material.bottomsheet.BottomSheetBehavior">
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_bottom_sheet_avatar"
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/relative_layout"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true"
......@@ -48,20 +48,11 @@
android:id="@+id/text_email"
style="@style/Profile.EditText"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:drawableStart="@drawable/ic_email_black_24dp"
android:hint="@string/msg_email"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/text_avatar_url"
style="@style/Profile.EditText"
android:layout_marginTop="16dp"
android:drawableStart="@drawable/ic_link_black_24dp"
android:hint="@string/msg_avatar_url"
android:inputType="text"
android:layout_marginBottom="16dp"/>
</LinearLayout>
</ScrollView>
<com.wang.avi.AVLoadingIndicatorView
......@@ -72,4 +63,19 @@
app:indicatorColor="@color/colorBlack"
app:indicatorName="BallPulseIndicator" />
<View
android:id="@+id/view_dim"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorDim"
android:visibility="gone" />
<include
android:id="@+id/layout_update_avatar_options"
layout="@layout/update_avatar_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone" />
</RelativeLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/style_attachment_options"
android:orientation="vertical">
<Button
android:id="@+id/button_open_gallery"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_image_black_24dp"
android:drawablePadding="20dp"
android:gravity="start|center"
android:text="@string/action_select_photo_from_gallery" />
<Button
android:id="@+id/button_take_photo"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_photo_camera_black_24dp"
android:drawablePadding="20dp"
android:gravity="start|center"
android:text="@string/action_take_photo" />
<Button
android:id="@+id/button_reset_avatar"
style="?android:attr/borderlessButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_close_black_24dp"
android:drawablePadding="20dp"
android:gravity="start|center"
android:text="@string/action_reset_avatar" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group
android:id="@+id/menu_section_1"
android:checkableBehavior="single">
<item
android:id="@+id/action_chat_rooms"
android:checked="true"
android:icon="@drawable/ic_chat_bubble_black_24dp"
android:title="@string/title_chats" />
<item
android:id="@+id/action_channel"
android:icon="@drawable/ic_create_black_24dp"
android:title="@string/action_create_channel" />
</group>
<group
android:id="@+id/menu_section_2"
android:checkableBehavior="single">
<item
android:id="@+id/action_profile"
android:icon="@drawable/ic_person_black_24dp"
android:title="@string/title_profile" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings_black_24dp"
android:title="@string/title_settings" />
</group>
<group
android:id="@+id/menu_section_3"
android:checkableBehavior="single">
<item
android:id="@+id/action_logout"
android:icon="@drawable/ic_exit_to_app_black_24dp"
android:title="@string/action_logout" />
</group>
</menu>
\ No newline at end of file
......@@ -11,6 +11,7 @@
<string name="title_profile">Profil</string>
<string name="title_members">Benutzer (%d)</string>
<string name="title_settings">Einstellungen</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation -->
<string name="title_password">Ändere Passwort</string>
<string name="title_update_profile">Update Profil</string>
<string name="title_about">Über</string>
......@@ -38,6 +39,9 @@
<string name="action_invisible">Unsichtbar</string>
<string name="action_drawing">Zeichnung</string>
<string name="action_save_to_gallery">Sichern in Gallerie</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_photo">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_reset_avatar">Reset avatar</string> <!-- TODO Add translation -->
<!-- Settings List -->
<string-array name="settings_actions">
......@@ -78,6 +82,7 @@
<string name="msg_content_description_log_in_using_meteor">Login mit Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Login mit Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Login mit Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Login mit WordPress</string>
<string name="msg_content_description_send_message">Sende Nachricht</string>
<string name="msg_content_description_show_attachment_options">Zeige Anhang Optionen</string>
<string name="msg_you">Du</string>
......@@ -94,8 +99,6 @@
<string name="msg_preview_photo">Bild</string>
<string name="msg_preview_file">Datei</string>
<string name="msg_no_messages_yet">Noch keine Nachrichten</string>
<string name="msg_version">Version %1$s</string>
<string name="msg_build">Build %1$d</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_ver_not_recommended">
......@@ -169,7 +172,6 @@
<string name="action_msg_share">Teilen</string>
<string name="action_title_editing">Nachricht bearbeiten</string>
<string name="action_msg_add_reaction">Reaktion hinzufügen</string>
<string name="action_share">Teilen</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">Bearbeiten nicht erlaubt</string>
......@@ -257,6 +259,7 @@
<string name="dialog_sort_by_activity">Aktivität</string>
<string name="dialog_group_by_type">Räume nach Typ</string>
<string name="dialog_group_favourites">Räume nach Favoriten</string>
<string name="dialog_button_done">Done</string><!-- TODO Add translation -->
<string name="chatroom_header">Kopf</string>
<!--ChatRooms Headers-->
......
......@@ -11,6 +11,7 @@
<string name="title_profile">Perfil</string>
<string name="title_members">Miembros (%d)</string>
<string name="title_settings">Configuraciones</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation -->
<string name="title_password">Cambia la contraseña</string>
<string name="title_update_profile">Actualización del perfil</string>
<string name="title_about">Acerca de</string>
......@@ -36,8 +37,11 @@
<string name="action_away">Ausente</string>
<string name="action_busy">Ocupado</string>
<string name="action_invisible">Invisible</string>
<string name="action_drawing">dibujo</string>
<string name="action_drawing">Dibujo</string>
<string name="action_save_to_gallery">Guardar en la galería</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_photo">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_reset_avatar">Reset avatar</string> <!-- TODO Add translation -->
<!-- Settings List -->
<string-array name="settings_actions">
......@@ -77,6 +81,7 @@
<string name="msg_content_description_log_in_using_meteor">Inicia sesión usando Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Inicia sesión usando Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Inicia sesión usando Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Login using WordPress</string> <!-- TODO Translate-->
<string name="msg_content_description_send_message">Enviar mensaje</string>
<string name="msg_content_description_show_attachment_options">Mostrar opciones de archivo adjunto</string>
<string name="msg_you"></string>
......@@ -91,8 +96,6 @@
<string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">Fichero</string>
<string name="msg_no_messages_yet">Aún no hay mensajes</string>
<string name="msg_version">Versión %1$s</string>
<string name="msg_build">Build %1$d</string>
<string name="msg_ok">OK</string>
<string name="msg_update_app_version_in_order_to_continue">Versión del servidor actualizada. Póngase en contacto con el administrador del servidor para actualizar la versión del servidor y continuar.</string>
<string name="msg_ver_not_recommended">
......@@ -134,7 +137,6 @@
<string name="msg_member_not_found">Miembro no encontrado</string>
<string name="msg_channel_created_successfully">Canal creado con éxito</string>
<!-- System messages -->
<string name="message_room_name_changed">Nombre de la sala cambiado para: %1$s por %2$s</string>
<string name="message_user_added_by">Usuario %1$s añadido por %2$s</string>
......@@ -252,6 +254,7 @@
<string name="dialog_sort_by_activity">Actividad</string>
<string name="dialog_group_by_type">Agrupar por tipo</string>
<string name="dialog_group_favourites">Agrupar favoritos</string>
<string name="dialog_button_done">Done</string><!-- TODO Add translation -->
<string name="chatroom_header">Cabezazo</string>
<!--ChatRooms Headers-->
......@@ -262,6 +265,7 @@
<string name="header_unknown">Desconocido</string>
<!--Notifications-->
<string name="share_label">Edita mensaje compartido</string>
<string name="notif_action_reply_hint">RESPUESTA</string>
<string name="notif_error_sending">La respuesta ha fallado. Inténtalo de nuevo.</string>
<string name="notif_success_sending">Mensaje enviado a %1$s!</string>
......@@ -272,4 +276,5 @@
<string name="msg_file_description">Descripción del archivo</string>
<string name="msg_send">Enviar</string>
<string name="msg_sent_attachment">Envió un archivo</string>
</resources>
......@@ -12,6 +12,7 @@
<string name="title_profile">Profil</string>
<string name="title_members">Membres (%d)</string>
<string name="title_settings">Paramètres</string>
<string name="title_admin_panel">Admin panel</string> <!-- TODO Add translation -->
<string name="title_password">Changer le mot de passe</string>
<string name="title_update_profile">Update profile</string>
<string name="title_about">Sur</string>
......@@ -27,8 +28,7 @@
<string name="action_search">Chercher</string>
<string name="action_update">Mettre à jour</string>
<string name="action_settings">Paramètres</string>
// TODO: Add proper translation.
<string name="action_create_channel">Create channel</string>
<string name="action_create_channel">Create channel</string> <!-- TODO Add translation -->
<string name="action_create">Create</string>
<string name="action_logout">Se déconnecter</string>
<string name="action_files">Fichiers</string>
......@@ -40,8 +40,10 @@
<string name="action_busy">Occupé</string>
<string name="action_invisible">Invisible</string>
<string name="action_drawing">Dessin</string>
// TODO: Add proper translation.
<string name="action_save_to_gallery">Save to gallery</string>
<string name="action_save_to_gallery">Save to gallery</string> <!-- TODO Add translation -->
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_photo">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_reset_avatar">Reset avatar</string> <!-- TODO Add translation -->
<!-- Settings List -->
<string-array name="settings_actions">
......@@ -85,6 +87,7 @@
<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_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_send_message">Envoyer message</string>
<string name="msg_content_description_show_attachment_options">Afficher les options de fichiers</string>
<string name="msg_you">Toi</string>
......@@ -99,8 +102,6 @@
<string name="msg_preview_photo">Photo</string>
<string name="msg_preview_file">File</string>
<string name="msg_no_messages_yet">Aucun message pour le moment</string>
<string name="msg_version">Version %1$s</string>
<string name="msg_build">Build %1$d</string>
<string name="msg_ok">OK</string>
// TODO: Add proper translation.
<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>
......@@ -185,7 +186,6 @@
<string name="action_msg_share">Partager</string>
<string name="action_title_editing">Modification du message</string>
<string name="action_msg_add_reaction">Ajouter une réaction</string>
<string name="action_share">Partager</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">L\'édition n\'est pas autorisée</string>
......@@ -279,6 +279,7 @@
<string name="dialog_sort_by_activity">Activité</string>
<string name="dialog_group_by_type">Grouper par type</string>
<string name="dialog_group_favourites">Grouper favoris</string>
<string name="dialog_button_done">Done</string><!-- TODO Add translation -->
<string name="chatroom_header">Entête</string>
<!--ChatRooms Headers-->
......
......@@ -12,6 +12,7 @@
<string name="title_profile">प्रोफाइल</string>
<string name="title_members">सदस्य (%d)</string>
<string name="title_settings">सेटिंग्स</string>
<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">परिचय</string>
......@@ -39,7 +40,9 @@
<string name="action_invisible">अदृश्य</string>
<string name="action_save_to_gallery">गैलरी में सहेजें</string>
<string name="action_drawing">चित्रकारी</string>
<string name="action_share">शेयर</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_take_photo">Select photo from gallery</string> <!-- TODO Add translation -->
<string name="action_reset_avatar">Reset avatar</string> <!-- TODO Add translation -->
<!-- Settings List -->
<string-array name="settings_actions">
......@@ -79,6 +82,7 @@
<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">Login using WordPress</string> <!-- TODO Translate-->
<string name="msg_content_description_send_message">मेसेज भेजें</string>
<string name="msg_content_description_show_attachment_options">अटैचमेंट विकल्प दिखाएं</string>
<string name="msg_you">आप</string>
......@@ -93,8 +97,6 @@
<string name="msg_preview_file">फ़ाइल</string>
<string name="msg_unread_messages">अपठित संदेश</string>
<string name="msg_no_messages_yet">अभी तक कोई पोस्ट नहीं</string>
<string name="msg_version">वर्शन %1$s</string>
<string name="msg_build">बिल्ड %1$d</string>
<string name="msg_update_app_version_in_order_to_continue">पुराना सर्वर वर्शन। जारी रखने के लिए सर्वर वर्शन को अद्यतन करने के लिए कृपया सर्वर व्यवस्थापक से संपर्क करें।</string>
<string name="msg_ok">ठीक है</string>
<string name="msg_ver_not_recommended">
......@@ -259,6 +261,7 @@
<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">Done</string><!-- TODO Add translation -->
<string name="chatroom_header">हैडर</string>
<!--ChatRooms Headers-->
......
......@@ -12,6 +12,7 @@
<string name="title_profile">Perfil</string>
<string name="title_members">Membros (%d)</string>
<string name="title_settings">Configurações</string>
<string name="title_admin_panel">Painel administrativo</string>
<string name="title_password">Alterar senha</string>
<string name="title_update_profile">Editar perfil</string>
<string name="title_about">Sobre</string>
......@@ -39,6 +40,9 @@
<string name="action_invisible">Invisível</string>
<string name="action_drawing">Desenhando</string>
<string name="action_save_to_gallery">Salvar na galeria</string>
<string name="action_select_photo_from_gallery">Escolher foto da galeria</string>
<string name="action_take_photo">Tirar foto</string>
<string name="action_reset_avatar">Resetar avatar</string>
<!-- Settings List -->
<string-array name="settings_actions">
......@@ -78,6 +82,7 @@
<string name="msg_content_description_log_in_using_meteor">Fazer login através do Meteor</string>
<string name="msg_content_description_log_in_using_twitter">Fazer login através do Twitter</string>
<string name="msg_content_description_log_in_using_gitlab">Fazer login através do Gitlab</string>
<string name="msg_content_description_log_in_using_wordpress">Fazer login através do WordPress</string>
<string name="msg_content_description_send_message">Enviar mensagem</string>
<string name="msg_content_description_show_attachment_options">Mostrar opções de anexo</string>
<string name="msg_you">Você</string>
......@@ -92,8 +97,6 @@
<string name="msg_preview_photo">Foto</string>
<string name="msg_preview_file">Arquivo</string>
<string name="msg_no_messages_yet">Nenhuma mensagem ainda</string>
<string name="msg_version">Versão %1$s</string>
<string name="msg_build">Build %1$d</string>
<string name="msg_ok">OK</string>
<string name="msg_update_app_version_in_order_to_continue">Versão do servidor desatualizada. Por favor, entre em contato com o administrador do sistema para continuar.</string>
<string name="msg_ver_not_recommended">
......@@ -257,6 +260,7 @@
<string name="dialog_sort_by_activity">Atividade</string>
<string name="dialog_group_by_type">Agrupar por tipo</string>
<string name="dialog_group_favourites">Grupos favoritos</string>
<string name="dialog_button_done">Done</string><!-- TODO Add translation -->
<string name="chatroom_header">Cabeçalho</string>
<!--ChatRooms Headers-->
......@@ -271,7 +275,6 @@
<string name="notif_action_reply_hint">RESPONDER</string>
<string name="notif_error_sending">Falha ao enviar a mensagem.</string>
<string name="notif_success_sending">Mensagem enviada para %1$s!</string>
<string name="action_share">Compartilhar</string>
<string name="read_by">Lida por</string>
<string name="message_information_title">Informações da mensagem</string>
<string name="msg_log_out">Deslogando…</string>
......
This diff is collapsed.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="menu_section_one" type="id"/>
<item name="menu_section_two" type="id"/>
<item name="menu_section_three" type="id"/>
<item name="menu_action_chats" type="id"/>
<item name="menu_action_create_channel" type="id"/>
<item name="menu_action_profile" type="id"/>
<item name="menu_action_settings" type="id"/>
<item name="menu_action_admin_panel" type="id"/>
<item name="menu_action_logout" type="id"/>
</resources>
\ No newline at end of file
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" translatable="false">Rocket.Chat</string>
<!-- Titles -->
......@@ -13,6 +13,7 @@
<string name="title_profile">Profile</string>
<string name="title_members">Members (%d)</string>
<string name="title_settings">Settings</string>
<string name="title_admin_panel">Admin panel</string>
<string name="title_password">Change Password</string>
<string name="title_update_profile">Update profile</string>
<string name="title_about">About</string>
......@@ -40,6 +41,9 @@
<string name="action_invisible">Invisible</string>
<string name="action_drawing">Drawing</string>
<string name="action_save_to_gallery">Save to gallery</string>
<string name="action_select_photo_from_gallery">Select photo from gallery</string>
<string name="action_take_photo">Take photo</string>
<string name="action_reset_avatar">Reset avatar</string>
<!-- Settings List -->
<string-array name="settings_actions">
......@@ -80,6 +84,7 @@
<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">Send message</string>
<string name="msg_content_description_show_attachment_options">Show attachment options</string>
<string name="msg_you">You</string>
......@@ -96,8 +101,7 @@
<string name="msg_preview_photo">Photo</string>
<string name="msg_preview_file">File</string>
<string name="msg_no_messages_yet">No messages yet</string>
<string name="msg_version">Version %1$s</string>
<string name="msg_build">Build %1$d</string>
<string name="msg_build" tools:ignore="MissingTranslation">Build %1$d - %2$s - %3$s</string>
<string name="msg_ok">OK</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">
......@@ -171,7 +175,6 @@
<string name="action_msg_share">Share</string>
<string name="action_title_editing">Editing Message</string>
<string name="action_msg_add_reaction">Add reaction</string>
<string name="action_share">Share</string>
<!-- Permission messages -->
<string name="permission_editing_not_allowed">Editing is not allowed</string>
......@@ -259,6 +262,7 @@
<string name="dialog_sort_by_activity">Activity</string>
<string name="dialog_group_by_type">Group by type</string>
<string name="dialog_group_favourites">Group favourites</string>
<string name="dialog_button_done">Done</string>
<string name="chatroom_header">Header</string>
<!--ChatRooms Headers-->
......@@ -275,4 +279,6 @@
<string name="notif_success_sending">Message sent to %1$s!</string>
<string name="read_by">Read by</string>
<string name="message_information_title">Message information</string>
<string name="foss" tools:ignore="MissingTranslation">(FOSS)</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/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
android:name=".app.RocketChatApplication"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_config"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
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
android:name=".push.FirebaseMessagingService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>
package chat.rocket.android.helper
import android.app.Activity
import android.content.Intent
import androidx.fragment.app.FragmentActivity
import com.google.android.gms.auth.api.credentials.Credential
import com.google.android.gms.auth.api.credentials.Credentials
fun FragmentActivity.saveCredentials(id: String, password: String) {
val credentialsClient = Credentials.getClient(this)
SmartLockHelper.save(credentialsClient, this, id, password)
}
fun Activity.requestStoredCredentials(): Pair<String, String>? {
val credentialsClient = Credentials.getClient(this)
return SmartLockHelper.requestStoredCredentials(credentialsClient, this)?.let {
null
}
}
fun getCredentials(data: Intent): Pair<String, String>? {
val credentials: Credential = data.getParcelableExtra(Credential.EXTRA_KEY)
return credentials?.let { cred ->
cred.password?.let {
Pair(cred.id, it)
}
}
}
fun hasCredentialsSupport() = true
\ No newline at end of file
package chat.rocket.android.util
import chat.rocket.android.BuildConfig
import io.fabric.sdk.android.Fabric
import com.crashlytics.android.Crashlytics
import com.crashlytics.android.core.CrashlyticsCore
import android.content.Context
import chat.rocket.android.app.RocketChatApplication
import chat.rocket.android.infrastructure.installCrashlyticsWrapper
fun setupCrashlytics(context: Context) {
val core = CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build()
Fabric.with(context, Crashlytics.Builder().core(core).build())
installCrashlyticsWrapper(context as RocketChatApplication,
context.getCurrentServerInteractor, context.settingsInteractor,
context.accountRepository, context.localRepository)
}
\ No newline at end of file
package chat.rocket.android.util
import chat.rocket.android.R
import chat.rocket.android.main.ui.MainActivity
import chat.rocket.android.main.presentation.MainPresenter
import com.google.firebase.iid.FirebaseInstanceId
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) {
FirebaseInstanceId.getInstance().deleteToken(token, FirebaseMessaging.INSTANCE_ID_SCOPE)
}
......@@ -10,10 +10,10 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0-alpha03'
classpath 'com.android.tools.build:gradle:3.3.0-alpha05'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:3.2.0'
classpath 'com.google.gms:google-services:4.0.2'
classpath 'io.fabric.tools:gradle:1.25.4'
// NOTE: Do not place your application dependencies here; they belong
......@@ -34,4 +34,4 @@ allprojects {
task clean(type: Delete) {
delete rootProject.buildDir
}
\ No newline at end of file
}
......@@ -18,6 +18,11 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/core.kotlin_module'
exclude 'META-INF/main.kotlin_module'
}
}
dependencies {
......
......@@ -5,12 +5,12 @@ ext {
compileSdk : 28,
targetSdk : 28,
minSdk : 21,
buildTools : '28.0.0-rc2',
buildTools : '28.0.1',
dokka : '0.9.16',
// For app
kotlin : '1.2.51',
coroutine : '0.23.1',
coroutine : '0.24.0',
appCompat : '1.0.0-beta01',
recyclerview : '1.0.0-beta01',
......@@ -21,9 +21,9 @@ ext {
dagger : '2.16',
firebase : '15.0.0',
playServices : '15.0.0',
exoPlayer : '2.6.0',
flexbox : '0.3.2',
playServices : '15.0.1',
exoPlayer : '2.8.2',
flexbox : '1.0.0',
material : '1.0.0-beta01',
room : '2.0.0-beta01',
......@@ -33,30 +33,30 @@ ext {
rxAndroid : '2.0.2',
moshi : '1.6.0',
okhttp : '3.10.0',
okhttp : '3.11.0',
timber : '4.7.0',
threeTenABP : '1.0.5',
rxBinding : '2.0.0',
timber : '4.7.1',
threeTenABP : '1.1.0',
rxBinding : '2.1.1',
fresco : '1.9.0',
fresco : '1.10.0',
kotshi : '1.0.2',
kotshi : '1.0.4',
frescoImageViewer : '0.5.1',
markwon : '1.0.6',
markwon : '1.1.0',
aVLoadingIndicatorView: '2.1.3',
// For wearable
wear : '2.3.0',
playServicesWearable : '15.0.1',
supportWearable : '26.1.0',
supportWearable : '27.1.1',
// For testing
junit : '4.12',
truth : '0.36',
espresso : '3.1.0-alpha2',
mockito : '2.10.0'
truth : '0.42',
espresso : '3.1.0-alpha4',
mockito : '2.21.0'
]
libraries = [
kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}",
......@@ -121,10 +121,10 @@ ext {
wearSupport : "com.android.support:wear:${versions.supportWearable}",
// For testing
junit : "junit:junit:$versions.junit",
junit : "junit:junit:${versions.junit}",
espressoCore : "androidx.test.espresso:espresso-core:${versions.espresso}",
espressoIntents : "androidx.test.espresso:espresso-intents:${versions.espresso}",
roomTest : "android.arch.persistence.room:testing:${versions.room}",
truth : "com.google.truth:truth:$versions.truth"
truth : "com.google.truth:truth:${versions.truth}"
]
}
\ No newline at end of file
}
......@@ -111,6 +111,12 @@ class DrawingActivity : DaggerAppCompatActivity(), DrawView {
}
private fun colorSelector() {
image_color_black.setOnClickListener {
custom_draw_view.setColor(
ResourcesCompat.getColor(resources, R.color.color_black, null)
)
scaleColorView(image_color_black)
}
image_color_red.setOnClickListener {
custom_draw_view.setColor(
ResourcesCompat.getColor(resources, R.color.color_red, null)
......
......@@ -26,6 +26,8 @@ android {
}
dependencies {
implementation project(':util')
implementation libraries.androidKtx
implementation libraries.appCompat
implementation libraries.kotlin
......
......@@ -17,11 +17,10 @@ import chat.rocket.android.emoji.R
import kotlinx.android.synthetic.main.emoji_category_layout.view.*
import kotlinx.android.synthetic.main.emoji_row_item.view.*
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.android.UI
import chat.rocket.android.util.temp.UI
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
internal class EmojiPagerAdapter(private val listener: EmojiKeyboardListener) : PagerAdapter() {
private val adapters = hashMapOf<EmojiCategory, EmojiAdapter>()
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment