Commit 4cbfb45a authored by Thomas Markiewicz's avatar Thomas Markiewicz
Browse files

Merge branch 'tm-merge-upstream' into 'master'

Merge upstream

See merge request !3
parents f0914b21 78689ac9
Pipeline #50187 passed with stages
in 9 minutes and 51 seconds
CHANGES
=======
# Changelog
Version 1.2.0
-------------------------------------------------------------
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Changed
- Updated to latest upstream version
## [1.2.0]
### Added
* Added links to support, policy, and stay safe in main menu
* Merged upstream Tusky version 9.0
Version 1.1.0
-------------------------------------------------------------
## [1.1.0]
### Changed
* Merged upstream Tusky version 8.0
Version 1.0.0
-------------------------------------------------------------
## [1.0.0]
### Changed
* Updated Librem Social source code link in the About screen
* Updated how versionCode is set to support future F-Droid builds
* Bumped MAJOR version to create versionCode big enough for Play Store
* Merged upstream Tusky changes up to version 7.1
Version 0.1.3 released to Google Play Store on May 1, 2019
-------------------------------------------------------------
## [0.1.3]
### Changed
* Removed unsupported timelines: local, federated, direct (#285)
* Removed unsupported posting options: private, direct (#285)
* Renamed "Toot" to "Post" (#285)
Version 0.1.2 released to Google Play Store on April 25, 2019
-------------------------------------------------------------
## [0.1.2]
### Changed
* Updated slide out drawer to display @user instead of @user@domain (#276)
Version 0.1.1 released to Google Play Store on April 22, 2019
-------------------------------------------------------------
## [0.1.1]
### Changed
* Rebased to upstream Tusky version 6.1
Version 0.1.0 released to Google Play Store on April 10, 2019
-------------------------------------------------------------
## [0.1.0]
### Added
* Initial release
......@@ -18,8 +18,8 @@ android {
applicationId "com.keylesspalace.tusky"
minSdkVersion 21
targetSdkVersion 28
versionCode 67
versionName "9.0"
versionCode 68
versionName "9.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
......@@ -83,6 +83,7 @@ android {
}
testOptions {
unitTests {
returnDefaultValues = true
includeAndroidResources = true
}
}
......@@ -118,22 +119,22 @@ dependencies {
implementation('com.mikepenz:materialdrawer:6.1.2@aar') {
transitive = true
}
implementation 'androidx.core:core:1.0.2'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core:1.1.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'com.google.android.material:material:1.1.0-alpha05'
implementation 'com.google.android.material:material:1.1.0-alpha10'
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference:1.1.0-alpha04'
implementation 'androidx.preference:preference:1.1.0'
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
implementation 'com.squareup.okhttp3:okhttp:4.0.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.0.1'
implementation 'org.conscrypt:conscrypt-android:2.1.0'
implementation 'com.github.connyduck:sparkbutton:2.0.0'
implementation 'com.squareup.okhttp3:okhttp:4.2.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.0'
implementation 'org.conscrypt:conscrypt-android:2.2.1'
implementation 'com.github.connyduck:sparkbutton:2.0.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.mikepenz:google-material-typeface:3.0.1.3.original@aar'
implementation('com.theartofdev.edmodo:android-image-cropper:2.8.0') {
......@@ -146,7 +147,7 @@ dependencies {
implementation 'androidx.emoji:emoji-appcompat:1.0.0'
implementation 'de.c1710:filemojicompat:1.0.17'
// architecture components
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
//room
implementation 'androidx.room:room-runtime:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
......@@ -159,8 +160,8 @@ dependencies {
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
testImplementation 'org.robolectric:robolectric:4.3'
testImplementation 'org.mockito:mockito-inline:2.28.2'
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0'
testImplementation 'org.mockito:mockito-inline:3.0.0'
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', {
exclude group: 'com.android.support', module: 'support-annotations'
})
......@@ -168,18 +169,17 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
testImplementation 'androidx.test.ext:junit:1.1.1'
debugImplementation 'im.dino:dbinspector:3.4.1@aar'
implementation 'io.reactivex.rxjava2:rxjava:2.2.10'
implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.3.0'
implementation 'com.uber.autodispose:autodispose:1.3.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'com.uber.autodispose:autodispose-android-archcomponents:1.4.0'
implementation 'com.uber.autodispose:autodispose:1.4.0'
implementation 'androidx.paging:paging-runtime-ktx:2.1.0'
//Glide
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.9.0'
implementation 'jp.wasabeef:glide-transformations:3.1.1' // intentionally use 3.x version because of 2mb smaller apk
implementation 'com.github.bumptech.glide:glide:4.10.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.10.0'
//Add some useful extensions
implementation 'androidx.core:core-ktx:1.2.0-alpha01'
implementation 'androidx.core:core-ktx:1.2.0-alpha04'
}
......@@ -103,7 +103,6 @@
<activity android:name=".ViewTagActivity" />
<activity
android:name=".ViewMediaActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:theme="@style/TuskyBaseTheme" />
<activity
android:name=".AccountActivity"
......@@ -136,6 +135,7 @@
android:name=".components.report.ReportActivity"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
<activity android:name=".components.instancemute.InstanceListActivity" />
<activity android:name=".ScheduledTootActivity" />
<receiver android:name=".receiver.NotificationClearBroadcastReceiver" />
<receiver
......
......@@ -227,20 +227,28 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
// Setup the toolbar.
setSupportActionBar(accountToolbar)
supportActionBar?.title = null
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
setDisplayShowTitleEnabled(false)
}
ThemeUtils.setDrawableTint(this, accountToolbar.navigationIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
ThemeUtils.setDrawableTint(this, accountToolbar.overflowIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
// Add a listener to change the toolbar icon color when it enters/exits its collapsed state.
accountAppBarLayout.addOnOffsetChangedListener(object : AppBarLayout.OnOffsetChangedListener {
@AttrRes
var priorAttribute = R.attr.account_toolbar_icon_tint_uncollapsed
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
if(verticalOffset == oldOffset) {
return
}
oldOffset = verticalOffset
@AttrRes val attribute = if (titleVisibleHeight + verticalOffset < 0) {
supportActionBar?.setDisplayShowTitleEnabled(true)
......@@ -265,7 +273,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
accountFloatingActionButton.hide()
}
}
oldOffset = verticalOffset
val scaledAvatarSize = (avatarSize + verticalOffset) / avatarSize
......
......@@ -36,6 +36,7 @@ import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
import com.keylesspalace.tusky.viewmodel.State
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
import com.uber.autodispose.autoDisposable
import com.uber.autodispose.autoDispose
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.fragment_accounts_in_list.*
......@@ -106,7 +107,7 @@ class AccountsInListFragment : DialogFragment(), Injectable {
viewModel.state
.observeOn(AndroidSchedulers.mainThread())
.autoDisposable(from(this))
.autoDispose(from(this))
.subscribe { state ->
adapter.submitList(state.accounts.asRightOrNull() ?: listOf())
......
......@@ -50,14 +50,8 @@ import java.util.List;
import javax.inject.Inject;
import retrofit2.Call;
public abstract class BaseActivity extends AppCompatActivity implements Injectable {
protected List<Call> callList;
@Inject
public ThemeUtils themeUtils;
@Inject
public AccountManager accountManager;
......@@ -79,8 +73,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
setTheme(R.style.TuskyBlackTheme);
}
themeUtils.setAppNightMode(theme, this);
/* set the taskdescription programmatically, the theme would turn it blue */
String appName = getString(R.string.app_name);
Bitmap appIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
......@@ -95,7 +87,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
redirectIfNotLoggedIn();
}
callList = new ArrayList<>();
requesters = new HashMap<>();
}
......@@ -164,14 +155,6 @@ public abstract class BaseActivity extends AppCompatActivity implements Injectab
}
}
@Override
protected void onDestroy() {
for (Call call : callList) {
call.cancel();
}
super.onDestroy();
}
public void showAccountChooserDialog(CharSequence dialogTitle, boolean showActiveAccount, AccountSelectionListener listener) {
List<AccountEntity> accounts = accountManager.getAllAccountsOrderedByActive();
AccountEntity activeAccount = accountManager.getActiveAccount();
......
......@@ -17,16 +17,16 @@ package com.keylesspalace.tusky
import android.content.Intent
import android.os.Bundle
import androidx.annotation.VisibleForTesting
import com.google.android.material.bottomsheet.BottomSheetBehavior
import android.view.View
import android.widget.LinearLayout
import com.keylesspalace.tusky.entity.SearchResults
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.LinkHelper
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
import com.uber.autodispose.autoDispose
import io.reactivex.android.schedulers.AndroidSchedulers
import java.net.URI
import java.net.URISyntaxException
import javax.inject.Inject
......@@ -48,17 +48,17 @@ abstract class BottomSheetActivity : BaseActivity() {
super.onPostCreate(savedInstanceState)
val bottomSheetLayout: LinearLayout = findViewById(R.id.item_status_bottom_sheet)
bottomSheet = BottomSheetBehavior.from(bottomSheetLayout)
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
bottomSheet.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
cancelActiveSearch()
}
bottomSheet = BottomSheetBehavior.from(bottomSheetLayout)
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
bottomSheet.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
cancelActiveSearch()
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
})
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
})
}
......@@ -68,41 +68,34 @@ abstract class BottomSheetActivity : BaseActivity() {
return
}
val call = mastodonApi.search(url, true)
call.enqueue(object : Callback<SearchResults> {
override fun onResponse(call: Call<SearchResults>, response: Response<SearchResults>) {
if (getCancelSearchRequested(url)) {
return
}
onEndSearch(url)
if (response.isSuccessful) {
// According to the mastodon API doc, if the search query is a url,
// only exact matches for statuses or accounts are returned
// which is good, because pleroma returns a different url
// than the public post link
val searchResult = response.body()
if(searchResult != null) {
if (searchResult.statuses.isNotEmpty()) {
viewThread(searchResult.statuses[0].id, searchResult.statuses[0].url)
return
} else if (searchResult.accounts.isNotEmpty()) {
viewAccount(searchResult.accounts[0].id)
return
}
mastodonApi.searchObservable(
query = url,
resolve = true
).observeOn(AndroidSchedulers.mainThread())
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
.subscribe({ (accounts, statuses) ->
if (getCancelSearchRequested(url)) {
return@subscribe
}
}
openLink(url)
}
override fun onFailure(call: Call<SearchResults>, t: Throwable) {
if (!getCancelSearchRequested(url)) {
onEndSearch(url)
if (statuses.isNotEmpty()) {
viewThread(statuses[0].id, statuses[0].url)
return@subscribe
} else if (accounts.isNotEmpty()) {
viewAccount(accounts[0].id)
return@subscribe
}
openLink(url)
}
}
})
callList.add(call)
}, {
if (!getCancelSearchRequested(url)) {
onEndSearch(url)
openLink(url)
}
})
onBeginSearch(url)
}
......@@ -159,11 +152,11 @@ abstract class BottomSheetActivity : BaseActivity() {
}
private fun showQuerySheet() {
bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
}
private fun hideQuerySheet() {
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
}
}
......
......@@ -149,7 +149,7 @@ class FiltersActivity: BaseActivity() {
addFilterButton.hide()
filterProgressBar.show()
api.filters.enqueue(object : Callback<List<Filter>> {
api.getFilters().enqueue(object : Callback<List<Filter>> {
override fun onResponse(call: Call<List<Filter>>, response: Response<List<Filter>>) {
val filterResponse = response.body()
if(response.isSuccessful && filterResponse != null) {
......
......@@ -42,7 +42,7 @@ import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.*
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
import com.uber.autodispose.autoDisposable
import com.uber.autodispose.autoDispose
import dagger.android.DispatchingAndroidInjector
import dagger.android.HasAndroidInjector
import io.reactivex.android.schedulers.AndroidSchedulers
......@@ -92,7 +92,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
viewModel = viewModelFactory.create(ListsViewModel::class.java)
viewModel.state
.observeOn(AndroidSchedulers.mainThread())
.autoDisposable(from(this))
.autoDispose(from(this))
.subscribe(this::update)
viewModel.retryLoading()
......@@ -101,7 +101,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
}
viewModel.events.observeOn(AndroidSchedulers.mainThread())
.autoDisposable(from(this))
.autoDispose(from(this))
.subscribe { event ->
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
when (event) {
......
......@@ -34,6 +34,7 @@ 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 kotlinx.android.synthetic.main.activity_login.*
......@@ -234,14 +235,14 @@ class LoginActivity : BaseActivity(), Injectable {
val code = uri.getQueryParameter("code")
val error = uri.getQueryParameter("error")
domain = preferences.getString(DOMAIN, "")!!
/* During the redirect roundtrip this Activity usually dies, which wipes out the
* instance variables, so they have to be recovered from where they were saved in
* SharedPreferences. */
domain = preferences.getNonNullString(DOMAIN, "")
clientId = preferences.getString(CLIENT_ID, null)
clientSecret = preferences.getString(CLIENT_SECRET, null)
if (code != null && domain.isNotEmpty()) {
/* During the redirect roundtrip this Activity usually dies, which wipes out the
* instance variables, so they have to be recovered from where they were saved in
* SharedPreferences. */
clientId = preferences.getString(CLIENT_ID, null)
clientSecret = preferences.getString(CLIENT_SECRET, null)
if (code != null && domain.isNotEmpty() && !clientId.isNullOrEmpty() && !clientSecret.isNullOrEmpty()) {
setLoading(true)
/* Since authorization has succeeded, the final step to log in is to exchange
......@@ -268,7 +269,7 @@ class LoginActivity : BaseActivity(), Injectable {
}
}
mastodonApi.fetchOAuthToken(domain, clientId, clientSecret, redirectUri, code,
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
......
......@@ -15,27 +15,11 @@
package com.keylesspalace.tusky;
import androidx.lifecycle.Lifecycle;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import androidx.emoji.text.EmojiCompat;
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AlertDialog;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
......@@ -44,6 +28,17 @@ import android.webkit.WebView;
import android.widget.ImageButton;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.emoji.text.EmojiCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager.widget.ViewPager;
import com.bumptech.glide.Glide;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.keylesspalace.tusky.appstore.CacheUpdater;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent;
......@@ -106,6 +101,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
private static final long DRAWER_ITEM_ABOUT = 10;
private static final long DRAWER_ITEM_LOG_OUT = 11;
private static final long DRAWER_ITEM_FOLLOW_REQUESTS = 12;
private static final long DRAWER_ITEM_SCHEDULED_TOOT = 13;
public static final String STATUS_URL = "statusUrl";
@Inject
......@@ -406,6 +402,7 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_LISTS).withName(R.string.action_lists).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_list));
listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SEARCH).withName(R.string.action_search).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_search));
listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SAVED_TOOT).withName(R.string.action_access_saved_toot).withSelectable(false).withIcon(R.drawable.ic_notebook).withIconTintingEnabled(true));
listItems.add(new PrimaryDrawerItem().withIdentifier(DRAWER_ITEM_SCHEDULED_TOOT).withName(R.string.action_access_scheduled_toot).withSelectable(false).withIcon(R.drawable.ic_access_time).withIconTintingEnabled(true));
listItems.add(new DividerDrawerItem());
listItems.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_ACCOUNT_SETTINGS).withName(R.string.action_view_account_preferences).withSelectable(false).withIcon(R.drawable.ic_account_settings).withIconTintingEnabled(true));
listItems.add(new SecondaryDrawerItem().withIdentifier(DRAWER_ITEM_SETTINGS).withName(R.string.action_view_preferences).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_settings));
......@@ -458,6 +455,8 @@ public final class MainActivity extends BottomSheetActivity implements ActionBut
} else if (drawerItemIdentifier == DRAWER_ITEM_SAVED_TOOT) {
Intent intent = new Intent(MainActivity.this, SavedTootActivity.class);
startActivityWithSlideInAnimation(intent);
} else if (drawerItemIdentifier == DRAWER_ITEM_SCHEDULED_TOOT) {
startActivityWithSlideInAnimation(ScheduledTootActivity.newIntent(this));
} else if (drawerItemIdentifier == DRAWER_ITEM_LISTS) {
startActivityWithSlideInAnimation(ListsActivity.newIntent(this));
}
......
......@@ -32,7 +32,6 @@ import dagger.android.DispatchingAndroidInjector
import kotlinx.android.synthetic.main.toolbar_basic.*
import java.lang.IllegalArgumentException
import javax.inject.Inject
import androidx.appcompat.app.AppCompatDelegate
import dagger.android.HasAndroidInjector
class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener,
......@@ -124,18 +123,11 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
"appTheme" -> {
val theme = sharedPreferences.getNonNullString("appTheme", ThemeUtils.APP_THEME_DEFAULT)
Log.d("activeTheme", theme)
themeUtils.setAppNightMode(theme, this)
ThemeUtils.setAppNightMode(theme)