diff --git a/res/drawable/state_list_drawer_background_dark.xml b/res/drawable/state_list_drawer_background_dark.xml new file mode 100644 --- /dev/null +++ b/res/drawable/state_list_drawer_background_dark.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/res/layout-v14/nav_dark_mode_switch.xml b/res/layout-v14/nav_dark_mode_switch.xml new file mode 100644 --- /dev/null +++ b/res/layout-v14/nav_dark_mode_switch.xml @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/res/layout/activity_device.xml b/res/layout/activity_device.xml --- a/res/layout/activity_device.xml +++ b/res/layout/activity_device.xml @@ -1,5 +1,7 @@ - - - - + android:orientation="horizontal" + android:gravity="center" + android:visibility="gone" > + + + + + + + + + style="?attr/mainNavigationViewStyle" /> diff --git a/res/layout/list_item_with_icon_entry.xml b/res/layout/list_item_with_icon_entry.xml --- a/res/layout/list_item_with_icon_entry.xml +++ b/res/layout/list_item_with_icon_entry.xml @@ -1,5 +1,7 @@ - - - diff --git a/res/layout/preference_with_button.xml b/res/layout/preference_with_button.xml --- a/res/layout/preference_with_button.xml +++ b/res/layout/preference_with_button.xml @@ -16,7 +16,9 @@ - - diff --git a/res/values-v21/styles-dark.xml b/res/values-v21/styles-dark.xml new file mode 100644 --- /dev/null +++ b/res/values-v21/styles-dark.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml new file mode 100644 --- /dev/null +++ b/res/values/attrs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/res/values/styles-dark.xml b/res/values/styles-dark.xml new file mode 100644 --- /dev/null +++ b/res/values/styles-dark.xml @@ -0,0 +1,40 @@ + + + #555555 + #222222 + #333333 + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/styles.xml b/res/values/styles.xml --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -10,6 +10,10 @@ @color/primary @color/primaryDark @color/accent + @style/KdeConnectTheme.Toolbar + @style/ThemeOverlay.AppCompat.Light + @style/MainNavigationView + @android:color/black @android:color/black @android:color/black @@ -21,8 +25,18 @@ + diff --git a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java --- a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java +++ b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java @@ -34,6 +34,7 @@ import android.view.Window; import android.view.WindowManager; +import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; public class FindMyPhoneActivity extends Activity { @@ -56,6 +57,7 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_find_my_phone); audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java --- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java +++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java @@ -37,6 +37,7 @@ import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; public class MousePadActivity extends AppCompatActivity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, MousePadGestureDetector.OnGestureListener { @@ -80,6 +81,7 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_mousepad); diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java @@ -46,6 +46,7 @@ import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.NetworkPacket; +import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import java.util.List; @@ -314,6 +315,7 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_mpris); final String targetPlayerName = getIntent().getStringExtra("player"); diff --git a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java --- a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java +++ b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java @@ -39,6 +39,7 @@ import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Helpers.StringsHelper; +import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import java.util.Arrays; @@ -93,6 +94,7 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_notification_filter); appDatabase = new AppDatabase(NotificationFilterActivity.this, false); diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java @@ -35,6 +35,7 @@ import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.UserInterface.List.ListAdapter; +import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; @@ -115,6 +116,7 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.activity_runcommand); deviceId = getIntent().getStringExtra("deviceId"); diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/SendFileActivity.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/SendFileActivity.java --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/SendFileActivity.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/SendFileActivity.java @@ -32,6 +32,7 @@ import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; @@ -44,6 +45,7 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeUtil.setUserPreferredTheme(this); mDeviceId = getIntent().getStringExtra("deviceId"); diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java @@ -38,6 +38,7 @@ import org.kde.kdeconnect.UserInterface.List.EntryItem; import org.kde.kdeconnect.UserInterface.List.ListAdapter; import org.kde.kdeconnect.UserInterface.List.SectionItem; +import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; @@ -145,8 +146,11 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.devices_list); + ActionBar actionBar = getSupportActionBar(); mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_list_layout); mSwipeRefreshLayout.setOnRefreshListener( diff --git a/src/org/kde/kdeconnect/UserInterface/AppCompatPreferenceActivity.java b/src/org/kde/kdeconnect/UserInterface/AppCompatPreferenceActivity.java --- a/src/org/kde/kdeconnect/UserInterface/AppCompatPreferenceActivity.java +++ b/src/org/kde/kdeconnect/UserInterface/AppCompatPreferenceActivity.java @@ -45,6 +45,8 @@ protected void onCreate(Bundle savedInstanceState) { getDelegate().installViewFactory(); getDelegate().onCreate(savedInstanceState); + // The superclass's onCreate() method calls setContentView, so this ThemeUtil call must be before that + ThemeUtil.setUserPreferredTheme(this); super.onCreate(savedInstanceState); } diff --git a/src/org/kde/kdeconnect/UserInterface/CustomDevicesActivity.java b/src/org/kde/kdeconnect/UserInterface/CustomDevicesActivity.java --- a/src/org/kde/kdeconnect/UserInterface/CustomDevicesActivity.java +++ b/src/org/kde/kdeconnect/UserInterface/CustomDevicesActivity.java @@ -57,6 +57,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initializeDeviceList(this); + ThemeUtil.setUserPreferredTheme(this); setContentView(R.layout.custom_ip_list); list = (ListView) findViewById(android.R.id.list); diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java --- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java +++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java @@ -334,6 +334,7 @@ boolean onData = NetworkHelper.isOnMobileNetwork(getContext()); rootView.findViewById(R.id.pairing_buttons).setVisibility(paired ? View.GONE : View.VISIBLE); + rootView.findViewById(R.id.error_message_container).setVisibility((paired && !reachable) ? View.VISIBLE : View.GONE); rootView.findViewById(R.id.not_reachable_message).setVisibility((paired && !reachable && !onData) ? View.VISIBLE : View.GONE); rootView.findViewById(R.id.on_data_message).setVisibility((paired && !reachable && onData) ? View.VISIBLE : View.GONE); diff --git a/src/org/kde/kdeconnect/UserInterface/MainActivity.java b/src/org/kde/kdeconnect/UserInterface/MainActivity.java --- a/src/org/kde/kdeconnect/UserInterface/MainActivity.java +++ b/src/org/kde/kdeconnect/UserInterface/MainActivity.java @@ -7,21 +7,27 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; import android.support.design.widget.NavigationView; import android.support.v4.app.Fragment; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.SwitchCompat; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; import android.widget.EditText; import android.widget.TextView; @@ -55,6 +61,10 @@ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + // We need to set this theme before the call to 'setContentView' below + ThemeUtil.setUserPreferredTheme(this); + setContentView(R.layout.activity_main); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mNavigationView = (NavigationView) findViewById(R.id.navigation_drawer); @@ -91,6 +101,10 @@ mDrawerHeader.findViewById(R.id.kdeconnect_label).setOnClickListener(renameListener); mDrawerHeader.findViewById(R.id.device_name).setOnClickListener(renameListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + addDarkModeSwitch((ViewGroup) mDrawerHeader); + } + mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { @@ -132,6 +146,36 @@ onDeviceSelected(savedDevice); } + /** + * Adds a {@link SwitchCompat} to the bottom of the navigation header for + * toggling dark mode on and off. Call from {@link #onCreate(Bundle)}. + *

+ * Only supports android ICS and higher because {@link SwitchCompat} + * requires that. + *

+ * + * @param drawerHeader the layout which should contain the switch + */ + @RequiresApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + private void addDarkModeSwitch(ViewGroup drawerHeader) { + getLayoutInflater().inflate(R.layout.nav_dark_mode_switch, drawerHeader); + + SwitchCompat darkThemeSwitch = (SwitchCompat) drawerHeader.findViewById(R.id.dark_theme); + darkThemeSwitch.setChecked(ThemeUtil.shouldUseDarkTheme(this)); + darkThemeSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @RequiresApi(Build.VERSION_CODES.HONEYCOMB) + @Override + public void onCheckedChanged(CompoundButton darkThemeSwitch, boolean isChecked) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this); + boolean isDarkAlready = prefs.getBoolean("darkTheme", false); + if (isDarkAlready != isChecked) { + prefs.edit().putBoolean("darkTheme", isChecked).apply(); + MainActivity.this.recreate(); + } + } + }); + } + //like onNewDeviceSelected but assumes that the new device is simply requesting to be paired //and can't be null private void onNewDeviceSelected(String deviceId, String pairStatus) { diff --git a/src/org/kde/kdeconnect/UserInterface/ThemeUtil.java b/src/org/kde/kdeconnect/UserInterface/ThemeUtil.java new file mode 100644 --- /dev/null +++ b/src/org/kde/kdeconnect/UserInterface/ThemeUtil.java @@ -0,0 +1,46 @@ +package org.kde.kdeconnect.UserInterface; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import org.kde.kdeconnect_tp.R; + +/** + * Utilities for working with android {@link android.content.res.Resources.Theme Themes}. + */ +public class ThemeUtil { + + /** + * This method should be called from the {@code activity}'s onCreate method, before + * any calls to {@link Activity#setContentView} or + * {@link android.preference.PreferenceActivity#setPreferenceScreen}. + * + * @param activity any Activity on screen + */ + public static void setUserPreferredTheme(Activity activity) { + boolean useDarkTheme = shouldUseDarkTheme(activity); + + // Only MainActivity sets its own Toolbar as the ActionBar. + boolean usesOwnActionBar = activity instanceof MainActivity; + + if (useDarkTheme) { + activity.setTheme(usesOwnActionBar ? R.style.KdeConnectTheme_Dark_NoActionBar : R.style.KdeConnectTheme_Dark); + } else { + activity.setTheme(usesOwnActionBar ? R.style.KdeConnectTheme_NoActionBar : R.style.KdeConnectTheme); + } + } + + /** + * Checks {@link SharedPreferences} to figure out whether we should use the light + * theme or the dark theme. The app defaults to light theme. + * + * @param context any active context (Activity, Service, Application, etc.) + * @return true if the dark theme should be active, false otherwise + */ + public static boolean shouldUseDarkTheme(Context context) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + return prefs.getBoolean("darkTheme", false); + } +}