diff --git a/app/src/main/java/im/conversations/android/Conversations.java b/app/src/main/java/im/conversations/android/Conversations.java
index c7982790b..ab7af6150 100644
--- a/app/src/main/java/im/conversations/android/Conversations.java
+++ b/app/src/main/java/im/conversations/android/Conversations.java
@@ -1,8 +1,12 @@
package im.conversations.android;
import android.app.Application;
+import android.content.Context;
+import android.content.SharedPreferences;
import androidx.appcompat.app.AppCompatDelegate;
+import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
+import com.google.android.material.color.DynamicColorsOptions;
import im.conversations.android.dns.Resolver;
import im.conversations.android.notification.Channels;
import im.conversations.android.xmpp.ConnectionPool;
@@ -30,8 +34,53 @@ public class Conversations extends Application {
channels.initialize();
Resolver.init(this);
ConnectionPool.getInstance(this).reconfigure();
- AppCompatDelegate.setDefaultNightMode(
- AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); // For night mode theme
- DynamicColors.applyToActivitiesIfAvailable(this);
+ applyThemeSettings();
+ }
+
+ public void applyThemeSettings() {
+ final var sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ if (sharedPreferences == null) {
+ return;
+ }
+ applyThemeSettings(sharedPreferences);
+ }
+
+ private void applyThemeSettings(final SharedPreferences sharedPreferences) {
+ AppCompatDelegate.setDefaultNightMode(getDesiredNightMode(this, sharedPreferences));
+ var dynamicColorsOptions =
+ new DynamicColorsOptions.Builder()
+ .setPrecondition((activity, t) -> isDynamicColorsDesired(activity))
+ .build();
+ DynamicColors.applyToActivitiesIfAvailable(this, dynamicColorsOptions);
+ }
+
+ public static int getDesiredNightMode(final Context context) {
+ final var sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ if (sharedPreferences == null) {
+ return AppCompatDelegate.getDefaultNightMode();
+ }
+ return getDesiredNightMode(context, sharedPreferences);
+ }
+
+ public static boolean isDynamicColorsDesired(final Context context) {
+ final var preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getBoolean("dynamic_colors", false);
+ }
+
+ private static int getDesiredNightMode(
+ final Context context, final SharedPreferences sharedPreferences) {
+ final String theme =
+ sharedPreferences.getString("theme", context.getString(R.string.theme));
+ return getDesiredNightMode(theme);
+ }
+
+ public static int getDesiredNightMode(final String theme) {
+ if ("automatic".equals(theme)) {
+ return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
+ } else if ("light".equals(theme)) {
+ return AppCompatDelegate.MODE_NIGHT_NO;
+ } else {
+ return AppCompatDelegate.MODE_NIGHT_YES;
+ }
}
}
diff --git a/app/src/main/java/im/conversations/android/ui/Activities.java b/app/src/main/java/im/conversations/android/ui/Activities.java
index 1cf2df191..fdfb5e732 100644
--- a/app/src/main/java/im/conversations/android/ui/Activities.java
+++ b/app/src/main/java/im/conversations/android/ui/Activities.java
@@ -13,11 +13,20 @@ public final class Activities {
public static void setStatusAndNavigationBarColors(
final AppCompatActivity activity, final View view) {
+ setStatusAndNavigationBarColors(activity, view, false);
+ }
+
+ public static void setStatusAndNavigationBarColors(
+ final AppCompatActivity activity, final View view, final boolean raisedStatusBar) {
final var isLightMode = isLightMode(activity);
final var window = activity.getWindow();
final var flags = view.getSystemUiVisibility();
// an elevation of 4 matches the MaterialToolbar elevation
- window.setStatusBarColor(SurfaceColors.SURFACE_0.getColor(activity));
+ if (raisedStatusBar) {
+ window.setStatusBarColor(SurfaceColors.SURFACE_5.getColor(activity));
+ } else {
+ window.setStatusBarColor(SurfaceColors.SURFACE_0.getColor(activity));
+ }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window.setNavigationBarColor(SurfaceColors.SURFACE_1.getColor(activity));
if (isLightMode) {
diff --git a/app/src/main/java/im/conversations/android/ui/activity/BaseActivity.java b/app/src/main/java/im/conversations/android/ui/activity/BaseActivity.java
new file mode 100644
index 000000000..4391f8c24
--- /dev/null
+++ b/app/src/main/java/im/conversations/android/ui/activity/BaseActivity.java
@@ -0,0 +1,49 @@
+package im.conversations.android.ui.activity;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+import im.conversations.android.Conversations;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class BaseActivity extends AppCompatActivity {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(BaseActivity.class);
+
+ private Boolean isDynamicColors;
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final int desiredNightMode = Conversations.getDesiredNightMode(this);
+ if (setDesiredNightMode(desiredNightMode)) {
+ return;
+ }
+ final boolean isDynamicColors = Conversations.isDynamicColorsDesired(this);
+ setDynamicColors(isDynamicColors);
+ }
+
+ public void setDynamicColors(final boolean isDynamicColors) {
+ if (this.isDynamicColors == null) {
+ this.isDynamicColors = isDynamicColors;
+ } else {
+ if (this.isDynamicColors != isDynamicColors) {
+ LOGGER.info(
+ "Recreating {} because dynamic color setting has changed",
+ getClass().getSimpleName());
+ recreate();
+ }
+ }
+ }
+
+ public boolean setDesiredNightMode(final int desiredNightMode) {
+ if (desiredNightMode == AppCompatDelegate.getDefaultNightMode()) {
+ return false;
+ }
+ AppCompatDelegate.setDefaultNightMode(desiredNightMode);
+ LOGGER.info(
+ "Recreating {} because desired night mode has changed", getClass().getSimpleName());
+ recreate();
+ return true;
+ }
+}
diff --git a/app/src/main/java/im/conversations/android/ui/activity/MainActivity.java b/app/src/main/java/im/conversations/android/ui/activity/MainActivity.java
index 6b7a58b4d..5608bde51 100644
--- a/app/src/main/java/im/conversations/android/ui/activity/MainActivity.java
+++ b/app/src/main/java/im/conversations/android/ui/activity/MainActivity.java
@@ -1,14 +1,13 @@
package im.conversations.android.ui.activity;
import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import im.conversations.android.R;
import im.conversations.android.databinding.ActivityMainBinding;
import im.conversations.android.service.ForegroundService;
import im.conversations.android.ui.Activities;
-public class MainActivity extends AppCompatActivity {
+public class MainActivity extends BaseActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
diff --git a/app/src/main/java/im/conversations/android/ui/activity/SettingsActivity.java b/app/src/main/java/im/conversations/android/ui/activity/SettingsActivity.java
index 34d2e7a56..550725e4b 100644
--- a/app/src/main/java/im/conversations/android/ui/activity/SettingsActivity.java
+++ b/app/src/main/java/im/conversations/android/ui/activity/SettingsActivity.java
@@ -1,7 +1,6 @@
package im.conversations.android.ui.activity;
import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import im.conversations.android.R;
import im.conversations.android.databinding.ActivitySettingsBinding;
@@ -9,19 +8,32 @@ import im.conversations.android.service.ForegroundService;
import im.conversations.android.ui.Activities;
import im.conversations.android.ui.fragment.settings.MainSettingsFragment;
-public class SettingsActivity extends AppCompatActivity {
-
- private ActivitySettingsBinding binding;
+public class SettingsActivity extends BaseActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ForegroundService.start(this);
- this.binding = DataBindingUtil.setContentView(this, R.layout.activity_settings);
- Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
- getSupportFragmentManager()
- .beginTransaction()
- .replace(R.id.fragment_container, new MainSettingsFragment())
- .commit();
+ final ActivitySettingsBinding binding =
+ DataBindingUtil.setContentView(this, R.layout.activity_settings);
+ setSupportActionBar(binding.materialToolbar);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot(), true);
+
+ final var fragmentManager = getSupportFragmentManager();
+ final var currentFragment = fragmentManager.findFragmentById(R.id.fragment_container);
+ if (currentFragment == null) {
+ fragmentManager
+ .beginTransaction()
+ .replace(R.id.fragment_container, new MainSettingsFragment())
+ .commit();
+ }
+ binding.materialToolbar.setNavigationOnClickListener(
+ view -> {
+ if (fragmentManager.getBackStackEntryCount() == 0) {
+ finish();
+ } else {
+ fragmentManager.popBackStack();
+ }
+ });
}
}
diff --git a/app/src/main/java/im/conversations/android/ui/activity/SetupActivity.java b/app/src/main/java/im/conversations/android/ui/activity/SetupActivity.java
index 2c3fecdd2..8b4bc10b5 100644
--- a/app/src/main/java/im/conversations/android/ui/activity/SetupActivity.java
+++ b/app/src/main/java/im/conversations/android/ui/activity/SetupActivity.java
@@ -2,7 +2,6 @@ package im.conversations.android.ui.activity;
import android.content.Intent;
import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
@@ -16,7 +15,7 @@ import im.conversations.android.ui.model.SetupViewModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class SetupActivity extends AppCompatActivity {
+public class SetupActivity extends BaseActivity {
private static final Logger LOGGER = LoggerFactory.getLogger(SetupActivity.class);
diff --git a/app/src/main/java/im/conversations/android/ui/fragment/settings/InterfaceSettingsFragment.java b/app/src/main/java/im/conversations/android/ui/fragment/settings/InterfaceSettingsFragment.java
new file mode 100644
index 000000000..623d58906
--- /dev/null
+++ b/app/src/main/java/im/conversations/android/ui/fragment/settings/InterfaceSettingsFragment.java
@@ -0,0 +1,54 @@
+package im.conversations.android.ui.fragment.settings;
+
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import im.conversations.android.Conversations;
+import im.conversations.android.R;
+import im.conversations.android.ui.activity.SettingsActivity;
+
+public class InterfaceSettingsFragment extends PreferenceFragmentCompat {
+
+ @Override
+ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
+ setPreferencesFromResource(R.xml.preferences_interface, rootKey);
+ final var themePreference = findPreference("theme");
+ final var dynamicColors = findPreference("dynamic_colors");
+ if (themePreference != null) {
+ themePreference.setOnPreferenceChangeListener(
+ (preference, newValue) -> {
+ if (newValue instanceof String) {
+ final String theme = (String) newValue;
+ final int desiredNightMode = Conversations.getDesiredNightMode(theme);
+ requireSettingsActivity().setDesiredNightMode(desiredNightMode);
+ }
+ return true;
+ });
+ }
+ if (dynamicColors != null) {
+ dynamicColors.setOnPreferenceChangeListener(
+ new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(
+ @NonNull Preference preference, Object newValue) {
+ requireSettingsActivity()
+ .setDynamicColors(Boolean.TRUE.equals(newValue));
+ return true;
+ }
+ });
+ }
+ }
+
+ public SettingsActivity requireSettingsActivity() {
+ final var activity = requireActivity();
+ if (activity instanceof SettingsActivity) {
+ return (SettingsActivity) activity;
+ }
+ throw new IllegalStateException(
+ String.format(
+ "%s is not %s",
+ activity.getClass().getName(), SettingsActivity.class.getName()));
+ }
+}
diff --git a/app/src/main/res/drawable/ic_arrow_back_24dp.xml b/app/src/main/res/drawable/ic_arrow_back_24dp.xml
new file mode 100644
index 000000000..cd06f3098
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_back_24dp.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_dark_mode_24dp.xml b/app/src/main/res/drawable/ic_dark_mode_24dp.xml
new file mode 100644
index 000000000..e52c5baff
--- /dev/null
+++ b/app/src/main/res/drawable/ic_dark_mode_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_touch_app_24dp.xml b/app/src/main/res/drawable/ic_touch_app_24dp.xml
new file mode 100644
index 000000000..0744c9698
--- /dev/null
+++ b/app/src/main/res/drawable/ic_touch_app_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index 0ae23458a..c2f463293 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -25,13 +25,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false"
- app:elevation="0dp">
+ app:elevation="4dp">
+ app:title="@string/title_activity_settings"
+ app:navigationIcon="@drawable/ic_arrow_back_24dp"/>
+
+
+ automatic
+
+ - @string/pref_theme_automatic
+ - @string/pref_theme_light
+ - @string/pref_theme_dark
+
+
+ - automatic
+ - light
+ - dark
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 57f688ecb..1fb076f4d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1017,10 +1017,14 @@
Accounts
Spaces
Chats
- Appearance
+ Interface
Security
Encryption, Blind Trust Before Verification, MIM Detection
Notification relay for UnifiedPush compatible third party apps
+ Dynamic colors
+ System colors (Material You)
+ Light/dark mode
+ Appearance
diff --git a/app/src/main/res/values/theme-settings.xml b/app/src/main/res/values/theme-settings.xml
new file mode 100644
index 000000000..b770b1b26
--- /dev/null
+++ b/app/src/main/res/values/theme-settings.xml
@@ -0,0 +1,14 @@
+
+
+
+ light
+
+ - @string/pref_theme_light
+ - @string/pref_theme_dark
+
+
+ - light
+ - dark
+
+
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 0b6f615bf..ddf74586f 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -27,6 +27,5 @@
- @color/md_theme_light_inverseOnSurface
- @color/md_theme_light_inverseSurface
- @color/md_theme_light_inversePrimary
- - ?colorSurface
diff --git a/app/src/main/res/xml/preferences_interface.xml b/app/src/main/res/xml/preferences_interface.xml
new file mode 100644
index 000000000..b0166d1c0
--- /dev/null
+++ b/app/src/main/res/xml/preferences_interface.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/preferences_main.xml b/app/src/main/res/xml/preferences_main.xml
index feb62cbfb..dab1626ea 100644
--- a/app/src/main/res/xml/preferences_main.xml
+++ b/app/src/main/res/xml/preferences_main.xml
@@ -2,10 +2,10 @@
+ android:icon="@drawable/ic_touch_app_24dp"
+ app:fragment="im.conversations.android.ui.fragment.settings.InterfaceSettingsFragment"/>