Init refactor
This commit is contained in:
parent
9efc706116
commit
60e40fe657
11 changed files with 367 additions and 55 deletions
|
@ -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)
|
||||
|
||||
|
|
19
husky/app/proguard-rules.pro
vendored
19
husky/app/proguard-rules.pro
vendored
|
@ -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(...);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))*/
|
|
@ -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
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
16
husky/app/src/main/res/layout/activity_navigation.xml
Normal file
16
husky/app/src/main/res/layout/activity_navigation.xml
Normal 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>
|
Loading…
Reference in a new issue