diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 06d66eb..ae388c2 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -10,8 +10,8 @@ diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/BaseActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/BaseActivity.java index 88c5b9f..7f73d5e 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/BaseActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/BaseActivity.java @@ -2,12 +2,15 @@ package com.cringe_studios.cringe_authenticator; import android.os.Bundle; import android.util.Log; +import android.view.WindowManager; import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.cringe_studios.cringe_authenticator.unlock.UnlockContract; +import com.cringe_studios.cringe_authenticator.util.SettingsUtil; +import com.cringe_studios.cringe_authenticator.util.ThemeUtil; public class BaseActivity extends AppCompatActivity { @@ -19,6 +22,12 @@ public class BaseActivity extends AppCompatActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); registerCallbacks(); + + if(SettingsUtil.isScreenSecurity(this)) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } + + ThemeUtil.loadTheme(this); } private void registerCallbacks() { diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/IntroActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/IntroActivity.java index 52430b4..56a6acb 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/IntroActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/IntroActivity.java @@ -13,9 +13,9 @@ import com.cringe_studios.cringe_authenticator.databinding.ActivityIntroBinding; import com.cringe_studios.cringe_authenticator.unlock.UnlockActivity; import com.cringe_studios.cringe_authenticator.util.SettingsUtil; -public class IntroActivity extends AppCompatActivity { +public class IntroActivity extends BaseActivity { - private static ActivityIntroBinding binding; + private ActivityIntroBinding binding; private MediaPlayer mMediaPlayer; diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java index 8a0c49c..b54156d 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/MainActivity.java @@ -7,6 +7,7 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Toast; @@ -61,10 +62,6 @@ public class MainActivity extends BaseActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - //getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); TODO: enable secure flag - - ThemeUtil.loadTheme(this); - setLocale(SettingsUtil.getLocale(this)); qrScanner = new QRScanner(); diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/SettingsFragment.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/SettingsFragment.java index 637886e..ef9f7f7 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/SettingsFragment.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/fragment/SettingsFragment.java @@ -3,6 +3,7 @@ package com.cringe_studios.cringe_authenticator.fragment; import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG; import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL; +import android.location.SettingInjectorService; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -29,6 +30,7 @@ import com.cringe_studios.cringe_authenticator.util.FabUtil; import com.cringe_studios.cringe_authenticator.util.OTPDatabase; import com.cringe_studios.cringe_authenticator.util.OTPDatabaseException; import com.cringe_studios.cringe_authenticator.util.SettingsUtil; +import com.cringe_studios.cringe_authenticator.util.Theme; import java.util.Arrays; import java.util.Locale; @@ -57,7 +59,7 @@ public class SettingsFragment extends NamedFragment { localeNames[i] = locales[i].getDisplayName(locales[i]); } - binding.settingsLanguage.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, localeNames)); + binding.settingsLanguage.setAdapter(new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, localeNames)); binding.settingsLanguage.setSelection(Arrays.asList(locales).indexOf(SettingsUtil.getLocale(requireContext()))); binding.settingsLanguage.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override @@ -79,7 +81,7 @@ public class SettingsFragment extends NamedFragment { binding.settingsEnableEncryption.setChecked(SettingsUtil.isDatabaseEncrypted(requireContext())); binding.settingsEnableEncryption.setOnCheckedChangeListener((view, checked) -> { if(checked) { - DialogUtil.showInputPasswordDialog(requireContext(), password -> { + DialogUtil.showInputPasswordDialog(requireContext(), password -> { CryptoParameters params = CryptoParameters.createNew(); Log.d("Crypto", "Created new crypto params"); @@ -89,6 +91,10 @@ public class SettingsFragment extends NamedFragment { OTPDatabase.encrypt(requireContext(), key, params); SettingsUtil.enableEncryption(requireContext(), params); Log.d("Crypto", "DB encryption enabled"); + + if(BiometricUtil.isSupported(requireContext())) { + binding.settingsBiometricLock.setEnabled(true); + } } catch (CryptoException | OTPDatabaseException e) { throw new RuntimeException(e); // TODO } @@ -98,18 +104,16 @@ public class SettingsFragment extends NamedFragment { OTPDatabase.decrypt(requireContext()); SettingsUtil.disableEncryption(requireContext()); Log.d("Crypto", "DB encryption disabled"); + + binding.settingsBiometricLock.setChecked(false); + binding.settingsBiometricLock.setEnabled(false); } catch (OTPDatabaseException | CryptoException e) { throw new RuntimeException(e); // TODO } } }); - binding.settingsEnableIntroVideo.setChecked(SettingsUtil.isIntroVideoEnabled(requireContext())); - binding.settingsEnableIntroVideo.setOnCheckedChangeListener((view, checked) -> SettingsUtil.setEnableIntroVideo(requireContext(), checked)); - - if(SettingsUtil.isDatabaseEncrypted(requireContext()) - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && BiometricManager.from(requireContext()).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) { + if(SettingsUtil.isDatabaseEncrypted(requireContext()) && BiometricUtil.isSupported(requireContext())) { binding.settingsBiometricLock.setChecked(SettingsUtil.isBiometricEncryption(requireContext())); binding.settingsBiometricLock.setOnCheckedChangeListener((view, checked) -> { if(checked) { @@ -140,20 +144,32 @@ public class SettingsFragment extends NamedFragment { // TODO: inform user } - binding.settingsTheme.setAdapter(new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, SettingsUtil.THEME_NAMES.toArray(new String[0]))); - binding.settingsTheme.setSelection(SettingsUtil.THEME_NAMES.indexOf(SettingsUtil.getTheme(requireContext()))); + binding.settingsEnableIntroVideo.setChecked(SettingsUtil.isIntroVideoEnabled(requireContext())); + binding.settingsEnableIntroVideo.setOnCheckedChangeListener((view, checked) -> SettingsUtil.setEnableIntroVideo(requireContext(), checked)); + + binding.settingsScreenSecurity.setChecked(SettingsUtil.isScreenSecurity(requireContext())); + binding.settingsScreenSecurity.setOnCheckedChangeListener((view, checked) -> { + SettingsUtil.setScreenSecurity(requireContext(), checked); + requireActivity().recreate(); + }); + + binding.settingsHideCodes.setChecked(SettingsUtil.isHideCodes(requireContext())); // TODO: implement functionality + binding.settingsHideCodes.setOnCheckedChangeListener((view, checked) -> SettingsUtil.setHideCodes(requireContext(), checked)); + + String[] themeNames = new String[Theme.values().length]; + for(int i = 0; i < Theme.values().length; i++) { + themeNames[i] = getResources().getString(Theme.values()[i].getName()); + } + + binding.settingsTheme.setAdapter(new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, themeNames)); + binding.settingsTheme.setSelection(SettingsUtil.getTheme(requireContext()).ordinal()); binding.settingsTheme.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - String theme = SettingsUtil.THEME_NAMES.get(position); - if(theme.equals(SettingsUtil.getTheme(requireContext()))) return; + Theme theme = Theme.values()[position]; + if(theme == SettingsUtil.getTheme(requireContext())) return; SettingsUtil.setTheme(requireContext(), theme); - - Integer themeID = SettingsUtil.THEMES.get(theme); - if(themeID == null) return; - - requireActivity().setTheme(themeID); requireActivity().recreate(); } diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java index 3f00022..063f92c 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/scanner/QRScannerActivity.java @@ -31,6 +31,7 @@ import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding; import com.cringe_studios.cringe_authenticator.model.OTPData; import com.cringe_studios.cringe_authenticator.model.OTPMigrationPart; +import com.cringe_studios.cringe_authenticator.util.SettingsUtil; import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder; import com.cringe_studios.cringe_authenticator.util.ThemeUtil; import com.google.common.util.concurrent.ListenableFuture; @@ -61,10 +62,6 @@ public class QRScannerActivity extends AppCompatActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); - - ThemeUtil.loadTheme(this); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[] {Manifest.permission.CAMERA}, 1234); } diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/unlock/UnlockActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/unlock/UnlockActivity.java index 310f2dd..568971d 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/unlock/UnlockActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/unlock/UnlockActivity.java @@ -8,6 +8,8 @@ import android.os.Bundle; import android.security.KeyStoreParameter; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProtection; +import android.view.View; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -16,6 +18,7 @@ import androidx.biometric.BiometricManager; import androidx.biometric.BiometricPrompt; import androidx.core.content.ContextCompat; +import com.cringe_studios.cringe_authenticator.BaseActivity; import com.cringe_studios.cringe_authenticator.MainActivity; import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.crypto.BiometricKey; @@ -40,7 +43,7 @@ import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; -public class UnlockActivity extends AppCompatActivity { +public class UnlockActivity extends BaseActivity { private static final long LOCK_TIMEOUT = 10000; @@ -50,8 +53,6 @@ public class UnlockActivity extends AppCompatActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ThemeUtil.loadTheme(this); - if(!SettingsUtil.isDatabaseEncrypted(this)) { success(); return; @@ -76,6 +77,8 @@ public class UnlockActivity extends AppCompatActivity { binding.unlockBiometrics.setOnClickListener(view -> BiometricUtil.promptBiometricAuth(this, onSuccess, () -> {})); BiometricUtil.promptBiometricAuth(this, onSuccess, () -> {}); + }else { + binding.unlockBiometrics.setVisibility(View.GONE); } binding.unlockButton.setOnClickListener(view -> { diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java index 4bc8191..693c207 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/urihandler/URIHandlerActivity.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.util.Log; +import android.view.WindowManager; import android.widget.Toast; import androidx.annotation.Nullable; @@ -29,8 +30,6 @@ public class URIHandlerActivity extends BaseActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ThemeUtil.loadTheme(this); - Intent intent = getIntent(); if(intent == null) { finishAndRemoveTask(); diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/BiometricUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/BiometricUtil.java index c6d357a..6a9457f 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/BiometricUtil.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/BiometricUtil.java @@ -4,6 +4,7 @@ import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRON import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL; import android.content.Context; +import android.os.Build; import androidx.annotation.NonNull; import androidx.biometric.BiometricManager; @@ -18,7 +19,7 @@ import java.util.concurrent.Executor; public class BiometricUtil { public static boolean isSupported(Context context) { - return BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS; + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS; } public static void promptBiometricAuth(FragmentActivity context, Runnable success, Runnable failure) { diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java index 3bf979f..fad2fcf 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/SettingsUtil.java @@ -6,6 +6,7 @@ import android.util.Base64; import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.crypto.BiometricKey; +import com.cringe_studios.cringe_authenticator.crypto.CryptoException; import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters; import com.google.gson.Gson; @@ -21,21 +22,6 @@ import java.util.Map; public class SettingsUtil { - public static final Map THEMES; - public static final List THEME_NAMES; - - static { - Map themes = new LinkedHashMap<>(); - themes.put("Blue/Green", R.style.Theme_CringeAuthenticator_Blue_Green); - themes.put("Red/Blue", R.style.Theme_CringeAuthenticator_Red_Blue); - themes.put("Pink/Green", R.style.Theme_CringeAuthenticator_Pink_Green); - themes.put("Blue/Yellow", R.style.Theme_CringeAuthenticator_Blue_Yellow); - themes.put("Green/Yellow", R.style.Theme_CringeAuthenticator_Green_Yellow); - themes.put("Orange/Turquoise", R.style.Theme_CringeAuthenticator_Orange_Turquoise); - THEMES = Collections.unmodifiableMap(themes); - THEME_NAMES = Collections.unmodifiableList(new ArrayList<>(THEMES.keySet())); - } - public static String GROUPS_PREFS_NAME = "groups", GENERAL_PREFS_NAME = "general"; @@ -130,8 +116,6 @@ public class SettingsUtil { ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit() .putBoolean("encryption", false) .remove("encryption.parameters") - .remove("encryption.biometric") - .remove("encryption.biometric.key") .apply(); } @@ -172,13 +156,36 @@ public class SettingsUtil { return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("enableIntroVideo", true); } - public static void setTheme(Context ctx, String theme) { + public static void setScreenSecurity(Context ctx, boolean screenSecurity) { SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); - prefs.edit().putString("theme", theme).apply(); + prefs.edit().putBoolean("screenSecurity", screenSecurity).apply(); } - public static String getTheme(Context ctx) { - return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("theme", THEME_NAMES.get(0)); + public static boolean isScreenSecurity(Context ctx) { + return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("screenSecurity", true); + } + + public static void setHideCodes(Context ctx, boolean hideCodes) { + SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); + prefs.edit().putBoolean("hideCodes", hideCodes).apply(); + } + + public static boolean isHideCodes(Context ctx) { + return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("hideCodes", false); + } + + public static void setTheme(Context ctx, Theme theme) { + SharedPreferences prefs = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE); + prefs.edit().putString("theme", theme.name()).apply(); + } + + public static Theme getTheme(Context ctx) { + String themeId = ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("theme", Theme.BLUE_GREEN.name()); + try { + return Theme.valueOf(themeId); + }catch(IllegalArgumentException e) { + return Theme.BLUE_GREEN; + } } public static void setLocale(Context ctx, Locale locale) { diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/Theme.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/Theme.java new file mode 100644 index 0000000..d22f8a5 --- /dev/null +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/Theme.java @@ -0,0 +1,39 @@ +package com.cringe_studios.cringe_authenticator.util; + +import androidx.annotation.StringRes; +import androidx.annotation.StyleRes; + +import com.cringe_studios.cringe_authenticator.R; + +public enum Theme { + + BLUE_GREEN(R.string.theme_blue_green, R.style.Theme_CringeAuthenticator_Blue_Green), + RED_BLUE(R.string.theme_red_blue, R.style.Theme_CringeAuthenticator_Red_Blue), + PINK_GREEN(R.string.theme_pink_green, R.style.Theme_CringeAuthenticator_Pink_Green), + BLUE_YELLOW(R.string.theme_blue_yellow, R.style.Theme_CringeAuthenticator_Blue_Yellow), + GREEN_YELLOW(R.string.theme_green_yellow, R.style.Theme_CringeAuthenticator_Green_Yellow), + ORANGE_TURQUOISE(R.string.theme_orange_turquoise, R.style.Theme_CringeAuthenticator_Orange_Turquoise), + ; + + @StringRes + private final int name; + + @StyleRes + private final int style; + + Theme(@StringRes int name, @StyleRes int style) { + this.name = name; + this.style = style; + } + + @StringRes + public int getName() { + return name; + } + + @StyleRes + public int getStyle() { + return style; + } + +} diff --git a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java index f8d3d2a..e95bc9e 100644 --- a/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java +++ b/app/src/main/java/com/cringe_studios/cringe_authenticator/util/ThemeUtil.java @@ -8,12 +8,8 @@ import com.cringe_studios.cringe_authenticator.R; public class ThemeUtil { public static void loadTheme(AppCompatActivity activity) { - Integer themeID = SettingsUtil.THEMES.get(SettingsUtil.getTheme(activity)); - if(themeID != null) { - activity.setTheme(themeID); - }else { - activity.setTheme(R.style.Theme_CringeAuthenticator_Blue_Green); - } + Theme theme = SettingsUtil.getTheme(activity); + activity.setTheme(theme.getStyle()); // TODO: use settings //AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index e44a5da..4c751eb 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -29,12 +29,6 @@ android:layout_height="wrap_content" android:text="@string/enable_encryption" /> - - + + @@ -66,7 +66,6 @@ android:paddingBottom="10dp" /> diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 0ef32bf..315c785 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -74,4 +74,10 @@ Zuhause Einstellungen Über + Blau/Grün + Rot/Blau + Pink/Grün + Blau/Gelb + Grün/Gelb + Orange/Türkis \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d58729..09db239 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,4 +76,10 @@ Rename Delete + Blue/Green + Red/Blue + Pink/Green + Blue/Yellow + Green/Yellow + Orange/Turquoise \ No newline at end of file