remove conductor

- replace remaining controllers with activities
- remove conductor lib
- modify some code related to account management and conductor

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2023-11-03 17:40:34 +01:00
parent 074fc301d6
commit 591d6dc3e8
No known key found for this signature in database
GPG key ID: C793F8B59F43CE7B
49 changed files with 840 additions and 1410 deletions

View file

@ -289,7 +289,6 @@ We are using [Dagger 2](https://dagger.dev/) to inject dependencies into major A
* `Activity` * `Activity`
* `Fragment` * `Fragment`
* `Controller`
* `Service` * `Service`
* `BroadcastReceiver` * `BroadcastReceiver`
* `ContentProvider` * `ContentProvider`

View file

@ -7,7 +7,7 @@
* @author Tim Krüger * @author Tim Krüger
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de> * Copyright (C) 2021-2023 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me> * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -210,8 +210,6 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "io.reactivex.rxjava2:rxjava:2.2.21" implementation "io.reactivex.rxjava2:rxjava:2.2.21"
implementation 'com.bluelinelabs:conductor:3.2.0'
implementation "com.squareup.okhttp3:okhttp:${okhttpVersion}" implementation "com.squareup.okhttp3:okhttp:${okhttpVersion}"
implementation "com.squareup.okhttp3:okhttp-urlconnection:${okhttpVersion}" implementation "com.squareup.okhttp3:okhttp-urlconnection:${okhttpVersion}"
implementation "com.squareup.okhttp3:logging-interceptor:${okhttpVersion}" implementation "com.squareup.okhttp3:logging-interceptor:${okhttpVersion}"

View file

@ -32,6 +32,5 @@
<issue id="ObsoleteLintCustomCheck" severity="warning"> <issue id="ObsoleteLintCustomCheck" severity="warning">
<ignore path="**/jetified-annotation-experimental-1.**/**/lint.jar" /> <ignore path="**/jetified-annotation-experimental-1.**/**/lint.jar" />
<ignore path="**/jetified-conductor-2.**/**/lint.jar" />
</issue> </issue>
</lint> </lint>

View file

@ -4,7 +4,7 @@
~ @author Mario Danic ~ @author Mario Danic
~ @author Marcel Hibbe ~ @author Marcel Hibbe
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com> ~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
~ Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de> ~ Copyright (C) 2021-2023 Marcel Hibbe <dev@mhibbe.de>
~ ~
~ This program is free software: you can redistribute it and/or modify ~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by ~ it under the terms of the GNU General Public License as published by
@ -131,6 +131,44 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".account.ServerSelectionActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".account.WebViewLoginActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".account.AccountVerificationActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".account.SwitchAccountActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".conversationlist.ConversationsListActivity"
android:theme="@style/AppTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<activity
android:name=".chat.ChatActivity"
android:theme="@style/AppTheme" />
<activity <activity
android:name=".activities.CallActivity" android:name=".activities.CallActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
@ -215,32 +253,10 @@
android:name=".contacts.ContactsActivity" android:name=".contacts.ContactsActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity
android:name=".chat.ChatActivity"
android:theme="@style/AppTheme" />
<activity <activity
android:name=".openconversations.ListOpenConversationsActivity" android:name=".openconversations.ListOpenConversationsActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity
android:name=".conversationlist.ConversationsListActivity"
android:theme="@style/AppTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<activity <activity
android:name=".lock.LockedActivity" android:name=".lock.LockedActivity"
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />

View file

@ -3,6 +3,8 @@
* *
* @author Mario Danic * @author Mario Danic
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
* *
@ -19,7 +21,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.nextcloud.talk.controllers package com.nextcloud.talk.account
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
@ -28,25 +30,24 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import android.view.View import android.widget.Toast
import androidx.work.Data import androidx.work.Data
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import autodagger.AutoInjector import autodagger.AutoInjector
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.logansquare.LoganSquare import com.bluelinelabs.logansquare.LoganSquare
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.conversationlist.ConversationsListActivity import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ControllerAccountVerificationBinding import com.nextcloud.talk.databinding.ActivityAccountVerificationBinding
import com.nextcloud.talk.events.EventStatus import com.nextcloud.talk.events.EventStatus
import com.nextcloud.talk.jobs.AccountRemovalWorker
import com.nextcloud.talk.jobs.CapabilitiesWorker import com.nextcloud.talk.jobs.CapabilitiesWorker
import com.nextcloud.talk.jobs.PushRegistrationWorker import com.nextcloud.talk.jobs.PushRegistrationWorker
import com.nextcloud.talk.jobs.SignalingSettingsWorker import com.nextcloud.talk.jobs.SignalingSettingsWorker
@ -63,6 +64,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PASSWORD
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
@ -71,20 +73,15 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import java.net.CookieManager import java.net.CookieManager
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class AccountVerificationController(args: Bundle? = null) : BaseController( class AccountVerificationActivity : BaseActivity() {
R.layout.controller_account_verification,
args private lateinit var binding: ActivityAccountVerificationBinding
) {
private val binding: ControllerAccountVerificationBinding? by viewBinding(
ControllerAccountVerificationBinding::bind
)
@Inject @Inject
lateinit var ncApi: NcApi lateinit var ncApi: NcApi
@ -95,9 +92,6 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
@Inject @Inject
lateinit var cookieManager: CookieManager lateinit var cookieManager: CookieManager
@Inject
lateinit var eventBus: EventBus
private var internalAccountId: Long = -1 private var internalAccountId: Long = -1
private val disposables: MutableList<Disposable> = ArrayList() private val disposables: MutableList<Disposable> = ArrayList()
private var baseUrl: String? = null private var baseUrl: String? = null
@ -106,43 +100,53 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
private var isAccountImport = false private var isAccountImport = false
private var originalProtocol: String? = null private var originalProtocol: String? = null
override fun onAttach(view: View) { @SuppressLint("SourceLockedOrientationActivity")
super.onAttach(view) override fun onCreate(savedInstanceState: Bundle?) {
eventBus.register(this) super.onCreate(savedInstanceState)
} sharedApplication!!.componentApplication.inject(this)
binding = ActivityAccountVerificationBinding.inflate(layoutInflater)
override fun onDetach(view: View) { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
super.onDetach(view) setContentView(binding.root)
eventBus.unregister(this)
}
override fun onViewBound(view: View) {
super.onViewBound(view)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
actionBar?.hide() actionBar?.hide()
setupPrimaryColors()
handleIntent()
}
private fun handleIntent() {
val extras = intent.extras!!
baseUrl = extras.getString(KEY_BASE_URL)
username = extras.getString(KEY_USERNAME)
token = extras.getString(KEY_TOKEN)
if (extras.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
isAccountImport = true
}
if (extras.containsKey(KEY_ORIGINAL_PROTOCOL)) {
originalProtocol = extras.getString(KEY_ORIGINAL_PROTOCOL)
}
}
override fun onResume() {
super.onResume()
if ( if (
isAccountImport && isAccountImport &&
!UriUtils.hasHttpProtocolPrefixed(baseUrl!!) || !UriUtils.hasHttpProtocolPrefixed(baseUrl!!) ||
isSameProtocol(baseUrl!!, originalProtocol!!) isNotSameProtocol(baseUrl!!, originalProtocol)
) { ) {
determineBaseUrlProtocol(true) determineBaseUrlProtocol(true)
} else { } else {
checkEverything() findServerTalkApp()
} }
} }
private fun isSameProtocol(baseUrl: String, originalProtocol: String): Boolean { private fun isNotSameProtocol(baseUrl: String, originalProtocol: String?): Boolean {
if (originalProtocol == null) {
return true
}
return !TextUtils.isEmpty(originalProtocol) && !baseUrl.startsWith(originalProtocol) return !TextUtils.isEmpty(originalProtocol) && !baseUrl.startsWith(originalProtocol)
} }
private fun checkEverything() {
val credentials = ApiUtils.getCredentials(username, token)
cookieManager.cookieStore.removeAll()
findServerTalkApp(credentials)
}
private fun determineBaseUrlProtocol(checkForcedHttps: Boolean) { private fun determineBaseUrlProtocol(checkForcedHttps: Boolean) {
cookieManager.cookieStore.removeAll() cookieManager.cookieStore.removeAll()
baseUrl = baseUrl!!.replace("http://", "").replace("https://", "") baseUrl = baseUrl!!.replace("http://", "").replace("https://", "")
@ -166,20 +170,16 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
"http://$baseUrl" "http://$baseUrl"
} }
if (isAccountImport) { if (isAccountImport) {
router.replaceTopController( val bundle = Bundle()
RouterTransaction.with( bundle.putString(KEY_BASE_URL, baseUrl)
WebViewLoginController( bundle.putString(KEY_USERNAME, username)
baseUrl, bundle.putString(KEY_PASSWORD, "")
false,
username, val intent = Intent(context, WebViewLoginActivity::class.java)
"" intent.putExtras(bundle)
) startActivity(intent)
)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
} else { } else {
checkEverything() findServerTalkApp()
} }
} }
@ -197,7 +197,10 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
}) })
} }
private fun findServerTalkApp(credentials: String) { private fun findServerTalkApp() {
val credentials = ApiUtils.getCredentials(username, token)
cookieManager.cookieStore.removeAll()
ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl)) ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe(object : Observer<CapabilitiesOverall> { .subscribe(object : Observer<CapabilitiesOverall> {
@ -214,27 +217,24 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
if (hasTalk) { if (hasTalk) {
fetchProfile(credentials, capabilitiesOverall) fetchProfile(credentials, capabilitiesOverall)
} else { } else {
if (activity != null && resources != null) { if (resources != null) {
activity!!.runOnUiThread { runOnUiThread {
binding?.progressText?.setText( binding.progressText.text = String.format(
String.format(
resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed), resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
resources!!.getString(R.string.nc_app_product_name) resources!!.getString(R.string.nc_app_product_name)
) )
)
} }
} }
ApplicationWideMessageHolder.getInstance().setMessageType( ApplicationWideMessageHolder.getInstance().messageType =
ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
)
abortVerification() abortVerification()
} }
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
if (activity != null && resources != null) { if (resources != null) {
activity!!.runOnUiThread { runOnUiThread {
binding?.progressText?.text = String.format( binding.progressText.text = String.format(
resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed), resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
resources!!.getString(R.string.nc_app_product_name) resources!!.getString(R.string.nc_app_product_name)
) )
@ -263,7 +263,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
displayName = displayName, displayName = displayName,
pushConfigurationState = null, pushConfigurationState = null,
capabilities = LoganSquare.serialize(capabilities), capabilities = LoganSquare.serialize(capabilities),
certificateAlias = appPreferences!!.temporaryClientCertAlias, certificateAlias = appPreferences.temporaryClientCertAlias,
externalSignalingServer = null externalSignalingServer = null
) )
) )
@ -279,9 +279,9 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) { if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
registerForPush() registerForPush()
} else { } else {
activity!!.runOnUiThread { runOnUiThread {
binding?.progressText?.text = binding.progressText.text =
""" ${binding?.progressText?.text} """ ${binding.progressText.text}
${resources!!.getString(R.string.nc_push_disabled)} ${resources!!.getString(R.string.nc_push_disabled)}
""".trimIndent() """.trimIndent()
} }
@ -291,7 +291,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
binding?.progressText?.text = """ ${binding?.progressText?.text}""".trimIndent() + binding.progressText.text = """ ${binding.progressText.text}""".trimIndent() +
resources!!.getString(R.string.nc_display_name_not_stored) resources!!.getString(R.string.nc_display_name_not_stored)
abortVerification() abortVerification()
} }
@ -328,30 +328,26 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
capabilities.ocs!!.data!!.capabilities!! capabilities.ocs!!.data!!.capabilities!!
) )
} else { } else {
if (activity != null) { runOnUiThread {
activity!!.runOnUiThread { binding.progressText.text =
binding?.progressText?.text =
""" """
${binding?.progressText?.text} ${binding.progressText.text}
${resources!!.getString(R.string.nc_display_name_not_fetched)} ${resources!!.getString(R.string.nc_display_name_not_fetched)}
""".trimIndent() """.trimIndent()
} }
}
abortVerification() abortVerification()
} }
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
if (activity != null) { runOnUiThread {
activity!!.runOnUiThread { binding.progressText.text =
binding?.progressText?.text =
""" """
${binding?.progressText?.text} ${binding.progressText.text}
${resources!!.getString(R.string.nc_display_name_not_fetched)} ${resources!!.getString(R.string.nc_display_name_not_fetched)}
""".trimIndent() """.trimIndent()
} }
}
abortVerification() abortVerification()
} }
@ -364,7 +360,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
private fun registerForPush() { private fun registerForPush() {
val data = val data =
Data.Builder() Data.Builder()
.putString(PushRegistrationWorker.ORIGIN, "AccountVerificationController#registerForPush") .putString(PushRegistrationWorker.ORIGIN, "AccountVerificationActivity#registerForPush")
.build() .build()
val pushRegistrationWork = val pushRegistrationWork =
OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
@ -377,11 +373,11 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
@Subscribe(threadMode = ThreadMode.BACKGROUND) @Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onMessageEvent(eventStatus: EventStatus) { fun onMessageEvent(eventStatus: EventStatus) {
if (eventStatus.eventType == EventStatus.EventType.PUSH_REGISTRATION) { if (eventStatus.eventType == EventStatus.EventType.PUSH_REGISTRATION) {
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood && activity != null) { if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
activity!!.runOnUiThread { runOnUiThread {
binding?.progressText?.text = binding.progressText.text =
""" """
${binding?.progressText?.text} ${binding.progressText.text}
${resources!!.getString(R.string.nc_push_disabled)} ${resources!!.getString(R.string.nc_push_disabled)}
""".trimIndent() """.trimIndent()
} }
@ -389,31 +385,27 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
fetchAndStoreCapabilities() fetchAndStoreCapabilities()
} else if (eventStatus.eventType == EventStatus.EventType.CAPABILITIES_FETCH) { } else if (eventStatus.eventType == EventStatus.EventType.CAPABILITIES_FETCH) {
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) { if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
if (activity != null) { runOnUiThread {
activity!!.runOnUiThread { binding.progressText.text =
binding?.progressText?.text =
""" """
${binding?.progressText?.text} ${binding.progressText.text}
${resources!!.getString(R.string.nc_capabilities_failed)} ${resources!!.getString(R.string.nc_capabilities_failed)}
""".trimIndent() """.trimIndent()
} }
}
abortVerification() abortVerification()
} else if (internalAccountId == eventStatus.userId && eventStatus.isAllGood) { } else if (internalAccountId == eventStatus.userId && eventStatus.isAllGood) {
fetchAndStoreExternalSignalingSettings() fetchAndStoreExternalSignalingSettings()
} }
} else if (eventStatus.eventType == EventStatus.EventType.SIGNALING_SETTINGS) { } else if (eventStatus.eventType == EventStatus.EventType.SIGNALING_SETTINGS) {
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) { if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
if (activity != null) { runOnUiThread {
activity!!.runOnUiThread { binding.progressText.text =
binding?.progressText?.text =
""" """
${binding?.progressText?.text} ${binding.progressText.text}
${resources!!.getString(R.string.nc_external_server_failed)} ${resources!!.getString(R.string.nc_external_server_failed)}
""".trimIndent() """.trimIndent()
} }
} }
}
proceedWithLogin() proceedWithLogin()
} }
} }
@ -457,8 +449,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
Log.d(TAG, "userToSetAsActive: " + userToSetAsActive.username) Log.d(TAG, "userToSetAsActive: " + userToSetAsActive.username)
if (userManager.setUserAsActive(userToSetAsActive).blockingGet()) { if (userManager.setUserAsActive(userToSetAsActive).blockingGet()) {
if (activity != null) { runOnUiThread {
activity!!.runOnUiThread {
if (userManager.users.blockingGet().size == 1) { if (userManager.users.blockingGet().size == 1) {
val intent = Intent(context, ConversationsListActivity::class.java) val intent = Intent(context, ConversationsListActivity::class.java)
startActivity(intent) startActivity(intent)
@ -471,10 +462,9 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
startActivity(intent) startActivity(intent)
} }
} }
}
} else { } else {
Log.e(TAG, "failed to set active user") Log.e(TAG, "failed to set active user")
Snackbar.make(binding!!.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
} }
} }
} }
@ -487,70 +477,70 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
} }
} }
override fun onDestroyView(view: View) {
super.onDestroyView(view)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
}
public override fun onDestroy() { public override fun onDestroy() {
dispose() dispose()
super.onDestroy() super.onDestroy()
} }
private fun abortVerification() { private fun abortVerification() {
if (!isAccountImport) { if (isAccountImport) {
if (internalAccountId != -1L) { ApplicationWideMessageHolder.getInstance().messageType = ApplicationWideMessageHolder.MessageType
val count = userManager.deleteUser(internalAccountId) .FAILED_TO_IMPORT_ACCOUNT
if (count > 0) { runOnUiThread {
activity?.runOnUiThread { Handler().postDelayed({ router.popToRoot() }, DELAY_IN_MILLIS) }
}
} else {
activity?.runOnUiThread { Handler().postDelayed({ router.popToRoot() }, DELAY_IN_MILLIS) }
}
} else {
ApplicationWideMessageHolder.getInstance().setMessageType(
ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT
)
activity?.runOnUiThread {
Handler().postDelayed({ Handler().postDelayed({
if (router.hasRootController()) { val intent = Intent(this, ServerSelectionActivity::class.java)
if (activity != null) {
router.popToRoot()
}
} else {
if (userManager.users.blockingGet().isNotEmpty()) {
val intent = Intent(context, ConversationsListActivity::class.java)
startActivity(intent) startActivity(intent)
} else {
router.setRoot(
RouterTransaction.with(ServerSelectionController())
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
}
}, DELAY_IN_MILLIS) }, DELAY_IN_MILLIS)
} }
} else {
if (internalAccountId != -1L) {
runOnUiThread {
deleteUserAndStartServerSelection(internalAccountId)
}
} else {
runOnUiThread {
Handler().postDelayed({
val intent = Intent(this, ServerSelectionActivity::class.java)
startActivity(intent)
}, DELAY_IN_MILLIS)
}
}
}
}
@SuppressLint("CheckResult")
private fun deleteUserAndStartServerSelection(userId: Long) {
userManager.scheduleUserForDeletionWithId(userId).blockingGet()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
.observeForever { workInfo: WorkInfo ->
when (workInfo.state) {
WorkInfo.State.SUCCEEDED -> {
val intent = Intent(this, ServerSelectionActivity::class.java)
startActivity(intent)
}
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
Toast.makeText(
context,
context.resources.getString(R.string.nc_common_error_sorry),
Toast.LENGTH_LONG
).show()
Log.e(TAG, "something went wrong when deleting user with id $userId")
val intent = Intent(this, ServerSelectionActivity::class.java)
startActivity(intent)
}
else -> {}
}
} }
} }
companion object { companion object {
const val TAG = "AccountVerification" private val TAG = AccountVerificationActivity::class.java.simpleName
const val DELAY_IN_MILLIS: Long = 7500 const val DELAY_IN_MILLIS: Long = 7500
} }
init {
sharedApplication!!.componentApplication.inject(this)
if (args != null) {
baseUrl = args.getString(KEY_BASE_URL)
username = args.getString(KEY_USERNAME)
token = args.getString(KEY_TOKEN)
if (args.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
isAccountImport = true
}
if (args.containsKey(KEY_ORIGINAL_PROTOCOL)) {
originalProtocol = args.getString(KEY_ORIGINAL_PROTOCOL)
}
}
}
} }

View file

@ -3,6 +3,8 @@
* *
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Mario Danic * @author Mario Danic
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
* *
@ -19,7 +21,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.nextcloud.talk.controllers package com.nextcloud.talk.account
import android.accounts.Account import android.accounts.Account
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -34,24 +36,21 @@ import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import androidx.core.content.res.ResourcesCompat import androidx.activity.OnBackPressedCallback
import autodagger.AutoInjector import autodagger.AutoInjector
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.databinding.ActivityServerSelectionBinding
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.databinding.ControllerServerSelectionBinding
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
import com.nextcloud.talk.models.json.generic.Status import com.nextcloud.talk.models.json.generic.Status
import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.AccountUtils import com.nextcloud.talk.utils.AccountUtils
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.UriUtils import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
@ -61,12 +60,12 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import java.security.cert.CertificateException import java.security.cert.CertificateException
import javax.inject.Inject import javax.inject.Inject
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class ServerSelectionController : class ServerSelectionActivity : BaseActivity() {
BaseController(R.layout.controller_server_selection) {
private val binding: ControllerServerSelectionBinding? by viewBinding(ControllerServerSelectionBinding::bind) private lateinit var binding: ActivityServerSelectionBinding
@Inject @Inject
lateinit var ncApi: NcApi lateinit var ncApi: NcApi
@ -76,44 +75,40 @@ class ServerSelectionController :
private var statusQueryDisposable: Disposable? = null private var statusQueryDisposable: Disposable? = null
fun onCertClick() { private val onBackPressedCallback = object : OnBackPressedCallback(true) {
if (activity != null) { override fun handleOnBackPressed() {
KeyChain.choosePrivateKeyAlias( if (intent.hasExtra(ADD_ADDITIONAL_ACCOUNT) && intent.getBooleanExtra(ADD_ADDITIONAL_ACCOUNT, false)) {
activity!!, finish()
{ alias: String? ->
if (alias != null) {
appPreferences!!.temporaryClientCertAlias = alias
} else { } else {
appPreferences!!.removeTemporaryClientCertAlias() finishAffinity()
} }
setCertTextView()
},
arrayOf("RSA", "EC"),
null,
null,
-1,
null
)
} }
} }
override fun onViewBound(view: View) { @SuppressLint("SourceLockedOrientationActivity")
super.onViewBound(view) override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedApplication!!.componentApplication.inject(this) sharedApplication!!.componentApplication.inject(this)
if (activity != null) { binding = ActivityServerSelectionBinding.inflate(layoutInflater)
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
actionBar?.hide()
setupPrimaryColors()
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
} }
actionBar?.hide() override fun onResume() {
super.onResume()
binding?.hostUrlInputHelperText?.text = String.format( binding.hostUrlInputHelperText.text = String.format(
resources!!.getString(R.string.nc_server_helper_text), resources!!.getString(R.string.nc_server_helper_text),
resources!!.getString(R.string.nc_server_product_name) resources!!.getString(R.string.nc_server_product_name)
) )
binding?.serverEntryTextInputLayout?.setEndIconOnClickListener { checkServerAndProceed() } binding.serverEntryTextInputLayout.setEndIconOnClickListener { checkServerAndProceed() }
if (resources!!.getBoolean(R.bool.hide_auth_cert)) { if (resources!!.getBoolean(R.bool.hide_auth_cert)) {
binding?.certTextView?.visibility = View.GONE binding.certTextView.visibility = View.GONE
} }
val loggedInUsers = userManager.users.blockingGet() val loggedInUsers = userManager.users.blockingGet()
@ -124,21 +119,54 @@ class ServerSelectionController :
} else if (isAbleToShowProviderLink() && loggedInUsers.isEmpty()) { } else if (isAbleToShowProviderLink() && loggedInUsers.isEmpty()) {
showVisitProvidersInfo() showVisitProvidersInfo()
} else { } else {
binding?.importOrChooseProviderText?.visibility = View.INVISIBLE binding.importOrChooseProviderText.visibility = View.INVISIBLE
} }
binding?.serverEntryTextInputEditText?.requestFocus() binding.serverEntryTextInputEditText.requestFocus()
if (!TextUtils.isEmpty(resources!!.getString(R.string.weblogin_url))) { if (!TextUtils.isEmpty(resources!!.getString(R.string.weblogin_url))) {
binding?.serverEntryTextInputEditText?.setText(resources!!.getString(R.string.weblogin_url)) binding.serverEntryTextInputEditText.setText(resources!!.getString(R.string.weblogin_url))
checkServerAndProceed() checkServerAndProceed()
} }
binding?.serverEntryTextInputEditText?.setOnEditorActionListener { _: TextView?, i: Int, _: KeyEvent? -> binding.serverEntryTextInputEditText.setOnEditorActionListener { _: TextView?, i: Int, _: KeyEvent? ->
if (i == EditorInfo.IME_ACTION_DONE) { if (i == EditorInfo.IME_ACTION_DONE) {
checkServerAndProceed() checkServerAndProceed()
} }
false false
} }
binding?.certTextView?.setOnClickListener { onCertClick() } binding.certTextView.setOnClickListener { onCertClick() }
if (ApplicationWideMessageHolder.getInstance().messageType != null) {
if (ApplicationWideMessageHolder.getInstance().messageType
== ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
) {
setErrorText(resources!!.getString(R.string.nc_settings_no_talk_installed))
} else if (ApplicationWideMessageHolder.getInstance().messageType
== ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT
) {
setErrorText(resources!!.getString(R.string.nc_server_failed_to_import_account))
}
ApplicationWideMessageHolder.getInstance().messageType = null
}
setCertTextView()
}
fun onCertClick() {
KeyChain.choosePrivateKeyAlias(
this,
{ alias: String? ->
if (alias != null) {
appPreferences.temporaryClientCertAlias = alias
} else {
appPreferences.removeTemporaryClientCertAlias()
}
setCertTextView()
},
arrayOf("RSA", "EC"),
null,
null,
-1,
null
)
} }
private fun isAbleToShowProviderLink(): Boolean { private fun isAbleToShowProviderLink(): Boolean {
@ -152,41 +180,37 @@ class ServerSelectionController :
) )
) { ) {
if (availableAccounts.size > 1) { if (availableAccounts.size > 1) {
binding?.importOrChooseProviderText?.text = String.format( binding.importOrChooseProviderText.text = String.format(
resources!!.getString(R.string.nc_server_import_accounts), resources!!.getString(R.string.nc_server_import_accounts),
AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from)) AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from))
) )
} else { } else {
binding?.importOrChooseProviderText?.text = String.format( binding.importOrChooseProviderText.text = String.format(
resources!!.getString(R.string.nc_server_import_account), resources!!.getString(R.string.nc_server_import_account),
AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from)) AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from))
) )
} }
} else { } else {
if (availableAccounts.size > 1) { if (availableAccounts.size > 1) {
binding?.importOrChooseProviderText?.text = binding.importOrChooseProviderText.text =
resources!!.getString(R.string.nc_server_import_accounts_plain) resources!!.getString(R.string.nc_server_import_accounts_plain)
} else { } else {
binding?.importOrChooseProviderText?.text = binding.importOrChooseProviderText.text =
resources!!.getString(R.string.nc_server_import_account_plain) resources!!.getString(R.string.nc_server_import_account_plain)
} }
} }
binding?.importOrChooseProviderText?.setOnClickListener { binding.importOrChooseProviderText.setOnClickListener {
val bundle = Bundle() val bundle = Bundle()
bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true) bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true)
router.pushController( val intent = Intent(context, SwitchAccountActivity::class.java)
RouterTransaction.with( intent.putExtras(bundle)
SwitchAccountController(bundle) startActivity(intent)
)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
} }
} }
private fun showVisitProvidersInfo() { private fun showVisitProvidersInfo() {
binding?.importOrChooseProviderText?.setText(R.string.nc_get_from_provider) binding.importOrChooseProviderText.setText(R.string.nc_get_from_provider)
binding?.importOrChooseProviderText?.setOnClickListener { binding.importOrChooseProviderText.setOnClickListener {
val browserIntent = Intent( val browserIntent = Intent(
Intent.ACTION_VIEW, Intent.ACTION_VIEW,
Uri.parse( Uri.parse(
@ -206,11 +230,11 @@ class ServerSelectionController :
@Suppress("Detekt.TooGenericExceptionCaught") @Suppress("Detekt.TooGenericExceptionCaught")
private fun checkServerAndProceed() { private fun checkServerAndProceed() {
dispose() dispose()
var url: String = binding?.serverEntryTextInputEditText?.text.toString().trim { it <= ' ' } var url: String = binding.serverEntryTextInputEditText.text.toString().trim { it <= ' ' }
showserverEntryProgressBar() showserverEntryProgressBar()
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) { if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
binding?.importOrChooseProviderText?.visibility = View.INVISIBLE binding.importOrChooseProviderText.visibility = View.INVISIBLE
binding?.certTextView?.visibility = View.INVISIBLE binding.certTextView.visibility = View.INVISIBLE
} }
if (url.endsWith("/")) { if (url.endsWith("/")) {
url = url.substring(0, url.length - 1) url = url.substring(0, url.length - 1)
@ -278,17 +302,17 @@ class ServerSelectionController :
hideserverEntryProgressBar() hideserverEntryProgressBar()
} }
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) { if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
binding?.importOrChooseProviderText?.visibility = View.VISIBLE binding.importOrChooseProviderText.visibility = View.VISIBLE
binding?.certTextView?.visibility = View.VISIBLE binding.certTextView.visibility = View.VISIBLE
} }
dispose() dispose()
} }
}) { }) {
hideserverEntryProgressBar() hideserverEntryProgressBar()
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) { if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
binding?.importOrChooseProviderText?.visibility = View.VISIBLE binding.importOrChooseProviderText.visibility = View.VISIBLE
binding?.certTextView?.visibility = View.VISIBLE binding.certTextView.visibility = View.VISIBLE
} }
dispose() dispose()
} }
@ -311,29 +335,25 @@ class ServerSelectionController :
capabilities.spreedCapability?.features?.isNotEmpty() == true capabilities.spreedCapability?.features?.isNotEmpty() == true
if (hasTalk) { if (hasTalk) {
activity?.runOnUiThread { runOnUiThread {
if (CapabilitiesUtilNew.isServerEOL(capabilities)) { if (CapabilitiesUtilNew.isServerEOL(capabilities)) {
if (resources != null) { if (resources != null) {
activity!!.runOnUiThread { runOnUiThread {
setErrorText(resources!!.getString(R.string.nc_settings_server_eol)) setErrorText(resources!!.getString(R.string.nc_settings_server_eol))
} }
} }
} else { } else {
router.pushController( val bundle = Bundle()
RouterTransaction.with( bundle.putString(BundleKeys.KEY_BASE_URL, queryUrl.replace("/status.php", ""))
WebViewLoginController(
queryUrl.replace("/status.php", ""), val intent = Intent(context, WebViewLoginActivity::class.java)
false intent.putExtras(bundle)
) startActivity(intent)
)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
} }
} }
} else { } else {
if (activity != null && resources != null) { if (resources != null) {
activity!!.runOnUiThread { runOnUiThread {
setErrorText(resources!!.getString(R.string.nc_server_unsupported)) setErrorText(resources!!.getString(R.string.nc_server_unsupported))
} }
} }
@ -342,8 +362,8 @@ class ServerSelectionController :
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
Log.e(TAG, "Error while checking capabilities", e) Log.e(TAG, "Error while checking capabilities", e)
if (activity != null && resources != null) { if (resources != null) {
activity!!.runOnUiThread { runOnUiThread {
setErrorText(resources!!.getString(R.string.nc_common_error_sorry)) setErrorText(resources!!.getString(R.string.nc_common_error_sorry))
} }
} }
@ -360,72 +380,31 @@ class ServerSelectionController :
} }
private fun setErrorText(text: String) { private fun setErrorText(text: String) {
binding?.errorWrapper?.visibility = View.VISIBLE binding.errorWrapper.visibility = View.VISIBLE
binding?.errorText?.text = text binding.errorText.text = text
hideserverEntryProgressBar() hideserverEntryProgressBar()
} }
private fun showserverEntryProgressBar() { private fun showserverEntryProgressBar() {
binding?.errorWrapper?.visibility = View.INVISIBLE binding.errorWrapper.visibility = View.INVISIBLE
binding?.serverEntryProgressBar?.visibility = View.VISIBLE binding.serverEntryProgressBar.visibility = View.VISIBLE
} }
private fun hideserverEntryProgressBar() { private fun hideserverEntryProgressBar() {
binding?.serverEntryProgressBar?.visibility = View.INVISIBLE binding.serverEntryProgressBar.visibility = View.INVISIBLE
}
override fun onAttach(view: View) {
super.onAttach(view)
if (ApplicationWideMessageHolder.getInstance().messageType != null) {
if (ApplicationWideMessageHolder.getInstance().messageType
== ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION
) {
setErrorText(resources!!.getString(R.string.nc_account_scheduled_for_deletion))
ApplicationWideMessageHolder.getInstance().messageType = null
} else if (ApplicationWideMessageHolder.getInstance().messageType
== ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
) {
setErrorText(resources!!.getString(R.string.nc_settings_no_talk_installed))
} else if (ApplicationWideMessageHolder.getInstance().messageType
== ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT
) {
setErrorText(resources!!.getString(R.string.nc_server_failed_to_import_account))
}
ApplicationWideMessageHolder.getInstance().messageType = null
}
if (activity != null && resources != null) {
DisplayUtils.applyColorToStatusBar(
activity,
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
)
DisplayUtils.applyColorToNavigationBar(
activity!!.window,
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
)
}
setCertTextView()
} }
@SuppressLint("LongLogTag") @SuppressLint("LongLogTag")
private fun setCertTextView() { private fun setCertTextView() {
if (activity != null) { runOnUiThread {
activity!!.runOnUiThread { if (!TextUtils.isEmpty(appPreferences.temporaryClientCertAlias)) {
if (!TextUtils.isEmpty(appPreferences!!.temporaryClientCertAlias)) { binding.certTextView.setText(R.string.nc_change_cert_auth)
binding?.certTextView?.setText(R.string.nc_change_cert_auth)
} else { } else {
binding?.certTextView?.setText(R.string.nc_configure_cert_auth) binding.certTextView.setText(R.string.nc_configure_cert_auth)
} }
hideserverEntryProgressBar() hideserverEntryProgressBar()
} }
} }
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
if (activity != null) {
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
}
}
public override fun onDestroy() { public override fun onDestroy() {
super.onDestroy() super.onDestroy()
@ -443,7 +422,7 @@ class ServerSelectionController :
get() = AppBarLayoutType.EMPTY get() = AppBarLayoutType.EMPTY
companion object { companion object {
const val TAG = "ServerSelectionController" private val TAG = ServerSelectionActivity::class.java.simpleName
const val MIN_SERVER_MAJOR_VERSION = 13 const val MIN_SERVER_MAJOR_VERSION = 13
} }
} }

View file

@ -3,6 +3,8 @@
* *
* @author Mario Danic * @author Mario Danic
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com> * Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
* *
@ -22,25 +24,23 @@
* Parts related to account import were either copied from or inspired by the great work done by David Luhmer at: * Parts related to account import were either copied from or inspired by the great work done by David Luhmer at:
* https://github.com/nextcloud/ownCloud-Account-Importer * https://github.com/nextcloud/ownCloud-Account-Importer
*/ */
package com.nextcloud.talk.controllers package com.nextcloud.talk.account
import android.accounts.Account import android.accounts.Account
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo
import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import autodagger.AutoInjector import autodagger.AutoInjector
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.adapters.items.AdvancedUserItem import com.nextcloud.talk.adapters.items.AdvancedUserItem
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ControllerGenericRvBinding import com.nextcloud.talk.databinding.ActivitySwitchAccountBinding
import com.nextcloud.talk.models.ImportAccount import com.nextcloud.talk.models.ImportAccount
import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.users.UserManager
@ -56,14 +56,11 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import org.osmdroid.config.Configuration import org.osmdroid.config.Configuration
import java.net.CookieManager import java.net.CookieManager
import javax.inject.Inject import javax.inject.Inject
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class SwitchAccountController(args: Bundle? = null) : class SwitchAccountActivity : BaseActivity() {
BaseController( private lateinit var binding: ActivitySwitchAccountBinding
R.layout.controller_generic_rv,
args
) {
private val binding: ControllerGenericRvBinding? by viewBinding(ControllerGenericRvBinding::bind)
@Inject @Inject
lateinit var userManager: UserManager lateinit var userManager: UserManager
@ -89,41 +86,52 @@ class SwitchAccountController(args: Bundle? = null) :
if (userManager.setUserAsActive(user).blockingGet()) { if (userManager.setUserAsActive(user).blockingGet()) {
cookieManager.cookieStore.removeAll() cookieManager.cookieStore.removeAll()
if (activity != null) { finish()
activity!!.runOnUiThread { router.popCurrentController() }
}
} }
} }
true true
} }
init { @SuppressLint("SourceLockedOrientationActivity")
setHasOptionsMenu(true) override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedApplication!!.componentApplication.inject(this) sharedApplication!!.componentApplication.inject(this)
binding = ActivitySwitchAccountBinding.inflate(layoutInflater)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
setupActionBar()
setupPrimaryColors()
Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context)) Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))
if (args?.containsKey(KEY_IS_ACCOUNT_IMPORT) == true) {
handleIntent()
}
private fun handleIntent() {
intent.extras?.let {
if (it.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
isAccountImport = true isAccountImport = true
} }
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
router.popCurrentController()
true
}
else -> super.onOptionsItemSelected(item)
}
} }
override fun onViewBound(view: View) { private fun setupActionBar() {
super.onViewBound(view) setSupportActionBar(binding.toolbar)
binding?.swipeRefreshLayout?.isEnabled = false binding.toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(R.color.transparent, null)))
supportActionBar?.title = resources!!.getString(R.string.nc_select_an_account)
}
actionBar?.show() @Suppress("Detekt.NestedBlockDepth")
override fun onResume() {
super.onResume()
if (adapter == null) { if (adapter == null) {
adapter = FlexibleAdapter(userItems, activity, false) adapter = FlexibleAdapter(userItems, this, false)
var participant: Participant var participant: Participant
if (!isAccountImport) { if (!isAccountImport) {
@ -166,11 +174,10 @@ class SwitchAccountController(args: Bundle? = null) :
} }
private fun prepareViews() { private fun prepareViews() {
val layoutManager: LinearLayoutManager = SmoothScrollLinearLayoutManager(activity) val layoutManager: LinearLayoutManager = SmoothScrollLinearLayoutManager(this)
binding?.recyclerView?.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
binding?.recyclerView?.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding?.recyclerView?.adapter = adapter binding.recyclerView.adapter = adapter
binding?.swipeRefreshLayout?.isEnabled = false
} }
private fun reauthorizeFromImport(account: Account?) { private fun reauthorizeFromImport(account: Account?) {
@ -180,14 +187,9 @@ class SwitchAccountController(args: Bundle? = null) :
bundle.putString(KEY_USERNAME, importAccount.getUsername()) bundle.putString(KEY_USERNAME, importAccount.getUsername())
bundle.putString(KEY_TOKEN, importAccount.getToken()) bundle.putString(KEY_TOKEN, importAccount.getToken())
bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true) bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true)
router.pushController(
RouterTransaction.with(AccountVerificationController(bundle))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
override val title: String val intent = Intent(context, AccountVerificationActivity::class.java)
get() = intent.putExtras(bundle)
resources!!.getString(R.string.nc_select_an_account) startActivity(intent)
}
} }

View file

@ -3,6 +3,8 @@
* *
* @author Mario Danic * @author Mario Danic
* @author Andy Scherzinger * @author Andy Scherzinger
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de> * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
* *
@ -19,9 +21,10 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.nextcloud.talk.controllers package com.nextcloud.talk.account
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.http.SslError import android.net.http.SslError
@ -40,34 +43,30 @@ import android.webkit.WebResourceResponse
import android.webkit.WebSettings import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity import androidx.activity.OnBackPressedCallback
import androidx.core.content.res.ResourcesCompat
import androidx.work.Data
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import autodagger.AutoInjector import autodagger.AutoInjector
import com.bluelinelabs.conductor.RouterTransaction import com.google.android.material.snackbar.Snackbar
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.controllers.base.BaseController import com.nextcloud.talk.databinding.ActivityWebViewLoginBinding
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.databinding.ControllerWebViewLoginBinding
import com.nextcloud.talk.events.CertificateEvent import com.nextcloud.talk.events.CertificateEvent
import com.nextcloud.talk.jobs.PushRegistrationWorker import com.nextcloud.talk.jobs.AccountRemovalWorker
import com.nextcloud.talk.models.LoginData import com.nextcloud.talk.models.LoginData
import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import com.nextcloud.talk.utils.ssl.TrustManager import com.nextcloud.talk.utils.ssl.TrustManager
import de.cotech.hw.fido.WebViewFidoBridge import de.cotech.hw.fido.WebViewFidoBridge
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import org.greenrobot.eventbus.EventBus
import java.lang.reflect.Field import java.lang.reflect.Field
import java.net.CookieManager import java.net.CookieManager
import java.net.URLDecoder import java.net.URLDecoder
@ -78,11 +77,9 @@ import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class WebViewLoginController(args: Bundle? = null) : BaseController( class WebViewLoginActivity : BaseActivity() {
R.layout.controller_web_view_login,
args private lateinit var binding: ActivityWebViewLoginBinding
) {
private val binding: ControllerWebViewLoginBinding? by viewBinding(ControllerWebViewLoginBinding::bind)
@Inject @Inject
lateinit var userManager: UserManager lateinit var userManager: UserManager
@ -90,34 +87,26 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
@Inject @Inject
lateinit var trustManager: TrustManager lateinit var trustManager: TrustManager
@Inject
lateinit var eventBus: EventBus
@Inject @Inject
lateinit var cookieManager: CookieManager lateinit var cookieManager: CookieManager
private var assembledPrefix: String? = null private var assembledPrefix: String? = null
private var userQueryDisposable: Disposable? = null private var userQueryDisposable: Disposable? = null
private var baseUrl: String? = null private var baseUrl: String? = null
private var isPasswordUpdate = false private var reauthorizeAccount = false
private var username: String? = null private var username: String? = null
private var password: String? = null private var password: String? = null
private var loginStep = 0 private var loginStep = 0
private var automatedLoginAttempted = false private var automatedLoginAttempted = false
private var webViewFidoBridge: WebViewFidoBridge? = null private var webViewFidoBridge: WebViewFidoBridge? = null
constructor(baseUrl: String?, isPasswordUpdate: Boolean) : this() { private val onBackPressedCallback = object : OnBackPressedCallback(true) {
this.baseUrl = baseUrl override fun handleOnBackPressed() {
this.isPasswordUpdate = isPasswordUpdate val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
} }
constructor(baseUrl: String?, isPasswordUpdate: Boolean, username: String?, password: String?) : this() {
this.baseUrl = baseUrl
this.isPasswordUpdate = isPasswordUpdate
this.username = username
this.password = password
} }
private val webLoginUserAgent: String private val webLoginUserAgent: String
get() = ( get() = (
Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) +
@ -129,33 +118,57 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
")" ")"
) )
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SourceLockedOrientationActivity")
override fun onViewBound(view: View) { override fun onCreate(savedInstanceState: Bundle?) {
super.onViewBound(view) super.onCreate(savedInstanceState)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT sharedApplication!!.componentApplication.inject(this)
binding = ActivityWebViewLoginBinding.inflate(layoutInflater)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
actionBar?.hide() actionBar?.hide()
setupPrimaryColors()
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
handleIntent()
setupWebView()
}
private fun handleIntent() {
val extras = intent.extras!!
baseUrl = extras.getString(KEY_BASE_URL)
username = extras.getString(KEY_USERNAME)
if (extras.containsKey(BundleKeys.KEY_REAUTHORIZE_ACCOUNT)) {
reauthorizeAccount = extras.getBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT)
}
if (extras.containsKey(BundleKeys.KEY_PASSWORD)) {
password = extras.getString(BundleKeys.KEY_PASSWORD)
}
}
@SuppressLint("SetJavaScriptEnabled")
private fun setupWebView() {
assembledPrefix = resources!!.getString(R.string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/" assembledPrefix = resources!!.getString(R.string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/"
binding?.webview?.settings?.allowFileAccess = false binding.webview.settings.allowFileAccess = false
binding?.webview?.settings?.allowFileAccessFromFileURLs = false binding.webview.settings.allowFileAccessFromFileURLs = false
binding?.webview?.settings?.javaScriptEnabled = true binding.webview.settings.javaScriptEnabled = true
binding?.webview?.settings?.javaScriptCanOpenWindowsAutomatically = false binding.webview.settings.javaScriptCanOpenWindowsAutomatically = false
binding?.webview?.settings?.domStorageEnabled = true binding.webview.settings.domStorageEnabled = true
binding?.webview?.settings?.setUserAgentString(webLoginUserAgent) binding.webview.settings.userAgentString = webLoginUserAgent
binding?.webview?.settings?.saveFormData = false binding.webview.settings.saveFormData = false
binding?.webview?.settings?.savePassword = false binding.webview.settings.savePassword = false
binding?.webview?.settings?.setRenderPriority(WebSettings.RenderPriority.HIGH) binding.webview.settings.setRenderPriority(WebSettings.RenderPriority.HIGH)
binding?.webview?.clearCache(true) binding.webview.clearCache(true)
binding?.webview?.clearFormData() binding.webview.clearFormData()
binding?.webview?.clearHistory() binding.webview.clearHistory()
WebView.clearClientCertPreferences(null) WebView.clearClientCertPreferences(null)
webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(activity as AppCompatActivity?, binding?.webview) webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(this, binding.webview)
CookieSyncManager.createInstance(activity) CookieSyncManager.createInstance(this)
android.webkit.CookieManager.getInstance().removeAllCookies(null) android.webkit.CookieManager.getInstance().removeAllCookies(null)
val headers: MutableMap<String, String> = HashMap() val headers: MutableMap<String, String> = HashMap()
headers.put("OCS-APIRequest", "true") headers["OCS-APIRequest"] = "true"
binding?.webview?.webViewClient = object : WebViewClient() { binding.webview.webViewClient = object : WebViewClient() {
private var basePageLoaded = false private var basePageLoaded = false
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
webViewFidoBridge?.delegateShouldInterceptRequest(view, request) webViewFidoBridge?.delegateShouldInterceptRequest(view, request)
@ -180,24 +193,24 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
loginStep++ loginStep++
if (!basePageLoaded) { if (!basePageLoaded) {
binding?.progressBar?.visibility = View.GONE binding.progressBar.visibility = View.GONE
binding?.webview?.visibility = View.VISIBLE binding.webview.visibility = View.VISIBLE
basePageLoaded = true basePageLoaded = true
} }
if (!TextUtils.isEmpty(username)) { if (!TextUtils.isEmpty(username)) {
if (loginStep == 1) { if (loginStep == 1) {
binding?.webview?.loadUrl( binding.webview.loadUrl(
"javascript: {document.getElementsByClassName('login')[0].click(); };" "javascript: {document.getElementsByClassName('login')[0].click(); };"
) )
} else if (!automatedLoginAttempted) { } else if (!automatedLoginAttempted) {
automatedLoginAttempted = true automatedLoginAttempted = true
if (TextUtils.isEmpty(password)) { if (TextUtils.isEmpty(password)) {
binding?.webview?.loadUrl( binding.webview.loadUrl(
"javascript:var justStore = document.getElementById('user').value = '$username';" "javascript:var justStore = document.getElementById('user').value = '$username';"
) )
} else { } else {
binding?.webview?.loadUrl( binding.webview.loadUrl(
"javascript: {" + "javascript: {" +
"document.getElementById('user').value = '" + username + "';" + "document.getElementById('user').value = '" + username + "';" +
"document.getElementById('password').value = '" + password + "';" + "document.getElementById('password').value = '" + password + "';" +
@ -213,8 +226,8 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
override fun onReceivedClientCertRequest(view: WebView, request: ClientCertRequest) { override fun onReceivedClientCertRequest(view: WebView, request: ClientCertRequest) {
val user = userManager.currentUser.blockingGet() val user = userManager.currentUser.blockingGet()
var alias: String? = null var alias: String? = null
if (!isPasswordUpdate) { if (!reauthorizeAccount) {
alias = appPreferences!!.temporaryClientCertAlias alias = appPreferences.temporaryClientCertAlias
} }
if (TextUtils.isEmpty(alias) && user != null) { if (TextUtils.isEmpty(alias) && user != null) {
alias = user.clientCertificate alias = user.clientCertificate
@ -223,9 +236,9 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
val finalAlias = alias val finalAlias = alias
Thread { Thread {
try { try {
val privateKey = KeyChain.getPrivateKey(activity!!, finalAlias!!) val privateKey = KeyChain.getPrivateKey(applicationContext, finalAlias!!)
val certificates = KeyChain.getCertificateChain( val certificates = KeyChain.getCertificateChain(
activity!!, applicationContext,
finalAlias finalAlias
) )
if (privateKey != null && certificates != null) { if (privateKey != null && certificates != null) {
@ -241,16 +254,16 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
}.start() }.start()
} else { } else {
KeyChain.choosePrivateKeyAlias( KeyChain.choosePrivateKeyAlias(
activity!!, this@WebViewLoginActivity,
{ chosenAlias: String? -> { chosenAlias: String? ->
if (chosenAlias != null) { if (chosenAlias != null) {
appPreferences!!.temporaryClientCertAlias = chosenAlias appPreferences!!.temporaryClientCertAlias = chosenAlias
Thread { Thread {
var privateKey: PrivateKey? = null var privateKey: PrivateKey? = null
try { try {
privateKey = KeyChain.getPrivateKey(activity!!, chosenAlias) privateKey = KeyChain.getPrivateKey(applicationContext, chosenAlias)
val certificates = KeyChain.getCertificateChain( val certificates = KeyChain.getCertificateChain(
activity!!, applicationContext,
chosenAlias chosenAlias
) )
if (privateKey != null && certificates != null) { if (privateKey != null && certificates != null) {
@ -304,7 +317,7 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
super.onReceivedError(view, errorCode, description, failingUrl) super.onReceivedError(view, errorCode, description, failingUrl)
} }
} }
binding?.webview?.loadUrl("$baseUrl/index.php/login/flow", headers) binding.webview.loadUrl("$baseUrl/index.php/login/flow", headers)
} }
private fun dispose() { private fun dispose() {
@ -318,25 +331,27 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
val loginData = parseLoginData(assembledPrefix, dataString) val loginData = parseLoginData(assembledPrefix, dataString)
if (loginData != null) { if (loginData != null) {
dispose() dispose()
val currentUser = userManager.currentUser.blockingGet()
var messageType: ApplicationWideMessageHolder.MessageType? = null
if (!isPasswordUpdate &&
userManager.checkIfUserExists(loginData.username!!, baseUrl!!).blockingGet()
) {
messageType = ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED
}
if (userManager.checkIfUserIsScheduledForDeletion(loginData.username!!, baseUrl!!).blockingGet()) {
ApplicationWideMessageHolder.getInstance().messageType =
ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION
if (!isPasswordUpdate) {
router.popToRoot()
} else {
router.popCurrentController()
}
}
val finalMessageType = messageType
cookieManager.cookieStore.removeAll() cookieManager.cookieStore.removeAll()
if (!isPasswordUpdate && finalMessageType == null) {
if (userManager.checkIfUserIsScheduledForDeletion(loginData.username!!, baseUrl!!).blockingGet()) {
Log.e(TAG, "Tried to add already existing user who is scheduled for deletion.")
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
// however the user is not yet deleted, just start AccountRemovalWorker again to make sure to delete it.
startAccountRemovalWorkerAndRestartApp()
} else if (userManager.checkIfUserExists(loginData.username!!, baseUrl!!).blockingGet()) {
if (reauthorizeAccount) {
updateUserAndRestartApp(loginData)
} else {
Log.w(TAG, "It was tried to add an account that account already exists. Skipped user creation.")
restartApp()
}
} else {
startAccountVerification(loginData)
}
}
}
private fun startAccountVerification(loginData: LoginData) {
val bundle = Bundle() val bundle = Bundle()
bundle.putString(KEY_USERNAME, loginData.username) bundle.putString(KEY_USERNAME, loginData.username)
bundle.putString(KEY_TOKEN, loginData.token) bundle.putString(KEY_TOKEN, loginData.token)
@ -350,46 +365,41 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
if (!TextUtils.isEmpty(protocol)) { if (!TextUtils.isEmpty(protocol)) {
bundle.putString(KEY_ORIGINAL_PROTOCOL, protocol) bundle.putString(KEY_ORIGINAL_PROTOCOL, protocol)
} }
router.pushController( val intent = Intent(context, AccountVerificationActivity::class.java)
RouterTransaction.with(AccountVerificationController(bundle)) intent.putExtras(bundle)
.pushChangeHandler(HorizontalChangeHandler()) startActivity(intent)
.popChangeHandler(HorizontalChangeHandler()) }
)
} else { private fun restartApp() {
if (isPasswordUpdate) { val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
}
private fun updateUserAndRestartApp(loginData: LoginData) {
val currentUser = userManager.currentUser.blockingGet()
if (currentUser != null) { if (currentUser != null) {
currentUser.clientCertificate = appPreferences!!.temporaryClientCertAlias currentUser.clientCertificate = appPreferences.temporaryClientCertAlias
currentUser.token = loginData.token currentUser.token = loginData.token
val rowsUpdated = userManager.updateOrCreateUser(currentUser).blockingGet() val rowsUpdated = userManager.updateOrCreateUser(currentUser).blockingGet()
Log.d(TAG, "User rows updated: $rowsUpdated") Log.d(TAG, "User rows updated: $rowsUpdated")
restartApp()
if (finalMessageType != null) { }
ApplicationWideMessageHolder.getInstance().messageType = finalMessageType
} }
val data = Data.Builder().putString( private fun startAccountRemovalWorkerAndRestartApp() {
PushRegistrationWorker.ORIGIN, val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
"WebViewLoginController#parseAndLoginFromWebView" WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
).build()
val pushRegistrationWork = OneTimeWorkRequest.Builder( WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
PushRegistrationWorker::class.java .observeForever { workInfo: WorkInfo ->
)
.setInputData(data)
.build()
WorkManager.getInstance().enqueue(pushRegistrationWork) when (workInfo.state) {
router.popCurrentController() WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
} restartApp()
} else {
if (finalMessageType != null) {
// FIXME when the user registers a new account that was setup before (aka
// ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED)
// The token is not updated in the database and therefore the account not visible/usable
ApplicationWideMessageHolder.getInstance().messageType = finalMessageType
}
router.popToRoot()
} }
else -> {}
} }
} }
} }
@ -432,30 +442,11 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
} }
} }
override fun onAttach(view: View) {
super.onAttach(view)
if (activity != null && resources != null) {
DisplayUtils.applyColorToStatusBar(
activity,
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
)
DisplayUtils.applyColorToNavigationBar(
activity!!.window,
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
)
}
}
public override fun onDestroy() { public override fun onDestroy() {
super.onDestroy() super.onDestroy()
dispose() dispose()
} }
override fun onDestroyView(view: View) {
super.onDestroyView(view)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
}
init { init {
sharedApplication!!.componentApplication.inject(this) sharedApplication!!.componentApplication.inject(this)
} }
@ -464,7 +455,7 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
get() = AppBarLayoutType.EMPTY get() = AppBarLayoutType.EMPTY
companion object { companion object {
const val TAG = "WebViewLoginController" private val TAG = WebViewLoginActivity::class.java.simpleName
private const val PROTOCOL_SUFFIX = "://" private const val PROTOCOL_SUFFIX = "://"
private const val LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":" private const val LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"
private const val PARAMETER_COUNT = 3 private const val PARAMETER_COUNT = 3

View file

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.nextcloud.talk.controllers.base.providers; package com.nextcloud.talk.activities;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;

View file

@ -24,17 +24,26 @@ package com.nextcloud.talk.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.webkit.SslErrorHandler import android.webkit.SslErrorHandler
import android.widget.EditText
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import autodagger.AutoInjector import autodagger.AutoInjector
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.account.AccountVerificationActivity
import com.nextcloud.talk.account.ServerSelectionActivity
import com.nextcloud.talk.account.SwitchAccountActivity
import com.nextcloud.talk.account.WebViewLoginActivity
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.events.CertificateEvent import com.nextcloud.talk.events.CertificateEvent
import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -77,6 +86,8 @@ open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
cleanTempCertPreference()
} }
public override fun onStart() { public override fun onStart() {
@ -87,6 +98,11 @@ open class BaseActivity : AppCompatActivity() {
public override fun onResume() { public override fun onResume() {
super.onResume() super.onResume()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) {
val viewGroup = (findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0) as ViewGroup
disableKeyboardPersonalisedLearning(viewGroup)
}
if (appPreferences.isScreenSecured) { if (appPreferences.isScreenSecured) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
} else { } else {
@ -104,6 +120,19 @@ open class BaseActivity : AppCompatActivity() {
colorizeNavigationBar() colorizeNavigationBar()
} }
fun setupPrimaryColors() {
if (resources != null) {
DisplayUtils.applyColorToStatusBar(
this,
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
)
DisplayUtils.applyColorToNavigationBar(
window,
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
)
}
}
open fun colorizeStatusBar() { open fun colorizeStatusBar() {
if (resources != null) { if (resources != null) {
if (appBarLayoutType == AppBarLayoutType.SEARCH_BAR) { if (appBarLayoutType == AppBarLayoutType.SEARCH_BAR) {
@ -123,7 +152,23 @@ open class BaseActivity : AppCompatActivity() {
} }
} }
fun showCertificateDialog( @RequiresApi(api = Build.VERSION_CODES.O)
private fun disableKeyboardPersonalisedLearning(viewGroup: ViewGroup) {
var view: View?
var editText: EditText
for (i in 0 until viewGroup.childCount) {
view = viewGroup.getChildAt(i)
if (view is EditText) {
editText = view
editText.imeOptions = editText.imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
} else if (view is ViewGroup) {
disableKeyboardPersonalisedLearning(view)
}
}
}
@Suppress("Detekt.NestedBlockDepth")
private fun showCertificateDialog(
cert: X509Certificate, cert: X509Certificate,
trustManager: TrustManager, trustManager: TrustManager,
sslErrorHandler: SslErrorHandler? sslErrorHandler: SslErrorHandler?
@ -160,15 +205,17 @@ open class BaseActivity : AppCompatActivity() {
validUntil validUntil
) )
val dialogBuilder = MaterialAlertDialogBuilder(this) val dialogBuilder = MaterialAlertDialogBuilder(this).setIcon(
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.ic_security_white_24dp)) viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
.setTitle(R.string.nc_certificate_dialog_title) context,
R.drawable.ic_security_white_24dp
)
).setTitle(R.string.nc_certificate_dialog_title)
.setMessage(dialogText) .setMessage(dialogText)
.setPositiveButton(R.string.nc_yes) { _, _ -> .setPositiveButton(R.string.nc_yes) { _, _ ->
trustManager.addCertInTrustStore(cert) trustManager.addCertInTrustStore(cert)
sslErrorHandler?.proceed() sslErrorHandler?.proceed()
} }.setNegativeButton(R.string.nc_no) { _, _ ->
.setNegativeButton(R.string.nc_no) { _, _ ->
sslErrorHandler?.cancel() sslErrorHandler?.cancel()
} }
@ -185,12 +232,23 @@ open class BaseActivity : AppCompatActivity() {
} }
} }
private fun cleanTempCertPreference() {
val temporaryClassNames: MutableList<String> = ArrayList()
temporaryClassNames.add(ServerSelectionActivity::class.java.name)
temporaryClassNames.add(AccountVerificationActivity::class.java.name)
temporaryClassNames.add(WebViewLoginActivity::class.java.name)
temporaryClassNames.add(SwitchAccountActivity::class.java.name)
if (!temporaryClassNames.contains(javaClass.name)) {
appPreferences.removeTemporaryClientCertAlias()
}
}
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: CertificateEvent) { fun onMessageEvent(event: CertificateEvent) {
showCertificateDialog(event.x509Certificate, event.magicTrustManager, event.sslErrorHandler) showCertificateDialog(event.x509Certificate, event.magicTrustManager, event.sslErrorHandler)
} }
companion object { companion object {
private val TAG = "BaseActivity" private val TAG = BaseActivity::class.java.simpleName
} }
} }

View file

@ -3096,7 +3096,7 @@ class CallActivity : CallBaseActivity() {
} }
override fun suppressFitsSystemWindows() { override fun suppressFitsSystemWindows() {
binding!!.controllerCallLayout.fitsSystemWindows = false binding!!.callLayout.fitsSystemWindows = false
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {

View file

@ -37,21 +37,14 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import autodagger.AutoInjector import autodagger.AutoInjector
import com.bluelinelabs.conductor.Conductor
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.account.ServerSelectionActivity
import com.nextcloud.talk.account.WebViewLoginActivity
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.callnotification.CallNotificationActivity import com.nextcloud.talk.callnotification.CallNotificationActivity
import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.controllers.ServerSelectionController
import com.nextcloud.talk.controllers.WebViewLoginController
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
import com.nextcloud.talk.conversationlist.ConversationsListActivity import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityMainBinding import com.nextcloud.talk.databinding.ActivityMainBinding
@ -61,7 +54,6 @@ import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.SecurityUtils import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ACCOUNT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.SingleObserver import io.reactivex.SingleObserver
@ -80,15 +72,11 @@ class MainActivity : BaseActivity(), ActionBarProvider {
@Inject @Inject
lateinit var userManager: UserManager lateinit var userManager: UserManager
private var router: Router? = null
private val onBackPressedCallback = object : OnBackPressedCallback(true) { private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
if (!router!!.handleBack()) {
finish() finish()
} }
} }
}
@Suppress("Detekt.TooGenericExceptionCaught") @Suppress("Detekt.TooGenericExceptionCaught")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -111,8 +99,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
router = Conductor.attachRouter(this, binding.controllerContainer, savedInstanceState)
handleIntent(intent) handleIntent(intent)
onBackPressedDispatcher.addCallback(this, onBackPressedCallback) onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
@ -128,28 +114,24 @@ class MainActivity : BaseActivity(), ActionBarProvider {
} }
} }
private fun launchLoginScreen() { private fun launchServerSelection() {
if (!TextUtils.isEmpty(resources.getString(R.string.weblogin_url))) { if (isBrandingUrlSet()) {
router!!.pushController( val intent = Intent(context, WebViewLoginActivity::class.java)
RouterTransaction.with( val bundle = Bundle()
WebViewLoginController(resources.getString(R.string.weblogin_url), false) bundle.putString(BundleKeys.KEY_BASE_URL, resources.getString(R.string.weblogin_url))
) intent.putExtras(bundle)
.pushChangeHandler(HorizontalChangeHandler()) startActivity(intent)
.popChangeHandler(HorizontalChangeHandler())
)
} else { } else {
router!!.setRoot( val intent = Intent(context, ServerSelectionActivity::class.java)
RouterTransaction.with(ServerSelectionController()) startActivity(intent)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
} }
} }
private fun isBrandingUrlSet() = !TextUtils.isEmpty(resources.getString(R.string.weblogin_url))
override fun onStart() { override fun onStart() {
Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString()) Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString())
super.onStart() super.onStart()
logRouterBackStack(router!!)
} }
override fun onResume() { override fun onResume() {
@ -178,14 +160,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
startActivity(intent) startActivity(intent)
} }
fun addAccount() {
router!!.pushController(
RouterTransaction.with(ServerSelectionController())
.pushChangeHandler(VerticalChangeHandler())
.popChangeHandler(VerticalChangeHandler())
)
}
private fun handleActionFromContact(intent: Intent) { private fun handleActionFromContact(intent: Intent) {
if (intent.action == Intent.ACTION_VIEW && intent.data != null) { if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
val cursor = contentResolver.query(intent.data!!, null, null, null, null) val cursor = contentResolver.query(intent.data!!, null, null, null, null)
@ -209,7 +183,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
startConversation(user) startConversation(user)
} else { } else {
Snackbar.make( Snackbar.make(
binding.controllerContainer, binding.root,
R.string.nc_phone_book_integration_account_not_found, R.string.nc_phone_book_integration_account_not_found,
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
).show() ).show()
@ -283,28 +257,18 @@ class MainActivity : BaseActivity(), ActionBarProvider {
} }
if (user != null && userManager.setUserAsActive(user).blockingGet()) { if (user != null && userManager.setUserAsActive(user).blockingGet()) {
// this should be avoided (it's still from conductor architecture). activities should be opened directly.
if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) { if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) { if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
if (!router!!.hasRootController()) {
openConversationList()
}
val callNotificationIntent = Intent(this, CallNotificationActivity::class.java) val callNotificationIntent = Intent(this, CallNotificationActivity::class.java)
intent.extras?.let { callNotificationIntent.putExtras(it) } intent.extras?.let { callNotificationIntent.putExtras(it) }
startActivity(callNotificationIntent) startActivity(callNotificationIntent)
} else { } else {
logRouterBackStack(router!!)
val chatIntent = Intent(context, ChatActivity::class.java) val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(intent.extras!!) chatIntent.putExtras(intent.extras!!)
startActivity(chatIntent) startActivity(chatIntent)
logRouterBackStack(router!!)
} }
} }
} else if (intent.hasExtra(ADD_ACCOUNT) && intent.getBooleanExtra(ADD_ACCOUNT, false)) { } else {
addAccount()
} else if (!router!!.hasRootController()) {
if (!appPreferences.isDbRoomMigrated) { if (!appPreferences.isDbRoomMigrated) {
appPreferences.isDbRoomMigrated = true appPreferences.isDbRoomMigrated = true
} }
@ -321,7 +285,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
} }
} else { } else {
runOnUiThread { runOnUiThread {
launchLoginScreen() launchServerSelection()
} }
} }
} }
@ -333,19 +297,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
} }
} }
private fun logRouterBackStack(router: Router) {
if (BuildConfig.DEBUG) {
val backstack = router.backstack
var routerTransaction: RouterTransaction?
Log.d(TAG, " backstack size: " + router.backstackSize)
for (i in 0 until router.backstackSize) {
routerTransaction = backstack[i]
Log.d(TAG, " controller: " + routerTransaction.controller)
}
}
}
companion object { companion object {
private const val TAG = "MainActivity" private val TAG = MainActivity::class.java.simpleName
} }
} }

View file

@ -231,6 +231,6 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
} }
companion object { companion object {
private val TAG = NextcloudTalkApplication::class.java.simpleName private val TAG = IncomingPollMessageViewHolder::class.java.simpleName
} }
} }

View file

@ -209,6 +209,6 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
} }
companion object { companion object {
private val TAG = NextcloudTalkApplication::class.java.simpleName private val TAG = OutcomingPollMessageViewHolder::class.java.simpleName
} }
} }

View file

@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.nextcloud.talk.controllers.bottomsheet.items package com.nextcloud.talk.bottomsheet.items
import android.widget.ImageView import android.widget.ImageView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes

View file

@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.nextcloud.talk.controllers.bottomsheet.items package com.nextcloud.talk.bottomsheet.items
import androidx.annotation.CheckResult import androidx.annotation.CheckResult
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager

View file

@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.nextcloud.talk.controllers.bottomsheet.items package com.nextcloud.talk.bottomsheet.items
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup

View file

@ -317,7 +317,7 @@ class CallNotificationActivity : CallBaseActivity() {
} }
override fun suppressFitsSystemWindows() { override fun suppressFitsSystemWindows() {
binding!!.controllerCallNotificationLayout.fitsSystemWindows = false binding!!.callNotificationLayout.fitsSystemWindows = false
} }
companion object { companion object {

View file

@ -1605,7 +1605,7 @@ class ChatActivity :
participantPermissions.hasChatPermission() && participantPermissions.hasChatPermission() &&
!isReadOnlyConversation() !isReadOnlyConversation()
) { ) {
val messageSwipeController = MessageSwipeCallback( val messageSwipeCallback = MessageSwipeCallback(
this, this,
object : MessageSwipeActions { object : MessageSwipeActions {
override fun showReplyUI(position: Int) { override fun showReplyUI(position: Int) {
@ -1617,7 +1617,7 @@ class ChatActivity :
} }
) )
val itemTouchHelper = ItemTouchHelper(messageSwipeController) val itemTouchHelper = ItemTouchHelper(messageSwipeCallback)
itemTouchHelper.attachToRecyclerView(binding.messagesListView) itemTouchHelper.attachToRecyclerView(binding.messagesListView)
} }
} }
@ -2561,7 +2561,7 @@ class ChatActivity :
super.onRequestPermissionsResult(requestCode, permissions, grantResults) super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) { if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(ConversationsListActivity.TAG, "upload starting after permissions were granted") Log.d(TAG, "upload starting after permissions were granted")
if (filesToUpload.isNotEmpty()) { if (filesToUpload.isNotEmpty()) {
uploadFiles(filesToUpload) uploadFiles(filesToUpload)
} }

View file

@ -488,17 +488,13 @@ class ContactsActivity :
} else { } else {
adapter?.filterItems() adapter?.filterItems()
} }
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
dispose(contactsQueryDisposable) dispose(contactsQueryDisposable)
} }
override fun onComplete() { override fun onComplete() {
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
dispose(contactsQueryDisposable) dispose(contactsQueryDisposable)
alreadyFetching = false alreadyFetching = false
disengageProgressBar() disengageProgressBar()
@ -656,12 +652,9 @@ class ContactsActivity :
private fun prepareViews() { private fun prepareViews() {
layoutManager = SmoothScrollLinearLayoutManager(this) layoutManager = SmoothScrollLinearLayoutManager(this)
binding.controllerGenericRv.recyclerView.layoutManager = layoutManager binding.contactsRv.layoutManager = layoutManager
binding.controllerGenericRv.recyclerView.setHasFixedSize(true) binding.contactsRv.setHasFixedSize(true)
binding.controllerGenericRv.recyclerView.adapter = adapter binding.contactsRv.adapter = adapter
binding.controllerGenericRv.swipeRefreshLayout.setOnRefreshListener { fetchData() }
binding.controllerGenericRv.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it.swipeRefreshLayout) }
binding.listOpenConversationsImage.background?.setColorFilter( binding.listOpenConversationsImage.background?.setColorFilter(
ResourcesCompat.getColor(resources!!, R.color.colorBackgroundDarker, null), ResourcesCompat.getColor(resources!!, R.color.colorBackgroundDarker, null),
@ -677,7 +670,7 @@ class ContactsActivity :
private fun disengageProgressBar() { private fun disengageProgressBar() {
if (!alreadyFetching) { if (!alreadyFetching) {
binding.loadingContent.visibility = View.GONE binding.loadingContent.visibility = View.GONE
binding.controllerGenericRv.root.visibility = View.VISIBLE binding.root.visibility = View.VISIBLE
if (isNewConversationView) { if (isNewConversationView) {
binding.callHeaderLayout.visibility = View.VISIBLE binding.callHeaderLayout.visibility = View.VISIBLE
} }
@ -713,8 +706,6 @@ class ContactsActivity :
adapter?.updateDataSet(contactItems as List<Nothing>?) adapter?.updateDataSet(contactItems as List<Nothing>?)
} }
binding.controllerGenericRv?.swipeRefreshLayout?.isEnabled = !adapter!!.hasFilter()
return true return true
} }

View file

@ -1,256 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* @author Andy Scherzinger
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.controllers
import android.annotation.SuppressLint
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.text.TextUtils
import android.util.Log
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import autodagger.AutoInjector
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.R
import com.nextcloud.talk.adapters.items.NotificationSoundItem
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.databinding.ControllerGenericRvBinding
import com.nextcloud.talk.models.RingtoneSettings
import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ARE_CALL_SOUNDS
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import java.io.IOException
@AutoInjector(NextcloudTalkApplication::class)
class RingtoneSelectionController(args: Bundle) :
BaseController(
R.layout.controller_generic_rv,
args
),
FlexibleAdapter.OnItemClickListener {
private val binding: ControllerGenericRvBinding? by viewBinding(ControllerGenericRvBinding::bind)
private var adapter: FlexibleAdapter<*>? = null
private var adapterDataObserver: RecyclerView.AdapterDataObserver? = null
private val abstractFlexibleItemList: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private val callNotificationSounds: Boolean
private var mediaPlayer: MediaPlayer? = null
private var cancelMediaPlayerHandler: Handler? = null
override fun onViewBound(view: View) {
super.onViewBound(view)
if (adapter == null) {
adapter = FlexibleAdapter(abstractFlexibleItemList, activity, false)
adapter!!.setNotifyChangeOfUnfilteredItems(true).mode = SelectableAdapter.Mode.SINGLE
adapter!!.addListener(this)
cancelMediaPlayerHandler = Handler()
}
adapter!!.addListener(this)
prepareViews()
fetchNotificationSounds()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == android.R.id.home) {
router.popCurrentController()
} else {
super.onOptionsItemSelected(item)
}
}
private fun prepareViews() {
val layoutManager: RecyclerView.LayoutManager = SmoothScrollLinearLayoutManager(activity)
binding?.recyclerView?.layoutManager = layoutManager
binding?.recyclerView?.setHasFixedSize(true)
binding?.recyclerView?.adapter = adapter
adapterDataObserver = object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
findSelectedSound()
}
}
adapter!!.registerAdapterDataObserver(adapterDataObserver!!)
binding?.swipeRefreshLayout?.isEnabled = false
}
@SuppressLint("LongLogTag")
private fun findSelectedSound() {
var foundDefault = false
var preferencesString: String? = null
val callsEnabledButNoRingtone = callNotificationSounds &&
TextUtils.isEmpty(appPreferences.callRingtoneUri.also { preferencesString = it })
val noCallsAndNoMessageTone = !callNotificationSounds &&
TextUtils.isEmpty(appPreferences.messageRingtoneUri.also { preferencesString = it })
if (callsEnabledButNoRingtone || noCallsAndNoMessageTone) {
adapter!!.toggleSelection(1)
foundDefault = true
}
if (!TextUtils.isEmpty(preferencesString) && !foundDefault) {
try {
val ringtoneSettings: RingtoneSettings =
LoganSquare.parse<RingtoneSettings>(preferencesString, RingtoneSettings::class.java)
if (ringtoneSettings.ringtoneUri == null) {
adapter!!.toggleSelection(0)
} else if (ringtoneSettings.ringtoneUri!!.toString() == ringtoneString) {
adapter!!.toggleSelection(1)
} else {
var notificationSoundItem: NotificationSoundItem?
for (i in 2 until adapter!!.itemCount) {
notificationSoundItem = adapter!!.getItem(i) as NotificationSoundItem?
if (
notificationSoundItem!!.notificationSoundUri == ringtoneSettings.ringtoneUri!!.toString()
) {
adapter!!.toggleSelection(i)
break
}
}
}
} catch (e: IOException) {
Log.e(TAG, "Failed to parse ringtone settings")
}
}
adapter!!.unregisterAdapterDataObserver(adapterDataObserver!!)
adapterDataObserver = null
}
private val ringtoneString: String
get() = if (callNotificationSounds) {
NotificationUtils.DEFAULT_CALL_RINGTONE_URI
} else {
NotificationUtils.DEFAULT_MESSAGE_RINGTONE_URI
}
private fun fetchNotificationSounds() {
abstractFlexibleItemList.add(
NotificationSoundItem(
resources!!.getString(R.string.nc_settings_no_ringtone),
null
)
)
abstractFlexibleItemList.add(
NotificationSoundItem(
resources!!.getString(R.string.nc_settings_default_ringtone),
ringtoneString
)
)
if (activity != null) {
val manager = RingtoneManager(activity)
if (callNotificationSounds) {
manager.setType(RingtoneManager.TYPE_RINGTONE)
} else {
manager.setType(RingtoneManager.TYPE_NOTIFICATION)
}
val cursor = manager.cursor
var notificationSoundItem: NotificationSoundItem
while (cursor.moveToNext()) {
val notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)
val notificationUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX)
val completeNotificationUri = notificationUri + "/" + cursor.getString(RingtoneManager.ID_COLUMN_INDEX)
notificationSoundItem = NotificationSoundItem(notificationTitle, completeNotificationUri)
abstractFlexibleItemList.add(notificationSoundItem)
}
}
adapter!!.updateDataSet(abstractFlexibleItemList as List<Nothing>?, false)
}
override fun onItemClick(view: View, position: Int): Boolean {
val notificationSoundItem = adapter!!.getItem(position) as NotificationSoundItem?
var ringtoneUri: Uri? = null
if (!TextUtils.isEmpty(notificationSoundItem!!.notificationSoundUri)) {
ringtoneUri = Uri.parse(notificationSoundItem.notificationSoundUri)
endMediaPlayer()
mediaPlayer = MediaPlayer.create(activity, ringtoneUri)
cancelMediaPlayerHandler = Handler()
cancelMediaPlayerHandler!!.postDelayed(
{ endMediaPlayer() },
(mediaPlayer!!.duration + DURATION_EXTENSION).toLong()
)
mediaPlayer!!.start()
}
if (adapter!!.selectedPositions.size == 0 || adapter!!.selectedPositions[0] != position) {
val ringtoneSettings = RingtoneSettings()
ringtoneSettings.ringtoneName = notificationSoundItem.notificationSoundName
ringtoneSettings.ringtoneUri = ringtoneUri
if (callNotificationSounds) {
try {
appPreferences!!.callRingtoneUri = LoganSquare.serialize(ringtoneSettings)
adapter!!.toggleSelection(position)
adapter!!.notifyDataSetChanged()
} catch (e: IOException) {
Log.e(TAG, "Failed to store selected ringtone for calls")
}
} else {
try {
appPreferences!!.messageRingtoneUri = LoganSquare.serialize(ringtoneSettings)
adapter!!.toggleSelection(position)
adapter!!.notifyDataSetChanged()
} catch (e: IOException) {
Log.e(TAG, "Failed to store selected ringtone for calls")
}
}
}
return true
}
private fun endMediaPlayer() {
if (cancelMediaPlayerHandler != null) {
cancelMediaPlayerHandler!!.removeCallbacksAndMessages(null)
}
if (mediaPlayer != null) {
if (mediaPlayer!!.isPlaying) {
mediaPlayer!!.stop()
}
mediaPlayer!!.release()
mediaPlayer = null
}
}
public override fun onDestroy() {
endMediaPlayer()
super.onDestroy()
}
companion object {
private const val TAG = "RingtoneSelection"
private const val DURATION_EXTENSION = 25
}
init {
setHasOptionsMenu(true)
sharedApplication!!.componentApplication.inject(this)
callNotificationSounds = args.getBoolean(KEY_ARE_CALL_SOUNDS, false)
}
override val title: String
get() =
resources!!.getString(R.string.nc_settings_notification_sounds)
}

View file

@ -1,309 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Andy Scherzinger
* @author BlueLine Labs, Inc.
* @author Mario Danic
* Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
* Copyright (C) 2021 BlueLine Labs, Inc.
* Copyright (C) 2020 Mario Danic (mario@lovelyhq.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nextcloud.talk.controllers.base
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.annotation.LayoutRes
import androidx.annotation.RequiresApi
import androidx.appcompat.app.ActionBar
import autodagger.AutoInjector
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.controllers.AccountVerificationController
import com.nextcloud.talk.controllers.ServerSelectionController
import com.nextcloud.talk.controllers.SwitchAccountController
import com.nextcloud.talk.controllers.WebViewLoginController
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import javax.inject.Inject
import kotlin.jvm.internal.Intrinsics
// TODO: check what needs to be migrated from this class to BaseActivity etc when conductor is removed
@AutoInjector(NextcloudTalkApplication::class)
abstract class BaseController(@LayoutRes var layoutRes: Int, args: Bundle? = null) : Controller(args) {
enum class AppBarLayoutType {
TOOLBAR, SEARCH_BAR, EMPTY
}
@Inject
lateinit var appPreferences: AppPreferences
@Inject
lateinit var context: Context
@Inject
lateinit var viewThemeUtils: ViewThemeUtils
protected open val title: String?
get() = null
@Suppress("Detekt.TooGenericExceptionCaught")
protected val actionBar: ActionBar?
get() {
var actionBarProvider: ActionBarProvider? = null
if (this.activity is ActionBarProvider) {
try {
actionBarProvider = this.activity as ActionBarProvider?
} catch (e: Exception) {
Log.d(TAG, "Failed to fetch the action bar provider", e)
}
}
return actionBarProvider?.supportActionBar
}
init {
@Suppress("LeakingThis")
sharedApplication!!.componentApplication.inject(this)
addLifecycleListener(object : LifecycleListener() {
override fun postCreateView(controller: Controller, view: View) {
onViewBound(view)
actionBar?.let { setTitle() }
}
})
cleanTempCertPreference()
}
fun isAlive(): Boolean {
return !isDestroyed && !isBeingDestroyed
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedViewState: Bundle?
): View {
return inflater.inflate(layoutRes, container, false)
}
protected open fun onViewBound(view: View) {
var activity: MainActivity? = null
// if (getActivity() != null && getActivity() is MainActivity) {
// activity = getActivity() as MainActivity?
// viewThemeUtils.material.themeCardView(activity!!.binding.searchToolbar)
// viewThemeUtils.material.themeToolbar(activity.binding.toolbar)
// viewThemeUtils.material.themeSearchBarText(activity.binding.searchText)
// }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) {
disableKeyboardPersonalisedLearning((view as ViewGroup))
if (activity != null) {
disableKeyboardPersonalisedLearning(activity.binding.appBar)
}
}
}
override fun onAttach(view: View) {
// showSearchOrToolbar()
setTitle()
if (actionBar != null) {
actionBar!!.setDisplayHomeAsUpEnabled(parentController != null || router.backstackSize >= 1)
}
super.onAttach(view)
}
// open fun showSearchOrToolbar() {
// if (isValidActivity(activity)) {
// val showSearchBar = appBarLayoutType == AppBarLayoutType.SEARCH_BAR
// val activity = activity as MainActivity
//
// if (appBarLayoutType == AppBarLayoutType.EMPTY) {
// hideBars(activity.binding)
// } else {
// if (showSearchBar) {
// showSearchBar(activity.binding)
// } else {
// showToolbar(activity.binding)
// }
// colorizeStatusBar(showSearchBar, activity, resources)
// }
//
// colorizeNavigationBar(activity, resources)
// }
// }
//
// private fun isValidActivity(activity: Activity?): Boolean {
// return activity != null && activity is MainActivity
// }
//
// private fun showSearchBar(binding: ActivityMainBinding) {
// val layoutParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
// binding.searchToolbar.visibility = View.VISIBLE
// binding.searchText.hint = searchHint
// binding.toolbar.visibility = View.GONE
// // layoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout
// // .LayoutParams.SCROLL_FLAG_SNAP | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
// layoutParams.scrollFlags = 0
// binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
// binding.appBar.context,
// R.animator.appbar_elevation_off
// )
// binding.searchToolbar.layoutParams = layoutParams
// }
//
// private fun showToolbar(binding: ActivityMainBinding) {
// val layoutParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
// binding.searchToolbar.visibility = View.GONE
// binding.toolbar.visibility = View.VISIBLE
// viewThemeUtils.material.colorToolbarOverflowIcon(binding.toolbar)
// layoutParams.scrollFlags = 0
// binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
// binding.appBar.context,
// R.animator.appbar_elevation_on
// )
// binding.searchToolbar.layoutParams = layoutParams
// }
//
// private fun hideBars(binding: ActivityMainBinding) {
// binding.toolbar.visibility = View.GONE
// binding.searchToolbar.visibility = View.GONE
// }
//
// fun hideSearchBar() {
// val activity = activity as MainActivity?
// val layoutParams = activity!!.binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
// activity.binding.searchToolbar.visibility = View.GONE
// activity.binding.toolbar.visibility = View.VISIBLE
// layoutParams.scrollFlags = 0
// activity.binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
// activity.binding.appBar.context,
// R.animator.appbar_elevation_on
// )
// }
//
// private fun colorizeStatusBar(showSearchBar: Boolean, activity: Activity?, resources: Resources?) {
// if (activity != null && resources != null) {
// if (showSearchBar) {
// view?.let { viewThemeUtils.platform.resetStatusBar(activity) }
// } else {
// view?.let { viewThemeUtils.platform.themeStatusBar(activity, it) }
// }
// }
// }
//
// private fun colorizeNavigationBar(activity: Activity?, resources: Resources?) {
// if (activity != null && resources != null) {
// DisplayUtils.applyColorToNavigationBar(
// activity.window,
// ResourcesCompat.getColor(resources, R.color.bg_default, null)
// )
// }
// }
override fun onDetach(view: View) {
super.onDetach(view)
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
protected fun setTitle() {
if (isTitleSetable()) {
run {
calculateValidParentController()
}
actionBar!!.title = title
}
}
private fun calculateValidParentController() {
var parentController = parentController
while (parentController != null) {
parentController = parentController.parentController
}
}
private fun isTitleSetable(): Boolean {
return title != null && actionBar != null
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
router.popCurrentController()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onChangeStarted(changeHandler: ControllerChangeHandler, changeType: ControllerChangeType) {
super.onChangeStarted(changeHandler, changeType)
if (changeType.isEnter && actionBar != null) {
configureMenu(actionBar!!)
}
}
fun configureMenu(toolbar: ActionBar) {
Intrinsics.checkNotNullParameter(toolbar, "toolbar")
}
// TODO: check if this must be migrated when using activities instead of conductor
private fun cleanTempCertPreference() {
val temporaryClassNames: MutableList<String> = ArrayList()
temporaryClassNames.add(ServerSelectionController::class.java.name)
temporaryClassNames.add(AccountVerificationController::class.java.name)
temporaryClassNames.add(WebViewLoginController::class.java.name)
temporaryClassNames.add(SwitchAccountController::class.java.name)
if (!temporaryClassNames.contains(javaClass.name)) {
appPreferences.removeTemporaryClientCertAlias()
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private fun disableKeyboardPersonalisedLearning(viewGroup: ViewGroup) {
var view: View?
var editText: EditText
for (i in 0 until viewGroup.childCount) {
view = viewGroup.getChildAt(i)
if (view is EditText) {
editText = view
editText.imeOptions = editText.imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
} else if (view is ViewGroup) {
disableKeyboardPersonalisedLearning(view)
}
}
}
open val appBarLayoutType: AppBarLayoutType
get() = AppBarLayoutType.TOOLBAR
val searchHint: String
get() = context.getString(R.string.appbar_search_in, context.getString(R.string.nc_app_product_name))
companion object {
private val TAG = BaseController::class.java.simpleName
}
}

View file

@ -1,52 +0,0 @@
/*
* Nextcloud Talk application
*
* @author BlueLine Labs, Inc.
* Copyright (C) 2016 BlueLine Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nextcloud.talk.controllers.util
import android.view.View
import androidx.lifecycle.LifecycleObserver
import androidx.viewbinding.ViewBinding
import com.bluelinelabs.conductor.Controller
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
fun <T : ViewBinding> Controller.viewBinding(bindingFactory: (View) -> T) =
ControllerViewBindingDelegate(this, bindingFactory)
class ControllerViewBindingDelegate<T : ViewBinding>(
controller: Controller,
private val viewBinder: (View) -> T
) : ReadOnlyProperty<Controller, T?>, LifecycleObserver {
private var binding: T? = null
init {
controller.addLifecycleListener(object : Controller.LifecycleListener() {
override fun postDestroyView(controller: Controller) {
binding = null
}
})
}
override fun getValue(thisRef: Controller, property: KProperty<*>): T? {
if (binding == null) {
binding = thisRef.view?.let { viewBinder(it) }
}
return binding
}
}

View file

@ -58,8 +58,8 @@ import com.nextcloud.talk.adapters.items.ParticipantItem
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.contacts.ContactsActivity import com.nextcloud.talk.contacts.ContactsActivity
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding import com.nextcloud.talk.databinding.ActivityConversationInfoBinding

View file

@ -50,6 +50,7 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
@ -71,8 +72,11 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.account.ServerSelectionActivity
import com.nextcloud.talk.account.WebViewLoginActivity
import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.activities.CallActivity
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.adapters.items.ConversationItem import com.nextcloud.talk.adapters.items.ConversationItem
import com.nextcloud.talk.adapters.items.GenericTextHeaderItem import com.nextcloud.talk.adapters.items.GenericTextHeaderItem
import com.nextcloud.talk.adapters.items.LoadMoreResultsItem import com.nextcloud.talk.adapters.items.LoadMoreResultsItem
@ -84,7 +88,7 @@ import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.contacts.ContactsActivity import com.nextcloud.talk.contacts.ContactsActivity
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ControllerConversationsRvBinding import com.nextcloud.talk.databinding.ActivityConversationsBinding
import com.nextcloud.talk.events.ConversationsListFetchDataEvent import com.nextcloud.talk.events.ConversationsListFetchDataEvent
import com.nextcloud.talk.events.EventStatus import com.nextcloud.talk.events.EventStatus
import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.jobs.AccountRemovalWorker
@ -109,6 +113,7 @@ import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.ParticipantPermissions import com.nextcloud.talk.utils.ParticipantPermissions
import com.nextcloud.talk.utils.UserIdUtils import com.nextcloud.talk.utils.UserIdUtils
import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT
@ -146,7 +151,7 @@ class ConversationsListActivity :
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener { FlexibleAdapter.OnItemLongClickListener {
private lateinit var binding: ControllerConversationsRvBinding private lateinit var binding: ActivityConversationsBinding
@Inject @Inject
lateinit var userManager: UserManager lateinit var userManager: UserManager
@ -202,7 +207,6 @@ class ConversationsListActivity :
private val onBackPressedCallback = object : OnBackPressedCallback(true) { private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
// TODO: replace this when conductor is removed. For now it avoids to load the MainActiviy which has no UI.
finishAffinity() finishAffinity()
} }
} }
@ -211,7 +215,7 @@ class ConversationsListActivity :
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
binding = ControllerConversationsRvBinding.inflate(layoutInflater) binding = ActivityConversationsBinding.inflate(layoutInflater)
setupActionBar() setupActionBar()
setContentView(binding.root) setContentView(binding.root)
setupSystemColors() setupSystemColors()
@ -740,11 +744,20 @@ class ConversationsListActivity :
} }
} }
if (resources!!.getBoolean(R.bool.multiaccount_support)) {
dialogBuilder.setNeutralButton(R.string.nc_account_chooser_add_account) { _, _ ->
val intent = Intent(this, ServerSelectionActivity::class.java)
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true)
startActivity(intent)
}
}
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder) viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
val dialog = dialogBuilder.show() val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons( viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE), dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE) dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
) )
} }
} }
@ -819,10 +832,10 @@ class ConversationsListActivity :
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun prepareViews() { private fun prepareViews() {
layoutManager = SmoothScrollLinearLayoutManager(this) layoutManager = SmoothScrollLinearLayoutManager(this)
binding?.recyclerView?.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
binding?.recyclerView?.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding?.recyclerView?.adapter = adapter binding.recyclerView.adapter = adapter
binding?.recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() { binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState) super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (newState == RecyclerView.SCROLL_STATE_IDLE) {
@ -1131,7 +1144,7 @@ class ConversationsListActivity :
selectedConversation!!.displayName selectedConversation!!.displayName
) )
} }
binding?.floatingActionButton?.let { binding.floatingActionButton.let {
val dialogBuilder = MaterialAlertDialogBuilder(it.context) val dialogBuilder = MaterialAlertDialogBuilder(it.context)
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.upload)) .setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.upload))
.setTitle(confirmationQuestion) .setTitle(confirmationQuestion)
@ -1358,30 +1371,17 @@ class ConversationsListActivity :
.setTitle(R.string.nc_dialog_invalid_password) .setTitle(R.string.nc_dialog_invalid_password)
.setMessage(R.string.nc_dialog_reauth_or_delete) .setMessage(R.string.nc_dialog_reauth_or_delete)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.nc_delete) { _, _ -> .setPositiveButton(R.string.nc_settings_remove_account) { _, _ ->
val otherUserExists = userManager deleteUserAndRestartApp()
.scheduleUserForDeletionWithId(currentUser!!.id!!) }
.blockingGet() .setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build() val intent = Intent(context, WebViewLoginActivity::class.java)
WorkManager.getInstance().enqueue(accountRemovalWork) val bundle = Bundle()
if (otherUserExists) { bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl)
finish() bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
intent.putExtras(bundle)
startActivity(intent) startActivity(intent)
} else if (!otherUserExists) {
Log.d(TAG, "No other users found. AccountRemovalWorker will restart the app.")
} }
}
// TODO: show negative button again when conductor is removed
// .setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
// // router.pushController(
// // RouterTransaction.with(
// // WebViewLoginController(currentUser!!.baseUrl, true)
// // )
// // .pushChangeHandler(VerticalChangeHandler())
// // .popChangeHandler(VerticalChangeHandler())
// // )
// }
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder) viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
val dialog = dialogBuilder.show() val dialog = dialogBuilder.show()
@ -1392,6 +1392,50 @@ class ConversationsListActivity :
} }
} }
@SuppressLint("CheckResult")
private fun deleteUserAndRestartApp() {
userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
.observeForever { workInfo: WorkInfo ->
when (workInfo.state) {
WorkInfo.State.SUCCEEDED -> {
val text = String.format(
context.resources.getString(R.string.nc_deleted_user),
currentUser!!.displayName
)
Toast.makeText(
context,
text,
Toast.LENGTH_LONG
).show()
restartApp()
}
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
Toast.makeText(
context,
context.resources.getString(R.string.nc_common_error_sorry),
Toast.LENGTH_LONG
).show()
Log.e(TAG, "something went wrong when deleting user with id " + currentUser!!.userId)
restartApp()
}
else -> {}
}
}
}
private fun restartApp() {
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
}
private fun showOutdatedClientDialog() { private fun showOutdatedClientDialog() {
binding.floatingActionButton.let { binding.floatingActionButton.let {
val dialogBuilder = MaterialAlertDialogBuilder(it.context) val dialogBuilder = MaterialAlertDialogBuilder(it.context)
@ -1423,11 +1467,20 @@ class ConversationsListActivity :
} }
} }
if (resources!!.getBoolean(R.bool.multiaccount_support)) {
dialogBuilder.setNeutralButton(R.string.nc_account_chooser_add_account) { _, _ ->
val intent = Intent(this, ServerSelectionActivity::class.java)
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true)
startActivity(intent)
}
}
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder) viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
val dialog = dialogBuilder.show() val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons( viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE), dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE) dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
) )
} }
} }
@ -1445,23 +1498,31 @@ class ConversationsListActivity :
.setTitle(R.string.nc_dialog_maintenance_mode) .setTitle(R.string.nc_dialog_maintenance_mode)
.setMessage(R.string.nc_dialog_maintenance_mode_description) .setMessage(R.string.nc_dialog_maintenance_mode_description)
.setCancelable(false) .setCancelable(false)
.setNegativeButton(R.string.nc_settings_remove_account) { _, _ ->
deleteUserAndRestartApp()
}
if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) { if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
dialogBuilder.setPositiveButton(R.string.nc_switch_account) { _, _ -> dialogBuilder.setPositiveButton(R.string.nc_switch_account) { _, _ ->
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance() val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG) newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
} }
} else { }
dialogBuilder.setPositiveButton(R.string.nc_close_app) { _, _ ->
finishAffinity() if (resources!!.getBoolean(R.bool.multiaccount_support)) {
finish() dialogBuilder.setNeutralButton(R.string.nc_account_chooser_add_account) { _, _ ->
val intent = Intent(this, ServerSelectionActivity::class.java)
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true)
startActivity(intent)
} }
} }
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder) viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
val dialog = dialogBuilder.show() val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons( viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE) dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
) )
} }
} else { } else {
@ -1470,34 +1531,28 @@ class ConversationsListActivity :
} }
private fun showServerEOLDialog() { private fun showServerEOLDialog() {
binding?.floatingActionButton?.let { binding.floatingActionButton.let {
val dialogBuilder = MaterialAlertDialogBuilder(it.context) val dialogBuilder = MaterialAlertDialogBuilder(it.context)
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.ic_warning_white)) .setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.ic_warning_white))
.setTitle(R.string.nc_settings_server_eol_title) .setTitle(R.string.nc_settings_server_eol_title)
.setMessage(R.string.nc_settings_server_eol) .setMessage(R.string.nc_settings_server_eol)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.nc_settings_remove_account) { _, _ -> .setPositiveButton(R.string.nc_settings_remove_account) { _, _ ->
val otherUserExists = userManager deleteUserAndRestartApp()
.scheduleUserForDeletionWithId(currentUser!!.id!!) }
.blockingGet()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build() if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
WorkManager.getInstance().enqueue(accountRemovalWork) dialogBuilder.setNegativeButton(R.string.nc_switch_account) { _, _ ->
if (otherUserExists) { val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
finish() newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
}
}
if (resources!!.getBoolean(R.bool.multiaccount_support)) {
dialogBuilder.setNeutralButton(R.string.nc_account_chooser_add_account) { _, _ ->
val intent = Intent(this, ServerSelectionActivity::class.java)
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true)
startActivity(intent) startActivity(intent)
} else if (!otherUserExists) {
restartApp(this)
}
}
.setNegativeButton(R.string.nc_cancel) { _, _ ->
if (userManager.users.blockingGet().isNotEmpty()) {
// TODO show SwitchAccount screen again when conductor is removed instead to close app
// router.pushController(RouterTransaction.with(SwitchAccountController()))
finishAffinity()
finish()
} else {
finishAffinity()
finish()
} }
} }
@ -1505,20 +1560,12 @@ class ConversationsListActivity :
val dialog = dialogBuilder.show() val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons( viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE), dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE) dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
) )
} }
} }
fun restartApp(context: Context) {
val packageManager = context.packageManager
val intent = packageManager.getLaunchIntentForPackage(context.packageName)
val componentName = intent!!.component
val mainIntent = Intent.makeRestartActivityTask(componentName)
context.startActivity(mainIntent)
Runtime.getRuntime().exit(0)
}
private fun deleteConversation(conversation: Conversation) { private fun deleteConversation(conversation: Conversation) {
val data = Data.Builder() val data = Data.Builder()
data.putLong( data.putLong(
@ -1613,10 +1660,10 @@ class ConversationsListActivity :
} }
companion object { companion object {
const val TAG = "ConvListController" private val TAG = ConversationsListActivity::class.java.simpleName
const val UNREAD_BUBBLE_DELAY = 2500 const val UNREAD_BUBBLE_DELAY = 2500
const val BOTTOM_SHEET_DELAY: Long = 2500 const val BOTTOM_SHEET_DELAY: Long = 2500
private const val KEY_SEARCH_QUERY = "ContactsController.searchQuery" private const val KEY_SEARCH_QUERY = "ConversationsListActivity.searchQuery"
const val SEARCH_DEBOUNCE_INTERVAL_MS = 300 const val SEARCH_DEBOUNCE_INTERVAL_MS = 300
const val SEARCH_MIN_CHARS = 2 const val SEARCH_MIN_CHARS = 2
const val HTTP_UNAUTHORIZED = 401 const val HTTP_UNAUTHORIZED = 401

View file

@ -23,10 +23,7 @@
package com.nextcloud.talk.jobs; package com.nextcloud.talk.jobs;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
@ -212,17 +209,5 @@ public class AccountRemovalWorker extends Worker {
Log.e(TAG, "error while trying to delete user", e); Log.e(TAG, "error while trying to delete user", e);
} }
} }
if (userManager.getUsers().blockingGet().isEmpty()) {
restartApp(getApplicationContext());
}
}
public static void restartApp(Context context) {
PackageManager packageManager = context.getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName());
ComponentName componentName = intent.getComponent();
Intent mainIntent = Intent.makeRestartActivityTask(componentName);
context.startActivity(mainIntent);
Runtime.getRuntime().exit(0);
} }
} }

View file

@ -121,7 +121,7 @@ class GeocodingActivity :
if (viewModel.getQuery().isNotEmpty() && adapter.itemCount == 0) { if (viewModel.getQuery().isNotEmpty() && adapter.itemCount == 0) {
viewModel.searchLocation() viewModel.searchLocation()
} else { } else {
Log.e(TAG, "search string that was passed to GeocodingController was null or empty") Log.e(TAG, "search string that was passed to GeocodingActivity was null or empty")
} }
adapter.setOnItemClickListener(object : GeocodingAdapter.OnItemClickListener { adapter.setOnItemClickListener(object : GeocodingAdapter.OnItemClickListener {
override fun onItemClick(position: Int) { override fun onItemClick(position: Int) {

View file

@ -608,7 +608,7 @@ class ProfileActivity : BaseActivity() {
class UserInfoAdapter( class UserInfoAdapter(
displayList: List<UserInfoDetailsItem>?, displayList: List<UserInfoDetailsItem>?,
private val viewThemeUtils: ViewThemeUtils, private val viewThemeUtils: ViewThemeUtils,
private val controller: ProfileActivity private val profileActivity: ProfileActivity
) : RecyclerView.Adapter<UserInfoAdapter.ViewHolder>() { ) : RecyclerView.Adapter<UserInfoAdapter.ViewHolder>() {
var displayList: List<UserInfoDetailsItem>? var displayList: List<UserInfoDetailsItem>?
var filteredDisplayList: MutableList<UserInfoDetailsItem> = LinkedList() var filteredDisplayList: MutableList<UserInfoDetailsItem> = LinkedList()
@ -643,7 +643,7 @@ class ProfileActivity : BaseActivity() {
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item: UserInfoDetailsItem = if (controller.edit) { val item: UserInfoDetailsItem = if (profileActivity.edit) {
displayList!![position] displayList!![position]
} else { } else {
filteredDisplayList[position] filteredDisplayList[position]
@ -656,11 +656,11 @@ class ProfileActivity : BaseActivity() {
holder.binding.icon.contentDescription = item.hint holder.binding.icon.contentDescription = item.hint
viewThemeUtils.platform.colorImageView(holder.binding.icon, ColorRole.PRIMARY) viewThemeUtils.platform.colorImageView(holder.binding.icon, ColorRole.PRIMARY)
if (!TextUtils.isEmpty(item.text) || controller.edit) { if (!TextUtils.isEmpty(item.text) || profileActivity.edit) {
holder.binding.userInfoDetailContainer.visibility = View.VISIBLE holder.binding.userInfoDetailContainer.visibility = View.VISIBLE
controller.viewThemeUtils.material.colorTextInputLayout(holder.binding.userInfoInputLayout) profileActivity.viewThemeUtils.material.colorTextInputLayout(holder.binding.userInfoInputLayout)
if (controller.edit && if (profileActivity.edit &&
controller.editableFields.contains(item.field.toString().lowercase()) profileActivity.editableFields.contains(item.field.toString().lowercase())
) { ) {
holder.binding.userInfoEditTextEdit.isEnabled = true holder.binding.userInfoEditTextEdit.isEnabled = true
holder.binding.userInfoEditTextEdit.isFocusableInTouchMode = true holder.binding.userInfoEditTextEdit.isFocusableInTouchMode = true
@ -700,7 +700,7 @@ class ProfileActivity : BaseActivity() {
} }
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (controller.edit) { if (profileActivity.edit) {
displayList!![holder.adapterPosition].text = holder.binding.userInfoEditTextEdit.text.toString() displayList!![holder.adapterPosition].text = holder.binding.userInfoEditTextEdit.text.toString()
} else { } else {
filteredDisplayList[holder.adapterPosition].text = filteredDisplayList[holder.adapterPosition].text =
@ -739,7 +739,7 @@ class ProfileActivity : BaseActivity() {
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return if (controller.edit) { return if (profileActivity.edit) {
displayList!!.size displayList!!.size
} else { } else {
filteredDisplayList.size filteredDisplayList.size
@ -762,7 +762,7 @@ class ProfileActivity : BaseActivity() {
} }
companion object { companion object {
private const val TAG: String = "ProfileController" private val TAG = ProfileActivity::class.java.simpleName
private const val DEFAULT_CACHE_SIZE: Int = 20 private const val DEFAULT_CACHE_SIZE: Int = 20
private const val DEFAULT_RETRIES: Long = 3 private const val DEFAULT_RETRIES: Long = 3
private const val HIGH_EMPHASIS_ALPHA: Float = 0.87f private const val HIGH_EMPHASIS_ALPHA: Float = 0.87f

View file

@ -91,8 +91,6 @@ class ShareRecordingToChatReceiver : BroadcastReceiver() {
// However, as we are in a broadcast receiver, this needs a TaskStackBuilder // However, as we are in a broadcast receiver, this needs a TaskStackBuilder
// combined with addNextIntentWithParentStack. For further reading, see // combined with addNextIntentWithParentStack. For further reading, see
// https://developer.android.com/develop/ui/views/notifications/navigation#DirectEntry // https://developer.android.com/develop/ui/views/notifications/navigation#DirectEntry
// As we are using the conductor framework it might be hard the combine this or to keep an overview.
// For this reason there is only a Snackbar for now until we got rid of conductor.
Snackbar.make( Snackbar.make(
View(context), View(context),

View file

@ -27,6 +27,7 @@ package com.nextcloud.talk.settings
import android.animation.Animator import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
@ -51,6 +52,7 @@ import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -66,6 +68,7 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme
@ -465,17 +468,47 @@ class SettingsActivity : BaseActivity() {
} }
} }
@SuppressLint("CheckResult")
private fun removeCurrentAccount() { private fun removeCurrentAccount() {
val otherUserExists = userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet() userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build() val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork) WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
if (otherUserExists) {
// TODO: find better solution once Conductor is removed WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
finish() .observeForever { workInfo: WorkInfo ->
startActivity(intent)
} else if (!otherUserExists) { when (workInfo.state) {
Log.d(TAG, "No other users found. AccountRemovalWorker will restart the app.") WorkInfo.State.SUCCEEDED -> {
val text = String.format(
context.resources.getString(R.string.nc_deleted_user),
currentUser!!.displayName
)
Toast.makeText(
context,
text,
Toast.LENGTH_LONG
).show()
restartApp()
} }
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
Toast.makeText(
context,
context.resources.getString(R.string.nc_common_error_sorry),
Toast.LENGTH_LONG
).show()
Log.e(TAG, "something went wrong when deleting user with id " + currentUser!!.userId)
restartApp()
}
else -> {}
}
}
}
private fun restartApp() {
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
} }
private fun getRingtoneName(context: Context, ringtoneUri: Uri?): String { private fun getRingtoneName(context: Context, ringtoneUri: Uri?): String {
@ -1205,7 +1238,7 @@ class SettingsActivity : BaseActivity() {
} }
companion object { companion object {
private const val TAG = "SettingsController" private val TAG = SettingsActivity::class.java.simpleName
private const val DURATION: Long = 2500 private const val DURATION: Long = 2500
private const val START_DELAY: Long = 5000 private const val START_DELAY: Long = 5000
private const val DISABLED_ALPHA: Float = 0.38f private const val DISABLED_ALPHA: Float = 0.38f

View file

@ -31,8 +31,8 @@ import com.afollestad.materialdialogs.bottomsheets.BottomSheet
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.hovercard.HoverCardAction import com.nextcloud.talk.models.json.hovercard.HoverCardAction

View file

@ -35,7 +35,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.talk.activities.MainActivity; import com.nextcloud.talk.account.ServerSelectionActivity;
import com.nextcloud.talk.adapters.items.AdvancedUserItem; import com.nextcloud.talk.adapters.items.AdvancedUserItem;
import com.nextcloud.talk.api.NcApi; import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
@ -71,7 +71,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import static com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ACCOUNT; import static com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT;
@AutoInjector(NextcloudTalkApplication.class) @AutoInjector(NextcloudTalkApplication.class)
public class ChooseAccountDialogFragment extends DialogFragment { public class ChooseAccountDialogFragment extends DialogFragment {
@ -185,11 +185,9 @@ public class ChooseAccountDialogFragment extends DialogFragment {
// Creating listeners for quick-actions // Creating listeners for quick-actions
binding.currentAccount.getRoot().setOnClickListener(v -> dismiss()); binding.currentAccount.getRoot().setOnClickListener(v -> dismiss());
binding.addAccount.setOnClickListener(v -> { binding.addAccount.setOnClickListener(v -> {
// TODO: change this when conductor is removed Intent intent = new Intent(getContext(), ServerSelectionActivity.class);
Intent intent = new Intent(getContext(), MainActivity.class); intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true);
intent.putExtra(ADD_ACCOUNT, true);
startActivity(intent); startActivity(intent);
dismiss(); dismiss();
}); });

View file

@ -84,6 +84,8 @@ object BundleKeys {
const val KEY_DISMISS_RECORDING_URL = "KEY_DISMISS_RECORDING_URL" const val KEY_DISMISS_RECORDING_URL = "KEY_DISMISS_RECORDING_URL"
const val KEY_SHARE_RECORDING_TO_CHAT_URL = "KEY_SHARE_RECORDING_TO_CHAT_URL" const val KEY_SHARE_RECORDING_TO_CHAT_URL = "KEY_SHARE_RECORDING_TO_CHAT_URL"
const val KEY_GEOCODING_RESULT = "KEY_GEOCODING_RESULT" const val KEY_GEOCODING_RESULT = "KEY_GEOCODING_RESULT"
const val ADD_ACCOUNT = "ADD_ACCOUNT" // temp workaround until conductor is removed const val ADD_ADDITIONAL_ACCOUNT = "ADD_ADDITIONAL_ACCOUNT"
const val SAVED_TRANSLATED_MESSAGE = "SAVED_TRANSLATED_MESSAGE" const val SAVED_TRANSLATED_MESSAGE = "SAVED_TRANSLATED_MESSAGE"
const val KEY_REAUTHORIZE_ACCOUNT = "KEY_REAUTHORIZE_ACCOUNT"
const val KEY_PASSWORD = "KEY_PASSWORD"
} }

View file

@ -39,7 +39,7 @@ public class ApplicationWideMessageHolder {
} }
public enum MessageType { public enum MessageType {
WRONG_ACCOUNT, ACCOUNT_UPDATED_NOT_ADDED, ACCOUNT_SCHEDULED_FOR_DELETION, SERVER_WITHOUT_TALK, WRONG_ACCOUNT, ACCOUNT_UPDATED_NOT_ADDED, SERVER_WITHOUT_TALK,
FAILED_TO_IMPORT_ACCOUNT, ACCOUNT_WAS_IMPORTED, CALL_PASSWORD_WRONG FAILED_TO_IMPORT_ACCOUNT, ACCOUNT_WAS_IMPORTED, CALL_PASSWORD_WRONG
} }

View file

@ -234,7 +234,7 @@
android:id="@+id/separator_1" android:id="@+id/separator_1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:background="@color/controller_chat_separator" /> android:background="@color/chat_separator" />
<TextView <TextView
android:id="@+id/typing_indicator" android:id="@+id/typing_indicator"

View file

@ -178,7 +178,15 @@
android:textAppearance="@style/ListItem" /> android:textAppearance="@style/ListItem" />
</RelativeLayout> </RelativeLayout>
<include <RelativeLayout
android:id="@+id/controller_generic_rv" android:layout_width="match_parent"
layout="@layout/controller_generic_rv" /> android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/contacts_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_anchorGravity="center" />
</RelativeLayout>
</LinearLayout> </LinearLayout>

View file

@ -40,8 +40,6 @@
android:windowContentOverlay="@null" android:windowContentOverlay="@null"
app:elevation="0dp"> app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -58,11 +56,4 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
android:id="@+id/controller_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_default"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -18,25 +18,34 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/generic_rv_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:orientation="vertical"
android:animateLayoutChanges="true">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/swipe_refresh_layout" android:id="@+id/appBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="wrap_content">
<FrameLayout <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="?attr/actionBarSize"
android:background="@color/appbar"
android:theme="?attr/actionBarPopupTheme"
app:layout_scrollFlags="enterAlwaysCollapsed|noScroll"
app:navigationIconTint="@color/fontAppbar"
app:popupTheme="@style/appActionBarPopupMenu"
app:titleTextColor="@color/fontAppbar"
tools:title="@string/nc_select_an_account">
</FrameLayout> </com.google.android.material.appbar.MaterialToolbar>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
@ -44,4 +53,4 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_anchor="@+id/swipe_refresh_layout" app:layout_anchor="@+id/swipe_refresh_layout"
app:layout_anchorGravity="center" /> app:layout_anchorGravity="center" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </LinearLayout>

View file

@ -28,7 +28,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/controllerCallLayout" android:id="@+id/callLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
@ -128,7 +128,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/controller_call_incomingCallTextView" android:textColor="@color/call_incomingCallTextView"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View file

@ -24,7 +24,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/controllerCallNotificationLayout" android:id="@+id/callNotificationLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/grey950"> android:background="@color/grey950">
@ -83,7 +83,7 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="@string/nc_call_unknown" android:text="@string/nc_call_unknown"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/controller_call_incomingCallTextView" android:textColor="@color/call_incomingCallTextView"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -109,7 +109,7 @@
android:layout_below="@+id/conversationNameTextView" android:layout_below="@+id/conversationNameTextView"
android:text="@string/nc_call_incoming" android:text="@string/nc_call_incoming"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/controller_call_incomingCallTextView" android:textColor="@color/call_incomingCallTextView"
android:textSize="16sp" /> android:textSize="16sp" />
</RelativeLayout> </RelativeLayout>

View file

@ -53,7 +53,7 @@
<color name="conversation_unread_bubble">#373737</color> <color name="conversation_unread_bubble">#373737</color>
<color name="conversation_unread_bubble_text">#D8D8D8</color> <color name="conversation_unread_bubble_text">#D8D8D8</color>
<color name="controller_chat_separator">#484848</color> <color name="chat_separator">#484848</color>
<color name="colorBackgroundDarker">#2C2C2C</color> <color name="colorBackgroundDarker">#2C2C2C</color>

View file

@ -61,10 +61,10 @@
<color name="nc_darkRed">#D32F2F</color> <color name="nc_darkRed">#D32F2F</color>
<color name="nc_darkYellow">#FF9800</color> <color name="nc_darkYellow">#FF9800</color>
<color name="nc_darkGreen">#006400</color> <color name="nc_darkGreen">#006400</color>
<color name="controller_chat_separator">#E8E8E8</color> <color name="chat_separator">#E8E8E8</color>
<color name="grey_600">#757575</color> <color name="grey_600">#757575</color>
<color name="nc_grey">#D5D5D5</color> <color name="nc_grey">#D5D5D5</color>
<color name="controller_call_incomingCallTextView">#E9FFFFFF</color> <color name="call_incomingCallTextView">#E9FFFFFF</color>
<color name="grey950">#111111</color> <color name="grey950">#111111</color>
<color name="textColorMaxContrast">#767676</color> <color name="textColorMaxContrast">#767676</color>
<color name="colorBackgroundDarker">#DBDBDB</color> <color name="colorBackgroundDarker">#DBDBDB</color>

View file

@ -35,7 +35,7 @@
<!-- Branding --> <!-- Branding -->
<bool name="hide_provider">false</bool> <bool name="hide_provider">false</bool>
<bool name="hide_auth_cert">false</bool> <bool name="hide_auth_cert">true</bool>
<bool name="multiaccount_support">true</bool> <bool name="multiaccount_support">true</bool>
<string name="weblogin_url" translatable="false"></string> <string name="weblogin_url" translatable="false"></string>

View file

@ -574,6 +574,7 @@ How to translate with transifex:
<string name="filename_progress">%1$s (%2$d)</string> <string name="filename_progress">%1$s (%2$d)</string>
<string name="nc_dialog_invalid_password">Invalid password</string> <string name="nc_dialog_invalid_password">Invalid password</string>
<string name="nc_dialog_reauth_or_delete">Do you want to reauthorize or delete this account?</string> <string name="nc_dialog_reauth_or_delete">Do you want to reauthorize or delete this account?</string>
<string name="nc_deleted_user">User %1$s was removed</string>
<string name="nc_dialog_outdated_client">App is outdated</string> <string name="nc_dialog_outdated_client">App is outdated</string>
<string name="nc_dialog_outdated_client_description">The app is too old and no longer supported by this server. Please update.</string> <string name="nc_dialog_outdated_client_description">The app is too old and no longer supported by this server. Please update.</string>

View file

@ -97,7 +97,7 @@ class BundleKeysTest {
assertEquals("KEY_DISMISS_RECORDING_URL", BundleKeys.KEY_DISMISS_RECORDING_URL) assertEquals("KEY_DISMISS_RECORDING_URL", BundleKeys.KEY_DISMISS_RECORDING_URL)
assertEquals("KEY_SHARE_RECORDING_TO_CHAT_URL", BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL) assertEquals("KEY_SHARE_RECORDING_TO_CHAT_URL", BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL)
assertEquals("KEY_GEOCODING_RESULT", BundleKeys.KEY_GEOCODING_RESULT) assertEquals("KEY_GEOCODING_RESULT", BundleKeys.KEY_GEOCODING_RESULT)
assertEquals("ADD_ACCOUNT", BundleKeys.ADD_ACCOUNT) assertEquals("ADD_ADDITIONAL_ACCOUNT", BundleKeys.ADD_ADDITIONAL_ACCOUNT)
assertEquals("SAVED_TRANSLATED_MESSAGE", BundleKeys.SAVED_TRANSLATED_MESSAGE) assertEquals("SAVED_TRANSLATED_MESSAGE", BundleKeys.SAVED_TRANSLATED_MESSAGE)
} }
} }