From d4f5151a3c2adcc52eb9366e622f3e0826cdc5b1 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 1 Jun 2018 07:34:00 +0200 Subject: theme: add dark theme with toggle --- .../java/com/wireguard/android/Application.java | 14 ++++ .../wireguard/android/activity/BaseActivity.java | 21 ++++- .../android/activity/SettingsActivity.java | 45 +++++++++- .../java/com/wireguard/android/util/Topic.java | 97 ++++++++++++++++++++++ 4 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/wireguard/android/util/Topic.java (limited to 'app/src/main/java') diff --git a/app/src/main/java/com/wireguard/android/Application.java b/app/src/main/java/com/wireguard/android/Application.java index 0066d930..b8562606 100644 --- a/app/src/main/java/com/wireguard/android/Application.java +++ b/app/src/main/java/com/wireguard/android/Application.java @@ -12,6 +12,7 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.preference.PreferenceManager; +import android.support.v7.app.AppCompatDelegate; import com.wireguard.android.backend.Backend; import com.wireguard.android.backend.GoBackend; @@ -22,6 +23,7 @@ import com.wireguard.android.model.TunnelManager; import com.wireguard.android.util.AsyncWorker; import com.wireguard.android.util.RootShell; import com.wireguard.android.util.ToolsInstaller; +import com.wireguard.android.util.Topic; import java.io.File; import java.util.concurrent.Executor; @@ -66,6 +68,8 @@ public class Application extends android.app.Application { ToolsInstaller getToolsInstaller(); TunnelManager getTunnelManager(); + + Topic getThemeChangeTopic(); } @Qualifier @@ -86,6 +90,10 @@ public class Application extends android.app.Application { private ApplicationModule(final Application application) { context = application.getApplicationContext(); + + AppCompatDelegate.setDefaultNightMode( + getPreferences(context).getBoolean("dark_theme", false) ? + AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); } @ApplicationScope @@ -111,6 +119,12 @@ public class Application extends android.app.Application { return new FileConfigStore(context); } + @ApplicationScope + @Provides + public static Topic getThemeChangeTopic() { + return new Topic(); + } + @ApplicationScope @Provides diff --git a/app/src/main/java/com/wireguard/android/activity/BaseActivity.java b/app/src/main/java/com/wireguard/android/activity/BaseActivity.java index a0f3c32f..16e66817 100644 --- a/app/src/main/java/com/wireguard/android/activity/BaseActivity.java +++ b/app/src/main/java/com/wireguard/android/activity/BaseActivity.java @@ -16,6 +16,7 @@ import com.wireguard.android.Application; import com.wireguard.android.backend.GoBackend; import com.wireguard.android.model.Tunnel; import com.wireguard.android.model.TunnelManager; +import com.wireguard.android.util.Topic; import java.util.Objects; @@ -23,7 +24,7 @@ import java.util.Objects; * Base class for activities that need to remember the currently-selected tunnel. */ -public abstract class BaseActivity extends AppCompatActivity { +public abstract class BaseActivity extends AppCompatActivity implements Topic.Subscriber { private static final String KEY_SELECTED_TUNNEL = "selected_tunnel"; private final SelectionChangeRegistry selectionChangeRegistry = new SelectionChangeRegistry(); @@ -40,6 +41,8 @@ public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(final Bundle savedInstanceState) { + subscribeTopics(); + // Restore the saved tunnel if there is one; otherwise grab it from the arguments. String savedTunnelName = null; if (savedInstanceState != null) @@ -62,6 +65,12 @@ public abstract class BaseActivity extends AppCompatActivity { } } + @Override + protected void onDestroy() { + unsubscribeTopics(); + super.onDestroy(); + } + @Override protected void onSaveInstanceState(final Bundle outState) { if (selectedTunnel != null) @@ -105,4 +114,14 @@ public abstract class BaseActivity extends AppCompatActivity { super(new SelectionChangeNotifier()); } } + + @Override + public void onTopicPublished(Topic topic) { + recreate(); + } + + @Override + public Topic[] getSubscription() { + return new Topic[] { Application.getComponent().getThemeChangeTopic() }; + } } diff --git a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java index 040c5efc..d358c532 100644 --- a/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java +++ b/app/src/main/java/com/wireguard/android/activity/SettingsActivity.java @@ -6,12 +6,14 @@ package com.wireguard.android.activity; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AppCompatDelegate; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceFragmentCompat; import android.view.MenuItem; @@ -19,6 +21,7 @@ import android.view.MenuItem; import com.wireguard.android.Application; import com.wireguard.android.R; import com.wireguard.android.backend.WgQuickBackend; +import com.wireguard.android.util.Topic; import java.util.ArrayList; import java.util.Arrays; @@ -30,7 +33,7 @@ import java.util.Map; * Interface for changing application-global persistent settings. */ -public class SettingsActivity extends AppCompatActivity { +public class SettingsActivity extends AppCompatActivity implements Topic.Subscriber { private final Map permissionRequestCallbacks = new HashMap<>(); private int permissionRequestCounter; @@ -56,6 +59,7 @@ public class SettingsActivity extends AppCompatActivity { @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + subscribeTopics(); if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) { getSupportFragmentManager().beginTransaction() .add(android.R.id.content, new SettingsFragment()) @@ -63,6 +67,12 @@ public class SettingsActivity extends AppCompatActivity { } } + @Override + protected void onDestroy() { + unsubscribeTopics(); + super.onDestroy(); + } + @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { @@ -89,7 +99,17 @@ public class SettingsActivity extends AppCompatActivity { void done(String[] permissions, int[] grantResults); } - public static class SettingsFragment extends PreferenceFragmentCompat { + @Override + public void onTopicPublished(Topic topic) { + recreate(); + } + + @Override + public Topic[] getSubscription() { + return new Topic[] { Application.getComponent().getThemeChangeTopic() }; + } + + public static class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { @Override public void onCreatePreferences(final Bundle savedInstanceState, final String key) { addPreferencesFromResource(R.xml.preferences); @@ -100,5 +120,26 @@ public class SettingsActivity extends AppCompatActivity { getPreferenceScreen().removePreference(pref); } } + + @Override + public void onResume() { + super.onResume(); + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + super.onPause(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if ("dark_theme".equals(key)) { + AppCompatDelegate.setDefaultNightMode( + sharedPreferences.getBoolean(key, false) ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO); + Application.getComponent().getThemeChangeTopic().publish(false); + } + } } } diff --git a/app/src/main/java/com/wireguard/android/util/Topic.java b/app/src/main/java/com/wireguard/android/util/Topic.java new file mode 100644 index 00000000..eed13c9e --- /dev/null +++ b/app/src/main/java/com/wireguard/android/util/Topic.java @@ -0,0 +1,97 @@ +/* + * Copyright © 2017 John Wu + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +package com.wireguard.android.util; + +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class Topic { + + private static final int NON_INIT = 0; + private static final int PENDING = 1; + private static final int PUBLISHED = 2; + + private int state = NON_INIT; + private List> subscribers; + private Object[] results; + + public void subscribe(Subscriber sub) { + if (subscribers == null) { + subscribers = new LinkedList<>(); + } + subscribers.add(new WeakReference<>(sub)); + } + + public void unsubscribe() { + subscribers = null; + } + + public void unsubscribe(Subscriber sub) { + for (Iterator> i = subscribers.iterator(); i.hasNext();) { + WeakReference subscriber = i.next(); + if (subscriber.get() == null || subscriber.get() == sub) { + i.remove(); + } + } + } + + public void reset() { + state = NON_INIT; + results = null; + } + + public boolean isPublished() { + return state == PUBLISHED; + } + + public void publish() { + publish(true); + } + + public void publish(boolean record, Object... results) { + if (record) + state = PUBLISHED; + this.results = results; + if (subscribers != null) { + for (WeakReference subscriber : subscribers) { + if (subscriber.get() != null) + subscriber.get().onTopicPublished(this); + } + } + } + + public Object[] getResults() { + return results; + } + + public boolean isPending() { + return state == PENDING; + } + + public void setPending() { + state = PENDING; + } + + public interface Subscriber { + default void subscribeTopics() { + for (Topic topic : getSubscription()) { + if (topic.isPublished()) { + onTopicPublished(topic); + } + topic.subscribe(this); + } + } + default void unsubscribeTopics() { + for (Topic event : getSubscription()) { + event.unsubscribe(this); + } + } + void onTopicPublished(Topic topic); + Topic[] getSubscription(); + } +} -- cgit v1.2.3