From 4d8289b2452cbf1d153dccaabb24380f6864a365 Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Tue, 8 Mar 2022 21:22:19 +0100 Subject: [PATCH 01/60] Implement Login via WebView (#2371) * Improve login process with newer APIs * Implement login with WebView instead of browser tab or external browser Oauth process requires us to open login prompt for correct instance and we need to receive the result back. Usually it is done with redirect parameter. Previously we've been using BrowserTabs API and have been falling back to just opening browser. This mostly worked but is very clumsy: - It relies on few system mechanisms for opening URLs in both directions - Browsers do weird things and tend to break quite a bit - There's a good chance that the app can die in the process and we need to recover our state. So instead we are now using WebView. It has disadvantages (users have to trust us to show correct page, logins are not shared w/ browser) but it should be more reliable. * Changes to login after review * Move login classes to their own package * Fix linting issues --- app/src/main/AndroidManifest.xml | 13 +- .../com/keylesspalace/tusky/BaseActivity.java | 1 + .../com/keylesspalace/tusky/LoginActivity.kt | 365 ------------------ .../com/keylesspalace/tusky/MainActivity.kt | 1 + .../com/keylesspalace/tusky/SplashActivity.kt | 1 + .../tusky/components/login/LoginActivity.kt | 295 ++++++++++++++ .../components/login/LoginWebViewActivity.kt | 148 +++++++ .../tusky/di/ActivitiesModule.kt | 6 +- .../tusky/network/MastodonApi.kt | 8 +- app/src/main/res/layout/activity_login.xml | 2 +- app/src/main/res/layout/login_webview.xml | 25 ++ 11 files changed, 483 insertions(+), 382 deletions(-) delete mode 100644 app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt create mode 100644 app/src/main/res/layout/login_webview.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 59e461752..a32259b77 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,19 +35,10 @@ - - - - - - - - + diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java index 8e193c349..d34dd6df8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseActivity.java @@ -38,6 +38,7 @@ import androidx.preference.PreferenceManager; import com.google.android.material.snackbar.Snackbar; import com.keylesspalace.tusky.adapter.AccountSelectionAdapter; +import com.keylesspalace.tusky.components.login.LoginActivity; import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.di.Injectable; diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt deleted file mode 100644 index 08140b63d..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ /dev/null @@ -1,365 +0,0 @@ -/* Copyright 2017 Andrew Dawson - * - * This file is a part of Tusky. - * - * 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. - * - * Tusky 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 Tusky; if not, - * see . */ - -package com.keylesspalace.tusky - -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.net.Uri -import android.os.Bundle -import android.text.method.LinkMovementMethod -import android.util.Log -import android.view.View -import android.widget.TextView -import androidx.appcompat.app.AlertDialog -import androidx.browser.customtabs.CustomTabColorSchemeParams -import androidx.browser.customtabs.CustomTabsIntent -import com.bumptech.glide.Glide -import com.keylesspalace.tusky.databinding.ActivityLoginBinding -import com.keylesspalace.tusky.di.Injectable -import com.keylesspalace.tusky.entity.AccessToken -import com.keylesspalace.tusky.entity.AppCredentials -import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.ThemeUtils -import com.keylesspalace.tusky.util.getNonNullString -import com.keylesspalace.tusky.util.rickRoll -import com.keylesspalace.tusky.util.shouldRickRoll -import com.keylesspalace.tusky.util.viewBinding -import okhttp3.HttpUrl -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response -import javax.inject.Inject - -class LoginActivity : BaseActivity(), Injectable { - - @Inject - lateinit var mastodonApi: MastodonApi - - private val binding by viewBinding(ActivityLoginBinding::inflate) - - private lateinit var preferences: SharedPreferences - - private val oauthRedirectUri: String - get() { - val scheme = getString(R.string.oauth_scheme) - val host = BuildConfig.APPLICATION_ID - return "$scheme://$host/" - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView(binding.root) - - if (savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) { - binding.domainEditText.setText(BuildConfig.CUSTOM_INSTANCE) - binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length) - } - - if (BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) { - Glide.with(binding.loginLogo) - .load(BuildConfig.CUSTOM_LOGO_URL) - .placeholder(null) - .into(binding.loginLogo) - } - - preferences = getSharedPreferences( - getString(R.string.preferences_file_key), Context.MODE_PRIVATE - ) - - binding.loginButton.setOnClickListener { onButtonClick() } - - binding.whatsAnInstanceTextView.setOnClickListener { - val dialog = AlertDialog.Builder(this) - .setMessage(R.string.dialog_whats_an_instance) - .setPositiveButton(R.string.action_close, null) - .show() - val textView = dialog.findViewById(android.R.id.message) - textView?.movementMethod = LinkMovementMethod.getInstance() - } - - if (isAdditionalLogin()) { - setSupportActionBar(binding.toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setDisplayShowTitleEnabled(false) - } else { - binding.toolbar.visibility = View.GONE - } - } - - override fun requiresLogin(): Boolean { - return false - } - - override fun finish() { - super.finish() - if (isAdditionalLogin()) { - overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right) - } - } - - /** - * Obtain the oauth client credentials for this app. This is only necessary the first time the - * app is run on a given server instance. So, after the first authentication, they are - * saved in SharedPreferences and every subsequent run they are simply fetched from there. - */ - private fun onButtonClick() { - - binding.loginButton.isEnabled = false - - val domain = canonicalizeDomain(binding.domainEditText.text.toString()) - - try { - HttpUrl.Builder().host(domain).scheme("https").build() - } catch (e: IllegalArgumentException) { - setLoading(false) - binding.domainTextInputLayout.error = getString(R.string.error_invalid_domain) - return - } - - if (shouldRickRoll(this, domain)) { - rickRoll(this) - return - } - - val callback = object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (!response.isSuccessful) { - binding.loginButton.isEnabled = true - binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration) - setLoading(false) - Log.e(TAG, "App authentication failed. " + response.message()) - return - } - val credentials = response.body() - val clientId = credentials!!.clientId - val clientSecret = credentials.clientSecret - - preferences.edit() - .putString("domain", domain) - .putString("clientId", clientId) - .putString("clientSecret", clientSecret) - .apply() - - redirectUserToAuthorizeAndLogin(domain, clientId) - } - - override fun onFailure(call: Call, t: Throwable) { - binding.loginButton.isEnabled = true - binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration) - setLoading(false) - Log.e(TAG, Log.getStackTraceString(t)) - } - } - - mastodonApi - .authenticateApp( - domain, getString(R.string.app_name), oauthRedirectUri, - OAUTH_SCOPES, getString(R.string.tusky_website) - ) - .enqueue(callback) - setLoading(true) - } - - private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) { - /* To authorize this app and log in it's necessary to redirect to the domain given, - * login there, and the server will redirect back to the app with its response. */ - val endpoint = MastodonApi.ENDPOINT_AUTHORIZE - val parameters = mapOf( - "client_id" to clientId, - "redirect_uri" to oauthRedirectUri, - "response_type" to "code", - "scope" to OAUTH_SCOPES - ) - val url = "https://" + domain + endpoint + "?" + toQueryString(parameters) - val uri = Uri.parse(url) - if (!openInCustomTab(uri, this)) { - val viewIntent = Intent(Intent.ACTION_VIEW, uri) - if (viewIntent.resolveActivity(packageManager) != null) { - startActivity(viewIntent) - } else { - binding.domainEditText.error = getString(R.string.error_no_web_browser_found) - setLoading(false) - } - } - } - - override fun onStart() { - super.onStart() - /* Check if we are resuming during authorization by seeing if the intent contains the - * redirect that was given to the server. If so, its response is here! */ - val uri = intent.data - val redirectUri = oauthRedirectUri - - if (uri != null && uri.toString().startsWith(redirectUri)) { - // This should either have returned an authorization code or an error. - val code = uri.getQueryParameter("code") - val error = uri.getQueryParameter("error") - - /* restore variables from SharedPreferences */ - val domain = preferences.getNonNullString(DOMAIN, "") - val clientId = preferences.getNonNullString(CLIENT_ID, "") - val clientSecret = preferences.getNonNullString(CLIENT_SECRET, "") - - if (code != null && domain.isNotEmpty() && clientId.isNotEmpty() && clientSecret.isNotEmpty()) { - - setLoading(true) - /* Since authorization has succeeded, the final step to log in is to exchange - * the authorization code for an access token. */ - val callback = object : Callback { - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - onLoginSuccess(response.body()!!.accessToken, domain) - } else { - setLoading(false) - binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token) - Log.e(TAG, "%s %s".format(getString(R.string.error_retrieving_oauth_token), response.message())) - } - } - - override fun onFailure(call: Call, t: Throwable) { - setLoading(false) - binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token) - Log.e(TAG, "%s %s".format(getString(R.string.error_retrieving_oauth_token), t.message)) - } - } - - mastodonApi.fetchOAuthToken( - domain, clientId, clientSecret, redirectUri, code, - "authorization_code" - ).enqueue(callback) - } else if (error != null) { - /* Authorization failed. Put the error response where the user can read it and they - * can try again. */ - setLoading(false) - binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied) - Log.e(TAG, "%s %s".format(getString(R.string.error_authorization_denied), error)) - } else { - // This case means a junk response was received somehow. - setLoading(false) - binding.domainTextInputLayout.error = getString(R.string.error_authorization_unknown) - } - } else { - // first show or user cancelled login - setLoading(false) - } - } - - private fun setLoading(loadingState: Boolean) { - if (loadingState) { - binding.loginLoadingLayout.visibility = View.VISIBLE - binding.loginInputLayout.visibility = View.GONE - } else { - binding.loginLoadingLayout.visibility = View.GONE - binding.loginInputLayout.visibility = View.VISIBLE - binding.loginButton.isEnabled = true - } - } - - private fun isAdditionalLogin(): Boolean { - return intent.getBooleanExtra(LOGIN_MODE, false) - } - - private fun onLoginSuccess(accessToken: String, domain: String) { - - setLoading(true) - - accountManager.addAccount(accessToken, domain) - - val intent = Intent(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(intent) - finish() - overridePendingTransition(R.anim.explode, R.anim.explode) - } - - companion object { - private const val TAG = "LoginActivity" // logging tag - private const val OAUTH_SCOPES = "read write follow" - private const val LOGIN_MODE = "LOGIN_MODE" - private const val DOMAIN = "domain" - private const val CLIENT_ID = "clientId" - private const val CLIENT_SECRET = "clientSecret" - - @JvmStatic - fun getIntent(context: Context, mode: Boolean): Intent { - val loginIntent = Intent(context, LoginActivity::class.java) - loginIntent.putExtra(LOGIN_MODE, mode) - return loginIntent - } - - /** Make sure the user-entered text is just a fully-qualified domain name. */ - private fun canonicalizeDomain(domain: String): String { - // Strip any schemes out. - var s = domain.replaceFirst("http://", "") - s = s.replaceFirst("https://", "") - // If a username was included (e.g. username@example.com), just take what's after the '@'. - val at = s.lastIndexOf('@') - if (at != -1) { - s = s.substring(at + 1) - } - return s.trim { it <= ' ' } - } - - /** - * Chain together the key-value pairs into a query string, for either appending to a URL or - * as the content of an HTTP request. - */ - private fun toQueryString(parameters: Map): String { - val s = StringBuilder() - var between = "" - for ((key, value) in parameters) { - s.append(between) - s.append(Uri.encode(key)) - s.append("=") - s.append(Uri.encode(value)) - between = "&" - } - return s.toString() - } - - private fun openInCustomTab(uri: Uri, context: Context): Boolean { - - val toolbarColor = ThemeUtils.getColor(context, R.attr.colorSurface) - val navigationbarColor = ThemeUtils.getColor(context, android.R.attr.navigationBarColor) - val navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor) - - val colorSchemeParams = CustomTabColorSchemeParams.Builder() - .setToolbarColor(toolbarColor) - .setNavigationBarColor(navigationbarColor) - .setNavigationBarDividerColor(navigationbarDividerColor) - .build() - - val customTabsIntent = CustomTabsIntent.Builder() - .setDefaultColorSchemeParams(colorSchemeParams) - .build() - - try { - customTabsIntent.launchUrl(context, uri) - } catch (e: ActivityNotFoundException) { - Log.w(TAG, "Activity was not found for intent $customTabsIntent") - return false - } - - return true - } - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index e7d748dbe..73aedbd95 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -64,6 +64,7 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.canH import com.keylesspalace.tusky.components.conversation.ConversationsRepository import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.components.drafts.DraftsActivity +import com.keylesspalace.tusky.components.login.LoginActivity import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.components.preference.PreferencesActivity import com.keylesspalace.tusky.components.scheduled.ScheduledTootActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt index 1b7e29947..0225147f6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt @@ -18,6 +18,7 @@ package com.keylesspalace.tusky import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import com.keylesspalace.tusky.components.login.LoginActivity import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.di.Injectable diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt new file mode 100644 index 000000000..cc2bd776b --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt @@ -0,0 +1,295 @@ +/* Copyright 2017 Andrew Dawson + * + * This file is a part of Tusky. + * + * 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. + * + * Tusky 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 Tusky; if not, + * see . */ + +package com.keylesspalace.tusky.components.login + +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.os.Bundle +import android.text.method.LinkMovementMethod +import android.util.Log +import android.view.View +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.core.net.toUri +import androidx.lifecycle.lifecycleScope +import com.bumptech.glide.Glide +import com.keylesspalace.tusky.BaseActivity +import com.keylesspalace.tusky.BuildConfig +import com.keylesspalace.tusky.MainActivity +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.databinding.ActivityLoginBinding +import com.keylesspalace.tusky.di.Injectable +import com.keylesspalace.tusky.entity.AppCredentials +import com.keylesspalace.tusky.network.MastodonApi +import com.keylesspalace.tusky.util.getNonNullString +import com.keylesspalace.tusky.util.rickRoll +import com.keylesspalace.tusky.util.shouldRickRoll +import com.keylesspalace.tusky.util.viewBinding +import kotlinx.coroutines.launch +import okhttp3.HttpUrl +import javax.inject.Inject + +/** Main login page, the first thing that users see. Has prompt for instance and login button. */ +class LoginActivity : BaseActivity(), Injectable { + + @Inject + lateinit var mastodonApi: MastodonApi + + private val binding by viewBinding(ActivityLoginBinding::inflate) + + private lateinit var preferences: SharedPreferences + + private val oauthRedirectUri: String + get() { + val scheme = getString(R.string.oauth_scheme) + val host = BuildConfig.APPLICATION_ID + return "$scheme://$host/" + } + + private val doWebViewAuth = registerForActivityResult(OauthLogin()) { result -> + when (result) { + is LoginResult.Ok -> lifecycleScope.launch { + fetchOauthToken(result.code) + } + is LoginResult.Err -> { + // Authorization failed. Put the error response where the user can read it and they + // can try again. + setLoading(false) + binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied) + Log.e( + TAG, + "%s %s".format( + getString(R.string.error_authorization_denied), + result.errorMessage + ) + ) + } + is LoginResult.Cancel -> { + setLoading(false) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(binding.root) + + if (savedInstanceState == null && + BuildConfig.CUSTOM_INSTANCE.isNotBlank() && + !isAdditionalLogin() + ) { + binding.domainEditText.setText(BuildConfig.CUSTOM_INSTANCE) + binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length) + } + + if (BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) { + Glide.with(binding.loginLogo) + .load(BuildConfig.CUSTOM_LOGO_URL) + .placeholder(null) + .into(binding.loginLogo) + } + + preferences = getSharedPreferences( + getString(R.string.preferences_file_key), Context.MODE_PRIVATE + ) + + binding.loginButton.setOnClickListener { onButtonClick() } + + binding.whatsAnInstanceTextView.setOnClickListener { + val dialog = AlertDialog.Builder(this) + .setMessage(R.string.dialog_whats_an_instance) + .setPositiveButton(R.string.action_close, null) + .show() + val textView = dialog.findViewById(android.R.id.message) + textView?.movementMethod = LinkMovementMethod.getInstance() + } + + if (isAdditionalLogin()) { + setSupportActionBar(binding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowTitleEnabled(false) + } else { + binding.toolbar.visibility = View.GONE + } + } + + override fun requiresLogin(): Boolean { + return false + } + + override fun finish() { + super.finish() + if (isAdditionalLogin()) { + overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right) + } + } + + /** + * Obtain the oauth client credentials for this app. This is only necessary the first time the + * app is run on a given server instance. So, after the first authentication, they are + * saved in SharedPreferences and every subsequent run they are simply fetched from there. + */ + private fun onButtonClick() { + binding.loginButton.isEnabled = false + binding.domainTextInputLayout.error = null + + val domain = canonicalizeDomain(binding.domainEditText.text.toString()) + + try { + HttpUrl.Builder().host(domain).scheme("https").build() + } catch (e: IllegalArgumentException) { + setLoading(false) + binding.domainTextInputLayout.error = getString(R.string.error_invalid_domain) + return + } + + if (shouldRickRoll(this, domain)) { + rickRoll(this) + return + } + + setLoading(true) + + lifecycleScope.launch { + val credentials: AppCredentials = try { + mastodonApi.authenticateApp( + domain, getString(R.string.app_name), oauthRedirectUri, + OAUTH_SCOPES, getString(R.string.tusky_website) + ) + } catch (e: Exception) { + binding.loginButton.isEnabled = true + binding.domainTextInputLayout.error = + getString(R.string.error_failed_app_registration) + setLoading(false) + Log.e(TAG, Log.getStackTraceString(e)) + return@launch + } + + // Before we open browser page we save the data. + // Even if we don't open other apps user may go to password manager or somewhere else + // and we will need to pick up the process where we left off. + // Alternatively we could pass it all as part of the intent and receive it back + // but it is a bit of a workaround. + preferences.edit() + .putString(DOMAIN, domain) + .putString(CLIENT_ID, credentials.clientId) + .putString(CLIENT_SECRET, credentials.clientSecret) + .apply() + + redirectUserToAuthorizeAndLogin(domain, credentials.clientId) + } + } + + private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) { + // To authorize this app and log in it's necessary to redirect to the domain given, + // login there, and the server will redirect back to the app with its response. + val url = HttpUrl.Builder() + .scheme("https") + .host(domain) + .addPathSegments(MastodonApi.ENDPOINT_AUTHORIZE) + .addQueryParameter("client_id", clientId) + .addQueryParameter("redirect_uri", oauthRedirectUri) + .addQueryParameter("response_type", "code") + .addQueryParameter("scope", OAUTH_SCOPES) + .build() + doWebViewAuth.launch(LoginData(url.toString().toUri(), oauthRedirectUri.toUri())) + } + + override fun onStart() { + super.onStart() + // first show or user cancelled login + setLoading(false) + } + + private suspend fun fetchOauthToken(code: String) { + /* restore variables from SharedPreferences */ + val domain = preferences.getNonNullString(DOMAIN, "") + val clientId = preferences.getNonNullString(CLIENT_ID, "") + val clientSecret = preferences.getNonNullString(CLIENT_SECRET, "") + + setLoading(true) + + val accessToken = try { + mastodonApi.fetchOAuthToken( + domain, clientId, clientSecret, oauthRedirectUri, code, + "authorization_code" + ) + } catch (e: Exception) { + setLoading(false) + binding.domainTextInputLayout.error = + getString(R.string.error_retrieving_oauth_token) + Log.e( + TAG, + "%s %s".format(getString(R.string.error_retrieving_oauth_token), e.message), + ) + return + } + + accountManager.addAccount(accessToken.accessToken, domain) + + val intent = Intent(this, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) + finish() + overridePendingTransition(R.anim.explode, R.anim.explode) + } + + private fun setLoading(loadingState: Boolean) { + if (loadingState) { + binding.loginLoadingLayout.visibility = View.VISIBLE + binding.loginInputLayout.visibility = View.GONE + } else { + binding.loginLoadingLayout.visibility = View.GONE + binding.loginInputLayout.visibility = View.VISIBLE + binding.loginButton.isEnabled = true + } + } + + private fun isAdditionalLogin(): Boolean { + return intent.getBooleanExtra(LOGIN_MODE, false) + } + + companion object { + private const val TAG = "LoginActivity" // logging tag + private const val OAUTH_SCOPES = "read write follow" + private const val LOGIN_MODE = "LOGIN_MODE" + private const val DOMAIN = "domain" + private const val CLIENT_ID = "clientId" + private const val CLIENT_SECRET = "clientSecret" + + @JvmStatic + fun getIntent(context: Context, mode: Boolean): Intent { + val loginIntent = Intent(context, LoginActivity::class.java) + loginIntent.putExtra(LOGIN_MODE, mode) + return loginIntent + } + + /** Make sure the user-entered text is just a fully-qualified domain name. */ + private fun canonicalizeDomain(domain: String): String { + // Strip any schemes out. + var s = domain.replaceFirst("http://", "") + s = s.replaceFirst("https://", "") + // If a username was included (e.g. username@example.com), just take what's after the '@'. + val at = s.lastIndexOf('@') + if (at != -1) { + s = s.substring(at + 1) + } + return s.trim { it <= ' ' } + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt new file mode 100644 index 000000000..16e07f99d --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginWebViewActivity.kt @@ -0,0 +1,148 @@ +package com.keylesspalace.tusky.components.login + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.net.Uri +import android.os.Bundle +import android.os.Parcelable +import android.util.Log +import android.webkit.CookieManager +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebStorage +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.activity.result.contract.ActivityResultContract +import com.keylesspalace.tusky.BaseActivity +import com.keylesspalace.tusky.BuildConfig +import com.keylesspalace.tusky.databinding.LoginWebviewBinding +import com.keylesspalace.tusky.di.Injectable +import com.keylesspalace.tusky.util.viewBinding +import kotlinx.parcelize.Parcelize + +/** Contract for starting [LoginWebViewActivity]. */ +class OauthLogin : ActivityResultContract() { + override fun createIntent(context: Context, input: LoginData): Intent { + val intent = Intent(context, LoginWebViewActivity::class.java) + intent.putExtra(DATA_EXTRA, input) + return intent + } + + override fun parseResult(resultCode: Int, intent: Intent?): LoginResult { + // Can happen automatically on up or back press + return if (resultCode == Activity.RESULT_CANCELED) { + LoginResult.Cancel + } else { + intent!!.getParcelableExtra(RESULT_EXTRA)!! + } + } + + companion object { + private const val RESULT_EXTRA = "result" + private const val DATA_EXTRA = "data" + + fun parseData(intent: Intent): LoginData { + return intent.getParcelableExtra(DATA_EXTRA)!! + } + + fun makeResultIntent(result: LoginResult): Intent { + val intent = Intent() + intent.putExtra(RESULT_EXTRA, result) + return intent + } + } +} + +@Parcelize +data class LoginData( + val url: Uri, + val oauthRedirectUrl: Uri, +) : Parcelable + +sealed class LoginResult : Parcelable { + @Parcelize + data class Ok(val code: String) : LoginResult() + + @Parcelize + data class Err(val errorMessage: String) : LoginResult() + + @Parcelize + object Cancel : LoginResult() +} + +/** Activity to do Oauth process using WebView. */ +class LoginWebViewActivity : BaseActivity(), Injectable { + private val binding by viewBinding(LoginWebviewBinding::inflate) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val data = OauthLogin.parseData(intent) + + setContentView(binding.root) + + setSupportActionBar(binding.loginToolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowTitleEnabled(false) + + val webView = binding.loginWebView + webView.settings.allowContentAccess = false + webView.settings.allowFileAccess = false + webView.settings.databaseEnabled = false + webView.settings.displayZoomControls = false + webView.settings.javaScriptCanOpenWindowsAutomatically = false + webView.settings.userAgentString += " Tusky/${BuildConfig.VERSION_NAME}" + + val oauthUrl = data.oauthRedirectUrl + + webView.webViewClient = object : WebViewClient() { + override fun onReceivedError( + view: WebView?, + request: WebResourceRequest?, + error: WebResourceError + ) { + Log.d("LoginWeb", "Failed to load ${data.url}: $error") + finish() + } + + override fun shouldOverrideUrlLoading( + view: WebView, + request: WebResourceRequest + ): Boolean { + val url = request.url + return if (url.scheme == oauthUrl.scheme && url.host == oauthUrl.host) { + val error = url.getQueryParameter("error") + if (error != null) { + sendResult(LoginResult.Err(error)) + } else { + val code = url.getQueryParameter("code").orEmpty() + sendResult(LoginResult.Ok(code)) + } + true + } else { + false + } + } + } + webView.setBackgroundColor(Color.TRANSPARENT) + webView.loadUrl(data.url.toString()) + } + + override fun onDestroy() { + // We don't want to keep user session in WebView, we just want our own accessToken + WebStorage.getInstance().deleteAllData() + CookieManager.getInstance().removeAllCookies(null) + super.onDestroy() + } + + override fun requiresLogin(): Boolean { + return false + } + + private fun sendResult(result: LoginResult) { + setResult(Activity.RESULT_OK, OauthLogin.makeResultIntent(result)) + finish() + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index 7d68f75f7..285fe916c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -22,7 +22,6 @@ import com.keylesspalace.tusky.EditProfileActivity import com.keylesspalace.tusky.FiltersActivity import com.keylesspalace.tusky.LicenseActivity import com.keylesspalace.tusky.ListsActivity -import com.keylesspalace.tusky.LoginActivity import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.SplashActivity import com.keylesspalace.tusky.StatusListActivity @@ -34,6 +33,8 @@ import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.drafts.DraftsActivity import com.keylesspalace.tusky.components.instancemute.InstanceListActivity +import com.keylesspalace.tusky.components.login.LoginActivity +import com.keylesspalace.tusky.components.login.LoginWebViewActivity import com.keylesspalace.tusky.components.preference.PreferencesActivity import com.keylesspalace.tusky.components.report.ReportActivity import com.keylesspalace.tusky.components.scheduled.ScheduledTootActivity @@ -84,6 +85,9 @@ abstract class ActivitiesModule { @ContributesAndroidInjector abstract fun contributesLoginActivity(): LoginActivity + @ContributesAndroidInjector + abstract fun contributesLoginWebViewActivity(): LoginWebViewActivity + @ContributesAndroidInjector abstract fun contributesSplashActivity(): SplashActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index 8804124cd..fe5def875 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -440,24 +440,24 @@ interface MastodonApi { @FormUrlEncoded @POST("api/v1/apps") - fun authenticateApp( + suspend fun authenticateApp( @Header(DOMAIN_HEADER) domain: String, @Field("client_name") clientName: String, @Field("redirect_uris") redirectUris: String, @Field("scopes") scopes: String, @Field("website") website: String - ): Call + ): AppCredentials @FormUrlEncoded @POST("oauth/token") - fun fetchOAuthToken( + suspend fun fetchOAuthToken( @Header(DOMAIN_HEADER) domain: String, @Field("client_id") clientId: String, @Field("client_secret") clientSecret: String, @Field("redirect_uri") redirectUri: String, @Field("code") code: String, @Field("grant_type") grantType: String - ): Call + ): AccessToken @FormUrlEncoded @POST("api/v1/lists") diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 70c912428..c8ce50793 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" - tools:context="com.keylesspalace.tusky.LoginActivity"> + tools:context="com.keylesspalace.tusky.components.login.LoginActivity"> + + + + + + + + + + + \ No newline at end of file From 34b7a3c8ee3baf8d86e38e4dfe86eb8ab8d88f66 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 8 Mar 2022 21:39:59 +0100 Subject: [PATCH 02/60] Don't hide potential timeline bugs by catching all exceptions (#2372) * don't hide potential timeline bugs by catching all exceptions * fix NetworkTimelineRemoteMediatorTest * improve ifExpected function * fix code formatting --- .../components/timeline/util/TimelineUtils.kt | 17 +++++++++++++++++ .../viewmodel/CachedTimelineRemoteMediator.kt | 5 ++++- .../viewmodel/CachedTimelineViewModel.kt | 5 ++++- .../viewmodel/NetworkTimelineRemoteMediator.kt | 5 ++++- .../viewmodel/NetworkTimelineViewModel.kt | 7 ++++++- .../timeline/viewmodel/TimelineViewModel.kt | 16 +--------------- .../NetworkTimelineRemoteMediatorTest.kt | 6 +++--- 7 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/keylesspalace/tusky/components/timeline/util/TimelineUtils.kt diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/util/TimelineUtils.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/util/TimelineUtils.kt new file mode 100644 index 000000000..c8d95fd81 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/util/TimelineUtils.kt @@ -0,0 +1,17 @@ +package com.keylesspalace.tusky.components.timeline.util + +import retrofit2.HttpException +import java.io.IOException + +fun Throwable.isExpected() = this is IOException || this is HttpException + +inline fun ifExpected( + t: Throwable, + cb: () -> T +): T { + if (t.isExpected()) { + return cb() + } else { + throw t + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt index e9d81e59c..1cd58f0a5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineRemoteMediator.kt @@ -23,6 +23,7 @@ import androidx.room.withTransaction import com.google.gson.Gson import com.keylesspalace.tusky.components.timeline.Placeholder import com.keylesspalace.tusky.components.timeline.toEntity +import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.TimelineStatusEntity @@ -109,7 +110,9 @@ class CachedTimelineRemoteMediator( } return MediatorResult.Success(endOfPaginationReached = statuses.isEmpty()) } catch (e: Exception) { - return MediatorResult.Error(e) + return ifExpected(e) { + MediatorResult.Error(e) + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt index 26691bf5c..d71da9bb7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/CachedTimelineViewModel.kt @@ -34,6 +34,7 @@ import com.keylesspalace.tusky.appstore.ReblogEvent import com.keylesspalace.tusky.components.timeline.Placeholder import com.keylesspalace.tusky.components.timeline.toEntity import com.keylesspalace.tusky.components.timeline.toViewData +import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.entity.Poll @@ -191,7 +192,9 @@ class CachedTimelineViewModel @Inject constructor( } } } catch (e: Exception) { - loadMoreFailed(placeholderId, e) + ifExpected(e) { + loadMoreFailed(placeholderId, e) + } } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt index 114faa922..7a4c779d8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineRemoteMediator.kt @@ -19,6 +19,7 @@ import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator +import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.util.HttpHeaderLink import com.keylesspalace.tusky.util.dec @@ -107,7 +108,9 @@ class NetworkTimelineRemoteMediator( viewModel.currentSource?.invalidate() return MediatorResult.Success(endOfPaginationReached = statuses.isEmpty()) } catch (e: Exception) { - return MediatorResult.Error(e) + return ifExpected(e) { + MediatorResult.Error(e) + } } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt index a750a3665..bb226292b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/NetworkTimelineViewModel.kt @@ -28,6 +28,7 @@ import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.FavoriteEvent import com.keylesspalace.tusky.appstore.PinEvent import com.keylesspalace.tusky.appstore.ReblogEvent +import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Status @@ -43,6 +44,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.rx3.await import retrofit2.HttpException import retrofit2.Response +import java.io.IOException import javax.inject.Inject /** @@ -170,7 +172,9 @@ class NetworkTimelineViewModel @Inject constructor( currentSource?.invalidate() } catch (e: Exception) { - loadMoreFailed(placeholderId, e) + ifExpected(e) { + loadMoreFailed(placeholderId, e) + } } } } @@ -214,6 +218,7 @@ class NetworkTimelineViewModel @Inject constructor( currentSource?.invalidate() } + @Throws(IOException::class, HttpException::class) suspend fun fetchStatusesForKind( fromId: String?, uptoId: String?, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/TimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/TimelineViewModel.kt index c7c956360..544d08181 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/TimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/viewmodel/TimelineViewModel.kt @@ -33,6 +33,7 @@ import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.appstore.ReblogEvent import com.keylesspalace.tusky.appstore.StatusDeletedEvent import com.keylesspalace.tusky.appstore.UnfollowEvent +import com.keylesspalace.tusky.components.timeline.util.ifExpected import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.Poll @@ -46,8 +47,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import kotlinx.coroutines.rx3.asFlow import kotlinx.coroutines.rx3.await -import retrofit2.HttpException -import java.io.IOException abstract class TimelineViewModel( private val timelineCases: TimelineCases, @@ -291,19 +290,6 @@ abstract class TimelineViewModel( } } - private fun isExpectedRequestException(t: Exception) = t is IOException || t is HttpException - - private inline fun ifExpected( - t: Exception, - cb: () -> Unit - ) { - if (isExpectedRequestException(t)) { - cb() - } else { - throw t - } - } - companion object { private const val TAG = "TimelineVM" internal const val LOAD_AT_ONCE = 30 diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt index 601fc00a4..456171efc 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt @@ -27,7 +27,7 @@ import org.junit.runner.RunWith import org.robolectric.annotation.Config import retrofit2.HttpException import retrofit2.Response -import java.lang.RuntimeException +import java.io.IOException @Config(sdk = [29]) @RunWith(AndroidJUnit4::class) @@ -66,7 +66,7 @@ class NetworkTimelineRemoteMediatorTest { val timelineViewModel: NetworkTimelineViewModel = mock { on { statusData } doReturn mutableListOf() - onBlocking { fetchStatusesForKind(anyOrNull(), anyOrNull(), anyOrNull()) } doThrow RuntimeException() + onBlocking { fetchStatusesForKind(anyOrNull(), anyOrNull(), anyOrNull()) } doThrow IOException() } val remoteMediator = NetworkTimelineRemoteMediator(accountManager, timelineViewModel) @@ -74,7 +74,7 @@ class NetworkTimelineRemoteMediatorTest { val result = runBlocking { remoteMediator.load(LoadType.REFRESH, state()) } assertTrue(result is RemoteMediator.MediatorResult.Error) - assertTrue((result as RemoteMediator.MediatorResult.Error).throwable is RuntimeException) + assertTrue((result as RemoteMediator.MediatorResult.Error).throwable is IOException) } @Test From 221cdb361188cd2e87a2f4a53c4986b26d93d3a3 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Tue, 8 Mar 2022 21:40:10 +0100 Subject: [PATCH 03/60] move "animate custom emojis" preference next to "animate avatars" (#2376) --- .../components/preference/PreferencesFragment.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index e585e78ff..9e41f5017 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -138,6 +138,13 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { isSingleLineTitle = false } + switchPreference { + setDefaultValue(false) + key = PrefKeys.ANIMATE_CUSTOM_EMOJIS + setTitle(R.string.pref_title_animate_custom_emojis) + isSingleLineTitle = false + } + switchPreference { setDefaultValue(true) key = PrefKeys.USE_BLURHASH @@ -179,13 +186,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { setTitle(R.string.pref_title_enable_swipe_for_tabs) isSingleLineTitle = false } - - switchPreference { - setDefaultValue(false) - key = PrefKeys.ANIMATE_CUSTOM_EMOJIS - setTitle(R.string.pref_title_animate_custom_emojis) - isSingleLineTitle = false - } } preferenceCategory(R.string.pref_title_browser_settings) { From 55513e8e2bfe76f6bc1b7f37766a71d6c703ff94 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Wed, 9 Mar 2022 20:50:23 +0100 Subject: [PATCH 04/60] Android 12 support, update AndroidX libraries (#2367) * Android 12 support, update AndroidX libraries * fix ktlint * add Android 12 splash screen support * fix comments in MainActivity * remove deprecated Intent.ACTION_CLOSE_SYSTEM_DIALOGS * delete TimelineViewModelTest * fix notifications on Android 12 * improve splash screen * handle pending intent flags in a dedicated function --- app/build.gradle | 30 +-- app/src/green/res/values/flavor-colors.xml | 6 + app/src/main/AndroidManifest.xml | 50 ++-- .../com/keylesspalace/tusky/MainActivity.kt | 5 + .../com/keylesspalace/tusky/SplashActivity.kt | 49 ---- .../components/compose/ComposeActivity.kt | 86 ++++--- .../components/compose/view/EditTextTyped.kt | 38 ++- .../notifications/NotificationHelper.java | 96 +++++--- .../components/preference/EmojiPreference.kt | 4 +- .../preference/PreferencesFragment.kt | 27 +-- .../components/timeline/TimelineFragment.kt | 12 +- .../tusky/di/ActivitiesModule.kt | 4 - .../receiver/SendStatusBroadcastReceiver.kt | 62 ++--- .../tusky/service/SendTootService.kt | 15 +- app/src/main/res/drawable-hdpi/splash.png | Bin 9353 -> 0 bytes app/src/main/res/drawable-mdpi/splash.png | Bin 5213 -> 0 bytes app/src/main/res/drawable-xhdpi/splash.png | Bin 12320 -> 0 bytes app/src/main/res/drawable-xxhdpi/splash.png | Bin 22663 -> 0 bytes app/src/main/res/drawable-xxxhdpi/splash.png | Bin 29769 -> 0 bytes .../main/res/drawable/background_splash.xml | 11 - app/src/main/res/drawable/ic_splash.xml | 15 ++ app/src/main/res/values-v27/styles.xml | 10 - app/src/main/res/values/colors.xml | 3 +- app/src/main/res/values/styles.xml | 9 +- .../timeline/TimelineViewModelTest.kt | 216 ------------------ 25 files changed, 260 insertions(+), 488 deletions(-) create mode 100644 app/src/green/res/values/flavor-colors.xml delete mode 100644 app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt delete mode 100644 app/src/main/res/drawable-hdpi/splash.png delete mode 100644 app/src/main/res/drawable-mdpi/splash.png delete mode 100644 app/src/main/res/drawable-xhdpi/splash.png delete mode 100644 app/src/main/res/drawable-xxhdpi/splash.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/splash.png delete mode 100644 app/src/main/res/drawable/background_splash.xml create mode 100644 app/src/main/res/drawable/ic_splash.xml delete mode 100644 app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineViewModelTest.kt diff --git a/app/build.gradle b/app/build.gradle index 43a078aae..be574f269 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,11 +15,11 @@ def getGitSha = { } android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { applicationId APP_ID minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 87 versionName "16.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -89,8 +89,8 @@ android { } ext.coroutinesVersion = "1.6.0" -ext.lifecycleVersion = "2.3.1" -ext.roomVersion = '2.3.0' +ext.lifecycleVersion = "2.4.1" +ext.roomVersion = '2.4.2' ext.retrofitVersion = '2.9.0' ext.okhttpVersion = '4.9.3' ext.glideVersion = '4.12.0' @@ -104,31 +104,33 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutinesVersion" - implementation "androidx.core:core-ktx:1.5.0" - implementation "androidx.appcompat:appcompat:1.3.0" - implementation "androidx.fragment:fragment-ktx:1.3.4" - implementation "androidx.browser:browser:1.3.0" + implementation "androidx.core:core-ktx:1.7.0" + implementation "androidx.appcompat:appcompat:1.4.1" + implementation "androidx.fragment:fragment-ktx:1.4.1" + implementation "androidx.browser:browser:1.4.0" implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.exifinterface:exifinterface:1.3.3" implementation "androidx.cardview:cardview:1.0.0" - implementation "androidx.preference:preference-ktx:1.1.1" - implementation "androidx.sharetarget:sharetarget:1.1.0" + implementation "androidx.preference:preference-ktx:1.2.0" + implementation "androidx.sharetarget:sharetarget:1.2.0-rc01" implementation "androidx.emoji:emoji:1.1.0" implementation "androidx.emoji:emoji-appcompat:1.1.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycleVersion" - implementation "androidx.constraintlayout:constraintlayout:2.1.2" - implementation "androidx.paging:paging-runtime-ktx:3.0.0" + implementation "androidx.constraintlayout:constraintlayout:2.1.3" + implementation "androidx.paging:paging-runtime-ktx:3.1.0" implementation "androidx.viewpager2:viewpager2:1.0.0" - implementation "androidx.work:work-runtime:2.5.0" + implementation "androidx.work:work-runtime:2.7.1" implementation "androidx.room:room-ktx:$roomVersion" + implementation "androidx.room:room-paging:$roomVersion" implementation "androidx.room:room-rxjava3:$roomVersion" kapt "androidx.room:room-compiler:$roomVersion" + implementation 'androidx.core:core-splashscreen:1.0.0-beta01' - implementation "com.google.android.material:material:1.4.0" + implementation "com.google.android.material:material:1.5.0" implementation "com.google.code.gson:gson:2.8.9" diff --git a/app/src/green/res/values/flavor-colors.xml b/app/src/green/res/values/flavor-colors.xml new file mode 100644 index 000000000..e1f58f2ea --- /dev/null +++ b/app/src/green/res/values/flavor-colors.xml @@ -0,0 +1,6 @@ + + + + #19A341 + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a32259b77..a5e49b741 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,20 +20,7 @@ android:supportsRtl="true" android:theme="@style/TuskyTheme" android:usesCleartextTraffic="false"> - - - - - - - - - @@ -41,7 +28,15 @@ + android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize" + android:theme="@style/SplashTheme" + android:exported="true"> + + + + + + @@ -88,6 +83,9 @@ + - @@ -115,7 +112,8 @@ android:theme="@style/Base.Theme.AppCompat" /> + android:launchMode="singleTop" + android:exported="false"> @@ -125,7 +123,6 @@ android:resource="@xml/searchable" /> - - + - + + tools:node="merge"> + + + diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 73aedbd95..2e9f6f3de 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -35,6 +35,7 @@ import androidx.appcompat.app.AlertDialog import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat.InitCallback import androidx.lifecycle.Lifecycle @@ -159,8 +160,12 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() super.onCreate(savedInstanceState) + // delete old notification channels + NotificationHelper.deleteLegacyNotificationChannels(this, accountManager) + val activeAccount = accountManager.activeAccount ?: return // will be redirected to LoginActivity by BaseActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt deleted file mode 100644 index 0225147f6..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright 2018 Conny Duck - * - * This file is a part of Tusky. - * - * 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. - * - * Tusky 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 Tusky; if not, - * see . */ - -package com.keylesspalace.tusky - -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import com.keylesspalace.tusky.components.login.LoginActivity -import com.keylesspalace.tusky.components.notifications.NotificationHelper -import com.keylesspalace.tusky.db.AccountManager -import com.keylesspalace.tusky.di.Injectable -import javax.inject.Inject - -class SplashActivity : AppCompatActivity(), Injectable { - - @Inject - lateinit var accountManager: AccountManager - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - /** delete old notification channels */ - NotificationHelper.deleteLegacyNotificationChannels(this, accountManager) - - /** Determine whether the user is currently logged in, and if so go ahead and load the - * timeline. Otherwise, start the activity_login screen. */ - - val intent = if (accountManager.activeAccount != null) { - Intent(this, MainActivity::class.java) - } else { - LoginActivity.getIntent(this, false) - } - startActivity(intent) - finish() - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index caee042fd..a9a9c2d4a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -16,7 +16,9 @@ package com.keylesspalace.tusky.components.compose import android.Manifest +import android.app.NotificationManager import android.app.ProgressDialog +import android.content.ClipData import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -45,8 +47,8 @@ import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider -import androidx.core.view.inputmethod.InputConnectionCompat -import androidx.core.view.inputmethod.InputContentInfoCompat +import androidx.core.view.ContentInfoCompat +import androidx.core.view.OnReceiveContentListener import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.preference.PreferenceManager @@ -105,7 +107,7 @@ class ComposeActivity : ComposeAutoCompleteAdapter.AutocompletionProvider, OnEmojiSelectedListener, Injectable, - InputConnectionCompat.OnCommitContentListener, + OnReceiveContentListener, ComposeScheduleView.OnTimeSetListener { @Inject @@ -149,6 +151,18 @@ class ComposeActivity : public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val notificationId = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1) + if (notificationId != -1) { + // ComposeActivity was opened from a notification, delete the notification + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(notificationId) + } + + val accountId = intent.getLongExtra(ACCOUNT_ID_EXTRA, -1) + if (accountId != -1L) { + accountManager.setActiveAccount(accountId) + } + val preferences = PreferenceManager.getDefaultSharedPreferences(this) val theme = preferences.getString("appTheme", ThemeUtils.APP_THEME_DEFAULT) if (theme == "black") { @@ -282,7 +296,7 @@ class ComposeActivity : } private fun setupComposeField(preferences: SharedPreferences, startingText: String?) { - binding.composeEditField.setOnCommitContentListener(this) + binding.composeEditField.setOnReceiveContentListener(this) binding.composeEditField.setOnKeyListener { _, keyCode, event -> this.onKeyDown(keyCode, event) } @@ -742,26 +756,18 @@ class ComposeActivity : } } - /** This is for the fancy keyboards which can insert images and stuff. */ - override fun onCommitContent(inputContentInfo: InputContentInfoCompat, flags: Int, opts: Bundle?): Boolean { - // Verify the returned content's type is of the correct MIME type - val supported = inputContentInfo.description.hasMimeType("image/*") - - if (supported) { - val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0 - if (lacksPermission) { - try { - inputContentInfo.requestPermission() - } catch (e: Exception) { - Log.e(TAG, "InputContentInfoCompat#requestPermission() failed." + e.message) - return false + /** This is for the fancy keyboards which can insert images and stuff, and drag&drop etc */ + override fun onReceiveContent(view: View, contentInfo: ContentInfoCompat): ContentInfoCompat? { + if (contentInfo.clip.description.hasMimeType("image/*")) { + val split = contentInfo.partition { item: ClipData.Item -> item.uri != null } + split.first?.let { content -> + for (i in 0 until content.clip.itemCount) { + pickMedia(content.clip.getItemAt(i).uri) } } - pickMedia(inputContentInfo.contentUri, inputContentInfo) - return true + return split.second } - - return false + return contentInfo } private fun sendStatus() { @@ -784,12 +790,11 @@ class ComposeActivity : } viewModel.sendStatus(contentText, spoilerText).observe( - this, - { - finishingUploadDialog?.dismiss() - deleteDraftAndFinish() - } - ) + this + ) { + finishingUploadDialog?.dismiss() + deleteDraftAndFinish() + } } else { binding.composeEditField.error = getString(R.string.error_compose_character_limit) enableButtons(true) @@ -859,12 +864,9 @@ class ComposeActivity : viewModel.removeMediaFromQueue(item) } - private fun pickMedia(uri: Uri, contentInfoCompat: InputContentInfoCompat? = null) { + private fun pickMedia(uri: Uri) { withLifecycleContext { viewModel.pickMedia(uri).observe { exceptionOrItem -> - - contentInfoCompat?.releasePermission() - exceptionOrItem.asLeftOrNull()?.let { val errorId = when (it) { is VideoSizeException -> { @@ -1043,12 +1045,32 @@ class ComposeActivity : private const val PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1 internal const val COMPOSE_OPTIONS_EXTRA = "COMPOSE_OPTIONS" + private const val NOTIFICATION_ID_EXTRA = "NOTIFICATION_ID" + private const val ACCOUNT_ID_EXTRA = "ACCOUNT_ID" private const val PHOTO_UPLOAD_URI_KEY = "PHOTO_UPLOAD_URI" + /** + * @param options ComposeOptions to configure the ComposeActivity + * @param notificationId the id of the notification that starts the Activity + * @param accountId the id of the account to compose with, null for the current account + * @return an Intent to start the ComposeActivity + */ @JvmStatic - fun startIntent(context: Context, options: ComposeOptions): Intent { + @JvmOverloads + fun startIntent( + context: Context, + options: ComposeOptions, + notificationId: Int? = null, + accountId: Long? = null + ): Intent { return Intent(context, ComposeActivity::class.java).apply { putExtra(COMPOSE_OPTIONS_EXTRA, options) + if (notificationId != null) { + putExtra(NOTIFICATION_ID_EXTRA, notificationId) + } + if (accountId != null) { + putExtra(ACCOUNT_ID_EXTRA, accountId) + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt index a8403c954..dca696d84 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt @@ -22,6 +22,8 @@ import android.util.AttributeSet import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView +import androidx.core.view.OnReceiveContentListener +import androidx.core.view.ViewCompat import androidx.core.view.inputmethod.EditorInfoCompat import androidx.core.view.inputmethod.InputConnectionCompat import androidx.emoji.widget.EmojiEditTextHelper @@ -32,41 +34,33 @@ class EditTextTyped @JvmOverloads constructor( ) : AppCompatMultiAutoCompleteTextView(context, attributeSet) { - private var onCommitContentListener: InputConnectionCompat.OnCommitContentListener? = null private val emojiEditTextHelper: EmojiEditTextHelper = EmojiEditTextHelper(this) init { // fix a bug with autocomplete and some keyboards val newInputType = inputType and (inputType xor InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) inputType = newInputType - super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener)) + super.setKeyListener(emojiEditTextHelper.getKeyListener(keyListener)) } - override fun setKeyListener(input: KeyListener) { - super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input)) + override fun setKeyListener(input: KeyListener?) { + if (input != null) { + super.setKeyListener(emojiEditTextHelper.getKeyListener(input)) + } else { + super.setKeyListener(input) + } } - fun setOnCommitContentListener(listener: InputConnectionCompat.OnCommitContentListener) { - onCommitContentListener = listener + fun setOnReceiveContentListener(listener: OnReceiveContentListener) { + ViewCompat.setOnReceiveContentListener(this, arrayOf("image/*"), listener) } override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection { val connection = super.onCreateInputConnection(editorInfo) - return if (onCommitContentListener != null) { - EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*")) - getEmojiEditTextHelper().onCreateInputConnection( - InputConnectionCompat.createWrapper( - connection, editorInfo, - onCommitContentListener!! - ), - editorInfo - )!! - } else { - connection - } - } - - private fun getEmojiEditTextHelper(): EmojiEditTextHelper { - return emojiEditTextHelper + EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*")) + return emojiEditTextHelper.onCreateInputConnection( + InputConnectionCompat.createWrapper(this, connection, editorInfo), + editorInfo + )!! } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java index c0bf149f4..6b9afce1d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; import android.os.Build; import android.provider.Settings; import android.text.TextUtils; @@ -46,9 +45,9 @@ import androidx.work.WorkRequest; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.FutureTarget; -import com.keylesspalace.tusky.BuildConfig; import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.R; +import com.keylesspalace.tusky.components.compose.ComposeActivity; import com.keylesspalace.tusky.db.AccountEntity; import com.keylesspalace.tusky.db.AccountManager; import com.keylesspalace.tusky.entity.Notification; @@ -67,6 +66,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -88,8 +88,6 @@ public class NotificationHelper { public static final String REPLY_ACTION = "REPLY_ACTION"; - public static final String COMPOSE_ACTION = "COMPOSE_ACTION"; - public static final String KEY_REPLY = "KEY_REPLY"; public static final String KEY_SENDER_ACCOUNT_ID = "KEY_SENDER_ACCOUNT_ID"; @@ -108,10 +106,6 @@ public class NotificationHelper { public static final String KEY_MENTIONS = "KEY_MENTIONS"; - public static final String KEY_CITED_TEXT = "KEY_CITED_TEXT"; - - public static final String KEY_CITED_AUTHOR_LOCAL = "KEY_CITED_AUTHOR_LOCAL"; - /** * notification channels used on Android O+ **/ @@ -206,21 +200,24 @@ public class NotificationHelper { .setLabel(context.getString(R.string.label_quick_reply)) .build(); - PendingIntent quickReplyPendingIntent = getStatusReplyIntent(REPLY_ACTION, context, body, account); + PendingIntent quickReplyPendingIntent = getStatusReplyIntent(context, body, account); NotificationCompat.Action quickReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp, - context.getString(R.string.action_quick_reply), quickReplyPendingIntent) + context.getString(R.string.action_quick_reply), + quickReplyPendingIntent) .addRemoteInput(replyRemoteInput) .build(); builder.addAction(quickReplyAction); - PendingIntent composePendingIntent = getStatusReplyIntent(COMPOSE_ACTION, context, body, account); + PendingIntent composeIntent = getStatusComposeIntent(context, body, account); NotificationCompat.Action composeAction = new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp, - context.getString(R.string.action_compose_shortcut), composePendingIntent) + context.getString(R.string.action_compose_shortcut), + composeIntent) + .setShowsUserInterface(true) .build(); builder.addAction(composeAction); @@ -237,7 +234,6 @@ public class NotificationHelper { } // Summary - // ======= final NotificationCompat.Builder summaryBuilder = newNotification(context, body, account, true); if (currentNotifications.length() != 1) { @@ -275,7 +271,7 @@ public class NotificationHelper { summaryStackBuilder.addNextIntent(summaryResultIntent); PendingIntent summaryResultPendingIntent = summaryStackBuilder.getPendingIntent((int) (notificationId + account.getId() * 10000), - PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntentFlags(false)); // we have to switch account here Intent eventResultIntent = new Intent(context, MainActivity.class); @@ -285,18 +281,18 @@ public class NotificationHelper { eventStackBuilder.addNextIntent(eventResultIntent); PendingIntent eventResultPendingIntent = eventStackBuilder.getPendingIntent((int) account.getId(), - PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntentFlags(false)); Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class); deleteIntent.putExtra(ACCOUNT_ID, account.getId()); PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, summary ? (int) account.getId() : notificationId, deleteIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntentFlags(false)); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getChannelId(account, body)) .setSmallIcon(R.drawable.ic_notify) .setContentIntent(summary ? summaryResultPendingIntent : eventResultPendingIntent) .setDeleteIntent(deletePendingIntent) - .setColor(BuildConfig.FLAVOR == "green" ? Color.parseColor("#19A341") : ContextCompat.getColor(context, R.color.tusky_blue)) + .setColor(ContextCompat.getColor(context, R.color.notification_color)) .setGroup(account.getAccountId()) .setAutoCancel(true) .setShortcutId(Long.toString(account.getId())) @@ -307,11 +303,9 @@ public class NotificationHelper { return builder; } - private static PendingIntent getStatusReplyIntent(String action, Context context, Notification body, AccountEntity account) { + private static PendingIntent getStatusReplyIntent(Context context, Notification body, AccountEntity account) { Status status = body.getStatus(); - String citedLocalAuthor = status.getAccount().getLocalUsername(); - String citedText = status.getContent().toString(); String inReplyToId = status.getId(); Status actionableStatus = status.getActionableStatus(); Status.Visibility replyVisibility = actionableStatus.getVisibility(); @@ -326,9 +320,7 @@ public class NotificationHelper { mentionedUsernames = new ArrayList<>(new LinkedHashSet<>(mentionedUsernames)); Intent replyIntent = new Intent(context, SendStatusBroadcastReceiver.class) - .setAction(action) - .putExtra(KEY_CITED_AUTHOR_LOCAL, citedLocalAuthor) - .putExtra(KEY_CITED_TEXT, citedText) + .setAction(REPLY_ACTION) .putExtra(KEY_SENDER_ACCOUNT_ID, account.getId()) .putExtra(KEY_SENDER_ACCOUNT_IDENTIFIER, account.getIdentifier()) .putExtra(KEY_SENDER_ACCOUNT_FULL_NAME, account.getFullName()) @@ -341,7 +333,50 @@ public class NotificationHelper { return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, replyIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + pendingIntentFlags(true)); + } + + private static PendingIntent getStatusComposeIntent(Context context, Notification body, AccountEntity account) { + Status status = body.getStatus(); + + String citedLocalAuthor = status.getAccount().getLocalUsername(); + String citedText = status.getContent().toString(); + String inReplyToId = status.getId(); + Status actionableStatus = status.getActionableStatus(); + Status.Visibility replyVisibility = actionableStatus.getVisibility(); + String contentWarning = actionableStatus.getSpoilerText(); + List mentions = actionableStatus.getMentions(); + Set mentionedUsernames = new LinkedHashSet<>(); + mentionedUsernames.add(actionableStatus.getAccount().getUsername()); + for (Status.Mention mention : mentions) { + String mentionedUsername = mention.getUsername(); + if (!mentionedUsername.equals(account.getUsername())) { + mentionedUsernames.add(mention.getUsername()); + } + } + + ComposeActivity.ComposeOptions composeOptions = new ComposeActivity.ComposeOptions(); + composeOptions.setInReplyToId(inReplyToId); + composeOptions.setReplyVisibility(replyVisibility); + composeOptions.setContentWarning(contentWarning); + composeOptions.setReplyingStatusAuthor(citedLocalAuthor); + composeOptions.setReplyingStatusContent(citedText); + composeOptions.setMentionedUsernames(mentionedUsernames); + composeOptions.setModifiedInitialState(true); + + Intent composeIntent = ComposeActivity.startIntent( + context, + composeOptions, + notificationId, + account.getId() + ); + + composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + return PendingIntent.getActivity(context.getApplicationContext(), + notificationId, + composeIntent, + pendingIntentFlags(false)); } public static void createNotificationChannelsForAccount(@NonNull AccountEntity account, @NonNull Context context) { @@ -409,9 +444,7 @@ public class NotificationHelper { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - //noinspection ConstantConditions notificationManager.deleteNotificationChannelGroup(account.getIdentifier()); - } } @@ -421,7 +454,6 @@ public class NotificationHelper { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); // used until Tusky 1.4 - //noinspection ConstantConditions notificationManager.deleteNotificationChannel(CHANNEL_MENTION); notificationManager.deleteNotificationChannel(CHANNEL_FAVOURITE); notificationManager.deleteNotificationChannel(CHANNEL_BOOST); @@ -440,7 +472,6 @@ public class NotificationHelper { // on Android >= O, notifications are enabled, if at least one channel is enabled NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - //noinspection ConstantConditions if (notificationManager.areNotificationsEnabled()) { for (NotificationChannel channel : notificationManager.getNotificationChannels()) { if (channel.getImportance() > NotificationManager.IMPORTANCE_NONE) { @@ -491,7 +522,6 @@ public class NotificationHelper { accountManager.saveAccount(account); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - //noinspection ConstantConditions notificationManager.cancel((int) account.getId()); return true; }) @@ -511,7 +541,6 @@ public class NotificationHelper { // unknown notificationtype return false; } - //noinspection ConstantConditions NotificationChannel channel = notificationManager.getNotificationChannel(channelId); return channel.getImportance() > NotificationManager.IMPORTANCE_NONE; } @@ -674,4 +703,11 @@ public class NotificationHelper { return null; } + public static int pendingIntentFlags(boolean mutable) { + if (mutable) { + return PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_MUTABLE : 0); + } else { + return PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0); + } + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt index e793f17f1..dc0cae4d2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt @@ -13,8 +13,8 @@ import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import androidx.preference.PreferenceManager +import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.SplashActivity import com.keylesspalace.tusky.databinding.DialogEmojicompatBinding import com.keylesspalace.tusky.databinding.ItemEmojiPrefBinding import com.keylesspalace.tusky.util.EmojiCompatFont @@ -215,7 +215,7 @@ class EmojiPreference( .setPositiveButton(R.string.restart) { _, _ -> // Restart the app // From https://stackoverflow.com/a/17166729/5070653 - val launchIntent = Intent(context, SplashActivity::class.java) + val launchIntent = Intent(context, MainActivity::class.java) val mPendingIntent = PendingIntent.getActivity( context, 0x1f973, // This is the codepoint of the party face emoji :D diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index 9e41f5017..9bd88bb56 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -280,23 +280,24 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { } private fun updateHttpProxySummary() { - val sharedPreferences = preferenceManager.sharedPreferences - val httpProxyEnabled = sharedPreferences.getBoolean(PrefKeys.HTTP_PROXY_ENABLED, false) - val httpServer = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_SERVER, "") + preferenceManager.sharedPreferences?.let { sharedPreferences -> + val httpProxyEnabled = sharedPreferences.getBoolean(PrefKeys.HTTP_PROXY_ENABLED, false) + val httpServer = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_SERVER, "") - try { - val httpPort = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_PORT, "-1") - .toInt() + try { + val httpPort = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_PORT, "-1") + .toInt() - if (httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) { - httpProxyPref?.summary = "$httpServer:$httpPort" - return + if (httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) { + httpProxyPref?.summary = "$httpServer:$httpPort" + return + } + } catch (e: NumberFormatException) { + // user has entered wrong port, fall back to empty summary } - } catch (e: NumberFormatException) { - // user has entered wrong port, fall back to empty summary - } - httpProxyPref?.summary = "" + httpProxyPref?.summary = "" + } } companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt index da7535849..95bf4a9d1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt @@ -90,9 +90,9 @@ class TimelineFragment : private val viewModel: TimelineViewModel by lazy { if (kind == TimelineViewModel.Kind.HOME) { - ViewModelProvider(this, viewModelFactory).get(CachedTimelineViewModel::class.java) + ViewModelProvider(this, viewModelFactory)[CachedTimelineViewModel::class.java] } else { - ViewModelProvider(this, viewModelFactory).get(NetworkTimelineViewModel::class.java) + ViewModelProvider(this, viewModelFactory)[NetworkTimelineViewModel::class.java] } } @@ -136,7 +136,7 @@ class TimelineFragment : isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true) - val preferences = PreferenceManager.getDefaultSharedPreferences(activity) + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val statusDisplayOptions = StatusDisplayOptions( animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), mediaPreviewEnabled = accountManager.activeAccount!!.mediaPreviewEnabled, @@ -224,7 +224,7 @@ class TimelineFragment : } if (actionButtonPresent()) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) hideFab = preferences.getBoolean("fabHide", false) scrollListener = object : RecyclerView.OnScrollListener() { override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) { @@ -401,7 +401,7 @@ class TimelineFragment : } private fun onPreferenceChanged(key: String) { - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) when (key) { PrefKeys.FAB_HIDE -> { hideFab = sharedPreferences.getBoolean(PrefKeys.FAB_HIDE, false) @@ -468,7 +468,7 @@ class TimelineFragment : * Auto dispose observable on pause */ private fun startUpdateTimestamp() { - val preferences = PreferenceManager.getDefaultSharedPreferences(activity) + val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val useAbsoluteTime = preferences.getBoolean(PrefKeys.ABSOLUTE_TIME_VIEW, false) if (!useAbsoluteTime) { Observable.interval(1, TimeUnit.MINUTES) diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index 285fe916c..d767a64c5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -23,7 +23,6 @@ import com.keylesspalace.tusky.FiltersActivity import com.keylesspalace.tusky.LicenseActivity import com.keylesspalace.tusky.ListsActivity import com.keylesspalace.tusky.MainActivity -import com.keylesspalace.tusky.SplashActivity import com.keylesspalace.tusky.StatusListActivity import com.keylesspalace.tusky.TabPreferenceActivity import com.keylesspalace.tusky.ViewMediaActivity @@ -88,9 +87,6 @@ abstract class ActivitiesModule { @ContributesAndroidInjector abstract fun contributesLoginWebViewActivity(): LoginWebViewActivity - @ContributesAndroidInjector - abstract fun contributesSplashActivity(): SplashActivity - @ContributesAndroidInjector(modules = [FragmentBuildersModule::class]) abstract fun contributesPreferencesActivity(): PreferencesActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt index fb1d78673..03c0d868b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt +++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt @@ -18,14 +18,14 @@ package com.keylesspalace.tusky.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.graphics.Color import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.RemoteInput import androidx.core.content.ContextCompat +import com.keylesspalace.tusky.BuildConfig import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.components.compose.ComposeActivity -import com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Status @@ -45,22 +45,19 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { AndroidInjection.inject(this, context) - val notificationId = intent.getIntExtra(NotificationHelper.KEY_NOTIFICATION_ID, -1) - val senderId = intent.getLongExtra(NotificationHelper.KEY_SENDER_ACCOUNT_ID, -1) - val senderIdentifier = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER) - val senderFullName = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_FULL_NAME) - val citedStatusId = intent.getStringExtra(NotificationHelper.KEY_CITED_STATUS_ID) - val visibility = intent.getSerializableExtra(NotificationHelper.KEY_VISIBILITY) as Status.Visibility - val spoiler = intent.getStringExtra(NotificationHelper.KEY_SPOILER) ?: "" - val mentions = intent.getStringArrayExtra(NotificationHelper.KEY_MENTIONS) ?: emptyArray() - val citedText = intent.getStringExtra(NotificationHelper.KEY_CITED_TEXT) - val localAuthorId = intent.getStringExtra(NotificationHelper.KEY_CITED_AUTHOR_LOCAL) - - val account = accountManager.getAccountById(senderId) - - val notificationManager = NotificationManagerCompat.from(context) - if (intent.action == NotificationHelper.REPLY_ACTION) { + val notificationId = intent.getIntExtra(NotificationHelper.KEY_NOTIFICATION_ID, -1) + val senderId = intent.getLongExtra(NotificationHelper.KEY_SENDER_ACCOUNT_ID, -1) + val senderIdentifier = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER) + val senderFullName = intent.getStringExtra(NotificationHelper.KEY_SENDER_ACCOUNT_FULL_NAME) + val citedStatusId = intent.getStringExtra(NotificationHelper.KEY_CITED_STATUS_ID) + val visibility = intent.getSerializableExtra(NotificationHelper.KEY_VISIBILITY) as Status.Visibility + val spoiler = intent.getStringExtra(NotificationHelper.KEY_SPOILER) ?: "" + val mentions = intent.getStringArrayExtra(NotificationHelper.KEY_MENTIONS) ?: emptyArray() + + val account = accountManager.getAccountById(senderId) + + val notificationManager = NotificationManagerCompat.from(context) val message = getReplyMessage(intent) @@ -109,9 +106,15 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { context.startService(sendIntent) + val color = if (BuildConfig.FLAVOR == "green") { + Color.parseColor("#19A341") + } else { + ContextCompat.getColor(context, R.color.tusky_blue) + } + val builder = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MENTION + senderIdentifier) .setSmallIcon(R.drawable.ic_notify) - .setColor(ContextCompat.getColor(context, (R.color.tusky_blue))) + .setColor(color) .setGroup(senderFullName) .setDefaults(0) // So it doesn't ring twice, notify only in Target callback @@ -125,29 +128,6 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { notificationManager.notify(notificationId, builder.build()) } - } else if (intent.action == NotificationHelper.COMPOSE_ACTION) { - - context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) - - notificationManager.cancel(notificationId) - - accountManager.setActiveAccount(senderId) - - val composeIntent = ComposeActivity.startIntent( - context, - ComposeOptions( - inReplyToId = citedStatusId, - replyVisibility = visibility, - contentWarning = spoiler, - mentionedUsernames = mentions.toSet(), - replyingStatusAuthor = localAuthorId, - replyingStatusContent = citedText - ) - ) - - composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - context.startActivity(composeIntent) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt index ed69a49d4..3a5dbb576 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt @@ -19,8 +19,8 @@ import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.StatusComposedEvent import com.keylesspalace.tusky.appstore.StatusScheduledEvent import com.keylesspalace.tusky.components.drafts.DraftHelper +import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.db.AccountManager -import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.NewStatus @@ -50,8 +50,6 @@ class SendTootService : Service(), Injectable { @Inject lateinit var eventHub: EventHub @Inject - lateinit var database: AppDatabase - @Inject lateinit var draftHelper: DraftHelper private val supervisorJob = SupervisorJob() @@ -95,7 +93,7 @@ class SendTootService : Service(), Injectable { .setContentText(notificationText) .setProgress(1, 0, true) .setOngoing(true) - .setColor(ContextCompat.getColor(this, R.color.tusky_blue)) + .setColor(ContextCompat.getColor(this, R.color.notification_color)) .addAction(0, getString(android.R.string.cancel), cancelSendingIntent(sendingNotificationId)) if (tootsToSend.size == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -183,7 +181,7 @@ class SendTootService : Service(), Injectable { .setSmallIcon(R.drawable.ic_notify) .setContentTitle(getString(R.string.send_toot_notification_error_title)) .setContentText(getString(R.string.send_toot_notification_saved_content)) - .setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) + .setColor(ContextCompat.getColor(this@SendTootService, R.color.notification_color)) notificationManager.cancel(tootId) notificationManager.notify(errorNotificationId--, builder.build()) @@ -232,7 +230,7 @@ class SendTootService : Service(), Injectable { .setSmallIcon(R.drawable.ic_notify) .setContentTitle(getString(R.string.send_toot_notification_cancel_title)) .setContentText(getString(R.string.send_toot_notification_saved_content)) - .setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) + .setColor(ContextCompat.getColor(this, R.color.notification_color)) notificationManager.notify(tootId, builder.build()) @@ -267,12 +265,9 @@ class SendTootService : Service(), Injectable { } private fun cancelSendingIntent(tootId: Int): PendingIntent { - val intent = Intent(this, SendTootService::class.java) - intent.putExtra(KEY_CANCEL, tootId) - - return PendingIntent.getService(this, tootId, intent, PendingIntent.FLAG_UPDATE_CURRENT) + return PendingIntent.getService(this, tootId, intent, NotificationHelper.pendingIntentFlags(false)) } override fun onDestroy() { diff --git a/app/src/main/res/drawable-hdpi/splash.png b/app/src/main/res/drawable-hdpi/splash.png deleted file mode 100644 index 965c97d30f6f01d72f64e2b7133d7b4e25c923f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9353 zcmbVS^;=Zm*S)~dT_X)2Qo4I63F(sV?iLt2r9(g(DM?YfQ%btK8|e}OCExq~7v6d9 zocZZKckbS2uf5jVCq`ZMEj9)j1^@uq3i2|V;BWf>zGx`m3PW(;0RS36K}Jf;XXVTo z9zbM$A6$CsGGZf*aTKBcCMI^66RdUr!Tv%oo?< zxFIYRf-|&2p8P_* z3@f>aEK`UO6YTfB43TVzt>Aci%H;7<4*OoD$cq_Vi)mVJ>%L~(m5{8x|At5uL#B=1 z{l0W%sW}GZ@;x;wj#m6?*~2D1Y0^_^ z_s?de^1Tp-K?1BEpR!u(wdGK#mIq+TC6LGJV^n>B!_mDI0!K8;%M_W(g+S4@&Lu7I z39&QL)9{=Shz3N%5i&%(-L~2SjiEg{@uQjj0~g72MNC_5gsk+MZ;|3qw1jS?Ezr9G zjFNl$F+F#nFLczHxDD~GC&H`PZM$ySI{dUS6$j}i3xht30P?WdVn3p9`FnfAZSCjB zCFr=gi+>;B78sbWbCr6vU(zoZM)U5rqo{A6f4msJRGCy$yNYP;u?L z@ts3aO-)o+-EI7HR+jz5#6;a_$lAQwD}?SN56Uuf|LL$eD4>kWO)d>&%~273Bhq@^ zD6J)SbaX^VNJtoUcoP)JM<$-K+v1}0?MBPxyrek_hce}er`3Z)+=Go`@I5lJZaz9D zreVdjT?fOF#H0d|teen>*p-!){E?B72`UlKzfsnsCKK<4n<;8sf<@MYm`399iBr@? zB+e?K3=uCbgzG0F8b)-xS3m(WKneIDgx~^)2;BgX3((*JVF)x3pckM)1ZWXxFn}+{ zALHZWe_~_*3XsAZ`|?^5yj08Hv9PfCJ^XxS8jS!11D8m^=D>*$KULcRcS~ZQp$(f9 zfcJYp!!)s3k4?6@dOJuc_ls5ivO|L0?$!RNsR~Mn36KN;Vh9DOfG1K2N5XERT}SoS zfWH@IQS;~$U&3TOU5C{k)R7S+Z2>gokV`p9StLm; zpdV54RizGjv&*{wotEz6XODcdoN=A(7*;g zWY-_f%^w*B1#NvjJ^vs^n%7KzDJ(498cP0ytEpM~GAjGDPW5T)R#f=rwfUSNM+;^& zz~PJ`sKUrTzSH()9>UOVAukX=2C0i~{*=XIUGb96ZYXg7Kz`qDI%UUqo+%1H&#||+ z_u>)2#;&oOQzZ1avdPO9bK44O)=&Q_j#l=r;b3pi>0+_^!qiHVe(3R3+$9F@+JY!x z#wV*~Odkb-D27&21@fvWsS<{ujoD<-eBSwYZf?$4BzIx)DNI&Y*3i;25VJ%tlKvV;xi-r~o{ z$2+hRHt(i(dw*8%4fM4mIX&IuK8)ww_2+kXcGAgjjf{?NE`QVA$zRjMK;_b~Nx{tg|oK)@W<&bIX~(4bEbE3$HJ*NZl^ zQ!=qf2%~1W@*#PLZ9J&iyl>$7{jag*?(2?)Ckt)u`7-}oWj(W6J#~Rp88iV!A_v)+ zHuJc+I7&g+jmNvg>7u9;&o*Grsg`Z@=fJ>*GtYdG|MA>cgV>Ow52Q$OSKoypn6=Ri zyxH&FG18apGJHs~hd5XoLh8O*AvsA$;qX!jr`)zyyN)(*Iy9zN8Z;iqXn)forpX*p zxU5oFVv_8sJ8w$&Pf9ZF@NqwF^E&Z%Y&4)uY&||XiNiwa3r8F9u;tJg;I%^I-t6 z7(^fgE<61S&+kf=(aD6|LeyksBh=r$E9yr|L*mdmo30^_{b~TPadN&iQi-)mu-QIB zq0o|_{W3o!qmg4EaLVR$ISV5lj#HXur{6b=4`s@kT+d#KyFzn>P)JXc@Aa=&PGi$KCMl;wx>c2f!4iO=WU+|&4A!B{J0aE!9BvU&mH@JdoC3L!VMv1oj{{MnO0 zfAp{^b$$J;+ry%doi`W3!MaSl$-A)k^p4zAb;NJg>DD$n{RbJCn11xRf7X#hUbsEP z4_0$`PbS{3+O?3R9dNU|%$jJEB8}VJR4Spd&T1LRi!ZL|V#`n6h z<}$MN%7lW)9PN&Tf`%dix}8lO{OGRn=1s39#o!4kN&}G6=g^qpyxH}XR+b-h%+w#Z za7ya5-sU_+_YwApdzVq%)WjhnB;->;BSevzVNCx)HM?q(pchl}vdYTdd!|?+^lrod zg4d%Ug&rM;TGdeB$jIt#%QL~7{J4;f!ME4U_;o}wS%TUSVA`P(gPD1DlRsx%j-nF=t{T76+aB6~<)e*TNYkDw=$nIlhg*yNFL;;aY7#mULZ>{;y~m0`2n_NQdD z`r;$lY?;~x%yEG(XFqe%>@se0d|W?~%hgSvxvnBfx?DDuDH@w=7%2g5#{BF`KVYVnehfXRwO7E78X$y zVt$@#=H`Xt;Sqs491tF`j)q#_A9O^uRml}y;bLL2Jnnr8-fKzdBu#>!F16CYU;$hX zl3iV0(`=(b{-;Z|WXdqS48_?$k9xR^C>qmFF4w=e$>&Sl)pRmW!|Bq$$7QONXm^6ehGAQ8Z_AP*i-Uy zaJUeKQRYmPEj~bYCJJYd5UB&)R`jyiuo7w2M}~fur=HeG`B7_VXqbS}&iI1jQmZ$@ zq^A0eg2Wgzz@=5Q5rLE$jgCf(&}=iFT?vi*`5~uZXlUrai>?P3Q{~6#cLcccadG4- zU#v_`^X0=9htg70iIXP?sW&YdX?Y_wcKGX3H4M`6Fn55E)jI1FK5A-LtA_i%;W0Hx z&vrjojC`#Qaw_X-%f(J6Ci%@~o0sGA&B@|RL=HuQTv0yEcSK7cSNTUQnPw?Q@mxTqqCsRBN=F!&haD-CtZzAeOkqmVC%W1skG*&L_)!uy1U5d zLn6MYU3OYPr6Oq`WGH9;FCC|Nq3Sy!fJ|~ZQme(=u0|w#@M}CjXxmeSvIYHkq4Fe^ z`CaJ;je)YH?i}vj!aUgILo#5_GY?Hh{Qip5MwcK#BQo?YbuPbbk6)paRvv>DPZBgQ zNAIL@(S+Kbi^Q)0CE#eLB=>`YEJJT&6BRLrk)*lfu(f`) zL%F1(Qg5!!`eLJV*EjEuVcp8u*qGQRCK3Vo!6-LNVfqFN=>uE&fP3H7{16|ECz4cp|Ql|Jb^7;eSC~_Tr8F?0K=__4rAR zBfb#5cLafhmbRCbgM%X`X${m*B_$=+E@blF&);<%8T2XdWw1zj`U{f20=6BqR(uV> z;`#au#ka+iK8;u0IpYzeq@;2X@cYbhiBkyv?Zc36#|3p6Nw9*r zTcj4|=7{$9_w||Q@$g8L9OxvaSbK440r}M?=kW%;@AH3K(ibk0-%78Q7DJD0aUbjNHq3d4=~%^+35fqr$DAT3AE_t zCm48#e#i_&VD+6F%^*8(3_wGcV13)s(V?tCl-m+_c+*wL*>SnUg;<`A;B>YEA58gj zmf%>e>F$0-T)%wU|G_c4tIKt>i${Yf=766BYa8N^(XNzJ5^p{Xy#fDR4qQ^Q%qPs8 z*{CBk5Q#86C8Uba(!U0ZVMiO;n9aR3lEykqi4zq;M6Rf+YJzs?c?dO+1lz4@X)C@4 zQAB(^bzR8k2TlY)C^w8A4bh&Gu%p8>7a_ksvc8rig$05|SqJvOE)$cD;_@0j9UDHn zCG=O8DM-NV#8^cA537$CKP1dqI5>vsjk`BD@vcA}l_<%jRX=%X&nxiLbCrMpJXE4Orw^eV_grYnmiWE zG}(22(EX8jHfCvYF$`tz9&_#Q)$W7fijWL5D{r1Ift|NV?w6kyl{83k^OCHwDK)7M z>JtkVbaZrIUONHh6&27GNfob#PT5^%MS!OB>DEvf%wfM*OGrp}W{Fb9O)s)SQBhGK z$TkGS3ip0ZO1$jkhfEm0-}J=}qmjqP#uj8&9e=l?UDs_XX2tp|U5PABhSxkHlHr=w zoLy61CcRNLZ|sq2d3MG}Ef3~PGFmrO{?OvSTO|EPWN;R1!=`>YgWdlhEJ;w9FAyUP z2n%UOCqH|Wj))se66YDl_~-twPgD{qCjF=3Jv&?Subom+7T-!AZ*oQ9c?|~6*u|e1 z>FKc)l$HNT_t&>DD5V!q`Gf<2ZZ(twasQ@;UJ4I_-2K~bLN8*|RW1g^LG>}*qG>z5 zf(x{sHFIECb|;2vr+JgA5~Sg_@~<#4FyeECuY>)DNH0M2MLV7?n|xg%r9?m>>_JbN z>KvCrCZ@nNPHb?kZr`-DD_L12DNaa4RNN=?=YqaG6;V}Y*$|Whx1$-?lSzyT<|LUC z!5dClX3TeYKU@P+t&gZehqe6Aa$LJwF%GR1)>PeN5L+rSu4(c2ZrYBf0W0`DY@cW!lc^`lMFm)96&s<|U}R&Rwba1H75t@RVbY9UzB(FN9q z;SL&}EB1Qa^u_g@uTM1H%jT$#E@eYtpgfHOVMS zUP*``67*~$!{)8Dv@|nvP0K{CXcW*I)17O>Z6q#+W5>>Tw^X~ks=z5QT6bDw-iHEq zm%fW(rdQ*o^sIQ0viBPuOtt7O$<`dy2OKE;jZ57#nEoGxmrrNKZiU z63}OZ(%C0b4A#z;m0=-2mg+Kiai;z8Doq&W#*m5(Z26}9!rW(5pT+E?UP014dqk8- z^>k~>z`!7&*vQnL;7FIP?wo8Icet< zN=L;PlFsgr`dSkmQy=}Ur6T#J66qi%6|E0B$`zy{Qqo0|tRTj=dM87^R@7ZrB?>`GTuMlOsXDT=TlWe{t#M6^Pk>B@5AEm;H>hry_m@pq{*5q|w zX^86W?d>26QDhEhM!;e(CmG>xSc130FE&z67OQPFeDSf6u=+w+@#^d9K7viRa%ul_ zKL-C4Pexu2C`&!{^n4J%_!Fs^=()jugmW z#8y}V9P3lJY`yXa))}>mgb5b)O=Sgz7%?%irw_A7#fp<%`wWT+)2?yVc!iQ;FF?RWf@V8W)_F>qzoWlLfzPCai8s+s{6}6kC~N|^PuSIspXoTltizPl$-k`R-gor zZ5aD$ju_IAM}P^$ue(HnK2lC!6fX6sJB9X$hn}9^MN-n#BWNt8LDK| zm0ChSnxn({`&B**F*|K`1-gESin?*jZ&^hVsXX*Fs18MXzzw)vJ~+q5#BA3GwVN%^ zya*)@s^sqp0o#ZnI?=FE>~c z2!7n9MlC5Vl4q~e(gY+QFBKg)$j7)ts#w6HBANRLCaC>u1cHP2dKwR(|UC~?-l(STFU&Hwk_*BdYw zPc8WJ-lXcS&Gd3BrWD(u{zR4DLLaCve!O(q^R>=5?;3Xct@HCRGk@h_XFqG3cSs{r z6;Y{wzanztaU0jZ;#i9KaCfnJbh+JN`!avX>MM&m6GboURqjuNZlx3 zcIx^E`tCu0{_cJ(Lu%a?O32->AW-@Wx6S?8-^$ms@>)i~o z1nNZE_V8MRO0Q1;J1RH`mVScmBM9u|%EH!pqDdjLj*f+}Qs`DidHEU{5mDyWF!n1b zG&k^~^GMHl{Ykq!^gM)J1;_9cbX`wz|(?xWkW!|N1$fT^|jlbxYpZTo=^R7*3H z;JK$rZbU@Hrmd9~g>hGa?*J%?#|t`ixzY8oZ0w^c`Rr<5t|ukncT@5bGMgp;(-u%lYhh|0RUI_iN$%7+I~2#R2p?=2}-ITU<|HJ+}JA#>wL zaGELJOFCO^cD}s3yO_b?95Sz8u6Xa^F)Q(RE4-Tgl??;#DXXSk$pDHQ1!PR<^E1eOph!T+%kzWuD^8P+_>y(gO*xBNy>DeIIq4oC z?3G{@Kopx&Zn{WEqHxi)Lys#@tH}OsZJAvtW&9REMnU=GfO%i_^=oS$h)3&S7Y`D8 z|0}+>qhn1+TYJB}vXU4S-;it)9gunrm~pv1KR;(*Ub-1uSru>g#}GcyGu%)^iHW_M zRms`R?8bYWbdbR$gX#2mGc1wYDA}zl#M|XRGXt$Uo65IBgqj8mJ!&rP0OGo^A%4 zt@lP?;%MvWtZ+9`^9K4U9=@dxY@y}jYtZ$Nr=#25d*HQ!HH_Qw8K8%F(8AmS8Ce^( zkO-t#&L7%HKd~G9(K?wdb1b z{PEv4=iuO-Pso99^+IBwOY@?xwzjceSHQoD@f^Wd(xc+@#rwRPpWx`I6l&eF?Ny8! zCcW^Jg>hTZxu}2mb95d3@uS*QFkv|v85s-6+nXQ$N4*{g63C%Ru2a}(@57M+kTNws zX@VbGx}x)02Y#{=C2_`2M;0J2`}O>U+U38Su*fN3qMM{IK!`SKqxinAJPT1*CmOWxm7Q7a8Mx4R9hsN9)_(*7@#```1r884P#r;<~aT=ddh+qGAjtbczn zk}iyCx(cBxJgOc(_Z#tiT8(bP{ls1?NvT-nTzLfdwl9-ycb919#F~Xh68y zTuK5VVF*gW+H;8&RAWoCjwo#0RZYcsZGT?Z)R>HdVot4S+UF%30Bs*OVq#*6&gXk_ zM~Q!X)PlhJ@*DITD<#~Hr`wA5H*XmC{_+C=MCN~I0Yo0IIwI>0#uIycX=lNl>>;&s zbc%*pJ#f>8P0kf)Vz7~^X;hI4b4)4-T)FIzH2aDD-I{x_w6uIniWPZI>0r-lio?AQ zN^d&@gOzRD{J>kbf<=A}d`Ha=yhbF2DiYKx@y<1$&jlURqWD^Q046Gj!R;Gsa=wQ> zp+s`=fEM$Wh9hH87mCn{h&X%)ceH%UpEw|O+3BmO7t8epxu6T|9-JJof}@Z0SGxK!PJJR8o)Y}qs{cpVgQ%X|b6 zY<7kcMg;Ev2=Wj2WCdfKC;x)W#fgzK;*79v(39i(pR*aiP@cGp=o0wBNiKiX0+XT__2Tweh(Yqle zjY_AC$n7x3l)m8`pKOTa8kga5hUqpa(|h&z!y(-y7zNuQZ!sBDSpMsrR#b!`AQu#i z@<~vllqaI3YbJ41`6(!qcWv`URx!!dK5UX^)u=fBil58-5Q<|r2``&LvD3103`&>O z&ml69)``JigR)ISuj!sN8UJQLxJr((Z4VVIGqHxHX_^l9c;IBPA690*J5LP*?;S8j z_Yz6_e=wQ{MeJILTn51q#$CzOe|VZPlbXKeA9$2q;bEy+vH4E0-;(7GNx>+1;W1ku yeo*iELyoLtxSo$eqphVOOZ@*?{&8l!pwd-%RmOgYS%XK600mi9nW{HtVgCmVt=Q)P diff --git a/app/src/main/res/drawable-mdpi/splash.png b/app/src/main/res/drawable-mdpi/splash.png deleted file mode 100644 index 019fc271ff04bdb6064058123ba0ee65c735fbdf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5213 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 diff --git a/app/src/main/res/drawable-xxhdpi/splash.png b/app/src/main/res/drawable-xxhdpi/splash.png deleted file mode 100644 index 0ba150ca11e621b36b3f760bf69c877421c1078e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22663 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 AWdHyG diff --git a/app/src/main/res/drawable-xxxhdpi/splash.png b/app/src/main/res/drawable-xxxhdpi/splash.png deleted file mode 100644 index 0b656a3056ffb3e296c559b94c3ad6eb47a40cf5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29769 zcmeFZWmBA8(*-&VHn>Z08C-)yaQEOA+})kv?he5v9LJXD-iw4}|tkw#XN5KpcPiJ^~~&=l>VpOf4G@b3Bf9YJT- zZy2nj!eA(sEt`xUb3-IMQ>vJfHJ*0;qVgcg;pQpC8T=wVBPchW_T!gD&$EHuX)6re z|Nr{`A_CK-vc*Vn(@ic_Q3u$Vm~d1SBUE+}SFHd0wtxZ`21bkmY+55l09OftK)_g- zyNXHCK|x=B7QcMTHu>;rH2weHW99$MX!Y15kJalW1_gsdM5m*9H1)P#wvQ~!oIr7Ml@11(9r{*41rKHtnSrtMkL#c)FMS7 zib1HbFh|saI$5R<3fU)ll8S3s@X(B?WhF%kmF5tzsB^8S&`rgVu@8ByzS8TYMpIw| zdp!L4B9r4Jg6)Wlk6>gA<52GGtqiP+BFXJy`v?eisH&@pg;s-5+XX}Z`!A2 z`ZM3Q-s%*r%H{Y8_bQ3-9_C|fe~2txG!R|>(J1QGkbdODLuxS+?8rMB8qM$pyW-#r zcsRIV_HN$gq5hZ_of-`vLI##7iFxCiD=KuYGuHq8@oA2oB*FlEjNASNm1O#0XY>9T zofayL)jpeNnWQLSBlz-U%-|vDX6hoIlmfKHAwdG2uXMolM-wdu{ty)q9ZXr2b^`pN zeyZxINbvTyfQb?rbf`aD(iut8jsh0s@sE52bOraJ86AL9n9|vr*8env0QK zT#XEa%0`E^82xV_a~IDGNs&WaLhYNxywgQA$rmc5*t9SbbR4AZrgvM5$w(o`0|2Tq7=mF$ry_m z1{81+B}S_qa}=CRPbC&ST2WyF3@&wzG*vs?fLi+hG7sD2YL;<0Ekm?L_Wf5%ED~`o zYD7IMN=+)RhU2>Uc8 z$6NvRo>&TLDNQhapikcXk7s2=lzW`=o^RX1>H>;(U5H2b!@^tGLQS~kYi=l=gM>@6puF<*;^Go zJ)RZQMqC82m~P5<98?6Me&YsJRa`F$OlL%Juyo(%%9dxB0wZJ9-P_ZNQ)KUpr|LNG z=4bU8o#-UExt!+@i`*pP#mk8`>5%yM0xOWvQHC#8;Z)I{;JFY%DkX~cI;K@2WUe}9 zjU|F*hgQp}?0k6m@P$*)Pjqs~za?XeMcE1A3TDnPA3wJS2lZ{7V=Q|M=5r*Dx|zPc z!k!#XzszL#Tpuh|-oVxoqb&L3CGg40h7Z-^Rc+#qAjsK@-N{C)eHzV%P$@&zaNh!J z$3ypgw9c58@EVtF!wHN&H)`IW34ItDUE_?CR!=dk|2X@{ofZU=OGy)@AmsD>I~GZx z@7#|sj#tb>@3JVPqrGUBD0J|J>rb#34mQb)hX83Z6E`LVj+pqjWK?YPN^vWpej)nO$b9HXm(gyCuywXn{&K0JyBo1l$UwwSZErTx4kV&rLc57 zkww?rD`K6jvU2aDImb5+Lez1D(D&o!97A#`aCGE!sz#g6Elo|f%86u$eFyFWc;eJU zDWV#ohg#K|ultz>AHSADdhK)@^kTvz*ff-*R zTe&<{Lu-5E_&D2RGq7)`H<{W=Am->8`EW$s17^$8D7^byz{?ZU2lu0gnRY(EPZ^=i zm=J{HV|_Y()|Ioo4_*Oo*HeK;szyfSP-nB`_IhWeJ$tHTxBZI0)=>C;`*UleE0s@= z&9un);jbDWo}SP9`q~Q%om+-Y|CqOqSdKTvEhe#QzsyZcOx(|i^sN!Mwzi5H?|wW~ zqL?x)5b&^ldbq@~q@|&`Nw&}(^__kEENtZAG1VUv6LXtN_ja8p728__bNhf34K|UZ zV#c8==JvnW*QW2rCgY+^v8GOvO-4MG=CKFMef*f2$*jvk#mRZ{1Z<^f&bp)&(oGW5la;iExuZ?tNsXM-=6ijspi8o*#8`$4+3$UNJXGS{ z+uu)c<=a7V7a(1>?dt6LNV%-! z$OP>;@&?{s@B0)w-T!!&4`QuMS#uG&5U~-WGA=D~=LG(9xg8vgKwE?V3vN=Cp#V|z zJnY=$&XqX%u)KfY7|E=lKWgLPV4y1}cUkq`Y^1x}Wp_jx4G&T$k96B3e#@kQoy^yL zXRi(wY^sngSO}9Wg>W@3?}^D%R6vX6DR5{NWpg?93ub6&X>T}l%!a>4jBpmBpu@L(xvF+oYI-&nX4a+FR#dr?t#@2! zVj!ZET!bV0R3yu(z+#(p^hFN$A~DF@E48dis9A_9K;;!2*oju~Fs{BMo$XX4arQQk zo2T&D|Md3r^t_w8yu2fcHl{pvT_Ok4cr4EGw?H7+T}%hRvb)`;n+uMr;Gh3W-EWM; zy}fnQ#vwj$42}Ag69y)xwSNOBY=X~sYfc25DwIG{E`yc*Cl&&7(MrXy^K4K*RG;Wa zL+85`N}>gwHKG`U1+T`a|J?uf+Ef@HAMd&G6}pq=ms0)yr)Hzqc{}r7`3t*=NCQJ& z5qGdk>gf1rm1cF_$!hDG1n{gCP?)yemvJ3_cX~|YX`}uFCs!wX}5TE(KxX_wFSRnMlCr+jMR8 zr^6zNNWm|R&?qq~vBFKZ5P2#t1W8l(7kziTOA0h1Did+4P6&5*n9Y}gcP?cCUK(c0 z>m_z8bqBX6yxsHP9CGG(KG}0*&0Q@P@VS^8uGATvZFabo^CHuc#>khRhFWfRd0!S! z?2an*^;`^~bdHSZaX_Ql;*V~9LkG95{RYu^?;LEA9XHxtBuFhk6j>h1g=t#3I6%n-@~>(L+xW!aX4tO%b#Ct7<}PsFqeXeN!XwQ=Xrc@mG49AH4qkE zUs?+FqHMLhy+uopL?`|H-%8J6eJW9j;KkLFGi?bvk;NvbUh+~?&2$<~ms)=Oh!c}8 z=`S6YtgNbH)$4G5Wa8cM-bGFq7F8wVI}VfaWlQW}G5Hzf_A5QS7%E%EBC}Uv#sJRy zPIV?$CE%=0CJT4<+iMcx{%GzcAd*@*v8Dg`gjoKY)8?i4+be-Tyt_xnK!;!p6=q6r zvP-?g+PBj)9*5O+Y+~Y^if{{;j^n&fZH_+Y^9pP)U%rQjKl3VC;>X9PWbnZyH&A03 z4h_UfLgR^rQ}+7h!*lNpy#7Ay_b=CeN{ou<9!=)aad0@@#$(d@NXGNG)1%O!NO%>G zvEHA813mil+Y83Z-QQokML_Y(#@8A?^;|jM=(%qu6Phl7d1%CBkqgsgH=z15IVTQ9 z4t=9`c_3>y7xim-&x@T*`4rS8mNXQ|80u?J{r5ULI{NA58Kd`SuYXq~*Pg0@ftz!G z2)q{>vFC5!pAjm^s~H<24B zQu!pxVl-eoJ04~3x>}-LZQAK3R40fG4zy@o*!Hb2I9_;n^n?bk*@iPES6ss_|kG_#WjKbOt z<2Fo@2-^f-<4~wW1(Do#Jyc2s<=F+u^H$$~mvkFNbcw6Q^?k3$a{%fHWDBpBX9*}I z;xrZ|YiA-3-x5|m`}!-|Hh5Z*$3~73$=oom7yF_McUL3^>XDvqxOrFen@QQ z0R+>lD0IhCEk}W?jtTo7HE}qp_0SK&sAPhCjw5j^8_G&bgTUB&WB&J;8L*LqkJ4Yi&-O z`?E!%OrGZ(rWDJiK>Z0%biA3o;tZ4bu(NzbmCoG-j_L;dixn$>FIb zw^Sr-=}Og2>xj~@@oFU$A6s!+ZF$Te7zn|VE}8q0DjFgt#qWKw)pU2dCWK&}r6cwo zrRB||21Fa(pp{j#&a9W$DprhOV^5=OtiP>Dt0)&HgPCV}0kh?V2?mGars$(0gm#+_ zPh>94tNG2A1!rVrIQ87j2dBRO{jmiHmTodMO%T+tiGSwT@PiiR{+~_YJy4vdes|q7 z-}vUVBP?6!rntJgvOfMPZ*8MwwKo5bQ5BivWXnj-E}T#sQ}R)p`jm zYu8j1Abe~!KcqVF%hd7lF9&tX_vF59M-FUVt#XgOZw?<`v(nRjkWTs6m91`*2@Jho zi;9aSghki>)ZPikOd?A)755@|jPgy#C=|8JfDqtmyF5;wu6q5S@t;2nlYhj1PlPrS z5)wjOrj(-J08&6*p*5)QrPoYDu?Gzdrpc#P*=nKb93=U z?T4)V{AHUz^KVMhgqea%vh=q>)xy?kQti*unv@k2p&B${YUu5Zk_dNFTuV=gOVb>; zd^W1_nXM56<(Ykxgt}_TMG6s?(44k)%`$TUzWT3WWVFO)(J@KA{|5JBOCn zm{L?!GvgKy(WvfSbL1+Q2mWj*!bnd`dmZt(P{7_hur#`x;@#9o`Fu}WF-4x%QHK9a z;bBxXBI#^O#3vadA)6wfixB=d``NJKl0x$%l6YWp1YSe5^ynAV7FQe075S%=lET98 zf_BS4cx`x*!5|Gy{`*bOwe}D9r)!798pQ3FF!PKZ%<^9f?}X(0N(hB=-@=PsKfV~M zsqNHPRQz<_4ks|cBqk=FiIoJhof6;w7+1 zt6V5kT8lI}N@sT@)h}SPdGyT16^_P=uIONP(f-&D=X$(4o%i7c2M5QC0N?Wr2gj`G ziV7=s7<;8!`<$n~hsTV;8ZphDnVSF~lh8sRo08>lV33m%XO-md;1TF_-3?!IfGS10H`}RL)0aVXjEWDB}VwxhhlvU z8BzH!gpjJ8Vt=KvZ+`zSM*|Jo9DgG^wsi%UbASB!Q6%tJlLha4_fK<{=NGwVe03!6 z#?;JI&G4YC%(ju=bQf^d{DkY zxNoiv<^}99$b8AGwPJB%Fg!XX`fKO$?{5yHyYTRjE0;A}pLBJ7+#fFb5??My@metj z)?z%Q@z1~^i>Iif2NNdA!NX?i_G6ooXvFyUJF#pcK>B$HFA?SZTamJ6C)BXDwY?|h zYX~$1a`%eRecOc*#4Z|%yV;x1|WtRpOe?=)MPmWTX+aa5ixmDZo_z5$Xl&Ucx z42ifO&Az^eOZiTToqIW_Ul8dgv2&M6l+5*f-H%8<_4jv|pQERzCo5=dcz8IHfJLw6 zFcd?$LXxDdo#79(5$<6x^`oyrz$4xa6w0?+6`xvGM(YH`ZGx$;{qsCf>$3T<5l`F$ zrjWv{w)8FKD4y@hy<0s_x`UbZwSn&+$|NH1)amgMk&;14yYmYU_KSYBCnQrDJwk z^;)cr`vP9xY}(f5+5d*A>FM=uUrn+RzpXj-`Dh++92ZkS9*7-W{(?Ed+h2v9m9fpG zDXO(N_^aMmlp0{Yl8c!}F4AiP?S^T}Z#l^i}-yGH=iFqCE zAq!)+IY-%_Xuu7>_@Y>32G>Ku59MEyHOMp}y*MZ$mo_US38Y;ITm3I0aH0JaY+yMh zC7V9)%lYS^j~BU84A`wJgrJzJTV-flLS?3xAjVI`Nn+5F{f?2Jl# zqR=F(f^i2v_k1(Z7|6?NVP@3tjb>8Bu5NC$YVB1z6&PgLc)YXnE!k%6qTf|1#u`)h z_RI&%ls;KnSy|x$l`L~o^Ko9GeuLNJnA4BJK(_*kpxuA+=5OLuPMhM7nVCKz+7a_| zn>JAjOg`tWSpw1z-vW^@PE%hy(pgoI8%@P?GS!i$TNF)mCB!1u_swAWu0nb>aAUI5yJ|-rG-p5t)O>&*AoX$*a7q%-w44 zu(7f6^5ywHXR%6?iF)fij_%h*v|V|leeVzB@Mr~g`q0cj$A@)YXH(NN=iZ_H#_aRx zARyJ++ua9KKPqpO?1WIg&#p(>lcm`pR0fRpJ^6dL-FErgV{7V^&tj zh*oV={o)^Y0j*6T;HWPYOU0g-xf{S#Xgh+wZ<2>tZ0a6#rP{BI{#KN|@qXAwBu`9C zoHTYxU0P}#CI9R^uTa=qiGFBIxabXAc@l%@Q&EYZjh0a1G0|c!#b0Q3l zjvhAr+h}5*)YZ)jWo_RA6VDc)gftN%#&|~#rHVSEcqP{*MUij%x7l0U+9KnuES+cJ zjt1&Cnaj1tP)eziZZF1WaM$Ztm^4yhrClqxP~le8)!hZ&{R(N*uX`dggr=mV%)87L z50|t$)3~USn54_Ty}mfI=a_vd09OK(&0)3WUC{QxfY6i%z{yz_PkuQud+?F?G+Xug z6Tn&^K^Wn9&qD2=Eu_zw@RR*BhUb9vc)B~a1F~aMHgz0#uy`R)`7n9Sk@y47oJtiI zf-)L2q(o08Uf|=Eu^iR+v3RK}f$5|A|CH~wMzhi7E5?_)m6JVmQUTPnjh?P_af>k$ zB*eC-Q=1jwG4gbet=Ps-HR~`7NQ!^gVx&!_%ox4TfFh}>t3UtS4xx_s0!a(Tx^K@+ zg?|ly0ufO?ZiT)nE6dB9H&HSZYHW=qn^k|a|1}>}2`Bb?w)g*f0aOgQTPnNS5fLs0 zAQC2$9PJZvOpWUS&kyg?(=}y$-VFfq+WI{}cFE8*AxfWNANAvtsb$J^Ya6rv=?5L` zeRAsIPL=4E1%ntcAx}R3UXj$jr{x9HDhO=OZRPFMb}xI4u^K#rFGae`cQ_V?56_jyIHd9E~hY zzp{ULXoO~e{{$+uvQ+JA)X9%d$c7I(E(TNF>=U6PJZ9U~b#n6XWQ;5CY?_hNEua5t z9t|~lQ1SoAhoEA&f;Z7|R0<7Q^|Zaj)Cei_v_c~lxDm0i!1MEYm-g_`z9&QO{}5mt zh|rO-=dx-yzp#MplTXAfhYh6_NMVm>am~p{OCv*pYp2-`9^5|S^SRpNi5r70ClSQq z#jnqo;%CM2Zm$J_GMIG*1X);mf|jNh7Z>qAX1j0tp^m{y`B%Uvs4gPbo*>iU#AQf2 zhaPjuhe~4QUshC9T+|!&-EjgU@4kf!@WGwJ&wrcq*tMtTWXU){V?gPteTXqYau<+5 z75`AQn!ZqXzS?^2%ucvZP}M45iJ&V{)#c3;8vJ^UJ&@YpEo`(=b-CRH@WZH z`*eKcg)|O(+l{WFjaKym5_CEunteZOZdo)W;XN!Zez!d%;`A_Za?5kcL0?(PkZ0uk z=WAV%wsRVfL@d`WH#9i@9j9&5Ft*vDJb4V*!-M-)^c#)nQg4mkt%QMkYFoyk(9cts z4jek=A5^Ncn8Gm8%Nd+5CYO+Ww_o=zOPbm=dj|)oX&;wEEkWfA9e}#!rHsj#=v)Q6 zWD#y=FNY>nnY|@O6(mZpC!+k!%KO`W;T{h!y^FsJb8WV0aKz>)z|=pk1r_A#>uW1K z14vO|7lAxhu$tY8d}GH4{MQFpJhNTs4Qw%NzRb4#iNMoC)p+w%%BUt z=}e=5$NYx}K1+sBfC0AoT$c?;1^6%G8m8nOwc~mDq}y95Q9)zS!(~Aanx{gQ;=KUS zy}DGVtPJP0?A{2a=HIf^Eb44gejy{avm5rCeSCr042F%YelH7yX+Lc<)Ac)_DF2>9 z4!g9p)DwIa#4k@DY-t2Z2b)am1$jB6rC>N8%@)bMd-twKHy?WFu0>8Libh(O#r z1c~`1P3fGIoP?<0YNDEovj6HV-&@^*6P>vBZl?ma8KXfveKPDH>}lm04=r6jS5TcQ;qGK zMH~hqUI~VY01~}94R)pA{rdI&>gk<(#(X=#MkCiw(M<_?aId~|uOmx3E)*<7LvkoY zeGAcBO)sBcCV+{f)&)z;mvx_mA6foEj=SWicCyNl^pS*~)776k&3SFxXmH?ok+<&Y zucnGH^y?BdG&G3(o}3_%PVI79*LiOjTgWMjREX%-NO6QBlZnPygvVC*ufqs5;*4B? zU&nrZs;IB8_j^9oMS&P-^3{+2e(sm|zi_t$8j}j8HMZJ!n%Jb+khu~We?!e-C$G+5 z_B=)&OIyi70RiaMKOx_`j_dmXVCJ>wQTNYi{l{%UAGxNI=$CNEvHM!X9$#(s)cWW{ z|08z+1`&}yfVgbn?$Bedy75{sxRg-RhMZszc~~@3#V5V!sj1Do??{g08*(=RkXS7RWy9w%4<%Um zF1V{8Z0|EN5@_${#=T_9a%RprE_mg{2Gn8?O~ZN4_TLPFM@i`VeMYo#k40uYSSiqR^;-?_%wpgo5`S=Vw z2@)3+nT z0n8pnKP3wP%2X`brQUz{k!23Nw|3~L!$fiRb-gf05o9I zATtQe(+?ZZ#*UAVOTHi^mo5q@Ke001&ZUU$PKf^@z_tK`3KhSw2PjENEm2N0mO2lF zA@Rf2(d9BTA5%Q_+$#&rY)8#3n~Hk(7|vYnGX)IlMDMmYC_k_5+ivAgFv(&6`?IH! zE8mH|^OAIi*w1*VOk)EXLtJz4xSA`H7@Y(jm3rmh>&K9_Zw_x*3l4b7@kt1?x=yHZ zT#eR$CXi(&xT;Ei*#-28=xAwmhFY=zc5E93a7pP}kW&Pn2d4kt*r$juQwKUM2x+6c zmDwV1sk;@>Ad}H)@3;O?#Ct?A%-KCIFrbQRWVdojL%_3^&r0g7UxZSty1e_ZCnP*6vv6u-%QvE=Ekr89=~d8YCik$txf?7Plpv2T`98^HFyiaT z=;*#RcM=iNF%x?_Tx0z6zw3H=L9*8@9qAH=?WRfASeeW34XzU5K4jJS_{RiE^a9|v zf6mRvP&&HNwA|d387mbY4P!!H4Q}yH*t~EPN1H!AT=%1<>a^(5xyy$D`7p$RT&y+y zd#((xod)nlb-K7s!@8{Z>gN5r+a_l95>fp)NDUW2D~y)Ehl=VnHPatkWcOS3%yu2S zFGtYBs)BilV`EWbrH>1j>trYs)a(5Z^X?-ANL*^YQ2{r_gc+m`7nh2X+Y=gdXb+BN zLTPo<85YTY`5TQx$68lcCp_KkLXz+R438}n;s1d;iTJ~Wvhl-{I5;^4Z1#Jvo?%~G z?N%HYtrg`#AV=O!H!%>%TsH#+S-@*hgM#lpIerd=Mgv?F9Y}G0Bk3HO=h_3gzR z-J{rFxZth4AYe626wLQ^#YGb$chOB#iGi(+KW)XpI0OSZ4jr5F`1j!AWMQ!{T$iDk zLczvX|IyaADtTZZ3hOKH#K*f*X$N#GFDNbTfep7eaJU!^1N}!Fe>_So-6$z3ts_aM zK%Mz^)aVz%^0Y*bap4`{{;7}X`_lsq+!QcDT+UnHy$yX1fXT1sdH3~n04jWt?*&eD zUw$h}#n)sA3SMy;iS;hI6*Eci+iO?75LsfTRSD3e4yN;-FVRG`ACkD@+kVaXKK-$( zZ(BQ!Ud4*4ZUIC=8UE>p164CKvmX3=OD>{ahy%xr(zXU2cFNO2l9z8WV{&=EukV*B zONE0u7qY~ztFdHi!vQ=_Wgi@FY%m_SsZDN4x}-Ds7<4R$T=6WfVxT=`7CVNFm$S*>Mr zhdcUl){$VTMmsS^r6dPubLS;d1zBP_kx5Xavq;g9k~jR?jP!m8HcBu%BV4-VVw5D% z!Taaun~c1Pv(wV{zHwSj_d+gsAQ2Pa#itf+m(#g{=t)Kn7G*s%VnPO9Ul2WQg+x40 zSDQsJA+CKQC3CVl1``%5{B>Z;_`t2@uVN;eKsx}OLc_zOYaQuw7rs3ngw&X<82ln%^5&uS<+={?J0i ze0zJ#?bIh^C)EE%JUTplXhx)->B?6q{vXniIY1_TMK|LUc5Y1?kLWy2!l}{H>_KVq zp@qfLTb1Sc9V3oy$HS^IIEw_iWu6;ZpN&HA8 z>v?t9`u7th^4AKEs1R{l=IH)}?aQ*35EhJk}S+ht;o$y=TUA3!M zP>`LS_J;?leEQ_4Z*eK@>+bq<#zkNfI9>I@_b#wb(9F6)j zvt0ekq-R(B8taNE#nxu8|49I9djF0`DUIT^)@&KWu6b9or5R;M>siO)S$*zwHueTu zMtZvLPk>14P_UhnvECKoqa>62{Y^Fjny+#-3^po)$!2fQ2N)rB)&AT9ll$j~&jDv} zRB|cnMG|X*6d&7$84Ny3@o^xIP}NS7YlREm%$7LRXxY)$E4y_Gc}!;3GfI=_x5H!M zAydCYBG6HQ5-n@`s;=u%jt*_~_z~w#SNV4n4{SS|PiJ%6!EIR>ZNtqCPEY;#@k3d! zvmP*;O=}zZd*Jo+sWNew@_6Bbg34%L-zRNWU(rG7t;pJECT4em$vw zThp|3i_6k3=jQey4tTQhUBn6q~t|1>?g?%T2z6u=;xquK2hJ>~;KmJh%GxZ_iHmZ+HN>9~!b z+}gHe&3-R7=unU_991qRu?@NP!H?ySw~~MZ2Vj_$NE5H{eX2T_ZbnvB_zjP`vh=S0 z+FLMqV&ckxh>+O-e8V3h_~{*4MLEHQZGGD>`=^$cMIJr$m`w$=T3z^~a^EWWaTs>q zdmi9r?NaaCvjG=35s!(9-0XVR(nYu=pG_eo9Y>gK+NhJ_?fKIPH%fGpmPnKxoqqpb zb(ezD=z7ENe9EnZNFUf75Qg8UZJYUsN58ZyerZjmjM>%qc*?1%sbys38K|pk3tCL1 zDM2QT#Pox<9nZpnRn8kHy;+KfZll7BMr{?|5Rh&4CJ;}W=cz*B4 zPsp8nMaQhKez^0R=*)^jA^|H(hKMAm;ne$hpjy7*6Xa#~>s0H6)N9>?dW%2%(RJikNr&HHv%T}2Hz41?KUVt2BK;*)dlo_2QBlqRx{+$+hhZ^=Mo);mi zV~-8(poEea{B~5@{*4JVH^cb2YzpwH1|%jY8u}%nSXQ~`wQ-c}xx1|FTb!2}(4{P= zbNNpMzBA3$X(ivs9lpMt6GRG-T*~R8hjj{Q4`0uS-1o#hBWVX0Y7z0>u9}S|bbRim z#f?kI zA7Ca10$J>i6l4M>Pm%KoSvfg=r-?L%y!T6pR|owvi$E^cPZe_v8LSj*R*6(2%o;75~GVvTd$L zTLqFO60ep{lM1ze?gFw))ZU`s>t$kKjk7>?;-w>V?65tV@V^K<(3`zGRC{^zEns&E zKUQj?hde$$9*x}pX1k3M`TZiFsM5An75nzBFKloSKC+r^r)Y=bXit=J!@}! zAYIAanE!VK@DQ@3{cEgaalehOB{ay!4$A?} zKWAZne$D!KBR98)RI}BLFtYjn-5?7HG>go^SiTlR;RSz%7%Zu8Vy#5ck_BT`6Q|cY zVLSK%RzVf_;`v2M^dJF;5$dZ6nXr7Hi`)b9ZcJKuJp@$6H^ta^Yb2zEMQy_@VN~{+ zTTnXJ#)?s(o2;v=Y3FjDk*=-_;i5ro1u~pRqU=~>8NJw9ovf}S4?=NxZT}NF#|Z}1 z*(%%vF-!4%qaNzK2t;!0uNI|keGz}9%2NF8d7wyNeN)qM5<<1M^-7%s@9%RawxyK6 zK{_gpg@x)L4M!Qm#MRYP0bICT^BwA*)+5>;nf@}a1^16=U0_CtrzYXxyKr*Su$FfX z*cf141HX^yL1c+5<^6@2w&8626}6(OlUfyDuT4AZU;pt1{2S6@;VJ(5_3MFR&Yh`~ zBteGlpVYwD>jHy?g$38vvm3y7H5W8L*tLh|aSI`bzM|8+@h!gyMo z!31o1YG0)AyY^3N1Q}q^#PL$KU9R$B01FWn3rp=3O{RWIm>h{!(&*!10eL}IR*}!e z`l+X1-@{IG{a_Lz$QL9;(qSB*YOI0+Rt;e>X&P6(^Pgt#_U#c%O%qG~)6y)xi2fZr ziq&=2)XJCM3*u>$3fOFG(BZ)AXkmj`=iUI0t%GeLJ)*pi@4lI5`j53n>v?%vSS)Q< zs+YeaY#YRszMvDvT(z9UV52&dAL%{tmqdxS?=ws`{pc}XJ4YTOggquE^=VDV4Zagz ziAE@wXA1Jw#le*-0f7{Ay$`(rMkS>ece1|Ttt?%liue%hjs${s{jPIbIOrCy^~!&1 z^l|1*(uPp{maH%qoWZfG1#dS}N+EM1-RN{CrdWYr62v%KK?KEw9eQ%^Ux2`erl z_e_0Pn?TeZzNz@7F0b=m-yUT*Z7+bKYyT{j)zl#h*B3mJdkX9&_--CJ~f zds^x9v$V9_lm6~KaJf6`1q%3To`zQ1zFYXqiEvmxXVi*`<%?*GOYgCJxxNlFdUd&~ zIr#4)Lc$u@;v)od6$lRaI4?vl#Vu z6V0kQ`@qi*w$giby(`|(S{!!RgDC0cBFvdCpaTNi;tTDz#<)>EXFQ^6_np4GYNMWJ z1k-&&bMz&u+1>4mkv3o6Qube@@iEz%&yV}sPenk=%l903>&*JuOdqQUI8=YZYbnr zM2%fG%vuBjqVMuN)vKPus`TwJ>U$*s4b01gu=i|gssZhE zXdK+_P0VhIldnah#lmKamcJTsnaS%Yxm`>ER}u<>?p8&pN;zl5S`9t;PqLR;B?Gz! z&{IM>ucrf{31^w zJt{glt@NRhO9>VUe(=ZAAk#+*yUo|_V#Hp z_E3lnJ$)%S$dHJ{N+RcKRszETh@(u4b=1vGOB_HPdrCXt)dB)~Yt2T{;q@|EU_cOr z(Eo(!W$=^kZP6<6Au1*nA*%1$Er1`E+*a@g4)t=us@8@pi>2}4_k=gD1;Ra@VU!`x z=GgL;g=O%~>rwP$!82Bcoti3;{T4tWGh$$6-S|43kJ)3$c6;h4Hghnt35?ykDQRf< z`LXkKG&TJ{?+mT=Y3Z8mnj1Gl6Vy-5IkL=q3{k)~SCX(|6)#5bt`!*dKJEd=k~Vx6 z>i_0hv{n(6;N19dzCYa+t*w$;1fQ0~fTa9{R$W{AvFmPvX@OvIWo5;7j=fgTV*h2A z{LT9Q@N&IUoz8DGf%J;HNEieH9&g}>fPd|X>cI^>WmT+)*E8of=@wZ`!Spd;Ztm7} zRD!*Ah)mg+sBW$B@}9Q3>HWp3CKvk}ei*lgq9J)_eI5E46xS^oF1|X}c~RfyDvOQo z#8@V#YIIx9X)`6WkvP(a!^XS2wet-?zskjpMz^-Mt^l067{V9}junha&&Sziat8)lrqH`jjo4>>$RhQeeDHyBP| z_(rq#-}pMc&K=DP#huOH%Uc*39xkBp;=exY(kEGtA0~XDWv!}m&7Unc0n~XM!+^*A zYCzTk2=ClBx=c3~L z1^EgktGF9*At#}Bcicw)x0+v9PL{$%?Ro45O65|YjOj5K7Y(s_Gbm!lu+SW%y}02+ zTC!Og8L!eZGwocJqo8ssDzlXf-9FcKHk-W$U9#MtH8uAyqj5&rz%8Acd<;EY$JBe> zN*`y&QY?B;9Q!uUA1_BexTALQhf4?#_vZpT!)#3<=$i(^u7i;tD~s~tsUq9D*#ESs>=k*6QmxS;(KBh$$QQ+?;W zG5ZnK{A>(ZN{TL^5}1*iv=GE^m#98rgMx-l4|*jw&+ogK809~Q?P|n-UP^1VWMx+K z3CDwdG?@3PY9(3(%ucjP<>ysjuXl1=Sz*?2(BPvRrf`Q;Dd{KlVL$->DEHFT(69z} z&OB+%@h(33I4(O|?{KklW*R>3dh8u0;xvg6EYQ0=H%@o1!>0tLJhE)TS#u(i7DDT= z2Dl&6PJuX&M33)BpWnfP_!d zJ{SFn!u54^tSdVQfFkcLXgEdOQ&d#6y_B^WNvXgr_Hja%mj-8vJ5u_M)+{);R)b$TMzO2cff^*DNR*XAC>9|;9ld_y`3M((8lIZFJS96;g#`-YT6o9qX>L^X8DDWvTXRC zs{Qu{kthn*R=Yw+XTzd^k_p$PiXB|u-8x|A)pztV3)c!=+jidjbL*Rh6wYR>T=j<<~e2^g_HHCs-31(9rGizOu*bIo7th@MU1;+1^5`1@&!Ay z!Ul-~hPLV5ya8eeT$({wz%K<+5-!h7@4pi*uzBLIC7q2x#>mSn|6O8ium;?OfzN9m z+OV(DVf61YTd86Scc#U&META~g$DQRDKM5%pt*aPrS21=6ot5WcrR5;UwDb)w{EYr zDS^33gUv!2Iad{Hzecc5Ezj9k!*$2RlTq>&d@O9l1T;(W4USNxu+B#r2eojTG{9jW zU}UtZQ=e`DFj$ATQ5bc=JjlfeXVFld45=crv5^d^Zm{;eS)g~SAsmxa%zIi7SzQ&F zQv2EJY>}RkK}yDH`9n<^T67>FomDWE5(%8?zn-52&V?@?Ki>k*ofdF1pDZsHc=ps) zJDVC)F9O#1Fz`c(2{4IuD=sQ}4bw^me0r46>Dq>WqK_=8XmG!)@Y}lPz(-fey;xJA zk`S0eHIcY8SPk@a4Gqh5*$wJfLO01lhQ)N45c;*Y^9KOzy_fxY4@HJuQpY^=a9(r3 zadqJNgnTY+ZF{xcpwTcOp!kL0C#qJ6swH|K%$CqNP=V-Y_~4%ixqQ5sy8`$ zQ~cat6Q7Vn!qL31$9@~ z|EIgN{EF(0w>Z+J3?bnUVd$YlN=g{%?(Qxrr3Iv0q)WOxWI$5sl8|nsL>L~b?5n%s9Pl0SYO}6a&sU)hZyz*as0!< z%yajJyHM7qOAlh5L+vRB3u~6thjdeP%S8z-G{2!(p%kl$71TMmDi3{dma0QyHmW z<-`dic{XhaV8ieR#>_llQ)D)dJOey++SDKmGxJLxx6P4Nlso2Zk!@31f!R+qvxZLK zC2d=5tE=mL8F0^pMF9!UH}|!ZLTki}?s*mFM#4~ys|LpVi*HVj?^+uYPZ=ixDA{6+ z)`bF%3;k5NqM@hzKgerrRwYyNCt!plxQAQC4e8QwY4C$#Y}lEH`6 zbw`}mrVulOhHH{8PGlw91!agOVDfwWbDhj9<3w%PD^P&@v&gL4%7J0Q2Zl_ zr{ur?EL3zE?*B#phKP(bI=D+U_^zZHP&I2Z-}|aJY@*6A}c`l1) z6MEOavn#7wn$n&v_5S-4bKz1((5g!zFjq7BvG>Tn2Nt*)v+VJLC_|1PJEOvh<5%ooZ0n50X)g zr}BZh_lJ{IH|P5%|Jqn3z2P4}_ITbb3~#@H5>Q+oFi6mlOu3z)#B(|a`SOeI+UFFD zQOL95$^=52Cl9__tm{}=Suy0gW66RQ5$2}X0I==*XTE&<+aH|_7MuS`%TNy%LKB{m z1AgE;rUgGsXZ!rN$}ewEudL9io*|?}oAB8*Q6=2f%9=}(AR+L-0587X2BMZLJCmAY zWv|ft%UW(=w6&I`svio;b$wgsVzox@o*fccA*tJ1qgs>KC!sn}I)C^M2(gEq!QlbZ zJeXcYKJ8TWIGV#9si`_it9}i9$WR1gCISUo`!B6>q37BeWP`T0-$EP;&nqL)`l?`5W6Aa%s0clYm z|AGYNzca!EJ~#6EmmBS1Wckkwp-RypjA7P!^C^?z@`Hv3yrs*Y_n9GFi}#I7t? z?(hfE`kSJ8Xg+kA%7yg(C!;2dzZ$(k`bat~=kLu1k-RJ%h!=f2;KV>GU3wsFnQ5GQJI%38btoaj!V;8)j(5X-He;jB=7o=hu6 zwC#uZQi2(+1cV6P?qV4HOXh4b%jfnQ)2X7TvpJK6>F(&^(|ED*c z!}Sdw``5ypZAV?hK*#timh(!T61AQ!c%Bjs+;bR{iT-LgSm`l@0!cs#cH zGw1j36U>f&b#_~rD_XNJ))g23CjVt)kT<7IG6s2CcQhv(fh^Flor)OV!UeeCAEqh| zpGPP>JUj~lH~SBa+^XdE6^s;@J+dwv%qW(D&KYS~)hD@1e%8s#QLU~@$(6tx;0`t* z9p7xmsK_W{rGtOkqU4NW#fN~ohxb|MzLR5lRn_AVC_aK;M`M32DU>oy((-AM+rnFg zRE`e17cbhYQuenmuz~;yZb032zBZ?|)GL|@ZXX)i(_M$IxHtJR^Qa=HZ- zDmi;FKR!I%dmdPQNGWGUGiIq-_@2tG-Ohv2{HrC=s~`WvBfSI|PWhB-MtJucSG7UY zT0DU8ZvLDJEe~Q&fWj>I^yJ2KTtZn{xk4&BWeLSA-AVH1g=Sy-LVo4^1(>(0bo~9p z{p;BrTKWgK+>({DuJ?SPD$QCj*&yo+p#;=CV6~|&44OB!xsCgG06d`Qy6=F~n#FZ&bTUuGAgRp=LNePMjE1|3d zUNo}DEa)iT9r-gcpHRB=E+CLhJ-xjY*aNSn=@VV#(fFRYEvzoLoYjKKj@@G4qg4s% zQBhST>U#7`LZFn1I^N6Uk~5#e@5vA&oKN7X8vf>{#g8<#L*Xnf89Hw7gv3OHkb1jjvY z5yYna8UA%Cln{6r{w%l7Vq^+9r}LO~h45qFg%;GREot)5u6@1^*xA`BzB=8$dqGD> zCqVQ@+h|`zp76$$@FCaLmAP`b`3rWjg){h+Rsh701t}MX&m(oQQN%qwv{SVz{NIyz zx~%nyTlPdsYkXC+mZkd2!LH50Me&E2V9JoLbXUz>ocqe@L%qXF_&8KTpX~xS9@-1H0d*c9nkb?d@sb1wdLCCCyK~ zM5xBfwO-xZ0Fvj`ody$>CwF)vFb78KKh zw6Vc$Z6c==MXO>n#UtL!_b{cxm+}h3=U{vL>`o@~9*Tl~Z%KB4jm>Kz97MAxSIV!| z)li0d=_^N)@;LmY+Y1tZD^VF(QmR?YBEXwQ)eZ*5A+4AZ;RRM?gT7w;@SfD)B&&@uekpu;z!s6mW zW7ppA4*ZUnS0=#kK%SVB<17^+Fo=Fkuz63xYpZ$!jD6Y8Ko`3Jc{C{ETFIv$Ru2hi zSqQ`pq3ayirKJ8sLCI8uLr=O4WvZSnFdM~`{NQM@=00&xxUma;vTcFWgo%9z3T zO?(bBn(Eatc7?B??@31*gyPmzF;B1I~N{1Nf^oGEE^YfT$|>C?iz}?7sp+@^sz(+sb0WEmrY9seI=r`71jT2X#^RP8&D-O@ z`idmgm*svR|4!?y;(z;{QQ@p4>q4KoAU+z|f5&&r1MiuOE#3tUT>FK#!g5%!#E>B!_NvYJS zRMoOmA zi@MoU0y8lvPBK?j3!an@Pi<^4?@jB7^$RwWj`fwGAmuv?bMw-nMDPSVHDR%lzGy%P zl@Z2S!^X{ku#F49WgIb!9^S)$Qjk4Irq*-V%^u67ZmILiUSxuv$Dk zJw1J(pdAgoR{cWv)#DNCec)?S!Nk-C(`Qal9=1S3(7_IZP0@-lkaJ^XpZrr4f(b;p z>`s7xSe`I4h{5{^N~pgRt?;bWA}&QlwVzpoXlJG>({H8iN<+VC6nQc$N>zB|)dp66 z5+#!o3PEGr1lqu0n3SCS9KfMBA5)Dp8bJ`o@?*&T`{);a@?cSV4D7@0g)(smqCj3< z6r?=<_@_lB=WtSGTZQ6<^%M*Cb2eZ?g#KaxnyAm3G_=r(jfw@mcMA%v*1sD;fcnN=t+1nWH2uOgbVKA+8_=g@i0D=-X%|f0^2;WD$LTIk5Im%ku;IImsY4 zpk#DLNmiEY-NDklnC-ZhH<|HVlK~uVZV!jUeSkBg#keLh|9B-TTHyj=H|Y}t&c0xu z87=Okk00m?#S1@2&IahhgXq%xy-sh?!0`LslBa9_gDDp_ z(6uM$=H^Oa5;?1f&I$B#{Nyp*z;R<)muB%8Gni!m0)WSk%PoHXW7*3k#9u2x!|ozV zU}HF|)ob8&i^_OPK|qOgcpi$08Z?cv8Udt>FqgU^ z80pk0D)#iu){qDdUtFgH)ut}+i(cZpZ-mOZ?ejTyc6P{Edl88RedfkZT zu8<%ex$kL((g${$jvKI%zIAl0oCb?ay(;b2Uh~GDp0t?CrGPdyo$Yd;%eCNu-6n@3m1SCaXDMH+#@}80hiuaaPyXmNsCI@ln7l&A}7&IvQ62TTgH70o3g^ zL*0zlnkj60qB_XLaywBFD-r2%l!?2KDcKs}{r@7LFuJj}!^unWI#jsn8Xs!fjH@!x-V71}t) zsGO0GBCx!3uw`iFEv!J29%LNQmET%D7klq@`aKYhvtF0YHJ(Zq#0I5hXRoZK7)X1p zG}s}>`s2v>9xc!)tA|h(N|S$xy^-Jlv+7cwECt7`zS*5RY+Cj(H~`L!fR?x0K?>}o zUw%7jW+`JCAI@(K*$)Ea@6s!mUms>O^mx}{SW8`zOG`Su=v@k-rG8X96tlIShsK@& zMKF5#vRO1%`gIRoG=Tn|gwt&RFzX;IEzRrQ)yb9>A>)%=-DC$>Ml9vq*9xe1{M8`n z<{g;pmj9>~NUz4n$EPq2yYXM#GqoTi$!Te*GE+Hnf1Y zDu^Bb!%C#X#YhJh71`7K?Bx2(nAqYK@Z_vyXJt)G)Xd8C3OazwL>~x^$IfeL*b`6X z*p90zg&m{n4RP52)`E=VRfK8K%5p2qb>md?r%#WLiNCZ`bay8wCN6l`Z(4rg=2q`3 zl~_oCj@r4=ud)ZS(sYddHev;yJTk^d|KR_Eh82}YZ_qZqML_{dBL~hfs`HB#--ng9 zw$Iu8nIlwhq$}%wJ_f==?s1;)wg_sVxXb>+*`cwU+{48q_VYk>*Ho*c&t7IQHANSa zr$I;`b3J}x`p&;dPrKV7jGR$LC8d{~3z!D(?n=D?G3aOol|!twy!=Fs<#7U3Xp{m? zpBXYEJf>!pBWuCS!J+&r;NB+SA5)gQxw%X?tfaL;lyVmY!y2oo}Y6 zr^TSdjrq$Lfy5&H8M8uRiInbc5ANoZE&Wqbg4xFxprE!%Tl}bYT5kCZK1ZYNGe_&U zijLaa+G_wHYpboTRagf5H+Pb`zUN-lk;Cf&Yt}+_EVdE<5y!6tJiI~!dxfI#)_2Tp zKfs$U^)^>Op>PtH1NSI=7J0^Ia^JlNR@ny@e}be%y;vx5esSEBP@4bBgQNU0!fe?3 zqT;*XPaPn11fDnl0qZY9`L0KNz&c1Jtle-1keQB`&MW=B5eFL zrA6c1b}YSGQQhJrsjY2|RZwSzK`N5HinhyMrLC>d1Ey<%MyD)Kf=~NA+j_k5kvf4) zfu9uZpXmU@7WVPy1`w(86YwHb4*dO+`%V=gxv+erKhD|zSD@UN6i$XEW_J^MeAfq(GnCMBBGpt z2z701D1Vt6`2(m0o%z^w^jp)bJ;Blu?K<>RB@e|dR1^`&=Y!);oCP5THh>XMU!z>i>p5WN}ilkZ59y@Z_BDNq{so`(wd*|_77j-SIXesIR#0T{4VNLl>{m4>C53#(AQ!3 za^9Qq{b9g{LjoYZ2L4Qo(Q?CC&|qBDTfJhV6#Cl|8sGbY*mZMYOTeh#l+?H=S3VX>m(T!=V5QXzG!wC>l-5hGj30bQa4@ScvU zC@VX?X<8&2?aOtuuqg1IE~oz1{5zu)uM2Sl(jAvafZErYTxVW@O(;TD;|pft!^f0@ zrW~dHA|$_vFHFnCc$pwXM0q0s#H1^yulGKkVOneyb39*6$-!?_)alnjfwGTy(+ z3HW#W&f-~!<@nbU@`Kh8=U#i zAlgjvB178A;{OC*Fm^eJdL2!-gI0O;%i;zPxU7~D>saATE$O$QFtFqZICtLs^MdOq z_4M|W%g_=SE(uvGrT;#H!uj9-_SL** z-9vQ&i%SgPf}33iS!8-aETbWd`*ut1zGL}722vt;O~uaeiB1!AVCIIxvG z8wc}vIRT}hYLh#126F~|0@R>6a6o+pDL*A!+hh%Z%a@OipGtH1l3I7Jc4;BmGlX!YzZSKc1hi5%#5xZ9ar)=umVY#uVvg9|RZPr}uD9+NPF&<6 z0IawGuTP-(_l=7iKhPj=C?VctqM#7({repO7dP$x-&T>QSE2!Jnm`#?0_hYmxr7y9 zUI&qCIN~@kTo?eBr4uYEP)LF$s4ck273Ol}rL}nP1GHWmK{i#VFqeF^(aJ z&hkvv;)4Ka1qncB@(Iq6%`GGBe*NuyR7^1Hv}2=nV&|Q6%SKODOiyc&6uuztlm}NG z)-6%6oaIK0gq(rC06)fTdL1{zpz43NjFWZxOir%-ij;82*%-9qwseZ71ZiMEc7{ykVg z++1<`m9;ctzwKmQUKZUyZ2NM!s$Hpjyea^@XOruW;nl?&VOOBmM6Ih&E`t5Gao>uU z?HeuvEs79$7K4%H6(vg>Zq0@;Bk1B^fW>y)od-Bw-CsOZGb-U-yL)86I^RsJ$QA~i(iT25++!EgKHBVZol6p7%gUN1BApnPUu#6F zB}p4_^0ltcb^f{M`+d$6S3~qo<>B&zf-mkP8PktJ@=`m!Di_b2irE7KDv`f)pI^T1 zpuoPw+r_SurT19_p-jGq-yS+EX_R{-FE= z_c$Oun`na5=997WB#J8*L8R2ChKA&AEH(lWpkuu7YR~3L;)fC|O*V{)318gg-2XK( zUTE`v+X{cN$fDpWu0|r2C2)A~J%#<-WoHs|;yoL6$t$@c_3I*q8uRpPXGpL{w zfL#_aM83lbK9gmQQs9wQpyomEniyJ7#_r{-)H5*Hp{j_=CuzUG4p=(6RWGt1K zAdo#O!{h$F4ie;~?wJU@S(xINe?*wY8|RCg-+GWieR(w|@al!@ddMQU7s}Na-Q9J0 zU^-KYqki~$EWE0BJ*wquDa0aA=Jl~0A?vK1<}Ns9O+A{IdTL`Uf2aJ9*Pp<(w1v_&7MP!;j<@ue3RU)>x-Q3J6gSdG9%%-X=Si1+f{*`B ziuUKL2E^U!gVS8?+IN^bzsBYWBT>pDHwq?Pc@^q<4B7;EWfFFUtS-9S{?$mPz?e~k z?Xg92vcL%26mgw+i>Srxor8n>*Kbb`&qY9jP^%*8)0GZEgyckaH^ODDs}5wG81nny zUh02aebro9=@?TvT3w9(cpr?BAUhGiGxTdCS`xG-8EUT1ZA1?bJE{DLHm(z`uY~?(ernWs}`M1|r9_|#w3k~0aB-zfP zZu>x82149GRk_fsM!U&xtM|Wxqjz8DjkEg)%(_Pp6YIV#X+iAydTBne103n|yTBazY8W7X|7ZEbCgkfZQ;`MqJl5XgFZ zj-@9u84$3W+|LU8`f@TxMiC&yJ1#$`Um?9Fv1%lFrg4I~11;t0EBE0PcHKWtzQl^u zGed;sLwM5D2$hRxZxRnVb!$z3fIvBwGmz1eMvR}La^|z_KhQ(20#cr#RE~`~WP5;3N|lst+aoPmM%zHggDe6+GNKW| zbBC|OW0pLCs6iqh-Y_H*(MBKz>-b6S>h9MXyz1t;9nZJFGdvip&K*uqQ z8u;C-X4YWq%;#2?={6=NrVt#d!Z+8nHUG)>2 z&$qp!yq;&39GhW;)P`JMBs)Te_UGGp*}|%_vYlUX{(plB=;CFgEI5;7Xst22CUFVp zIiw`v%N^!&)S7Z37ss|W{VhKn83=@N=naUt7bn)=xTYsZ(JK7=ZeGXh;~FZWd;z;t zWlh)btu{odROXOpNy1smHhE7595RaW2?%7cu)hCW~I*Y0FhRM;sG-Xa6mT#9K#>-2o#7K+=`Y@tshU>7fYDr;CjZhJXc;GvznB(Gmx`UgFy%tT#$*r{;s=V)bf+?ZX-j(Jq~vF zmgi4o?9|la;oQIfT^q6`-)=!xB%553QO11G@;z|KJdTZ_$1<;5V~v;HgexcvAA=)1 z=k8Z8NbN)AC#ASt9|6(X>Sa8PB+&=*rHlk6dt#QEl2 zg2tIl3=43oW_pMo5jQJAHY)oDH$vPJQOCp1nVc&a(ZKGdfn6akIf2p3SLcmK918wg zZi`CD{EMUt7FAI0=-!|uhTXI0`&;kIs;Nnm7|*#?{x^<^rf+ijcj>OySvk=(j?+5X z-Cr+t#4S@C(~*(MJ4x^-JT+QQc;1`k;+AqDMG^W8B*v~sOJ9m1716;lDHvzzL-`50 zX}=dg={kbn@06vi7hPw|lV_{46)NJU2TfvUU*V;F4b#RAruEhpy{B>DOx*qqVJWL^ zBaIU;je2sZtk*cf949WRjy~qF)ga<#1kGp7`||;MMlxIPpHkv3l?5-I5uoxu!VbBP zv6`PWQGGr_S5Rcts~lSC9@_L&Doe0Wi-=H z5m;MHKo`B3IPD(3{q|Ej8-D)9lfX^YXYn*d6zbeJqT2D6*T#+Lv_fg48nsrkp-oB* zZXu{i)WyJ944eUb7b%(#6qe?B*4iob8sADV8r1F^%ooSNuL=hF?YevuFv2d&A zFl4`7hmi!<-pAET(rPejmIcdGv2>mgE<0GftwNli9#~ZQD{u!?Uo)F~km?zHjo@|HM3>g4t*c#o#%`;usDh2D_!*ZemxhC8f0h>02~D=P`t zx~(D!Gx~T+memoY&($^oAq)@HPnZrefvkTJuV*D9Ob2hHN9NGS;KC^l?K$^#QM&%7 zBEuvp33)Ws_|s<9Vj^MC=e>5fdh?Zsaq5dYWgEMI0PKzvrJ%?o3FU9AQ*sma8pJK1H%YSKBUI zD)l;^V~TSAQyGIff>#nR&8&O>QI|Ms=QiW^-b5k z686wdtz`S5ZNiJajsn4k-nC7$wfS?@uu6Ztr7C;`g_vI1I5TnUwkyw@5s4`FV5#ga)heE>o>2w%tc(rBy3HS$06*61_nZct| zF-AkdM&JBV-)$%Z!35ta#Y(wdpq11{7A^M=3Y!Uv9?Az)wAvpu_$aE1C^HxXs3;_l vKb)bvp`e65LNTb8fq*2G|Bqj8$q!hAcRov5)Al$2KA)VFvSc;fH0XZ-L0D3f diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml deleted file mode 100644 index d79dee514..000000000 --- a/app/src/main/res/drawable/background_splash.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_splash.xml b/app/src/main/res/drawable/ic_splash.xml new file mode 100644 index 000000000..9405fce2a --- /dev/null +++ b/app/src/main/res/drawable/ic_splash.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml index 78d327474..586c7f348 100644 --- a/app/src/main/res/values-v27/styles.xml +++ b/app/src/main/res/values-v27/styles.xml @@ -1,15 +1,5 @@ - - -