Init refactor

This commit is contained in:
Adolfo Santiago 2021-11-13 19:51:04 +01:00
parent 9efc706116
commit 60e40fe657
No known key found for this signature in database
GPG key ID: 244D6F9A317B4A65
11 changed files with 367 additions and 55 deletions

View file

@ -199,6 +199,9 @@ dependencies {
implementation(ApplicationLibs.RxJava.rxJava)
implementation(ApplicationLibs.RxJava.rxKotlin)
implementation(ApplicationLibs.SimpleStack.ext)
implementation(ApplicationLibs.SimpleStack.lib)
implementation(ApplicationLibs.Square.retrofit)
implementation(ApplicationLibs.Square.retrofitAdapterRxJ2)
implementation(ApplicationLibs.Square.retrofitConvGson)
@ -219,7 +222,6 @@ dependencies {
implementation(ApplicationLibs.materialDrawer)
implementation(ApplicationLibs.materialDrawerIconics)
implementation(ApplicationLibs.materialDrawerTypeface)
implementation(ApplicationLibs.filemojiCompat)
implementation(ApplicationLibs.sparkButton)
implementation(ApplicationLibs.timber)

View file

@ -2,7 +2,7 @@
# turn on all optimizations except those that are known to cause problems on Android
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 6
-optimizationpasses 7
-allowaccessmodification
-dontpreverify
@ -53,12 +53,27 @@
# remove all logging from production apk
-assumenosideeffects class android.util.Log {
public static *** getStackTraceString(...);
public static boolean isLoggable(java.lang.String, int);
public static int d(...);
public static int w(...);
public static int v(...);
public static int i(...);
public static int e(...);
}
-assumenosideeffects class timber.log.Timber* {
public static *** d(...);
public static *** w(...);
public static *** v(...);
public static *** i(...);
public static *** e(...);
public static *** plant(...);
}
-assumenosideeffects class java.lang.Exception* {
public void printStackTrace();
}
-assumenosideeffects class java.lang.String {
public static java.lang.String format(...);
}

View file

@ -42,6 +42,8 @@ import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import javax.inject.Inject
import timber.log.Timber
import timber.log.Timber.Forest
class LoginActivity : BaseActivity(), Injectable {
@ -62,7 +64,7 @@ class LoginActivity : BaseActivity(), Injectable {
setContentView(R.layout.activity_login)
if(savedInstanceState == null ) {
if(savedInstanceState == null) {
if(BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) {
domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
@ -76,27 +78,28 @@ class LoginActivity : BaseActivity(), Injectable {
if(BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) {
Glide.with(loginLogo)
.load(BuildConfig.CUSTOM_LOGO_URL)
.placeholder(null)
.into(loginLogo)
.load(BuildConfig.CUSTOM_LOGO_URL)
.placeholder(null)
.into(loginLogo)
}
preferences = getSharedPreferences(
getString(R.string.preferences_file_key), Context.MODE_PRIVATE)
getString(R.string.preferences_file_key), Context.MODE_PRIVATE
)
loginButton.setOnClickListener { onButtonClick() }
settingsButton.setOnClickListener { onSettingsButtonClick() }
whatsAnInstanceTextView.setOnClickListener {
val dialog = AlertDialog.Builder(this)
.setMessage(R.string.dialog_whats_an_instance)
.setPositiveButton(R.string.action_close, null)
.show()
.setMessage(R.string.dialog_whats_an_instance)
.setPositiveButton(R.string.action_close, null)
.show()
val textView = dialog.findViewById<TextView>(android.R.id.message)
textView?.movementMethod = LinkMovementMethod.getInstance()
}
if (isAdditionalLogin()) {
if(isAdditionalLogin()) {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
@ -118,7 +121,7 @@ class LoginActivity : BaseActivity(), Injectable {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
if(item.itemId == android.R.id.home) {
onBackPressed()
return true
}
@ -147,16 +150,18 @@ class LoginActivity : BaseActivity(), Injectable {
try {
HttpUrl.Builder().host(domain).scheme("https").build()
} catch (e: IllegalArgumentException) {
} catch(e: IllegalArgumentException) {
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_invalid_domain)
return
}
val callback = object : Callback<AppCredentials> {
override fun onResponse(call: Call<AppCredentials>,
response: Response<AppCredentials>) {
if (!response.isSuccessful) {
override fun onResponse(
call: Call<AppCredentials>,
response: Response<AppCredentials>
) {
if(!response.isSuccessful) {
loginButton.isEnabled = true
domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
setLoading(false)
@ -168,10 +173,10 @@ class LoginActivity : BaseActivity(), Injectable {
val clientSecret = credentials.clientSecret
preferences.edit()
.putString("domain", domain)
.putString("clientId", clientId)
.putString("clientSecret", clientSecret)
.apply()
.putString("domain", domain)
.putString("clientId", clientId)
.putString("clientSecret", clientSecret)
.apply()
redirectUserToAuthorizeAndLogin(domain, clientId)
}
@ -192,9 +197,11 @@ class LoginActivity : BaseActivity(), Injectable {
}
mastodonApi
.authenticateApp(domain, appname, oauthRedirectUri,
OAUTH_SCOPES, website)
.enqueue(callback)
.authenticateApp(
domain, appname, oauthRedirectUri,
OAUTH_SCOPES, website
)
.enqueue(callback)
setLoading(true)
}
@ -204,16 +211,16 @@ class LoginActivity : BaseActivity(), Injectable {
* 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
"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)) {
if(!openInCustomTab(uri, this)) {
val viewIntent = Intent(Intent.ACTION_VIEW, uri)
if (viewIntent.resolveActivity(packageManager) != null) {
if(viewIntent.resolveActivity(packageManager) != null) {
startActivity(viewIntent)
} else {
domainEditText.error = getString(R.string.error_no_web_browser_found)
@ -229,7 +236,7 @@ class LoginActivity : BaseActivity(), Injectable {
val uri = intent.data
val redirectUri = oauthRedirectUri
if (uri != null && uri.toString().startsWith(redirectUri)) {
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")
@ -239,43 +246,62 @@ class LoginActivity : BaseActivity(), Injectable {
val clientId = preferences.getNonNullString(CLIENT_ID, "")
val clientSecret = preferences.getNonNullString(CLIENT_SECRET, "")
if (code != null && domain.isNotEmpty() && clientId.isNotEmpty() && clientSecret.isNotEmpty()) {
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<AccessToken> {
override fun onResponse(call: Call<AccessToken>, response: Response<AccessToken>) {
if (response.isSuccessful) {
override fun onResponse(
call: Call<AccessToken>,
response: Response<AccessToken>
) {
if(response.isSuccessful) {
onLoginSuccess(response.body()!!.accessToken, domain)
} else {
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
Log.e(TAG, String.format("%s %s",
domainTextInputLayout.error =
getString(R.string.error_retrieving_oauth_token)
Log.e(
TAG, String.format(
"%s %s",
getString(R.string.error_retrieving_oauth_token),
response.message()))
response.message()
)
)
}
}
override fun onFailure(call: Call<AccessToken>, t: Throwable) {
setLoading(false)
domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
Log.e(TAG, String.format("%s %s",
domainTextInputLayout.error =
getString(R.string.error_retrieving_oauth_token)
Log.e(
TAG, String.format(
"%s %s",
getString(R.string.error_retrieving_oauth_token),
t.message))
t.message
)
)
}
}
mastodonApi.fetchOAuthToken(domain, clientId, clientSecret, redirectUri, code,
"authorization_code").enqueue(callback)
} else if (error != null) {
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)
domainTextInputLayout.error = getString(R.string.error_authorization_denied)
Log.e(TAG, String.format("%s %s",
Log.e(
TAG, String.format(
"%s %s",
getString(R.string.error_authorization_denied),
error))
error
)
)
} else {
// This case means a junk response was received somehow.
setLoading(false)
@ -288,7 +314,7 @@ class LoginActivity : BaseActivity(), Injectable {
}
private fun setLoading(loadingState: Boolean) {
if (loadingState) {
if(loadingState) {
loginLoadingLayout.visibility = View.VISIBLE
loginInputLayout.visibility = View.GONE
} else {
@ -337,7 +363,7 @@ class LoginActivity : BaseActivity(), Injectable {
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) {
if(at != -1) {
s = s.substring(at + 1)
}
return s.trim { it <= ' ' }
@ -350,7 +376,7 @@ class LoginActivity : BaseActivity(), Injectable {
private fun toQueryString(parameters: Map<String, String>): String {
val s = StringBuilder()
var between = ""
for ((key, value) in parameters) {
for((key, value) in parameters) {
s.append(between)
s.append(Uri.encode(key))
s.append("=")
@ -367,18 +393,18 @@ class LoginActivity : BaseActivity(), Injectable {
val navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor)
val colorSchemeParams = CustomTabColorSchemeParams.Builder()
.setToolbarColor(toolbarColor)
.setNavigationBarColor(navigationbarColor)
.setNavigationBarDividerColor(navigationbarDividerColor)
.build()
.setToolbarColor(toolbarColor)
.setNavigationBarColor(navigationbarColor)
.setNavigationBarDividerColor(navigationbarDividerColor)
.build()
val customTabsIntent = CustomTabsIntent.Builder()
.setDefaultColorSchemeParams(colorSchemeParams)
.build()
.setDefaultColorSchemeParams(colorSchemeParams)
.build()
try {
customTabsIntent.launchUrl(context, uri)
} catch (e: ActivityNotFoundException) {
} catch(e: ActivityNotFoundException) {
Log.w(TAG, "Activity was not found for intent $customTabsIntent")
return false
}

View file

@ -0,0 +1,33 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.core.extensions
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
fun <T : Any, L : LiveData<T>> Fragment.viewObserve(liveData: L, body: (T?) -> Unit) =
liveData.observe(viewLifecycleOwner, Observer(body))
// TODO: When Failure class is ready
/*fun <L : LiveData<Failure>> Fragment.viewFailureObserve(
liveData: L,
body: (Failure?) -> Unit
) = liveData.observe(viewLifecycleOwner, Observer(body))*/

View file

@ -0,0 +1,38 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.core.extensions
import android.view.View
fun View.isVisible(): Boolean {
return (this.visibility == View.VISIBLE)
}
fun View.visible() {
this.visibility = View.VISIBLE
}
fun View.invisible() {
this.visibility = View.INVISIBLE
}
fun View.gone() {
this.visibility = View.GONE
}

View file

@ -0,0 +1,79 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.core.navigation
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
import com.keylesspalace.tusky.core.extensions.viewBinding
import com.keylesspalace.tusky.databinding.ActivityNavigationBinding
import com.zhuinden.simplestack.SimpleStateChanger
import com.zhuinden.simplestack.StateChange
import com.zhuinden.simplestack.navigator.Navigator
import com.zhuinden.simplestackextensions.fragments.DefaultFragmentStateChanger
import timber.log.Timber
class NavigationActivity : AppCompatActivity(), SimpleStateChanger.NavigationHandler {
private val binding by viewBinding(ActivityNavigationBinding::inflate)
private lateinit var fragmentStateChanger: DefaultFragmentStateChanger
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(binding.root)
initNavigation()
}
override fun onBackPressed() {
if(!Navigator.onBackPressed(this)) {
Timber.i("No keys found, exiting the application.")
this.finishAndRemoveTask()
}
}
override fun onNavigationEvent(stateChange: StateChange) {
fragmentStateChanger.handleStateChange(stateChange)
}
private fun initNavigation() {
fragmentStateChanger = DefaultFragmentStateChanger(
supportFragmentManager,
binding.fragmentContainer.id
)
/*
Navigator.configure()
.setStateChanger(SimpleStateChanger(this))
.setScopedServices(DefaultServiceProvider())
//.setGlobalServices(GlobalServices(applicationContext).getGlobalServices())
.install(
this,
binding.fragmentContainer,
getHistoryKeys()
)
*/
Timber.d("Navigation setup completely")
}
private fun getHistoryKeys() {
}
}

View file

@ -0,0 +1,23 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.core.ui.fragment
class BaseFragment {
}

View file

@ -0,0 +1,29 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.core.ui.navigation
import com.zhuinden.simplestackextensions.fragments.DefaultFragmentKey
abstract class BaseKey : DefaultFragmentKey() {
override fun getFragmentTag(): String {
return this.javaClass.simpleName
}
}

View file

@ -0,0 +1,29 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.core.ui.navigation
import com.zhuinden.simplestackextensions.services.DefaultServiceProvider
abstract class BaseServiceKey : BaseKey(), DefaultServiceProvider.HasServices {
override fun getScopeTag(): String {
return this.javaClass.simpleName
}
}

View file

@ -0,0 +1,22 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.core.ui.viewmodel
abstract class BaseViewModel

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>