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`
* `Fragment`
* `Controller`
* `Service`
* `BroadcastReceiver`
* `ContentProvider`

View file

@ -7,7 +7,7 @@
* @author Tim Krüger
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* 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>
*
* 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:rxjava:2.2.21"
implementation 'com.bluelinelabs:conductor:3.2.0'
implementation "com.squareup.okhttp3:okhttp:${okhttpVersion}"
implementation "com.squareup.okhttp3:okhttp-urlconnection:${okhttpVersion}"
implementation "com.squareup.okhttp3:logging-interceptor:${okhttpVersion}"

View file

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

View file

@ -4,7 +4,7 @@
~ @author Mario Danic
~ @author Marcel Hibbe
~ 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
~ it under the terms of the GNU General Public License as published by
@ -131,6 +131,44 @@
</intent-filter>
</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
android:name=".activities.CallActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
@ -215,32 +253,10 @@
android:name=".contacts.ContactsActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".chat.ChatActivity"
android:theme="@style/AppTheme" />
<activity
android:name=".openconversations.ListOpenConversationsActivity"
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=".lock.LockedActivity"
android:theme="@style/AppTheme" />

View file

@ -3,6 +3,8 @@
*
* @author Mario Danic
* @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) 2017 Mario Danic (mario@lovelyhq.com)
*
@ -19,7 +21,7 @@
* 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
package com.nextcloud.talk.account
import android.annotation.SuppressLint
import android.content.Intent
@ -28,25 +30,24 @@ import android.os.Bundle
import android.os.Handler
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import autodagger.AutoInjector
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.logansquare.LoganSquare
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi
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.conversationlist.ConversationsListActivity
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.jobs.AccountRemovalWorker
import com.nextcloud.talk.jobs.CapabilitiesWorker
import com.nextcloud.talk.jobs.PushRegistrationWorker
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_IS_ACCOUNT_IMPORT
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_USERNAME
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
@ -71,20 +73,15 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.net.CookieManager
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class AccountVerificationController(args: Bundle? = null) : BaseController(
R.layout.controller_account_verification,
args
) {
private val binding: ControllerAccountVerificationBinding? by viewBinding(
ControllerAccountVerificationBinding::bind
)
class AccountVerificationActivity : BaseActivity() {
private lateinit var binding: ActivityAccountVerificationBinding
@Inject
lateinit var ncApi: NcApi
@ -95,9 +92,6 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
@Inject
lateinit var cookieManager: CookieManager
@Inject
lateinit var eventBus: EventBus
private var internalAccountId: Long = -1
private val disposables: MutableList<Disposable> = ArrayList()
private var baseUrl: String? = null
@ -106,43 +100,53 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
private var isAccountImport = false
private var originalProtocol: String? = null
override fun onAttach(view: View) {
super.onAttach(view)
eventBus.register(this)
}
override fun onDetach(view: View) {
super.onDetach(view)
eventBus.unregister(this)
}
override fun onViewBound(view: View) {
super.onViewBound(view)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedApplication!!.componentApplication.inject(this)
binding = ActivityAccountVerificationBinding.inflate(layoutInflater)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
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 (
isAccountImport &&
!UriUtils.hasHttpProtocolPrefixed(baseUrl!!) ||
isSameProtocol(baseUrl!!, originalProtocol!!)
isNotSameProtocol(baseUrl!!, originalProtocol)
) {
determineBaseUrlProtocol(true)
} 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)
}
private fun checkEverything() {
val credentials = ApiUtils.getCredentials(username, token)
cookieManager.cookieStore.removeAll()
findServerTalkApp(credentials)
}
private fun determineBaseUrlProtocol(checkForcedHttps: Boolean) {
cookieManager.cookieStore.removeAll()
baseUrl = baseUrl!!.replace("http://", "").replace("https://", "")
@ -166,20 +170,16 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
"http://$baseUrl"
}
if (isAccountImport) {
router.replaceTopController(
RouterTransaction.with(
WebViewLoginController(
baseUrl,
false,
username,
""
)
)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
val bundle = Bundle()
bundle.putString(KEY_BASE_URL, baseUrl)
bundle.putString(KEY_USERNAME, username)
bundle.putString(KEY_PASSWORD, "")
val intent = Intent(context, WebViewLoginActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
} 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))
.subscribeOn(Schedulers.io())
.subscribe(object : Observer<CapabilitiesOverall> {
@ -214,27 +217,24 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
if (hasTalk) {
fetchProfile(credentials, capabilitiesOverall)
} else {
if (activity != null && resources != null) {
activity!!.runOnUiThread {
binding?.progressText?.setText(
String.format(
if (resources != null) {
runOnUiThread {
binding.progressText.text = String.format(
resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
resources!!.getString(R.string.nc_app_product_name)
)
)
}
}
ApplicationWideMessageHolder.getInstance().setMessageType(
ApplicationWideMessageHolder.getInstance().messageType =
ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
)
abortVerification()
}
}
override fun onError(e: Throwable) {
if (activity != null && resources != null) {
activity!!.runOnUiThread {
binding?.progressText?.text = String.format(
if (resources != null) {
runOnUiThread {
binding.progressText.text = String.format(
resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
resources!!.getString(R.string.nc_app_product_name)
)
@ -263,7 +263,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
displayName = displayName,
pushConfigurationState = null,
capabilities = LoganSquare.serialize(capabilities),
certificateAlias = appPreferences!!.temporaryClientCertAlias,
certificateAlias = appPreferences.temporaryClientCertAlias,
externalSignalingServer = null
)
)
@ -279,9 +279,9 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
registerForPush()
} else {
activity!!.runOnUiThread {
binding?.progressText?.text =
""" ${binding?.progressText?.text}
runOnUiThread {
binding.progressText.text =
""" ${binding.progressText.text}
${resources!!.getString(R.string.nc_push_disabled)}
""".trimIndent()
}
@ -291,7 +291,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
@SuppressLint("SetTextI18n")
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)
abortVerification()
}
@ -328,30 +328,26 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
capabilities.ocs!!.data!!.capabilities!!
)
} else {
if (activity != null) {
activity!!.runOnUiThread {
binding?.progressText?.text =
runOnUiThread {
binding.progressText.text =
"""
${binding?.progressText?.text}
${binding.progressText.text}
${resources!!.getString(R.string.nc_display_name_not_fetched)}
""".trimIndent()
}
}
abortVerification()
}
}
@SuppressLint("SetTextI18n")
override fun onError(e: Throwable) {
if (activity != null) {
activity!!.runOnUiThread {
binding?.progressText?.text =
runOnUiThread {
binding.progressText.text =
"""
${binding?.progressText?.text}
${binding.progressText.text}
${resources!!.getString(R.string.nc_display_name_not_fetched)}
""".trimIndent()
}
}
abortVerification()
}
@ -364,7 +360,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
private fun registerForPush() {
val data =
Data.Builder()
.putString(PushRegistrationWorker.ORIGIN, "AccountVerificationController#registerForPush")
.putString(PushRegistrationWorker.ORIGIN, "AccountVerificationActivity#registerForPush")
.build()
val pushRegistrationWork =
OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
@ -377,11 +373,11 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onMessageEvent(eventStatus: EventStatus) {
if (eventStatus.eventType == EventStatus.EventType.PUSH_REGISTRATION) {
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood && activity != null) {
activity!!.runOnUiThread {
binding?.progressText?.text =
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
runOnUiThread {
binding.progressText.text =
"""
${binding?.progressText?.text}
${binding.progressText.text}
${resources!!.getString(R.string.nc_push_disabled)}
""".trimIndent()
}
@ -389,31 +385,27 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
fetchAndStoreCapabilities()
} else if (eventStatus.eventType == EventStatus.EventType.CAPABILITIES_FETCH) {
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
if (activity != null) {
activity!!.runOnUiThread {
binding?.progressText?.text =
runOnUiThread {
binding.progressText.text =
"""
${binding?.progressText?.text}
${binding.progressText.text}
${resources!!.getString(R.string.nc_capabilities_failed)}
""".trimIndent()
}
}
abortVerification()
} else if (internalAccountId == eventStatus.userId && eventStatus.isAllGood) {
fetchAndStoreExternalSignalingSettings()
}
} else if (eventStatus.eventType == EventStatus.EventType.SIGNALING_SETTINGS) {
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
if (activity != null) {
activity!!.runOnUiThread {
binding?.progressText?.text =
runOnUiThread {
binding.progressText.text =
"""
${binding?.progressText?.text}
${binding.progressText.text}
${resources!!.getString(R.string.nc_external_server_failed)}
""".trimIndent()
}
}
}
proceedWithLogin()
}
}
@ -457,8 +449,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
Log.d(TAG, "userToSetAsActive: " + userToSetAsActive.username)
if (userManager.setUserAsActive(userToSetAsActive).blockingGet()) {
if (activity != null) {
activity!!.runOnUiThread {
runOnUiThread {
if (userManager.users.blockingGet().size == 1) {
val intent = Intent(context, ConversationsListActivity::class.java)
startActivity(intent)
@ -471,10 +462,9 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
startActivity(intent)
}
}
}
} else {
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() {
dispose()
super.onDestroy()
}
private fun abortVerification() {
if (!isAccountImport) {
if (internalAccountId != -1L) {
val count = userManager.deleteUser(internalAccountId)
if (count > 0) {
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 {
if (isAccountImport) {
ApplicationWideMessageHolder.getInstance().messageType = ApplicationWideMessageHolder.MessageType
.FAILED_TO_IMPORT_ACCOUNT
runOnUiThread {
Handler().postDelayed({
if (router.hasRootController()) {
if (activity != null) {
router.popToRoot()
}
} else {
if (userManager.users.blockingGet().isNotEmpty()) {
val intent = Intent(context, ConversationsListActivity::class.java)
val intent = Intent(this, ServerSelectionActivity::class.java)
startActivity(intent)
} else {
router.setRoot(
RouterTransaction.with(ServerSelectionController())
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
}
}, 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 {
const val TAG = "AccountVerification"
private val TAG = AccountVerificationActivity::class.java.simpleName
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 Mario Danic
* @author Marcel Hibbe
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
@ -19,7 +21,7 @@
* 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
package com.nextcloud.talk.account
import android.accounts.Account
import android.annotation.SuppressLint
@ -34,24 +36,21 @@ import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.core.content.res.ResourcesCompat
import androidx.activity.OnBackPressedCallback
import autodagger.AutoInjector
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi
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.ControllerServerSelectionBinding
import com.nextcloud.talk.databinding.ActivityServerSelectionBinding
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
import com.nextcloud.talk.models.json.generic.Status
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.AccountUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
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.database.user.CapabilitiesUtilNew
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
@ -61,12 +60,12 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.security.cert.CertificateException
import javax.inject.Inject
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@AutoInjector(NextcloudTalkApplication::class)
class ServerSelectionController :
BaseController(R.layout.controller_server_selection) {
class ServerSelectionActivity : BaseActivity() {
private val binding: ControllerServerSelectionBinding? by viewBinding(ControllerServerSelectionBinding::bind)
private lateinit var binding: ActivityServerSelectionBinding
@Inject
lateinit var ncApi: NcApi
@ -76,44 +75,40 @@ class ServerSelectionController :
private var statusQueryDisposable: Disposable? = null
fun onCertClick() {
if (activity != null) {
KeyChain.choosePrivateKeyAlias(
activity!!,
{ alias: String? ->
if (alias != null) {
appPreferences!!.temporaryClientCertAlias = alias
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (intent.hasExtra(ADD_ADDITIONAL_ACCOUNT) && intent.getBooleanExtra(ADD_ADDITIONAL_ACCOUNT, false)) {
finish()
} else {
appPreferences!!.removeTemporaryClientCertAlias()
finishAffinity()
}
setCertTextView()
},
arrayOf("RSA", "EC"),
null,
null,
-1,
null
)
}
}
override fun onViewBound(view: View) {
super.onViewBound(view)
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedApplication!!.componentApplication.inject(this)
if (activity != null) {
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
binding = ActivityServerSelectionBinding.inflate(layoutInflater)
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_product_name)
)
binding?.serverEntryTextInputLayout?.setEndIconOnClickListener { checkServerAndProceed() }
binding.serverEntryTextInputLayout.setEndIconOnClickListener { checkServerAndProceed() }
if (resources!!.getBoolean(R.bool.hide_auth_cert)) {
binding?.certTextView?.visibility = View.GONE
binding.certTextView.visibility = View.GONE
}
val loggedInUsers = userManager.users.blockingGet()
@ -124,21 +119,54 @@ class ServerSelectionController :
} else if (isAbleToShowProviderLink() && loggedInUsers.isEmpty()) {
showVisitProvidersInfo()
} 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))) {
binding?.serverEntryTextInputEditText?.setText(resources!!.getString(R.string.weblogin_url))
binding.serverEntryTextInputEditText.setText(resources!!.getString(R.string.weblogin_url))
checkServerAndProceed()
}
binding?.serverEntryTextInputEditText?.setOnEditorActionListener { _: TextView?, i: Int, _: KeyEvent? ->
binding.serverEntryTextInputEditText.setOnEditorActionListener { _: TextView?, i: Int, _: KeyEvent? ->
if (i == EditorInfo.IME_ACTION_DONE) {
checkServerAndProceed()
}
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 {
@ -152,41 +180,37 @@ class ServerSelectionController :
)
) {
if (availableAccounts.size > 1) {
binding?.importOrChooseProviderText?.text = String.format(
binding.importOrChooseProviderText.text = String.format(
resources!!.getString(R.string.nc_server_import_accounts),
AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from))
)
} else {
binding?.importOrChooseProviderText?.text = String.format(
binding.importOrChooseProviderText.text = String.format(
resources!!.getString(R.string.nc_server_import_account),
AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from))
)
}
} else {
if (availableAccounts.size > 1) {
binding?.importOrChooseProviderText?.text =
binding.importOrChooseProviderText.text =
resources!!.getString(R.string.nc_server_import_accounts_plain)
} else {
binding?.importOrChooseProviderText?.text =
binding.importOrChooseProviderText.text =
resources!!.getString(R.string.nc_server_import_account_plain)
}
}
binding?.importOrChooseProviderText?.setOnClickListener {
binding.importOrChooseProviderText.setOnClickListener {
val bundle = Bundle()
bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true)
router.pushController(
RouterTransaction.with(
SwitchAccountController(bundle)
)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
val intent = Intent(context, SwitchAccountActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
}
}
private fun showVisitProvidersInfo() {
binding?.importOrChooseProviderText?.setText(R.string.nc_get_from_provider)
binding?.importOrChooseProviderText?.setOnClickListener {
binding.importOrChooseProviderText.setText(R.string.nc_get_from_provider)
binding.importOrChooseProviderText.setOnClickListener {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse(
@ -206,11 +230,11 @@ class ServerSelectionController :
@Suppress("Detekt.TooGenericExceptionCaught")
private fun checkServerAndProceed() {
dispose()
var url: String = binding?.serverEntryTextInputEditText?.text.toString().trim { it <= ' ' }
var url: String = binding.serverEntryTextInputEditText.text.toString().trim { it <= ' ' }
showserverEntryProgressBar()
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) {
binding?.importOrChooseProviderText?.visibility = View.INVISIBLE
binding?.certTextView?.visibility = View.INVISIBLE
if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
binding.importOrChooseProviderText.visibility = View.INVISIBLE
binding.certTextView.visibility = View.INVISIBLE
}
if (url.endsWith("/")) {
url = url.substring(0, url.length - 1)
@ -278,17 +302,17 @@ class ServerSelectionController :
hideserverEntryProgressBar()
}
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) {
binding?.importOrChooseProviderText?.visibility = View.VISIBLE
binding?.certTextView?.visibility = View.VISIBLE
if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
binding.importOrChooseProviderText.visibility = View.VISIBLE
binding.certTextView.visibility = View.VISIBLE
}
dispose()
}
}) {
hideserverEntryProgressBar()
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) {
binding?.importOrChooseProviderText?.visibility = View.VISIBLE
binding?.certTextView?.visibility = View.VISIBLE
if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
binding.importOrChooseProviderText.visibility = View.VISIBLE
binding.certTextView.visibility = View.VISIBLE
}
dispose()
}
@ -311,29 +335,25 @@ class ServerSelectionController :
capabilities.spreedCapability?.features?.isNotEmpty() == true
if (hasTalk) {
activity?.runOnUiThread {
runOnUiThread {
if (CapabilitiesUtilNew.isServerEOL(capabilities)) {
if (resources != null) {
activity!!.runOnUiThread {
runOnUiThread {
setErrorText(resources!!.getString(R.string.nc_settings_server_eol))
}
}
} else {
router.pushController(
RouterTransaction.with(
WebViewLoginController(
queryUrl.replace("/status.php", ""),
false
)
)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_BASE_URL, queryUrl.replace("/status.php", ""))
val intent = Intent(context, WebViewLoginActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
}
}
} else {
if (activity != null && resources != null) {
activity!!.runOnUiThread {
if (resources != null) {
runOnUiThread {
setErrorText(resources!!.getString(R.string.nc_server_unsupported))
}
}
@ -342,8 +362,8 @@ class ServerSelectionController :
override fun onError(e: Throwable) {
Log.e(TAG, "Error while checking capabilities", e)
if (activity != null && resources != null) {
activity!!.runOnUiThread {
if (resources != null) {
runOnUiThread {
setErrorText(resources!!.getString(R.string.nc_common_error_sorry))
}
}
@ -360,72 +380,31 @@ class ServerSelectionController :
}
private fun setErrorText(text: String) {
binding?.errorWrapper?.visibility = View.VISIBLE
binding?.errorText?.text = text
binding.errorWrapper.visibility = View.VISIBLE
binding.errorText.text = text
hideserverEntryProgressBar()
}
private fun showserverEntryProgressBar() {
binding?.errorWrapper?.visibility = View.INVISIBLE
binding?.serverEntryProgressBar?.visibility = View.VISIBLE
binding.errorWrapper.visibility = View.INVISIBLE
binding.serverEntryProgressBar.visibility = View.VISIBLE
}
private fun hideserverEntryProgressBar() {
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()
binding.serverEntryProgressBar.visibility = View.INVISIBLE
}
@SuppressLint("LongLogTag")
private fun setCertTextView() {
if (activity != null) {
activity!!.runOnUiThread {
if (!TextUtils.isEmpty(appPreferences!!.temporaryClientCertAlias)) {
binding?.certTextView?.setText(R.string.nc_change_cert_auth)
runOnUiThread {
if (!TextUtils.isEmpty(appPreferences.temporaryClientCertAlias)) {
binding.certTextView.setText(R.string.nc_change_cert_auth)
} else {
binding?.certTextView?.setText(R.string.nc_configure_cert_auth)
binding.certTextView.setText(R.string.nc_configure_cert_auth)
}
hideserverEntryProgressBar()
}
}
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
if (activity != null) {
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
}
}
public override fun onDestroy() {
super.onDestroy()
@ -443,7 +422,7 @@ class ServerSelectionController :
get() = AppBarLayoutType.EMPTY
companion object {
const val TAG = "ServerSelectionController"
private val TAG = ServerSelectionActivity::class.java.simpleName
const val MIN_SERVER_MAJOR_VERSION = 13
}
}

View file

@ -3,6 +3,8 @@
*
* @author Mario Danic
* @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) 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:
* https://github.com/nextcloud/ownCloud-Account-Importer
*/
package com.nextcloud.talk.controllers
package com.nextcloud.talk.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.view.MenuItem
import android.view.View
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import autodagger.AutoInjector
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.adapters.items.AdvancedUserItem
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.databinding.ControllerGenericRvBinding
import com.nextcloud.talk.databinding.ActivitySwitchAccountBinding
import com.nextcloud.talk.models.ImportAccount
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.users.UserManager
@ -56,14 +56,11 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import org.osmdroid.config.Configuration
import java.net.CookieManager
import javax.inject.Inject
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@AutoInjector(NextcloudTalkApplication::class)
class SwitchAccountController(args: Bundle? = null) :
BaseController(
R.layout.controller_generic_rv,
args
) {
private val binding: ControllerGenericRvBinding? by viewBinding(ControllerGenericRvBinding::bind)
class SwitchAccountActivity : BaseActivity() {
private lateinit var binding: ActivitySwitchAccountBinding
@Inject
lateinit var userManager: UserManager
@ -89,41 +86,52 @@ class SwitchAccountController(args: Bundle? = null) :
if (userManager.setUserAsActive(user).blockingGet()) {
cookieManager.cookieStore.removeAll()
if (activity != null) {
activity!!.runOnUiThread { router.popCurrentController() }
}
finish()
}
}
true
}
init {
setHasOptionsMenu(true)
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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))
if (args?.containsKey(KEY_IS_ACCOUNT_IMPORT) == true) {
handleIntent()
}
private fun handleIntent() {
intent.extras?.let {
if (it.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
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) {
super.onViewBound(view)
binding?.swipeRefreshLayout?.isEnabled = false
private fun setupActionBar() {
setSupportActionBar(binding.toolbar)
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) {
adapter = FlexibleAdapter(userItems, activity, false)
adapter = FlexibleAdapter(userItems, this, false)
var participant: Participant
if (!isAccountImport) {
@ -166,11 +174,10 @@ class SwitchAccountController(args: Bundle? = null) :
}
private fun prepareViews() {
val layoutManager: LinearLayoutManager = SmoothScrollLinearLayoutManager(activity)
binding?.recyclerView?.layoutManager = layoutManager
binding?.recyclerView?.setHasFixedSize(true)
binding?.recyclerView?.adapter = adapter
binding?.swipeRefreshLayout?.isEnabled = false
val layoutManager: LinearLayoutManager = SmoothScrollLinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.adapter = adapter
}
private fun reauthorizeFromImport(account: Account?) {
@ -180,14 +187,9 @@ class SwitchAccountController(args: Bundle? = null) :
bundle.putString(KEY_USERNAME, importAccount.getUsername())
bundle.putString(KEY_TOKEN, importAccount.getToken())
bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true)
router.pushController(
RouterTransaction.with(AccountVerificationController(bundle))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
override val title: String
get() =
resources!!.getString(R.string.nc_select_an_account)
val intent = Intent(context, AccountVerificationActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
}
}

View file

@ -3,6 +3,8 @@
*
* @author Mario Danic
* @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) 2017 Mario Danic (mario@lovelyhq.com)
*
@ -19,9 +21,10 @@
* 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
package com.nextcloud.talk.account
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo
import android.graphics.Bitmap
import android.net.http.SslError
@ -40,34 +43,30 @@ import android.webkit.WebResourceResponse
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.work.Data
import androidx.activity.OnBackPressedCallback
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import autodagger.AutoInjector
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.google.android.material.snackbar.Snackbar
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.Companion.sharedApplication
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.controllers.util.viewBinding
import com.nextcloud.talk.databinding.ControllerWebViewLoginBinding
import com.nextcloud.talk.databinding.ActivityWebViewLoginBinding
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.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_ORIGINAL_PROTOCOL
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import com.nextcloud.talk.utils.ssl.TrustManager
import de.cotech.hw.fido.WebViewFidoBridge
import io.reactivex.disposables.Disposable
import org.greenrobot.eventbus.EventBus
import java.lang.reflect.Field
import java.net.CookieManager
import java.net.URLDecoder
@ -78,11 +77,9 @@ import java.util.Locale
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class WebViewLoginController(args: Bundle? = null) : BaseController(
R.layout.controller_web_view_login,
args
) {
private val binding: ControllerWebViewLoginBinding? by viewBinding(ControllerWebViewLoginBinding::bind)
class WebViewLoginActivity : BaseActivity() {
private lateinit var binding: ActivityWebViewLoginBinding
@Inject
lateinit var userManager: UserManager
@ -90,34 +87,26 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
@Inject
lateinit var trustManager: TrustManager
@Inject
lateinit var eventBus: EventBus
@Inject
lateinit var cookieManager: CookieManager
private var assembledPrefix: String? = null
private var userQueryDisposable: Disposable? = null
private var baseUrl: String? = null
private var isPasswordUpdate = false
private var reauthorizeAccount = false
private var username: String? = null
private var password: String? = null
private var loginStep = 0
private var automatedLoginAttempted = false
private var webViewFidoBridge: WebViewFidoBridge? = null
constructor(baseUrl: String?, isPasswordUpdate: Boolean) : this() {
this.baseUrl = baseUrl
this.isPasswordUpdate = isPasswordUpdate
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
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
get() = (
Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) +
@ -129,33 +118,57 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
")"
)
@SuppressLint("SetJavaScriptEnabled")
override fun onViewBound(view: View) {
super.onViewBound(view)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedApplication!!.componentApplication.inject(this)
binding = ActivityWebViewLoginBinding.inflate(layoutInflater)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
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/"
binding?.webview?.settings?.allowFileAccess = false
binding?.webview?.settings?.allowFileAccessFromFileURLs = false
binding?.webview?.settings?.javaScriptEnabled = true
binding?.webview?.settings?.javaScriptCanOpenWindowsAutomatically = false
binding?.webview?.settings?.domStorageEnabled = true
binding?.webview?.settings?.setUserAgentString(webLoginUserAgent)
binding?.webview?.settings?.saveFormData = false
binding?.webview?.settings?.savePassword = false
binding?.webview?.settings?.setRenderPriority(WebSettings.RenderPriority.HIGH)
binding?.webview?.clearCache(true)
binding?.webview?.clearFormData()
binding?.webview?.clearHistory()
binding.webview.settings.allowFileAccess = false
binding.webview.settings.allowFileAccessFromFileURLs = false
binding.webview.settings.javaScriptEnabled = true
binding.webview.settings.javaScriptCanOpenWindowsAutomatically = false
binding.webview.settings.domStorageEnabled = true
binding.webview.settings.userAgentString = webLoginUserAgent
binding.webview.settings.saveFormData = false
binding.webview.settings.savePassword = false
binding.webview.settings.setRenderPriority(WebSettings.RenderPriority.HIGH)
binding.webview.clearCache(true)
binding.webview.clearFormData()
binding.webview.clearHistory()
WebView.clearClientCertPreferences(null)
webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(activity as AppCompatActivity?, binding?.webview)
CookieSyncManager.createInstance(activity)
webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(this, binding.webview)
CookieSyncManager.createInstance(this)
android.webkit.CookieManager.getInstance().removeAllCookies(null)
val headers: MutableMap<String, String> = HashMap()
headers.put("OCS-APIRequest", "true")
binding?.webview?.webViewClient = object : WebViewClient() {
headers["OCS-APIRequest"] = "true"
binding.webview.webViewClient = object : WebViewClient() {
private var basePageLoaded = false
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
webViewFidoBridge?.delegateShouldInterceptRequest(view, request)
@ -180,24 +193,24 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
override fun onPageFinished(view: WebView, url: String) {
loginStep++
if (!basePageLoaded) {
binding?.progressBar?.visibility = View.GONE
binding?.webview?.visibility = View.VISIBLE
binding.progressBar.visibility = View.GONE
binding.webview.visibility = View.VISIBLE
basePageLoaded = true
}
if (!TextUtils.isEmpty(username)) {
if (loginStep == 1) {
binding?.webview?.loadUrl(
binding.webview.loadUrl(
"javascript: {document.getElementsByClassName('login')[0].click(); };"
)
} else if (!automatedLoginAttempted) {
automatedLoginAttempted = true
if (TextUtils.isEmpty(password)) {
binding?.webview?.loadUrl(
binding.webview.loadUrl(
"javascript:var justStore = document.getElementById('user').value = '$username';"
)
} else {
binding?.webview?.loadUrl(
binding.webview.loadUrl(
"javascript: {" +
"document.getElementById('user').value = '" + username + "';" +
"document.getElementById('password').value = '" + password + "';" +
@ -213,8 +226,8 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
override fun onReceivedClientCertRequest(view: WebView, request: ClientCertRequest) {
val user = userManager.currentUser.blockingGet()
var alias: String? = null
if (!isPasswordUpdate) {
alias = appPreferences!!.temporaryClientCertAlias
if (!reauthorizeAccount) {
alias = appPreferences.temporaryClientCertAlias
}
if (TextUtils.isEmpty(alias) && user != null) {
alias = user.clientCertificate
@ -223,9 +236,9 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
val finalAlias = alias
Thread {
try {
val privateKey = KeyChain.getPrivateKey(activity!!, finalAlias!!)
val privateKey = KeyChain.getPrivateKey(applicationContext, finalAlias!!)
val certificates = KeyChain.getCertificateChain(
activity!!,
applicationContext,
finalAlias
)
if (privateKey != null && certificates != null) {
@ -241,16 +254,16 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
}.start()
} else {
KeyChain.choosePrivateKeyAlias(
activity!!,
this@WebViewLoginActivity,
{ chosenAlias: String? ->
if (chosenAlias != null) {
appPreferences!!.temporaryClientCertAlias = chosenAlias
Thread {
var privateKey: PrivateKey? = null
try {
privateKey = KeyChain.getPrivateKey(activity!!, chosenAlias)
privateKey = KeyChain.getPrivateKey(applicationContext, chosenAlias)
val certificates = KeyChain.getCertificateChain(
activity!!,
applicationContext,
chosenAlias
)
if (privateKey != null && certificates != null) {
@ -304,7 +317,7 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
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() {
@ -318,25 +331,27 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
val loginData = parseLoginData(assembledPrefix, dataString)
if (loginData != null) {
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()
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()
bundle.putString(KEY_USERNAME, loginData.username)
bundle.putString(KEY_TOKEN, loginData.token)
@ -350,46 +365,41 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
if (!TextUtils.isEmpty(protocol)) {
bundle.putString(KEY_ORIGINAL_PROTOCOL, protocol)
}
router.pushController(
RouterTransaction.with(AccountVerificationController(bundle))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
} else {
if (isPasswordUpdate) {
val intent = Intent(context, AccountVerificationActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
}
private fun restartApp() {
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) {
currentUser.clientCertificate = appPreferences!!.temporaryClientCertAlias
currentUser.clientCertificate = appPreferences.temporaryClientCertAlias
currentUser.token = loginData.token
val rowsUpdated = userManager.updateOrCreateUser(currentUser).blockingGet()
Log.d(TAG, "User rows updated: $rowsUpdated")
if (finalMessageType != null) {
ApplicationWideMessageHolder.getInstance().messageType = finalMessageType
restartApp()
}
}
val data = Data.Builder().putString(
PushRegistrationWorker.ORIGIN,
"WebViewLoginController#parseAndLoginFromWebView"
).build()
private fun startAccountRemovalWorkerAndRestartApp() {
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
val pushRegistrationWork = OneTimeWorkRequest.Builder(
PushRegistrationWorker::class.java
)
.setInputData(data)
.build()
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
.observeForever { workInfo: WorkInfo ->
WorkManager.getInstance().enqueue(pushRegistrationWork)
router.popCurrentController()
}
} 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()
when (workInfo.state) {
WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
restartApp()
}
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() {
super.onDestroy()
dispose()
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
}
init {
sharedApplication!!.componentApplication.inject(this)
}
@ -464,7 +455,7 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
get() = AppBarLayoutType.EMPTY
companion object {
const val TAG = "WebViewLoginController"
private val TAG = WebViewLoginActivity::class.java.simpleName
private const val PROTOCOL_SUFFIX = "://"
private const val LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"
private const val PARAMETER_COUNT = 3

View file

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

View file

@ -24,17 +24,26 @@ package com.nextcloud.talk.activities
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.webkit.SslErrorHandler
import android.widget.EditText
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import autodagger.AutoInjector
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.events.CertificateEvent
import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -77,6 +86,8 @@ open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
super.onCreate(savedInstanceState)
cleanTempCertPreference()
}
public override fun onStart() {
@ -87,6 +98,11 @@ open class BaseActivity : AppCompatActivity() {
public override fun 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) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
} else {
@ -104,6 +120,19 @@ open class BaseActivity : AppCompatActivity() {
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() {
if (resources != null) {
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,
trustManager: TrustManager,
sslErrorHandler: SslErrorHandler?
@ -160,15 +205,17 @@ open class BaseActivity : AppCompatActivity() {
validUntil
)
val dialogBuilder = MaterialAlertDialogBuilder(this)
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.ic_security_white_24dp))
.setTitle(R.string.nc_certificate_dialog_title)
val dialogBuilder = MaterialAlertDialogBuilder(this).setIcon(
viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
context,
R.drawable.ic_security_white_24dp
)
).setTitle(R.string.nc_certificate_dialog_title)
.setMessage(dialogText)
.setPositiveButton(R.string.nc_yes) { _, _ ->
trustManager.addCertInTrustStore(cert)
sslErrorHandler?.proceed()
}
.setNegativeButton(R.string.nc_no) { _, _ ->
}.setNegativeButton(R.string.nc_no) { _, _ ->
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)
fun onMessageEvent(event: CertificateEvent) {
showCertificateDialog(event.x509Certificate, event.magicTrustManager, event.sslErrorHandler)
}
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() {
binding!!.controllerCallLayout.fitsSystemWindows = false
binding!!.callLayout.fitsSystemWindows = false
}
override fun onConfigurationChanged(newConfig: Configuration) {

View file

@ -37,21 +37,14 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
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.nextcloud.talk.BuildConfig
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.application.NextcloudTalkApplication
import com.nextcloud.talk.callnotification.CallNotificationActivity
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.data.user.model.User
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.SecurityUtils
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 io.reactivex.Observer
import io.reactivex.SingleObserver
@ -80,15 +72,11 @@ class MainActivity : BaseActivity(), ActionBarProvider {
@Inject
lateinit var userManager: UserManager
private var router: Router? = null
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!router!!.handleBack()) {
finish()
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
override fun onCreate(savedInstanceState: Bundle?) {
@ -111,8 +99,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
setSupportActionBar(binding.toolbar)
router = Conductor.attachRouter(this, binding.controllerContainer, savedInstanceState)
handleIntent(intent)
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
@ -128,28 +114,24 @@ class MainActivity : BaseActivity(), ActionBarProvider {
}
}
private fun launchLoginScreen() {
if (!TextUtils.isEmpty(resources.getString(R.string.weblogin_url))) {
router!!.pushController(
RouterTransaction.with(
WebViewLoginController(resources.getString(R.string.weblogin_url), false)
)
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
private fun launchServerSelection() {
if (isBrandingUrlSet()) {
val intent = Intent(context, WebViewLoginActivity::class.java)
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_BASE_URL, resources.getString(R.string.weblogin_url))
intent.putExtras(bundle)
startActivity(intent)
} else {
router!!.setRoot(
RouterTransaction.with(ServerSelectionController())
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
val intent = Intent(context, ServerSelectionActivity::class.java)
startActivity(intent)
}
}
private fun isBrandingUrlSet() = !TextUtils.isEmpty(resources.getString(R.string.weblogin_url))
override fun onStart() {
Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString())
super.onStart()
logRouterBackStack(router!!)
}
override fun onResume() {
@ -178,14 +160,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
startActivity(intent)
}
fun addAccount() {
router!!.pushController(
RouterTransaction.with(ServerSelectionController())
.pushChangeHandler(VerticalChangeHandler())
.popChangeHandler(VerticalChangeHandler())
)
}
private fun handleActionFromContact(intent: Intent) {
if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
val cursor = contentResolver.query(intent.data!!, null, null, null, null)
@ -209,7 +183,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
startConversation(user)
} else {
Snackbar.make(
binding.controllerContainer,
binding.root,
R.string.nc_phone_book_integration_account_not_found,
Snackbar.LENGTH_LONG
).show()
@ -283,28 +257,18 @@ class MainActivity : BaseActivity(), ActionBarProvider {
}
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.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
if (!router!!.hasRootController()) {
openConversationList()
}
val callNotificationIntent = Intent(this, CallNotificationActivity::class.java)
intent.extras?.let { callNotificationIntent.putExtras(it) }
startActivity(callNotificationIntent)
} else {
logRouterBackStack(router!!)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(intent.extras!!)
startActivity(chatIntent)
logRouterBackStack(router!!)
}
}
} else if (intent.hasExtra(ADD_ACCOUNT) && intent.getBooleanExtra(ADD_ACCOUNT, false)) {
addAccount()
} else if (!router!!.hasRootController()) {
} else {
if (!appPreferences.isDbRoomMigrated) {
appPreferences.isDbRoomMigrated = true
}
@ -321,7 +285,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
}
} else {
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 {
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 {
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 {
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/>.
*/
package com.nextcloud.talk.controllers.bottomsheet.items
package com.nextcloud.talk.bottomsheet.items
import android.widget.ImageView
import androidx.annotation.DrawableRes

View file

@ -18,7 +18,7 @@
* 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.recyclerview.widget.LinearLayoutManager

View file

@ -18,7 +18,7 @@
* 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.ViewGroup

View file

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

View file

@ -1605,7 +1605,7 @@ class ChatActivity :
participantPermissions.hasChatPermission() &&
!isReadOnlyConversation()
) {
val messageSwipeController = MessageSwipeCallback(
val messageSwipeCallback = MessageSwipeCallback(
this,
object : MessageSwipeActions {
override fun showReplyUI(position: Int) {
@ -1617,7 +1617,7 @@ class ChatActivity :
}
)
val itemTouchHelper = ItemTouchHelper(messageSwipeController)
val itemTouchHelper = ItemTouchHelper(messageSwipeCallback)
itemTouchHelper.attachToRecyclerView(binding.messagesListView)
}
}
@ -2561,7 +2561,7 @@ class ChatActivity :
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) {
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()) {
uploadFiles(filesToUpload)
}

View file

@ -488,17 +488,13 @@ class ContactsActivity :
} else {
adapter?.filterItems()
}
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
}
override fun onError(e: Throwable) {
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
dispose(contactsQueryDisposable)
}
override fun onComplete() {
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
dispose(contactsQueryDisposable)
alreadyFetching = false
disengageProgressBar()
@ -656,12 +652,9 @@ class ContactsActivity :
private fun prepareViews() {
layoutManager = SmoothScrollLinearLayoutManager(this)
binding.controllerGenericRv.recyclerView.layoutManager = layoutManager
binding.controllerGenericRv.recyclerView.setHasFixedSize(true)
binding.controllerGenericRv.recyclerView.adapter = adapter
binding.controllerGenericRv.swipeRefreshLayout.setOnRefreshListener { fetchData() }
binding.controllerGenericRv.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it.swipeRefreshLayout) }
binding.contactsRv.layoutManager = layoutManager
binding.contactsRv.setHasFixedSize(true)
binding.contactsRv.adapter = adapter
binding.listOpenConversationsImage.background?.setColorFilter(
ResourcesCompat.getColor(resources!!, R.color.colorBackgroundDarker, null),
@ -677,7 +670,7 @@ class ContactsActivity :
private fun disengageProgressBar() {
if (!alreadyFetching) {
binding.loadingContent.visibility = View.GONE
binding.controllerGenericRv.root.visibility = View.VISIBLE
binding.root.visibility = View.VISIBLE
if (isNewConversationView) {
binding.callHeaderLayout.visibility = View.VISIBLE
}
@ -713,8 +706,6 @@ class ContactsActivity :
adapter?.updateDataSet(contactItems as List<Nothing>?)
}
binding.controllerGenericRv?.swipeRefreshLayout?.isEnabled = !adapter!!.hasFilter()
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.application.NextcloudTalkApplication
import com.nextcloud.talk.contacts.ContactsActivity
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding

View file

@ -50,6 +50,7 @@ import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog
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.nextcloud.android.common.ui.theme.utils.ColorRole
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.CallActivity
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.adapters.items.ConversationItem
import com.nextcloud.talk.adapters.items.GenericTextHeaderItem
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.contacts.ContactsActivity
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.EventStatus
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.UserIdUtils
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_MSG_FLAG
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT
@ -146,7 +151,7 @@ class ConversationsListActivity :
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener {
private lateinit var binding: ControllerConversationsRvBinding
private lateinit var binding: ActivityConversationsBinding
@Inject
lateinit var userManager: UserManager
@ -202,7 +207,6 @@ class ConversationsListActivity :
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// TODO: replace this when conductor is removed. For now it avoids to load the MainActiviy which has no UI.
finishAffinity()
}
}
@ -211,7 +215,7 @@ class ConversationsListActivity :
super.onCreate(savedInstanceState)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
binding = ControllerConversationsRvBinding.inflate(layoutInflater)
binding = ActivityConversationsBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
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)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
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")
private fun prepareViews() {
layoutManager = SmoothScrollLinearLayoutManager(this)
binding?.recyclerView?.layoutManager = layoutManager
binding?.recyclerView?.setHasFixedSize(true)
binding?.recyclerView?.adapter = adapter
binding?.recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.adapter = adapter
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
@ -1131,7 +1144,7 @@ class ConversationsListActivity :
selectedConversation!!.displayName
)
}
binding?.floatingActionButton?.let {
binding.floatingActionButton.let {
val dialogBuilder = MaterialAlertDialogBuilder(it.context)
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.upload))
.setTitle(confirmationQuestion)
@ -1358,30 +1371,17 @@ class ConversationsListActivity :
.setTitle(R.string.nc_dialog_invalid_password)
.setMessage(R.string.nc_dialog_reauth_or_delete)
.setCancelable(false)
.setPositiveButton(R.string.nc_delete) { _, _ ->
val otherUserExists = userManager
.scheduleUserForDeletionWithId(currentUser!!.id!!)
.blockingGet()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
WorkManager.getInstance().enqueue(accountRemovalWork)
if (otherUserExists) {
finish()
.setPositiveButton(R.string.nc_settings_remove_account) { _, _ ->
deleteUserAndRestartApp()
}
.setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
val intent = Intent(context, WebViewLoginActivity::class.java)
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl)
bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
intent.putExtras(bundle)
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)
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() {
binding.floatingActionButton.let {
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)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
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)
.setMessage(R.string.nc_dialog_maintenance_mode_description)
.setCancelable(false)
.setNegativeButton(R.string.nc_settings_remove_account) { _, _ ->
deleteUserAndRestartApp()
}
if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
dialogBuilder.setPositiveButton(R.string.nc_switch_account) { _, _ ->
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
}
} else {
dialogBuilder.setPositiveButton(R.string.nc_close_app) { _, _ ->
finishAffinity()
finish()
}
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)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE)
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
)
}
} else {
@ -1470,34 +1531,28 @@ class ConversationsListActivity :
}
private fun showServerEOLDialog() {
binding?.floatingActionButton?.let {
binding.floatingActionButton.let {
val dialogBuilder = MaterialAlertDialogBuilder(it.context)
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.ic_warning_white))
.setTitle(R.string.nc_settings_server_eol_title)
.setMessage(R.string.nc_settings_server_eol)
.setCancelable(false)
.setPositiveButton(R.string.nc_settings_remove_account) { _, _ ->
val otherUserExists = userManager
.scheduleUserForDeletionWithId(currentUser!!.id!!)
.blockingGet()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
WorkManager.getInstance().enqueue(accountRemovalWork)
if (otherUserExists) {
finish()
deleteUserAndRestartApp()
}
if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
dialogBuilder.setNegativeButton(R.string.nc_switch_account) { _, _ ->
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
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)
} 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()
viewThemeUtils.platform.colorTextButtons(
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) {
val data = Data.Builder()
data.putLong(
@ -1613,10 +1660,10 @@ class ConversationsListActivity :
}
companion object {
const val TAG = "ConvListController"
private val TAG = ConversationsListActivity::class.java.simpleName
const val UNREAD_BUBBLE_DELAY = 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_MIN_CHARS = 2
const val HTTP_UNAUTHORIZED = 401

View file

@ -23,10 +23,7 @@
package com.nextcloud.talk.jobs;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
@ -212,17 +209,5 @@ public class AccountRemovalWorker extends Worker {
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) {
viewModel.searchLocation()
} 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 {
override fun onItemClick(position: Int) {

View file

@ -608,7 +608,7 @@ class ProfileActivity : BaseActivity() {
class UserInfoAdapter(
displayList: List<UserInfoDetailsItem>?,
private val viewThemeUtils: ViewThemeUtils,
private val controller: ProfileActivity
private val profileActivity: ProfileActivity
) : RecyclerView.Adapter<UserInfoAdapter.ViewHolder>() {
var displayList: List<UserInfoDetailsItem>?
var filteredDisplayList: MutableList<UserInfoDetailsItem> = LinkedList()
@ -643,7 +643,7 @@ class ProfileActivity : BaseActivity() {
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item: UserInfoDetailsItem = if (controller.edit) {
val item: UserInfoDetailsItem = if (profileActivity.edit) {
displayList!![position]
} else {
filteredDisplayList[position]
@ -656,11 +656,11 @@ class ProfileActivity : BaseActivity() {
holder.binding.icon.contentDescription = item.hint
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
controller.viewThemeUtils.material.colorTextInputLayout(holder.binding.userInfoInputLayout)
if (controller.edit &&
controller.editableFields.contains(item.field.toString().lowercase())
profileActivity.viewThemeUtils.material.colorTextInputLayout(holder.binding.userInfoInputLayout)
if (profileActivity.edit &&
profileActivity.editableFields.contains(item.field.toString().lowercase())
) {
holder.binding.userInfoEditTextEdit.isEnabled = true
holder.binding.userInfoEditTextEdit.isFocusableInTouchMode = true
@ -700,7 +700,7 @@ class ProfileActivity : BaseActivity() {
}
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()
} else {
filteredDisplayList[holder.adapterPosition].text =
@ -739,7 +739,7 @@ class ProfileActivity : BaseActivity() {
}
override fun getItemCount(): Int {
return if (controller.edit) {
return if (profileActivity.edit) {
displayList!!.size
} else {
filteredDisplayList.size
@ -762,7 +762,7 @@ class ProfileActivity : BaseActivity() {
}
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_RETRIES: Long = 3
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
// combined with addNextIntentWithParentStack. For further reading, see
// 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(
View(context),

View file

@ -27,6 +27,7 @@ package com.nextcloud.talk.settings
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.app.KeyguardManager
import android.content.Context
import android.content.DialogInterface
@ -51,6 +52,7 @@ import android.view.View
import android.view.WindowManager
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ContextThemeWrapper
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.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme
@ -465,17 +468,47 @@ class SettingsActivity : BaseActivity() {
}
}
@SuppressLint("CheckResult")
private fun removeCurrentAccount() {
val otherUserExists = userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
if (otherUserExists) {
// TODO: find better solution once Conductor is removed
finish()
startActivity(intent)
} else if (!otherUserExists) {
Log.d(TAG, "No other users found. AccountRemovalWorker will restart the app.")
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 getRingtoneName(context: Context, ringtoneUri: Uri?): String {
@ -1205,7 +1238,7 @@ class SettingsActivity : BaseActivity() {
}
companion object {
private const val TAG = "SettingsController"
private val TAG = SettingsActivity::class.java.simpleName
private const val DURATION: Long = 2500
private const val START_DELAY: Long = 5000
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.api.NcApi
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.hovercard.HoverCardAction

View file

@ -35,7 +35,7 @@ import android.view.View;
import android.view.ViewGroup;
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.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
@ -71,7 +71,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
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)
public class ChooseAccountDialogFragment extends DialogFragment {
@ -185,11 +185,9 @@ public class ChooseAccountDialogFragment extends DialogFragment {
// Creating listeners for quick-actions
binding.currentAccount.getRoot().setOnClickListener(v -> dismiss());
binding.addAccount.setOnClickListener(v -> {
// TODO: change this when conductor is removed
Intent intent = new Intent(getContext(), MainActivity.class);
intent.putExtra(ADD_ACCOUNT, true);
Intent intent = new Intent(getContext(), ServerSelectionActivity.class);
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true);
startActivity(intent);
dismiss();
});

View file

@ -84,6 +84,8 @@ object BundleKeys {
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_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 KEY_REAUTHORIZE_ACCOUNT = "KEY_REAUTHORIZE_ACCOUNT"
const val KEY_PASSWORD = "KEY_PASSWORD"
}

View file

@ -39,7 +39,7 @@ public class ApplicationWideMessageHolder {
}
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
}

View file

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

View file

@ -178,7 +178,15 @@
android:textAppearance="@style/ListItem" />
</RelativeLayout>
<include
android:id="@+id/controller_generic_rv"
layout="@layout/controller_generic_rv" />
<RelativeLayout
android:layout_width="match_parent"
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>

View file

@ -40,8 +40,6 @@
android:windowContentOverlay="@null"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
@ -58,11 +56,4 @@
</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>

View file

@ -18,25 +18,34 @@
~ 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:tools="http://schemas.android.com/tools"
android:id="@+id/generic_rv_layout"
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
android:id="@+id/swipe_refresh_layout"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
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_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
android:id="@+id/recycler_view"
@ -44,4 +53,4 @@
android:layout_height="match_parent"
app:layout_anchor="@+id/swipe_refresh_layout"
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"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/controllerCallLayout"
android:id="@+id/callLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
@ -128,7 +128,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="@color/controller_call_incomingCallTextView"
android:textColor="@color/call_incomingCallTextView"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

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

View file

@ -53,7 +53,7 @@
<color name="conversation_unread_bubble">#373737</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>

View file

@ -61,10 +61,10 @@
<color name="nc_darkRed">#D32F2F</color>
<color name="nc_darkYellow">#FF9800</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="nc_grey">#D5D5D5</color>
<color name="controller_call_incomingCallTextView">#E9FFFFFF</color>
<color name="call_incomingCallTextView">#E9FFFFFF</color>
<color name="grey950">#111111</color>
<color name="textColorMaxContrast">#767676</color>
<color name="colorBackgroundDarker">#DBDBDB</color>

View file

@ -35,7 +35,7 @@
<!-- Branding -->
<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>
<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="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_deleted_user">User %1$s was removed</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>

View file

@ -97,7 +97,7 @@ class BundleKeysTest {
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_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)
}
}