diff --git a/.editorconfig b/.editorconfig index 7de780eb349..be62f0799ad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -38,6 +38,10 @@ indent_size=2 [*.{kt,kts}] # IDE does not follow this Ktlint rule strictly, but the default ordering is pretty good anyway, so let's ditch it +insert_final_newline = true +ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than=unset +ktlint_function_signature_body_expression_wrapping=multiline ktlint_standard_import-ordering = disabled +ktlint_standard_wrapping = enabled ij_kotlin_allow_trailing_comma = false ij_kotlin_allow_trailing_comma_on_call_site = false diff --git a/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt b/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt index e6d9ca62a66..6c854458169 100644 --- a/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt +++ b/app/src/gplay/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.kt @@ -55,10 +55,7 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi // unused atm } - override fun onProviderInstallFailed( - p0: Int, - p1: Intent? - ) { + override fun onProviderInstallFailed(p0: Int, p1: Intent?) { // unused atm } diff --git a/app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt b/app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt index 9d8aef034d9..7f1b8400e7b 100644 --- a/app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/account/AccountVerificationActivity.kt @@ -148,51 +148,55 @@ class AccountVerificationActivity : BaseActivity() { private fun determineBaseUrlProtocol(checkForcedHttps: Boolean) { cookieManager.cookieStore.removeAll() baseUrl = baseUrl!!.replace("http://", "").replace("https://", "") - val queryUrl: String = if (checkForcedHttps) { - "https://" + baseUrl + ApiUtils.getUrlPostfixForStatus() - } else { - "http://" + baseUrl + ApiUtils.getUrlPostfixForStatus() - } + val queryUrl: String = + if (checkForcedHttps) { + "https://" + baseUrl + ApiUtils.getUrlPostfixForStatus() + } else { + "http://" + baseUrl + ApiUtils.getUrlPostfixForStatus() + } ncApi.getServerStatus(queryUrl) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposables.add(d) - } - - override fun onNext(status: Status) { - baseUrl = if (checkForcedHttps) { - "https://$baseUrl" - } else { - "http://$baseUrl" + .subscribe( + object : Observer { + override fun onSubscribe(d: Disposable) { + disposables.add(d) } - if (isAccountImport) { - 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 { - findServerTalkApp() + + override fun onNext(status: Status) { + baseUrl = + if (checkForcedHttps) { + "https://$baseUrl" + } else { + "http://$baseUrl" + } + if (isAccountImport) { + 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 { + findServerTalkApp() + } } - } - override fun onError(e: Throwable) { - if (checkForcedHttps) { - determineBaseUrlProtocol(false) - } else { - abortVerification() + override fun onError(e: Throwable) { + if (checkForcedHttps) { + determineBaseUrlProtocol(false) + } else { + abortVerification() + } } - } - override fun onComplete() { - // unused atm + override fun onComplete() { + // unused atm + } } - }) + ) } private fun findServerTalkApp() { @@ -201,52 +205,58 @@ class AccountVerificationActivity : BaseActivity() { ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl)) .subscribeOn(Schedulers.io()) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposables.add(d) - } + .subscribe( + object : Observer { + override fun onSubscribe(d: Disposable) { + disposables.add(d) + } - override fun onNext(capabilitiesOverall: CapabilitiesOverall) { - val hasTalk = - capabilitiesOverall.ocs!!.data!!.capabilities != null && - capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability != null && - capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features != null && - !capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features!!.isEmpty() - if (hasTalk) { - fetchProfile(credentials, capabilitiesOverall) - } else { + override fun onNext(capabilitiesOverall: CapabilitiesOverall) { + val hasTalk = + capabilitiesOverall.ocs!!.data!!.capabilities != null && + capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability != null && + capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features != null && + !capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features!!.isEmpty() + if (hasTalk) { + fetchProfile(credentials, capabilitiesOverall) + } else { + 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().messageType = + ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK + abortVerification() + } + } + + override fun onError(e: Throwable) { 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) - ) + 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().messageType = ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK abortVerification() } - } - override fun onError(e: Throwable) { - 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) - ) - } + override fun onComplete() { + // unused atm } - ApplicationWideMessageHolder.getInstance().messageType = - ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK - abortVerification() } - - override fun onComplete() { - // unused atm - } - }) + ) } private fun storeProfile(displayName: String?, userId: String, capabilities: Capabilities) { @@ -266,39 +276,41 @@ class AccountVerificationActivity : BaseActivity() { ) ) .subscribeOn(Schedulers.io()) - .subscribe(object : MaybeObserver { - override fun onSubscribe(d: Disposable) { - disposables.add(d) - } + .subscribe( + object : MaybeObserver { + override fun onSubscribe(d: Disposable) { + disposables.add(d) + } - @SuppressLint("SetTextI18n") - override fun onSuccess(user: User) { - internalAccountId = user.id!! - if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) { - ClosedInterfaceImpl().setUpPushTokenRegistration() - } else { - Log.w(TAG, "Skipping push registration.") - runOnUiThread { - binding.progressText.text = - """ ${binding.progressText.text} + @SuppressLint("SetTextI18n") + override fun onSuccess(user: User) { + internalAccountId = user.id!! + if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) { + ClosedInterfaceImpl().setUpPushTokenRegistration() + } else { + Log.w(TAG, "Skipping push registration.") + runOnUiThread { + binding.progressText.text = + """ ${binding.progressText.text} ${resources!!.getString(R.string.nc_push_disabled)} - """.trimIndent() + """.trimIndent() + } + fetchAndStoreCapabilities() } - fetchAndStoreCapabilities() } - } - @SuppressLint("SetTextI18n") - override fun onError(e: Throwable) { - binding.progressText.text = """ ${binding.progressText.text}""".trimIndent() + - resources!!.getString(R.string.nc_display_name_not_stored) - abortVerification() - } + @SuppressLint("SetTextI18n") + override fun onError(e: Throwable) { + binding.progressText.text = """ ${binding.progressText.text}""".trimIndent() + + resources!!.getString(R.string.nc_display_name_not_stored) + abortVerification() + } - override fun onComplete() { - // unused atm + override fun onComplete() { + // unused atm + } } - }) + ) } private fun fetchProfile(credentials: String, capabilities: CapabilitiesOverall) { @@ -307,53 +319,55 @@ class AccountVerificationActivity : BaseActivity() { ApiUtils.getUrlForUserProfile(baseUrl) ) .subscribeOn(Schedulers.io()) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposables.add(d) - } + .subscribe( + object : Observer { + override fun onSubscribe(d: Disposable) { + disposables.add(d) + } - @SuppressLint("SetTextI18n") - override fun onNext(userProfileOverall: UserProfileOverall) { - var displayName: String? = null - if (!TextUtils.isEmpty(userProfileOverall.ocs!!.data!!.displayName)) { - displayName = userProfileOverall.ocs!!.data!!.displayName - } else if (!TextUtils.isEmpty(userProfileOverall.ocs!!.data!!.displayNameAlt)) { - displayName = userProfileOverall.ocs!!.data!!.displayNameAlt + @SuppressLint("SetTextI18n") + override fun onNext(userProfileOverall: UserProfileOverall) { + var displayName: String? = null + if (!TextUtils.isEmpty(userProfileOverall.ocs!!.data!!.displayName)) { + displayName = userProfileOverall.ocs!!.data!!.displayName + } else if (!TextUtils.isEmpty(userProfileOverall.ocs!!.data!!.displayNameAlt)) { + displayName = userProfileOverall.ocs!!.data!!.displayNameAlt + } + if (!TextUtils.isEmpty(displayName)) { + storeProfile( + displayName, + userProfileOverall.ocs!!.data!!.userId!!, + capabilities.ocs!!.data!!.capabilities!! + ) + } else { + runOnUiThread { + binding.progressText.text = + """ + ${binding.progressText.text} + ${resources!!.getString(R.string.nc_display_name_not_fetched)} + """.trimIndent() + } + abortVerification() + } } - if (!TextUtils.isEmpty(displayName)) { - storeProfile( - displayName, - userProfileOverall.ocs!!.data!!.userId!!, - capabilities.ocs!!.data!!.capabilities!! - ) - } else { + + @SuppressLint("SetTextI18n") + override fun onError(e: Throwable) { runOnUiThread { binding.progressText.text = """ - ${binding.progressText.text} - ${resources!!.getString(R.string.nc_display_name_not_fetched)} + ${binding.progressText.text} + ${resources!!.getString(R.string.nc_display_name_not_fetched)} """.trimIndent() } abortVerification() } - } - @SuppressLint("SetTextI18n") - override fun onError(e: Throwable) { - runOnUiThread { - binding.progressText.text = - """ - ${binding.progressText.text} - ${resources!!.getString(R.string.nc_display_name_not_fetched)} - """.trimIndent() + override fun onComplete() { + // unused atm } - abortVerification() - } - - override fun onComplete() { - // unused atm } - }) + ) } @SuppressLint("SetTextI18n") @@ -365,8 +379,8 @@ class AccountVerificationActivity : BaseActivity() { runOnUiThread { binding.progressText.text = """ - ${binding.progressText.text} - ${resources!!.getString(R.string.nc_push_disabled)} + ${binding.progressText.text} + ${resources!!.getString(R.string.nc_push_disabled)} """.trimIndent() } } @@ -376,8 +390,8 @@ class AccountVerificationActivity : BaseActivity() { runOnUiThread { binding.progressText.text = """ - ${binding.progressText.text} - ${resources!!.getString(R.string.nc_capabilities_failed)} + ${binding.progressText.text} + ${resources!!.getString(R.string.nc_capabilities_failed)} """.trimIndent() } abortVerification() @@ -389,8 +403,8 @@ class AccountVerificationActivity : BaseActivity() { runOnUiThread { binding.progressText.text = """ - ${binding.progressText.text} - ${resources!!.getString(R.string.nc_external_server_failed)} + ${binding.progressText.text} + ${resources!!.getString(R.string.nc_external_server_failed)} """.trimIndent() } } @@ -415,10 +429,15 @@ class AccountVerificationActivity : BaseActivity() { Data.Builder() .putLong(KEY_INTERNAL_USER_ID, internalAccountId) .build() - val signalingSettingsWorker = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java) - .setInputData(userData) - .build() - val websocketConnectionsWorker = OneTimeWorkRequest.Builder(WebsocketConnectionsWorker::class.java).build() + val signalingSettingsWorker = + OneTimeWorkRequest + .Builder(SignalingSettingsWorker::class.java) + .setInputData(userData) + .build() + val websocketConnectionsWorker = + OneTimeWorkRequest + .Builder(WebsocketConnectionsWorker::class.java) + .build() WorkManager.getInstance(applicationContext!!) .beginWith(signalingSettingsWorker) @@ -473,8 +492,10 @@ class AccountVerificationActivity : BaseActivity() { private fun abortVerification() { if (isAccountImport) { - ApplicationWideMessageHolder.getInstance().messageType = ApplicationWideMessageHolder.MessageType - .FAILED_TO_IMPORT_ACCOUNT + ApplicationWideMessageHolder.getInstance().messageType = + ApplicationWideMessageHolder + .MessageType + .FAILED_TO_IMPORT_ACCOUNT runOnUiThread { Handler().postDelayed({ val intent = Intent(this, ServerSelectionActivity::class.java) diff --git a/app/src/main/java/com/nextcloud/talk/account/ServerSelectionActivity.kt b/app/src/main/java/com/nextcloud/talk/account/ServerSelectionActivity.kt index d6bc6712903..4aa51dcf13a 100644 --- a/app/src/main/java/com/nextcloud/talk/account/ServerSelectionActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/account/ServerSelectionActivity.kt @@ -74,15 +74,16 @@ class ServerSelectionActivity : BaseActivity() { private var statusQueryDisposable: Disposable? = null - private val onBackPressedCallback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - if (intent.hasExtra(ADD_ADDITIONAL_ACCOUNT) && intent.getBooleanExtra(ADD_ADDITIONAL_ACCOUNT, false)) { - finish() - } else { - finishAffinity() + private val onBackPressedCallback = + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (intent.hasExtra(ADD_ADDITIONAL_ACCOUNT) && intent.getBooleanExtra(ADD_ADDITIONAL_ACCOUNT, false)) { + finish() + } else { + finishAffinity() + } } } - } @SuppressLint("SourceLockedOrientationActivity") override fun onCreate(savedInstanceState: Bundle?) { @@ -100,10 +101,12 @@ class ServerSelectionActivity : BaseActivity() { override fun onResume() { super.onResume() - binding.hostUrlInputHelperText.text = String.format( - resources!!.getString(R.string.nc_server_helper_text), - resources!!.getString(R.string.nc_server_product_name) - ) + 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() } if (resources!!.getBoolean(R.bool.hide_auth_cert)) { @@ -179,15 +182,21 @@ class ServerSelectionActivity : BaseActivity() { ) ) { if (availableAccounts.size > 1) { - binding.importOrChooseProviderText.text = String.format( - resources!!.getString(R.string.nc_server_import_accounts), - AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from)) - ) + 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( - resources!!.getString(R.string.nc_server_import_account), - AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from)) - ) + 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) { @@ -210,13 +219,14 @@ class ServerSelectionActivity : BaseActivity() { private fun showVisitProvidersInfo() { binding.importOrChooseProviderText.setText(R.string.nc_get_from_provider) binding.importOrChooseProviderText.setOnClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse( - resources!! - .getString(R.string.nc_providers_url) + val browserIntent = + Intent( + Intent.ACTION_VIEW, + Uri.parse( + resources!! + .getString(R.string.nc_providers_url) + ) ) - ) startActivity(browserIntent) } } @@ -249,129 +259,133 @@ class ServerSelectionActivity : BaseActivity() { private fun checkServer(url: String, checkForcedHttps: Boolean) { val queryStatusUrl = url + ApiUtils.getUrlPostfixForStatus() - statusQueryDisposable = ncApi.getServerStatus(queryStatusUrl) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ status: Status -> - val productName = resources!!.getString(R.string.nc_server_product_name) - val versionString: String = status.version!!.substring(0, status.version!!.indexOf(".")) - val version: Int = versionString.toInt() - - if (isServerStatusQueryable(status) && version >= MIN_SERVER_MAJOR_VERSION) { - findServerTalkApp(url) - } else if (!status.installed) { - setErrorText( - String.format( - resources!!.getString(R.string.nc_server_not_installed), - productName + statusQueryDisposable = + ncApi + .getServerStatus(queryStatusUrl) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ status: Status -> + val productName = resources!!.getString(R.string.nc_server_product_name) + val versionString: String = status.version!!.substring(0, status.version!!.indexOf(".")) + val version: Int = versionString.toInt() + + if (isServerStatusQueryable(status) && version >= MIN_SERVER_MAJOR_VERSION) { + findServerTalkApp(url) + } else if (!status.installed) { + setErrorText( + String.format( + resources!!.getString(R.string.nc_server_not_installed), + productName + ) ) - ) - } else if (status.needsUpgrade) { - setErrorText( - String.format( - resources!!.getString(R.string.nc_server_db_upgrade_needed), - productName + } else if (status.needsUpgrade) { + setErrorText( + String.format( + resources!!.getString(R.string.nc_server_db_upgrade_needed), + productName + ) ) - ) - } else if (status.maintenance) { - setErrorText( - String.format( - resources!!.getString(R.string.nc_server_maintenance), - productName + } else if (status.maintenance) { + setErrorText( + String.format( + resources!!.getString(R.string.nc_server_maintenance), + productName + ) ) - ) - } else if (!status.version!!.startsWith("13.")) { - setErrorText( - String.format( - resources!!.getString(R.string.nc_server_version), - resources!!.getString(R.string.nc_app_product_name), - productName + } else if (!status.version!!.startsWith("13.")) { + setErrorText( + String.format( + resources!!.getString(R.string.nc_server_version), + resources!!.getString(R.string.nc_app_product_name), + productName + ) ) - ) - } - }, { throwable: Throwable -> - if (checkForcedHttps) { - checkServer(queryStatusUrl.replace("https://", "http://"), false) - } else { - if (throwable.localizedMessage != null) { - setErrorText(throwable.localizedMessage) - } else if (throwable.cause is CertificateException) { - setErrorText(resources!!.getString(R.string.nc_certificate_error)) - } else { - hideserverEntryProgressBar() } + }, { throwable: Throwable -> + if (checkForcedHttps) { + checkServer(queryStatusUrl.replace("https://", "http://"), false) + } else { + if (throwable.localizedMessage != null) { + setErrorText(throwable.localizedMessage) + } else if (throwable.cause is CertificateException) { + setErrorText(resources!!.getString(R.string.nc_certificate_error)) + } else { + hideserverEntryProgressBar() + } + 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 } dispose() } - }) { - hideserverEntryProgressBar() - if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) { - binding.importOrChooseProviderText.visibility = View.VISIBLE - binding.certTextView.visibility = View.VISIBLE - } - dispose() - } } private fun findServerTalkApp(queryUrl: String) { ncApi.getCapabilities(ApiUtils.getUrlForCapabilities(queryUrl)) .subscribeOn(Schedulers.io()) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - // unused atm - } + .subscribe( + object : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } - override fun onNext(capabilitiesOverall: CapabilitiesOverall) { - val capabilities = capabilitiesOverall.ocs?.data?.capabilities + override fun onNext(capabilitiesOverall: CapabilitiesOverall) { + val capabilities = capabilitiesOverall.ocs?.data?.capabilities - val hasTalk = - capabilities?.spreedCapability != null && - capabilities.spreedCapability?.features != null && - capabilities.spreedCapability?.features?.isNotEmpty() == true + val hasTalk = + capabilities?.spreedCapability != null && + capabilities.spreedCapability?.features != null && + capabilities.spreedCapability?.features?.isNotEmpty() == true - if (hasTalk) { - runOnUiThread { - if (CapabilitiesUtilNew.isServerEOL(capabilities)) { - if (resources != null) { - runOnUiThread { - setErrorText(resources!!.getString(R.string.nc_settings_server_eol)) + if (hasTalk) { + runOnUiThread { + if (CapabilitiesUtilNew.isServerEOL(capabilities)) { + if (resources != null) { + runOnUiThread { + setErrorText(resources!!.getString(R.string.nc_settings_server_eol)) + } } - } - } else { - val bundle = Bundle() - bundle.putString(BundleKeys.KEY_BASE_URL, queryUrl.replace("/status.php", "")) + } else { + 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) + val intent = Intent(context, WebViewLoginActivity::class.java) + intent.putExtras(bundle) + startActivity(intent) + } + } + } else { + if (resources != null) { + runOnUiThread { + setErrorText(resources!!.getString(R.string.nc_server_unsupported)) + } } } - } else { + } + + override fun onError(e: Throwable) { + Log.e(TAG, "Error while checking capabilities", e) if (resources != null) { runOnUiThread { - setErrorText(resources!!.getString(R.string.nc_server_unsupported)) + setErrorText(resources!!.getString(R.string.nc_common_error_sorry)) } } } - } - override fun onError(e: Throwable) { - Log.e(TAG, "Error while checking capabilities", e) - if (resources != null) { - runOnUiThread { - setErrorText(resources!!.getString(R.string.nc_common_error_sorry)) - } + override fun onComplete() { + // unused atm } } - - override fun onComplete() { - // unused atm - } - }) + ) } private fun isServerStatusQueryable(status: Status): Boolean { diff --git a/app/src/main/java/com/nextcloud/talk/account/SwitchAccountActivity.kt b/app/src/main/java/com/nextcloud/talk/account/SwitchAccountActivity.kt index c19c3223fd2..03748132434 100644 --- a/app/src/main/java/com/nextcloud/talk/account/SwitchAccountActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/account/SwitchAccountActivity.kt @@ -72,25 +72,29 @@ class SwitchAccountActivity : BaseActivity() { private val userItems: MutableList> = ArrayList() private var isAccountImport = false - private val onImportItemClickListener = FlexibleAdapter.OnItemClickListener { _, position -> - if (userItems.size > position) { - val account = (userItems[position] as AdvancedUserItem).account - reauthorizeFromImport(account) - } - true - } + private val onImportItemClickListener = + FlexibleAdapter + .OnItemClickListener { _, position -> + if (userItems.size > position) { + val account = (userItems[position] as AdvancedUserItem).account + reauthorizeFromImport(account) + } + true + } - private val onSwitchItemClickListener = FlexibleAdapter.OnItemClickListener { _, position -> - if (userItems.size > position) { - val user = (userItems[position] as AdvancedUserItem).user + private val onSwitchItemClickListener = + FlexibleAdapter + .OnItemClickListener { _, position -> + if (userItems.size > position) { + val user = (userItems[position] as AdvancedUserItem).user - if (userManager.setUserAsActive(user).blockingGet()) { - cookieManager.cookieStore.removeAll() - finish() + if (userManager.setUserAsActive(user).blockingGet()) { + cookieManager.cookieStore.removeAll() + finish() + } + } + true } - } - true - } @SuppressLint("SourceLockedOrientationActivity") override fun onCreate(savedInstanceState: Bundle?) { @@ -137,11 +141,12 @@ class SwitchAccountActivity : BaseActivity() { if (!isAccountImport) { for (user in userManager.users.blockingGet()) { if (!user.current) { - val userId: String? = if (user.userId != null) { - user.userId - } else { - user.username - } + val userId: String? = + if (user.userId != null) { + user.userId + } else { + user.username + } participant = Participant() participant.actorType = Participant.ActorType.USERS participant.actorId = userId diff --git a/app/src/main/java/com/nextcloud/talk/account/WebViewLoginActivity.kt b/app/src/main/java/com/nextcloud/talk/account/WebViewLoginActivity.kt index 00c9232137e..39944d25ff0 100644 --- a/app/src/main/java/com/nextcloud/talk/account/WebViewLoginActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/account/WebViewLoginActivity.kt @@ -99,13 +99,14 @@ class WebViewLoginActivity : BaseActivity() { private var automatedLoginAttempted = false private var webViewFidoBridge: WebViewFidoBridge? = null - 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) + 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) + } } - } private val webLoginUserAgent: String get() = ( Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) + @@ -167,155 +168,166 @@ class WebViewLoginActivity : BaseActivity() { android.webkit.CookieManager.getInstance().removeAllCookies(null) val headers: MutableMap = HashMap() 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) - return super.shouldInterceptRequest(view, request) - } + binding.webview.webViewClient = + object : WebViewClient() { + private var basePageLoaded = false - override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { - super.onPageStarted(view, url, favicon) - webViewFidoBridge?.delegateOnPageStarted(view, url, favicon) - } - - @Deprecated("Use shouldOverrideUrlLoading(WebView view, WebResourceRequest request)") - override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { - if (url.startsWith(assembledPrefix!!)) { - parseAndLoginFromWebView(url) - return true + override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? { + webViewFidoBridge?.delegateShouldInterceptRequest(view, request) + return super.shouldInterceptRequest(view, request) } - return false - } - @Suppress("Detekt.TooGenericExceptionCaught") - override fun onPageFinished(view: WebView, url: String) { - loginStep++ - if (!basePageLoaded) { - binding.progressBar.visibility = View.GONE - binding.webview.visibility = View.VISIBLE - - basePageLoaded = true + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + webViewFidoBridge?.delegateOnPageStarted(view, url, favicon) } - if (!TextUtils.isEmpty(username)) { - if (loginStep == 1) { - binding.webview.loadUrl( - "javascript: {document.getElementsByClassName('login')[0].click(); };" - ) - } else if (!automatedLoginAttempted) { - automatedLoginAttempted = true - if (TextUtils.isEmpty(password)) { - binding.webview.loadUrl( - "javascript:var justStore = document.getElementById('user').value = '$username';" - ) - } else { - binding.webview.loadUrl( - "javascript: {" + - "document.getElementById('user').value = '" + username + "';" + - "document.getElementById('password').value = '" + password + "';" + - "document.getElementById('submit').click(); };" - ) - } + + @Deprecated("Use shouldOverrideUrlLoading(WebView view, WebResourceRequest request)") + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + if (url.startsWith(assembledPrefix!!)) { + parseAndLoginFromWebView(url) + return true } + return false } - super.onPageFinished(view, url) - } + @Suppress("Detekt.TooGenericExceptionCaught") + override fun onPageFinished(view: WebView, url: String) { + loginStep++ + if (!basePageLoaded) { + binding.progressBar.visibility = View.GONE + binding.webview.visibility = View.VISIBLE - override fun onReceivedClientCertRequest(view: WebView, request: ClientCertRequest) { - val user = userManager.currentUser.blockingGet() - var alias: String? = null - if (!reauthorizeAccount) { - alias = appPreferences.temporaryClientCertAlias - } - if (TextUtils.isEmpty(alias) && user != null) { - alias = user.clientCertificate - } - if (!TextUtils.isEmpty(alias)) { - val finalAlias = alias - Thread { - try { - val privateKey = KeyChain.getPrivateKey(applicationContext, finalAlias!!) - val certificates = KeyChain.getCertificateChain( - applicationContext, - finalAlias + basePageLoaded = true + } + if (!TextUtils.isEmpty(username)) { + if (loginStep == 1) { + binding.webview.loadUrl( + "javascript: {document.getElementsByClassName('login')[0].click(); };" ) - if (privateKey != null && certificates != null) { - request.proceed(privateKey, certificates) + } else if (!automatedLoginAttempted) { + automatedLoginAttempted = true + if (TextUtils.isEmpty(password)) { + binding.webview.loadUrl( + "javascript:var justStore = document.getElementById('user').value = '$username';" + ) } else { - request.cancel() + binding.webview.loadUrl( + "javascript: {" + + "document.getElementById('user').value = '" + username + "';" + + "document.getElementById('password').value = '" + password + "';" + + "document.getElementById('submit').click(); };" + ) } - } catch (e: KeyChainException) { - request.cancel() - } catch (e: InterruptedException) { - request.cancel() } - }.start() - } else { - KeyChain.choosePrivateKeyAlias( - this@WebViewLoginActivity, - { chosenAlias: String? -> - if (chosenAlias != null) { - appPreferences!!.temporaryClientCertAlias = chosenAlias - Thread { - var privateKey: PrivateKey? = null - try { - privateKey = KeyChain.getPrivateKey(applicationContext, chosenAlias) - val certificates = KeyChain.getCertificateChain( + } + + super.onPageFinished(view, url) + } + + override fun onReceivedClientCertRequest(view: WebView, request: ClientCertRequest) { + val user = userManager.currentUser.blockingGet() + var alias: String? = null + if (!reauthorizeAccount) { + alias = appPreferences.temporaryClientCertAlias + } + if (TextUtils.isEmpty(alias) && user != null) { + alias = user.clientCertificate + } + if (!TextUtils.isEmpty(alias)) { + val finalAlias = alias + Thread { + try { + val privateKey = KeyChain.getPrivateKey(applicationContext, finalAlias!!) + val certificates = + KeyChain + .getCertificateChain( applicationContext, - chosenAlias + finalAlias ) - if (privateKey != null && certificates != null) { - request.proceed(privateKey, certificates) - } else { - request.cancel() - } - } catch (e: KeyChainException) { - request.cancel() - } catch (e: InterruptedException) { - request.cancel() - } - }.start() - } else { + if (privateKey != null && certificates != null) { + request.proceed(privateKey, certificates) + } else { + request.cancel() + } + } catch (e: KeyChainException) { + request.cancel() + } catch (e: InterruptedException) { request.cancel() } - }, - arrayOf("RSA", "EC"), - null, - request.host, - request.port, - null - ) + }.start() + } else { + KeyChain.choosePrivateKeyAlias( + this@WebViewLoginActivity, + { chosenAlias: String? -> + if (chosenAlias != null) { + appPreferences!!.temporaryClientCertAlias = chosenAlias + Thread { + var privateKey: PrivateKey? = null + try { + privateKey = + KeyChain + .getPrivateKey(applicationContext, chosenAlias) + val certificates = KeyChain.getCertificateChain( + applicationContext, + chosenAlias + ) + if (privateKey != null && certificates != null) { + request.proceed(privateKey, certificates) + } else { + request.cancel() + } + } catch (e: KeyChainException) { + request.cancel() + } catch (e: InterruptedException) { + request.cancel() + } + }.start() + } else { + request.cancel() + } + }, + arrayOf("RSA", "EC"), + null, + request.host, + request.port, + null + ) + } } - } - @Suppress("Detekt.TooGenericExceptionCaught") - override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { - try { - val sslCertificate = error.certificate - val f: Field = sslCertificate.javaClass.getDeclaredField("mX509Certificate") - f.isAccessible = true - val cert = f[sslCertificate] as X509Certificate - if (cert == null) { - handler.cancel() - } else { - try { - trustManager.checkServerTrusted(arrayOf(cert), "generic") - handler.proceed() - } catch (exception: CertificateException) { - eventBus.post(CertificateEvent(cert, trustManager, handler)) + @Suppress("Detekt.TooGenericExceptionCaught") + override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { + try { + val sslCertificate = error.certificate + val f: Field = sslCertificate.javaClass.getDeclaredField("mX509Certificate") + f.isAccessible = true + val cert = f[sslCertificate] as X509Certificate + if (cert == null) { + handler.cancel() + } else { + try { + trustManager.checkServerTrusted(arrayOf(cert), "generic") + handler.proceed() + } catch (exception: CertificateException) { + eventBus.post(CertificateEvent(cert, trustManager, handler)) + } } + } catch (exception: Exception) { + handler.cancel() } - } catch (exception: Exception) { - handler.cancel() } - } - @Deprecated("Deprecated in super implementation") - override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { - super.onReceivedError(view, errorCode, description, failingUrl) + @Deprecated("Deprecated in super implementation") + override fun onReceivedError( + view: WebView, + errorCode: Int, + description: String, + failingUrl: String + ) { + super.onReceivedError(view, errorCode, description, failingUrl) + } } - } binding.webview.loadUrl("$baseUrl/index.php/login/flow", headers) } diff --git a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt index 6efc1e8a16a..7b528fc1eda 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/BaseActivity.kt @@ -61,7 +61,9 @@ import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) open class BaseActivity : AppCompatActivity() { enum class AppBarLayoutType { - TOOLBAR, SEARCH_BAR, EMPTY + TOOLBAR, + SEARCH_BAR, + EMPTY } @Inject @@ -196,27 +198,31 @@ open class BaseActivity : AppCompatActivity() { } @SuppressLint("StringFormatMatches") - val dialogText = String.format( - resources.getString(R.string.nc_certificate_dialog_text), - issuedBy, - issuedFor, - validFrom, - validUntil - ) - - 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) { _, _ -> - sslErrorHandler?.cancel() - } + val dialogText = + String + .format( + resources.getString(R.string.nc_certificate_dialog_text), + issuedBy, + issuedFor, + validFrom, + validUntil + ) + + 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) { _, _ -> + sslErrorHandler?.cancel() + } viewThemeUtils.dialog.colorMaterialAlertDialogBackground(context, dialogBuilder) diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt index 8a6c92466d7..9fe3654f751 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt @@ -268,35 +268,38 @@ class CallActivity : CallBaseActivity() { private val screenParticipantDisplayItemManagersHandler = Handler(Looper.getMainLooper()) private val callParticipantEventDisplayers: MutableMap = HashMap() private val callParticipantEventDisplayersHandler = Handler(Looper.getMainLooper()) - private val callParticipantListObserver: CallParticipantList.Observer = object : CallParticipantList.Observer { - override fun onCallParticipantsChanged( - joined: Collection, - updated: Collection, - left: Collection, - unchanged: Collection - ) { - handleCallParticipantsChanged(joined, updated, left, unchanged) - } + private val callParticipantListObserver: CallParticipantList.Observer = + object : CallParticipantList.Observer { + override fun onCallParticipantsChanged( + joined: Collection, + updated: Collection, + left: Collection, + unchanged: Collection + ) { + handleCallParticipantsChanged(joined, updated, left, unchanged) + } - override fun onCallEndedForAll() { - Log.d(TAG, "A moderator ended the call for all.") - hangup(true) + override fun onCallEndedForAll() { + Log.d(TAG, "A moderator ended the call for all.") + hangup(true) + } } - } private var callParticipantList: CallParticipantList? = null private var switchToRoomToken = "" private var isBreakoutRoom = false - private val localParticipantMessageListener = LocalParticipantMessageListener { token -> - switchToRoomToken = token - hangup(true) - } - private val offerMessageListener = OfferMessageListener { sessionId, roomType, sdp, nick -> - getOrCreatePeerConnectionWrapperForSessionIdAndType( - sessionId, - roomType, - false - ) - } + private val localParticipantMessageListener = + LocalParticipantMessageListener { token -> + switchToRoomToken = token + hangup(true) + } + private val offerMessageListener = + OfferMessageListener { sessionId, roomType, sdp, nick -> + getOrCreatePeerConnectionWrapperForSessionIdAndType( + sessionId, + roomType, + false + ) + } private var externalSignalingServer: ExternalSignalingServer? = null private var webSocketClient: WebSocketInstance? = null private var webSocketConnectionHelper: WebSocketConnectionHelper? = null @@ -314,54 +317,55 @@ class CallActivity : CallBaseActivity() { private var moreCallActionsDialog: MoreCallActionsDialog? = null private var elapsedSeconds: Long = 0 - private var requestPermissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions() - ) { permissionMap: Map -> - val rationaleList: MutableList = ArrayList() - val audioPermission = permissionMap[Manifest.permission.RECORD_AUDIO] - if (audioPermission != null) { - if (java.lang.Boolean.TRUE == audioPermission) { - if (!microphoneOn) { - onMicrophoneClick() + private var requestPermissionLauncher = + registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissionMap: Map -> + val rationaleList: MutableList = ArrayList() + val audioPermission = permissionMap[Manifest.permission.RECORD_AUDIO] + if (audioPermission != null) { + if (java.lang.Boolean.TRUE == audioPermission) { + if (!microphoneOn) { + onMicrophoneClick() + } + } else { + rationaleList.add(resources.getString(R.string.nc_microphone_permission_hint)) } - } else { - rationaleList.add(resources.getString(R.string.nc_microphone_permission_hint)) } - } - val cameraPermission = permissionMap[Manifest.permission.CAMERA] - if (cameraPermission != null) { - if (java.lang.Boolean.TRUE == cameraPermission) { - if (!videoOn) { - onCameraClick() - } - if (cameraEnumerator!!.deviceNames.isEmpty()) { - binding!!.cameraButton.visibility = View.GONE - } - if (cameraEnumerator!!.deviceNames.size > 1) { - binding!!.switchSelfVideoButton.visibility = View.VISIBLE + val cameraPermission = permissionMap[Manifest.permission.CAMERA] + if (cameraPermission != null) { + if (java.lang.Boolean.TRUE == cameraPermission) { + if (!videoOn) { + onCameraClick() + } + if (cameraEnumerator!!.deviceNames.isEmpty()) { + binding!!.cameraButton.visibility = View.GONE + } + if (cameraEnumerator!!.deviceNames.size > 1) { + binding!!.switchSelfVideoButton.visibility = View.VISIBLE + } + } else { + rationaleList.add(resources.getString(R.string.nc_camera_permission_hint)) } - } else { - rationaleList.add(resources.getString(R.string.nc_camera_permission_hint)) } - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val bluetoothPermission = permissionMap[Manifest.permission.BLUETOOTH_CONNECT] - if (bluetoothPermission != null) { - if (java.lang.Boolean.TRUE == bluetoothPermission) { - enableBluetoothManager() - } else { - // Only ask for bluetooth when already asking to grant microphone or camera access. Asking - // for bluetooth solely is not important enough here and would most likely annoy the user. - if (rationaleList.isNotEmpty()) { - rationaleList.add(resources.getString(R.string.nc_bluetooth_permission_hint)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val bluetoothPermission = permissionMap[Manifest.permission.BLUETOOTH_CONNECT] + if (bluetoothPermission != null) { + if (java.lang.Boolean.TRUE == bluetoothPermission) { + enableBluetoothManager() + } else { + // Only ask for bluetooth when already asking to grant microphone or camera access. Asking + // for bluetooth solely is not important enough here and would most likely annoy the user. + if (rationaleList.isNotEmpty()) { + rationaleList.add(resources.getString(R.string.nc_bluetooth_permission_hint)) + } } } } + if (rationaleList.isNotEmpty()) { + showRationaleDialogForSettings(rationaleList) + } } - if (rationaleList.isNotEmpty()) { - showRationaleDialogForSettings(rationaleList) - } - } private var canPublishAudioStream = false private var canPublishVideoStream = false private var isModerator = false @@ -371,11 +375,12 @@ class CallActivity : CallBaseActivity() { private lateinit var micInputAudioRecorder: AudioRecord private var micInputAudioRecordThread: Thread? = null private var isMicInputAudioThreadRunning: Boolean = false - private val bufferSize = AudioRecord.getMinBufferSize( - SAMPLE_RATE, - AudioFormat.CHANNEL_IN_MONO, - AudioFormat.ENCODING_PCM_16BIT - ) + private val bufferSize = + AudioRecord.getMinBufferSize( + SAMPLE_RATE, + AudioFormat.CHANNEL_IN_MONO, + AudioFormat.ENCODING_PCM_16BIT + ) private var recordingConsentGiven = false @@ -432,9 +437,10 @@ class CallActivity : CallBaseActivity() { } } } - callRecordingViewModel = ViewModelProvider(this, viewModelFactory).get( - CallRecordingViewModel::class.java - ) + callRecordingViewModel = + ViewModelProvider(this, viewModelFactory).get( + CallRecordingViewModel::class.java + ) callRecordingViewModel!!.setData(roomToken!!) callRecordingViewModel!!.setRecordingState(extras.getInt(KEY_RECORDING_STATE)) callRecordingViewModel!!.viewState.observe(this) { viewState: CallRecordingViewModel.ViewState? -> diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index ce631df93f7..1b58c371b9e 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -74,22 +74,27 @@ class MainActivity : BaseActivity(), ActionBarProvider { @Inject lateinit var userManager: UserManager - private val onBackPressedCallback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - finish() + private val onBackPressedCallback = + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + finish() + } } - } override fun onCreate(savedInstanceState: Bundle?) { Log.d(TAG, "onCreate: Activity: " + System.identityHashCode(this).toString()) super.onCreate(savedInstanceState) - ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onStart(owner: LifecycleOwner) { - lockScreenIfConditionsApply() - } - }) + ProcessLifecycleOwner + .get() + .lifecycle + .addObserver( + object : DefaultLifecycleObserver { + override fun onStart(owner: LifecycleOwner) { + lockScreenIfConditionsApply() + } + }) // Set the default theme to replace the launch screen theme. setTheme(R.style.AppTheme) @@ -274,33 +279,34 @@ class MainActivity : BaseActivity(), ActionBarProvider { appPreferences.isDbRoomMigrated = true } - userManager.users.subscribe(object : SingleObserver> { - override fun onSubscribe(d: Disposable) { - // unused atm - } + userManager.users.subscribe( + object : SingleObserver> { + override fun onSubscribe(d: Disposable) { + // unused atm + } - override fun onSuccess(users: List) { - if (users.isNotEmpty()) { - ClosedInterfaceImpl().setUpPushTokenRegistration() - runOnUiThread { - openConversationList() - } - } else { - runOnUiThread { - launchServerSelection() + override fun onSuccess(users: List) { + if (users.isNotEmpty()) { + ClosedInterfaceImpl().setUpPushTokenRegistration() + runOnUiThread { + openConversationList() + } + } else { + runOnUiThread { + launchServerSelection() + } } } - } - override fun onError(e: Throwable) { - Log.e(TAG, "Error loading existing users", e) - Toast.makeText( - context, - context.resources.getString(R.string.nc_common_error_sorry), - Toast.LENGTH_SHORT - ).show() - } - }) + override fun onError(e: Throwable) { + Log.e(TAG, "Error loading existing users", e) + Toast.makeText( + context, + context.resources.getString(R.string.nc_common_error_sorry), + Toast.LENGTH_SHORT + ).show() + } + }) } } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt b/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt index 603c73537e0..8e922b359b4 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt @@ -32,7 +32,6 @@ import fr.dudie.nominatim.model.Address class GeocodingAdapter(private val context: Context, private var dataSource: List
) : RecyclerView.Adapter() { - interface OnItemClickListener { fun onItemClick(position: Int) } @@ -44,11 +43,15 @@ class GeocodingAdapter(private val context: Context, private var dataSource: Lis } private var listener: OnItemClickListener? = null + fun setOnItemClickListener(listener: OnItemClickListener) { this.listener = listener } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ViewHolder { val inflater = LayoutInflater.from(context) val view = inflater.inflate(R.layout.geocoding_item, parent, false) return ViewHolder(view) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt index 4906bb7bcfa..5bbd505a65a 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt @@ -102,7 +102,10 @@ class ConversationItem( return VIEW_TYPE } - override fun createViewHolder(view: View, adapter: FlexibleAdapter?>?): ConversationItemViewHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter?>? + ): ConversationItemViewHolder { return ConversationItemViewHolder(view, adapter) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt index c9384ce2aae..34cc2a00292 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/items/MessageResultItem.kt @@ -49,7 +49,6 @@ data class MessageResultItem constructor( AbstractFlexibleItem(), IFilterable, ISectionable { - class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) { var binding: RvItemSearchMessageBinding diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedMessageInterface.kt index 9d39d987c5b..a71e19943ad 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedMessageInterface.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedMessageInterface.kt @@ -21,5 +21,6 @@ package com.nextcloud.talk.adapters.messages interface CallStartedMessageInterface { fun joinAudioCall() + fun joinVideoCall() } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt index c6154ddaa14..bf32e5e0278 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt @@ -75,34 +75,39 @@ class CallStartedViewHolder(incomingView: View, payload: Any) : private fun setUpAvatarProfile(message: ChatMessage) { val user = userManager.currentUser.blockingGet() - val url: String = if (message.actorType == "guests" || message.actorType == "guest") { - ApiUtils.getUrlForGuestAvatar( - user!!.baseUrl, - message.actorDisplayName, - true - ) - } else { - ApiUtils.getUrlForAvatar(user!!.baseUrl, message.actorDisplayName, false) - } - - val imageRequest: ImageRequest = ImageRequest.Builder(context) - .data(url) - .crossfade(true) - .transformations(CircleCropTransformation()) - .target(object : Target { - override fun onStart(placeholder: Drawable?) { - // unused atm - } - - override fun onError(error: Drawable?) { - // unused atm - } - - override fun onSuccess(result: Drawable) { - binding.callAuthorChip.chipIcon = result - } - }) - .build() + val url: String = + if (message.actorType == "guests" || message.actorType == "guest") { + ApiUtils.getUrlForGuestAvatar( + user!!.baseUrl, + message.actorDisplayName, + true + ) + } else { + ApiUtils.getUrlForAvatar(user!!.baseUrl, message.actorDisplayName, false) + } + + val imageRequest: ImageRequest = + ImageRequest + .Builder(context) + .data(url) + .crossfade(true) + .transformations(CircleCropTransformation()) + .target( + object : Target { + override fun onStart(placeholder: Drawable?) { + // unused atm + } + + override fun onError(error: Drawable?) { + // unused atm + } + + override fun onSuccess(result: Drawable) { + binding.callAuthorChip.chipIcon = result + } + } + ) + .build() imageLoader(context).enqueue(imageRequest) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt index 34116479bfa..d334cc25612 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt @@ -4,6 +4,8 @@ import com.nextcloud.talk.models.json.chat.ChatMessage interface CommonMessageInterface { fun onLongClickReactions(chatMessage: ChatMessage) + fun onClickReaction(chatMessage: ChatMessage, emoji: String) + fun onOpenMessageActionsDialog(chatMessage: ChatMessage) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt index 60ea4bb88cb..62f37ec69a4 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt @@ -81,20 +81,24 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) : sharedApplication!!.componentApplication.inject(this) binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) - var processedMessageText = messageUtils.enrichChatMessageText( - binding.messageText.context, - message, - true, - viewThemeUtils - ) + var processedMessageText = + messageUtils + .enrichChatMessageText( + binding.messageText.context, + message, + true, + viewThemeUtils + ) - processedMessageText = messageUtils.processMessageParameters( - binding.messageText.context, - viewThemeUtils, - processedMessageText!!, - message, - itemView - ) + processedMessageText = + messageUtils + .processMessageParameters( + binding.messageText.context, + viewThemeUtils, + processedMessageText!!, + message, + itemView + ) binding.messageText.text = processedMessageText @@ -195,13 +199,14 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) : } binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName ?: context.getText(R.string.nc_nick_guest) - binding.messageQuote.quotedMessage.text = messageUtils - .enrichChatReplyMessageText( - binding.messageQuote.quotedMessage.context, - parentChatMessage, - true, - viewThemeUtils - ) + binding.messageQuote.quotedMessage.text = + messageUtils + .enrichChatReplyMessageText( + binding.messageQuote.quotedMessage.context, + parentChatMessage, + true, + viewThemeUtils + ) binding.messageQuote.quotedMessageAuthor .setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast)) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt index a88a1de93a6..b3d5e3283c1 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt @@ -180,13 +180,14 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : } binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName ?: context.getText(R.string.nc_nick_guest) - binding.messageQuote.quotedMessage.text = messageUtils - .enrichChatReplyMessageText( - binding.messageQuote.quotedMessage.context, - parentChatMessage, - true, - viewThemeUtils - ) + binding.messageQuote.quotedMessage.text = + messageUtils + .enrichChatReplyMessageText( + binding.messageQuote.quotedMessage.context, + parentChatMessage, + true, + viewThemeUtils + ) binding.messageQuote.quotedMessageAuthor .setTextColor(context.resources.getColor(R.color.textColorMaxContrast, null)) @@ -219,18 +220,19 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : binding.webview.settings.javaScriptEnabled = true - binding.webview.webViewClient = object : WebViewClient() { - @Deprecated("Use shouldOverrideUrlLoading(WebView view, WebResourceRequest request)") - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - return if (url != null && UriUtils.hasHttpProtocolPrefixed(url) - ) { - view?.context?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) - true - } else { - false + binding.webview.webViewClient = + object : WebViewClient() { + @Deprecated("Use shouldOverrideUrlLoading(WebView view, WebResourceRequest request)") + override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { + return if (url != null && UriUtils.hasHttpProtocolPrefixed(url) + ) { + view?.context?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) + true + } else { + false + } } } - } val urlStringBuffer = StringBuffer("file:///android_asset/leafletMapMessagePreview.html") urlStringBuffer.append( @@ -246,15 +248,16 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) : binding.webview.loadUrl(urlStringBuffer.toString()) - binding.webview.setOnTouchListener(object : View.OnTouchListener { - override fun onTouch(v: View?, event: MotionEvent?): Boolean { - when (event?.action) { - MotionEvent.ACTION_UP -> openGeoLink() - } + binding.webview.setOnTouchListener( + object : View.OnTouchListener { + override fun onTouch(v: View?, event: MotionEvent?): Boolean { + when (event?.action) { + MotionEvent.ACTION_UP -> openGeoLink() + } - return v?.onTouchEvent(event) ?: true - } - }) + return v?.onTouchEvent(event) ?: true + } + }) } private fun openGeoLink() { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt index 14867564693..c6fe536314a 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt @@ -49,7 +49,6 @@ import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageHolders.IncomingTextMessageViewHolder(incomingView, payload) { - private val binding: ItemCustomIncomingPollMessageBinding = ItemCustomIncomingPollMessageBinding.bind(itemView) @Inject @@ -132,7 +131,9 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : val isOwnerOrModerator = (payload as? MessagePayload)!!.isOwnerOrModerator ?: false binding.bubble.setOnClickListener { - val pollVoteDialog = PollMainDialogFragment.newInstance( + val pollVoteDialog = + PollMainDialogFragment + .newInstance( message.activeUser!!, roomToken, isOwnerOrModerator, @@ -203,7 +204,8 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : } binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName ?: context.getText(R.string.nc_nick_guest) - binding.messageQuote.quotedMessage.text = messageUtils + binding.messageQuote.quotedMessage.text = + messageUtils .enrichChatReplyMessageText( binding.messageQuote.quotedMessage.context, parentChatMessage, diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 5b07dbbd5ba..9aeef171bf9 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -85,20 +85,24 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : var textSize = context.resources!!.getDimension(R.dimen.chat_text_size) - var processedMessageText = messageUtils.enrichChatMessageText( - binding.messageText.context, - message, - true, - viewThemeUtils - ) + var processedMessageText = + messageUtils + .enrichChatMessageText( + binding.messageText.context, + message, + true, + viewThemeUtils + ) - processedMessageText = messageUtils.processMessageParameters( - binding.messageText.context, - viewThemeUtils, - processedMessageText!!, - message, - itemView - ) + processedMessageText = + messageUtils + .processMessageParameters( + binding.messageText.context, + viewThemeUtils, + processedMessageText!!, + message, + itemView + ) val messageParameters = message.messageParameters if ( @@ -197,19 +201,21 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : } ?: run { binding.messageQuote.quotedMessageImage.visibility = View.GONE } - binding.messageQuote.quotedMessageAuthor.text = if (parentChatMessage.actorDisplayName.isNullOrEmpty()) { - context.getText(R.string.nc_nick_guest) - } else { - parentChatMessage.actorDisplayName - } + binding.messageQuote.quotedMessageAuthor.text = + if (parentChatMessage.actorDisplayName.isNullOrEmpty()) { + context.getText(R.string.nc_nick_guest) + } else { + parentChatMessage.actorDisplayName + } - binding.messageQuote.quotedMessage.text = messageUtils - .enrichChatReplyMessageText( - binding.messageQuote.quotedMessage.context, - parentChatMessage, - true, - viewThemeUtils - ) + binding.messageQuote.quotedMessage.text = + messageUtils + .enrichChatReplyMessageText( + binding.messageQuote.quotedMessage.context, + parentChatMessage, + true, + viewThemeUtils + ) if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) { viewThemeUtils.platform.colorViewBackground(binding.messageQuote.quoteColoredView, ColorRole.PRIMARY) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt index f73524cf3cd..18b0a4cf01b 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt @@ -37,12 +37,7 @@ import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers class LinkPreview { - fun showLink( - message: ChatMessage, - ncApi: NcApi, - binding: ReferenceInsideMessageBinding, - context: Context - ) { + fun showLink(message: ChatMessage, ncApi: NcApi, binding: ReferenceInsideMessageBinding, context: Context) { binding.referenceName.text = "" binding.referenceDescription.text = "" binding.referenceLink.text = "" diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt index 7a1f9aa81b9..e8d5cd2d533 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt @@ -47,7 +47,6 @@ import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) : MessageHolders.OutcomingTextMessageViewHolder(outcomingView, payload) { - private val binding: ItemCustomOutcomingLinkPreviewMessageBinding = ItemCustomOutcomingLinkPreviewMessageBinding.bind(itemView) @@ -84,13 +83,15 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) : colorizeMessageBubble(message) var processedMessageText = messageUtils.enrichChatMessageText(binding.messageText.context, message, false, viewThemeUtils) - processedMessageText = messageUtils.processMessageParameters( - binding.messageText.context, - viewThemeUtils, - processedMessageText!!, - message, - itemView - ) + processedMessageText = + messageUtils + .processMessageParameters( + binding.messageText.context, + viewThemeUtils, + processedMessageText!!, + message, + itemView + ) binding.messageText.text = processedMessageText diff --git a/app/src/main/java/com/nextcloud/talk/viewmodels/CallRecordingViewModel.kt b/app/src/main/java/com/nextcloud/talk/viewmodels/CallRecordingViewModel.kt index 3cff3d02b55..fb4fc976983 100644 --- a/app/src/main/java/com/nextcloud/talk/viewmodels/CallRecordingViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/viewmodels/CallRecordingViewModel.kt @@ -34,8 +34,8 @@ import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import javax.inject.Inject -class CallRecordingViewModel @Inject constructor(private val repository: CallRecordingRepository) : ViewModel() { - +class CallRecordingViewModel +@Inject constructor(private val repository: CallRecordingRepository) : ViewModel() { @Inject lateinit var userManager: UserManager @@ -45,11 +45,16 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec open class RecordingStartedState(val hasVideo: Boolean, val showStartedInfo: Boolean) : ViewState object RecordingStoppedState : ViewState + open class RecordingStartingState(val hasVideo: Boolean) : ViewState + object RecordingStoppingState : ViewState + object RecordingConfirmStopState : ViewState + object RecordingErrorState : ViewState + private val _viewState: MutableLiveData = MutableLiveData(RecordingStoppedState) val viewState: LiveData get() = _viewState diff --git a/app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt b/app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt index d66d9059c8d..d1ac09f9f1e 100644 --- a/app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt @@ -40,6 +40,7 @@ class GeoCodingViewModel : ViewModel() { private val okHttpClient: OkHttpClient = OkHttpClient.Builder().build() private var geocodingResults: List
= ArrayList() private var query: String = "" + fun getGeocodingResultsLiveData(): LiveData> { return geocodingResultsLiveData } @@ -57,11 +58,12 @@ class GeoCodingViewModel : ViewModel() { } init { - nominatimClient = TalkJsonNominatimClient( - "https://nominatim.openstreetmap.org/", - okHttpClient, - " android@nextcloud.com" - ) + nominatimClient = + TalkJsonNominatimClient( + "https://nominatim.openstreetmap.org/", + okHttpClient, + " android@nextcloud.com" + ) } fun searchLocation() { diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt b/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt index 7e4c5b4ac81..21d872a6f87 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt +++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt @@ -323,10 +323,12 @@ class WebSocketInstance internal constructor( isConnected = true reconnecting = false val oldResumeId = resumeId - val (_, helloResponseWebSocketMessage1) = LoganSquare.parse( - text, - HelloResponseOverallWebSocketMessage::class.java - ) + val (_, helloResponseWebSocketMessage1) = + LoganSquare + .parse( + text, + HelloResponseOverallWebSocketMessage::class.java + ) if (helloResponseWebSocketMessage1 != null) { resumeId = helloResponseWebSocketMessage1.resumeId sessionId = helloResponseWebSocketMessage1.sessionId @@ -382,9 +384,11 @@ class WebSocketInstance internal constructor( Log.d(TAG, " roomToken: $roomToken") Log.d(TAG, " session: $normalBackendSession") try { - val message = LoganSquare.serialize( - webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, normalBackendSession) - ) + val message = + LoganSquare + .serialize( + webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, normalBackendSession) + ) if (roomToken == "") { Log.d(TAG, "sending 'leave room' via websocket") currentNormalBackendSession = "" @@ -404,9 +408,11 @@ class WebSocketInstance internal constructor( private fun sendCallMessage(ncSignalingMessage: NCSignalingMessage) { try { - val message = LoganSquare.serialize( - webSocketConnectionHelper.getAssembledCallMessageModel(ncSignalingMessage) - ) + val message = + LoganSquare + .serialize( + webSocketConnectionHelper.getAssembledCallMessageModel(ncSignalingMessage) + ) sendMessage(message) } catch (e: IOException) { Log.e(TAG, "Failed to serialize signaling message", e) diff --git a/app/src/main/java/third/parties/fresco/BetterImageSpan.kt b/app/src/main/java/third/parties/fresco/BetterImageSpan.kt index fbd003ed0e7..5c39dad086f 100644 --- a/app/src/main/java/third/parties/fresco/BetterImageSpan.kt +++ b/app/src/main/java/third/parties/fresco/BetterImageSpan.kt @@ -45,94 +45,97 @@ import androidx.annotation.IntDef * DynamicDrawableSpan (ImageSpan's parent) adjusts sizes as if alignment was ALIGN_BASELINE * which can lead to unnecessary whitespace. */ -open class BetterImageSpan @JvmOverloads constructor( - val drawable: Drawable, - @param:BetterImageSpanAlignment private val mAlignment: Int = ALIGN_BASELINE -) : ReplacementSpan() { - @IntDef(*[ALIGN_BASELINE, ALIGN_BOTTOM, ALIGN_CENTER]) - @Retention(AnnotationRetention.SOURCE) - annotation class BetterImageSpanAlignment +open class BetterImageSpan + @JvmOverloads + constructor( + val drawable: Drawable, + @param:BetterImageSpanAlignment private val mAlignment: Int = ALIGN_BASELINE + ) : ReplacementSpan() { + @IntDef(*[ALIGN_BASELINE, ALIGN_BOTTOM, ALIGN_CENTER]) + @Retention(AnnotationRetention.SOURCE) + annotation class BetterImageSpanAlignment - private var mWidth = 0 - private var mHeight = 0 - private var mBounds: Rect? = null - private val mFontMetricsInt = FontMetricsInt() + private var mWidth = 0 + private var mHeight = 0 + private var mBounds: Rect? = null + private val mFontMetricsInt = FontMetricsInt() - init { - updateBounds() - } + init { + updateBounds() + } - /** - * Returns the width of the image span and increases the height if font metrics are available. - */ - override fun getSize( - paint: Paint, - text: CharSequence, - start: Int, - end: Int, - fontMetrics: FontMetricsInt? - ): Int { - updateBounds() - if (fontMetrics == null) { + /** + * Returns the width of the image span and increases the height if font metrics are available. + */ + override fun getSize( + paint: Paint, + text: CharSequence, + start: Int, + end: Int, + fontMetrics: FontMetricsInt? + ): Int { + updateBounds() + if (fontMetrics == null) { + return mWidth + } + val offsetAbove = getOffsetAboveBaseline(fontMetrics) + val offsetBelow = mHeight + offsetAbove + if (offsetAbove < fontMetrics.ascent) { + fontMetrics.ascent = offsetAbove + } + if (offsetAbove < fontMetrics.top) { + fontMetrics.top = offsetAbove + } + if (offsetBelow > fontMetrics.descent) { + fontMetrics.descent = offsetBelow + } + if (offsetBelow > fontMetrics.bottom) { + fontMetrics.bottom = offsetBelow + } return mWidth } - val offsetAbove = getOffsetAboveBaseline(fontMetrics) - val offsetBelow = mHeight + offsetAbove - if (offsetAbove < fontMetrics.ascent) { - fontMetrics.ascent = offsetAbove - } - if (offsetAbove < fontMetrics.top) { - fontMetrics.top = offsetAbove - } - if (offsetBelow > fontMetrics.descent) { - fontMetrics.descent = offsetBelow - } - if (offsetBelow > fontMetrics.bottom) { - fontMetrics.bottom = offsetBelow + + override fun draw( + canvas: Canvas, + text: CharSequence, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint + ) { + paint.getFontMetricsInt(mFontMetricsInt) + val iconTop = y + getOffsetAboveBaseline(mFontMetricsInt) + canvas.translate(x, iconTop.toFloat()) + drawable.draw(canvas) + canvas.translate(-x, -iconTop.toFloat()) } - return mWidth - } - override fun draw( - canvas: Canvas, - text: CharSequence, - start: Int, - end: Int, - x: Float, - top: Int, - y: Int, - bottom: Int, - paint: Paint - ) { - paint.getFontMetricsInt(mFontMetricsInt) - val iconTop = y + getOffsetAboveBaseline(mFontMetricsInt) - canvas.translate(x, iconTop.toFloat()) - drawable.draw(canvas) - canvas.translate(-x, -iconTop.toFloat()) - } + private fun updateBounds() { + mBounds = drawable.bounds + mWidth = mBounds!!.width() + mHeight = mBounds!!.height() + } - private fun updateBounds() { - mBounds = drawable.bounds - mWidth = mBounds!!.width() - mHeight = mBounds!!.height() - } + private fun getOffsetAboveBaseline(fm: FontMetricsInt): Int { + return when (mAlignment) { + ALIGN_BOTTOM -> fm.descent - mHeight + ALIGN_CENTER -> { + val textHeight = fm.descent - fm.ascent + val offset = (textHeight - mHeight) / 2 + fm.ascent + offset + } - private fun getOffsetAboveBaseline(fm: FontMetricsInt): Int { - return when (mAlignment) { - ALIGN_BOTTOM -> fm.descent - mHeight - ALIGN_CENTER -> { - val textHeight = fm.descent - fm.ascent - val offset = (textHeight - mHeight) / 2 - fm.ascent + offset + ALIGN_BASELINE -> -mHeight + else -> -mHeight } - ALIGN_BASELINE -> -mHeight - else -> -mHeight } - } - companion object { - const val ALIGN_BOTTOM = 0 - const val ALIGN_BASELINE = 1 - const val ALIGN_CENTER = 2 + companion object { + const val ALIGN_BOTTOM = 0 + const val ALIGN_BASELINE = 1 + const val ALIGN_CENTER = 2 + } } -}