commit 9881dc03dc02dc481d26ef0cabef9d0d5711e654
Author: leszek <leszek@koltunski.pl>
Date:   Fri Apr 12 22:36:59 2024 +0200

    common code from all activities to one BaseActivity

diff --git a/src/main/java/org/distorted/bandaged/BandagedActivity.java b/src/main/java/org/distorted/bandaged/BandagedActivity.java
index a1ec4611..285123ba 100644
--- a/src/main/java/org/distorted/bandaged/BandagedActivity.java
+++ b/src/main/java/org/distorted/bandaged/BandagedActivity.java
@@ -21,8 +21,6 @@ import android.os.Bundle;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
-import androidx.preference.PreferenceManager;
-
 import org.distorted.dialogs.RubikDialogError;
 import org.distorted.dialogs.RubikDialogMessage;
 import org.distorted.external.RubikFiles;
@@ -166,8 +164,7 @@ public class BandagedActivity extends BaseActivity
 
     private void savePreferences()
       {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      SharedPreferences.Editor editor = preferences.edit();
+      SharedPreferences.Editor editor = mPreferences.edit();
       mScreen.savePreferences(editor);
 
       editor.putBoolean("bandageDisplayDialog", mDisplayMessageDialog );
@@ -179,10 +176,9 @@ public class BandagedActivity extends BaseActivity
 
     private void restorePreferences()
       {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      mScreen.restorePreferences(this,preferences);
+      mScreen.restorePreferences(this,mPreferences);
 
-      mDisplayMessageDialog = preferences.getBoolean("bandageDisplayDialog",true);
+      mDisplayMessageDialog = mPreferences.getBoolean("bandageDisplayDialog",true);
 
       if( mDisplayMessageDialog )
         {
@@ -254,8 +250,7 @@ public class BandagedActivity extends BaseActivity
 
         if( !object.getError() )
           {
-          SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-          SharedPreferences.Editor editor = preferences.edit();
+          SharedPreferences.Editor editor = mPreferences.edit();
           OSInterface os = new OSInterface(this,null);
           os.setEditor(editor);
           object.removePreferences(os);
diff --git a/src/main/java/org/distorted/config/ConfigActivity.java b/src/main/java/org/distorted/config/ConfigActivity.java
index 28b41a3e..75be066b 100644
--- a/src/main/java/org/distorted/config/ConfigActivity.java
+++ b/src/main/java/org/distorted/config/ConfigActivity.java
@@ -9,10 +9,8 @@
 
 package org.distorted.config;
 
-import android.content.SharedPreferences;
 import android.os.Bundle;
-
-import androidx.preference.PreferenceManager;
+import android.content.SharedPreferences;
 
 import org.distorted.dialogs.RubikDialogError;
 import org.distorted.dialogs.RubikDialogMessage;
@@ -95,11 +93,9 @@ public class ConfigActivity extends BaseActivity
           }
         }
 
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
       OSInterface os = view.getInterface();
-      os.setPreferences(preferences);
-
-      restorePreferences(preferences);
+      os.setPreferences(mPreferences);
+      restorePreferences();
 
       if( mScreen==null ) mScreen = new ConfigScreen();
       mScreen.onAttachedToWindow(this,mObjectOrdinal);
@@ -157,8 +153,7 @@ public class ConfigActivity extends BaseActivity
 
     private void savePreferences()
       {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      SharedPreferences.Editor editor = preferences.edit();
+      SharedPreferences.Editor editor = mPreferences.edit();
 
       ConfigSurfaceView view = findViewById(R.id.configSurfaceView);
       OSInterface os = view.getInterface();
@@ -172,12 +167,12 @@ public class ConfigActivity extends BaseActivity
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    private void restorePreferences(SharedPreferences preferences)
+    private void restorePreferences()
       {
       ConfigSurfaceView view = findViewById(R.id.configSurfaceView);
       view.getObjectControl().restoreStickers();
 
-      mDisplayMessageDialog = preferences.getBoolean("configDisplayDialog",true);
+      mDisplayMessageDialog = mPreferences.getBoolean("configDisplayDialog",true);
 
       if( mDisplayMessageDialog )
         {
diff --git a/src/main/java/org/distorted/helpers/BaseActivity.java b/src/main/java/org/distorted/helpers/BaseActivity.java
index 6745ff34..221be8d3 100644
--- a/src/main/java/org/distorted/helpers/BaseActivity.java
+++ b/src/main/java/org/distorted/helpers/BaseActivity.java
@@ -9,6 +9,7 @@
 
 package org.distorted.helpers;
 
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
@@ -19,6 +20,7 @@ import android.view.WindowManager;
 import android.widget.LinearLayout;
 
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceManager;
 
 import org.distorted.main.R;
 
@@ -26,6 +28,11 @@ import org.distorted.main.R;
 
 public class BaseActivity extends AppCompatActivity
 {
+    protected static final int THEME_GREY  = 0;
+    protected static final int THEME_WHITE = 1;
+    protected static final int THEME_GREEN = 2;
+
+    public static final float RATIO_HID = 0.050f;
     public static final float RATIO_BAR = 0.080f;
     public static final int FLAGS =  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -34,18 +41,44 @@ public class BaseActivity extends AppCompatActivity
                                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 
     private int mCurrentApiVersion;
+    protected int mCurrentTheme;
     protected int mScreenWidth, mScreenHeight;
     protected int mHeightLowerBar, mHeightUpperBar;
+    protected SharedPreferences mPreferences;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
     protected void onCreate(Bundle savedState)
       {
-      setTheme(R.style.MaterialThemeNoActionBar);
+      mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+      mCurrentTheme = mPreferences.getInt("theme",THEME_GREY);
+
+      switch(mCurrentTheme)
+        {
+        case THEME_WHITE : setTheme(R.style.WhiteTheme); break;
+        case THEME_GREEN : setTheme(R.style.GreenTheme); break;
+        default          : setTheme(R.style.GreyTheme);  break;
+        }
+
+      android.util.Log.e("D", "current theme: "+mCurrentTheme);
+
       super.onCreate(savedState);
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void changeThemeTo(int theme)
+      {
+      mCurrentTheme = theme;
+
+      SharedPreferences.Editor editor = mPreferences.edit();
+      editor.putInt("theme", mCurrentTheme );
+      editor.apply();
+
+      recreate();
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     protected void getWindowWidth(Configuration conf)
diff --git a/src/main/java/org/distorted/main/MainActivity.java b/src/main/java/org/distorted/main/MainActivity.java
index b507a096..7bfc549e 100644
--- a/src/main/java/org/distorted/main/MainActivity.java
+++ b/src/main/java/org/distorted/main/MainActivity.java
@@ -25,7 +25,6 @@ import android.widget.ScrollView;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
-import androidx.preference.PreferenceManager;
 
 import com.google.firebase.analytics.FirebaseAnalytics;
 import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
@@ -79,12 +78,18 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
       cutoutHack();
       computeHeights();
 
+      getWindowWidth(getResources().getConfiguration());
+android.util.Log.e("D", "onCreate: "+mScreenWidth+" "+mScreenHeight);
+
       mCurrVersion = getAppVers();
 
       mBubbleUpdates = findViewById(R.id.bubbleUpdates);
       mBubbleUpdates.setVisibility(View.INVISIBLE);
       mNumUpdates = 0;
 
+      mGrid = new MainScrollGrid();
+      mGrid.createGrid(this,mScreenWidth,mSortMode);
+
       Thread thread = new Thread()
         {
         public void run()
@@ -116,13 +121,18 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-
+/*
     @Override
     public void onAttachedToWindow()
       {
       super.onAttachedToWindow();
 
       getWindowWidth(getResources().getConfiguration());
+
+
+android.util.Log.e("D", "onAttachedToWindow: "+mScreenWidth+" "+mScreenHeight);
+
+
       mGrid = new MainScrollGrid();
       mGrid.createGrid(this,mScreenWidth,mSortMode);
 
@@ -133,17 +143,17 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
 
         if( insetHeight>0 )
           {
-          LinearLayout.LayoutParams pH = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, RATIO_BAR);
+          LinearLayout.LayoutParams pH = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, RATIO_HID);
           LinearLayout layoutHid = findViewById(R.id.hiddenBar);
           layoutHid.setLayoutParams(pH);
 
-          LinearLayout.LayoutParams pS = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1-3*RATIO_BAR);
+          LinearLayout.LayoutParams pS = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1-2*RATIO_BAR-RATIO_HID);
           ScrollView scroll = findViewById(R.id.objectScroll);
           scroll.setLayoutParams(pS);
           }
         }
       }
-
+*/
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
@@ -151,6 +161,8 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
       {
       super.onConfigurationChanged(conf);
 
+android.util.Log.e("D", "onConfigurationChanged");
+
       getWindowWidth(conf);
       if( mGrid!=null ) mGrid.updateGrid(this,mScreenWidth);
       }
@@ -163,6 +175,10 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
       super.onPause();
       RubikNetwork.onPause();
       savePreferences();
+
+
+android.util.Log.e("D", "onPause");
+
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -172,8 +188,9 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
       {
       super.onResume();
 
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      restorePreferences(preferences,mJustStarted);
+android.util.Log.e("D", "onResume");
+
+      restorePreferences(mJustStarted);
 
       RubikNetwork network = RubikNetwork.getInstance();
       network.signUpForUpdates(this);
@@ -208,6 +225,8 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
     protected void onDestroy() 
       {
       super.onDestroy();
+
+android.util.Log.e("D", "onDestroy");
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -229,8 +248,7 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
 
     private void savePreferences()
       {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      SharedPreferences.Editor editor = preferences.edit();
+      SharedPreferences.Editor editor = mPreferences.edit();
 
       editor.putString("appVersion", mCurrVersion );
       editor.putInt("sortMode", mSortMode);
@@ -243,14 +261,14 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    private void restorePreferences(SharedPreferences preferences, boolean justStarted)
+    private void restorePreferences(boolean justStarted)
       {
-      mOldVersion = preferences.getString("appVersion","");
-      mSortMode = preferences.getInt("sortMode", MainSettingsPopup.SORT_DEFAULT);
+      mOldVersion = mPreferences.getString("appVersion","");
+      mSortMode = mPreferences.getInt("sortMode", MainSettingsPopup.SORT_DEFAULT);
 
-      RubikObjectList.restorePreferences(this,preferences,justStarted);
+      RubikObjectList.restorePreferences(this,mPreferences,justStarted);
       RubikScores scores = RubikScores.getInstance();
-      scores.restorePreferences(preferences);
+      scores.restorePreferences(mPreferences);
 
       if( scores.isVerified() )
         {
@@ -399,7 +417,7 @@ public class MainActivity extends BaseActivity implements RubikNetwork.Updatee,
 
       int vw = v.getWidth();
 
-      MainSettingsPopup popup = new MainSettingsPopup(this,mSortMode,mScreenWidth,mScreenHeight);
+      MainSettingsPopup popup = new MainSettingsPopup(this,mSortMode,mCurrentTheme,mScreenWidth,mScreenHeight);
       popup.displayPopup(this,v,sw,sh,((vw-sw)/2),0);
       }
 
diff --git a/src/main/java/org/distorted/main/MainSettingsPopup.java b/src/main/java/org/distorted/main/MainSettingsPopup.java
index 5928cca3..46fead80 100644
--- a/src/main/java/org/distorted/main/MainSettingsPopup.java
+++ b/src/main/java/org/distorted/main/MainSettingsPopup.java
@@ -42,10 +42,12 @@ public class MainSettingsPopup implements AdapterView.OnItemSelectedListener
   private static final float MENU_TEXT_SIZE = 0.060f;
   private static final int[] mLocation = new int[2];
 
-  private final PopupWindow mPopup;
-  private final WeakReference<MainActivity> mAct;
+  private PopupWindow mPopup;
+  private WeakReference<MainActivity> mAct;
   private int mCurrMethod;
   private String[] mSortNames;
+  private int mCurrTheme;
+  private String[] mThemeNames;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // this is supposed to prevent showing the navigational bar when we show the drop down list,
@@ -83,7 +85,7 @@ public class MainSettingsPopup implements AdapterView.OnItemSelectedListener
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  MainSettingsPopup(MainActivity act, int sortMethod, int width, int height)
+  MainSettingsPopup(MainActivity act, int sortMethod, int themeValue, int width, int height)
     {
     mAct = new WeakReference<>(act);
 
@@ -101,22 +103,36 @@ public class MainSettingsPopup implements AdapterView.OnItemSelectedListener
     int titleSize = (int)(MENU_TITLE_SIZE*width);
     int textSize  = (int)(MENU_TEXT_SIZE*width);
 
-    TextView title = layout.findViewById(R.id.sortTitle);
+    TextView title = layout.findViewById(R.id.settingsTitle);
     title.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
-    TextView text = layout.findViewById(R.id.sortText);
-    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+    TextView sortText = layout.findViewById(R.id.sortText);
+    sortText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+    TextView themeTitle = layout.findViewById(R.id.themeText);
+    themeTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
 
-    Spinner actSpinner  = layout.findViewById(R.id.sortMethod);
-    actSpinner.setOnItemSelectedListener(this);
+    Spinner sortSpinner  = layout.findViewById(R.id.sortMethod);
+    sortSpinner.setOnItemSelectedListener(this);
 
     mCurrMethod = sortMethod;
     buildSortOptions(act);
 
-    ArrayAdapter<String> actAdapter = new ArrayAdapter<>(act, android.R.layout.simple_spinner_item, mSortNames);
-    actAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-    actSpinner.setAdapter(actAdapter);
+    ArrayAdapter<String> sortAdapter = new ArrayAdapter<>(act, android.R.layout.simple_spinner_item, mSortNames);
+    sortAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+    sortSpinner.setAdapter(sortAdapter);
 
-    if( sortMethod>=0 && sortMethod<mSortNames.length ) actSpinner.setSelection(sortMethod);
+    if( sortMethod>=0 && sortMethod<mSortNames.length ) sortSpinner.setSelection(sortMethod);
+
+    Spinner themeSpinner  = layout.findViewById(R.id.themeValue);
+    themeSpinner.setOnItemSelectedListener(this);
+
+    mCurrTheme = themeValue;
+    buildThemeOptions(act);
+
+    ArrayAdapter<String> themeAdapter = new ArrayAdapter<>(act, android.R.layout.simple_spinner_item, mThemeNames);
+    themeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+    themeSpinner.setAdapter(themeAdapter);
+
+    if( themeValue>=0 && themeValue<mThemeNames.length ) themeSpinner.setSelection(themeValue);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -133,6 +149,18 @@ public class MainSettingsPopup implements AdapterView.OnItemSelectedListener
     mSortNames[4] = res.getString(R.string.sort_year);
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void buildThemeOptions(MainActivity act)
+    {
+    Resources res = act.getResources();
+    mThemeNames = new String[3];
+
+    mThemeNames[0] = res.getString(R.string.theme_grey);
+    mThemeNames[1] = res.getString(R.string.theme_white);
+    mThemeNames[2] = res.getString(R.string.theme_green);
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // work around lame bugs in Android's version <= 10 pop-up and split-screen modes
 
@@ -187,10 +215,19 @@ public class MainSettingsPopup implements AdapterView.OnItemSelectedListener
     {
     if( parent.getId()==R.id.sortMethod && mCurrMethod!=pos )
       {
+      mPopup.dismiss();
       mCurrMethod = pos;
       MainActivity act = mAct.get();
       act.sortObjectsBy(pos);
+      }
+    if( parent.getId()==R.id.themeValue && mCurrTheme!=pos )
+      {
       mPopup.dismiss();
+      mPopup = null;
+      mCurrTheme = pos;
+      MainActivity act = mAct.get();
+      mAct = null;
+      act.changeThemeTo(pos);
       }
     }
 
diff --git a/src/main/java/org/distorted/patternui/PatternActivity.java b/src/main/java/org/distorted/patternui/PatternActivity.java
index d90aa076..456ef46c 100644
--- a/src/main/java/org/distorted/patternui/PatternActivity.java
+++ b/src/main/java/org/distorted/patternui/PatternActivity.java
@@ -16,8 +16,6 @@ import android.view.DisplayCutout;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
-import androidx.preference.PreferenceManager;
-
 import org.distorted.dialogs.RubikDialogError;
 import org.distorted.helpers.BaseActivity;
 import org.distorted.library.main.DistortedLibrary;
@@ -108,8 +106,7 @@ public class PatternActivity extends BaseActivity
       view.onResume();
 
       createObject();
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      restorePreferences(preferences);
+      restorePreferences();
       ScreenList.setScreen(this);
       }
 
@@ -126,8 +123,7 @@ public class PatternActivity extends BaseActivity
 
     private void savePreferences()
       {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      SharedPreferences.Editor editor = preferences.edit();
+      SharedPreferences.Editor editor = mPreferences.edit();
 
       for(int i=0; i< ScreenList.LENGTH; i++ )
         ScreenList.getScreen(i).getScreenClass().savePreferences(editor);
@@ -140,12 +136,12 @@ public class PatternActivity extends BaseActivity
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    private void restorePreferences(SharedPreferences preferences)
+    private void restorePreferences()
       {
       for (int i=0; i<ScreenList.LENGTH; i++)
-        ScreenList.getScreen(i).getScreenClass().restorePreferences(preferences);
+        ScreenList.getScreen(i).getScreenClass().restorePreferences(mPreferences);
 
-      ScreenList.restorePreferences(preferences);
+      ScreenList.restorePreferences(mPreferences);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/playui/PlayActivity.java b/src/main/java/org/distorted/playui/PlayActivity.java
index c48979c0..9b222d7a 100644
--- a/src/main/java/org/distorted/playui/PlayActivity.java
+++ b/src/main/java/org/distorted/playui/PlayActivity.java
@@ -18,8 +18,6 @@ import android.view.DisplayCutout;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
-import androidx.preference.PreferenceManager;
-
 import com.google.firebase.analytics.FirebaseAnalytics;
 
 import org.distorted.dialogs.RubikDialogScores;
@@ -138,8 +136,7 @@ public class PlayActivity extends BaseActivity implements RubikDialogScores.Scor
       ObjectControl control = view.getObjectControl();
       view.onResume();
 
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      restorePreferences(preferences);
+      restorePreferences();
 
       ScreenList sl =  mJustStarted ?
                       (mModeFree ? ScreenList.FREE : ScreenList.SCRA) :
@@ -147,7 +144,7 @@ public class PlayActivity extends BaseActivity implements RubikDialogScores.Scor
 
       ScreenList.switchScreen(this,sl);
 
-      if( !mJustStarted ) restoreMoves(preferences);
+      if( !mJustStarted ) restoreMoves();
 
       if( mObjectName.length()>0 )
         {
@@ -175,8 +172,7 @@ public class PlayActivity extends BaseActivity implements RubikDialogScores.Scor
 
   private void savePreferences()
     {
-    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-    SharedPreferences.Editor editor = preferences.edit();
+    SharedPreferences.Editor editor = mPreferences.edit();
 
     for( int i=0; i<ScreenList.LENGTH; i++ )
       {
@@ -211,36 +207,36 @@ public class PlayActivity extends BaseActivity implements RubikDialogScores.Scor
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void restorePreferences(SharedPreferences preferences)
+  private void restorePreferences()
     {
     for( int i=0; i<ScreenList.LENGTH; i++)
       {
-      ScreenList.getScreen(i).getScreenClass().restorePreferences(preferences);
+      ScreenList.getScreen(i).getScreenClass().restorePreferences(mPreferences);
       }
 
-    if( !mJustStarted ) ScreenList.restorePreferences(preferences);
+    if( !mJustStarted ) ScreenList.restorePreferences(mPreferences);
 
     PlayView view = findViewById(R.id.playView);
     OSInterface os = view.getInterface();
-    os.setPreferences(preferences);
+    os.setPreferences(mPreferences);
     view.getObjectControl().restorePreferences();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void restoreMoves(SharedPreferences preferences)
+  private void restoreMoves()
     {
     ScreenList curr = ScreenList.getCurrentScreen();
 
     if( curr==ScreenList.FREE )
       {
       ScreenFree free = (ScreenFree) ScreenList.FREE.getScreenClass();
-      free.restoreMovePreferences(this,KEY_FREE,preferences);
+      free.restoreMovePreferences(this,KEY_FREE,mPreferences);
       }
     if( curr==ScreenList.SOLV )
       {
       ScreenSolving solv = (ScreenSolving) ScreenList.SOLV.getScreenClass();
-      solv.restoreMovePreferences(this,KEY_SOLV,preferences);
+      solv.restoreMovePreferences(this,KEY_SOLV,mPreferences);
       }
     }
 
diff --git a/src/main/java/org/distorted/purchase/PurchaseActivity.java b/src/main/java/org/distorted/purchase/PurchaseActivity.java
deleted file mode 100644
index 6ac0f608..00000000
--- a/src/main/java/org/distorted/purchase/PurchaseActivity.java
+++ /dev/null
@@ -1,207 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is proprietary software licensed under an EULA which you should have received      //
-// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.purchase;
-
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.preference.PreferenceManager;
-
-import org.distorted.dialogs.RubikDialogError;
-import org.distorted.external.RubikScores;
-import org.distorted.helpers.BaseActivity;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.main.R;
-import org.distorted.objectlib.main.InitAssets;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objects.RubikObject;
-import org.distorted.objects.RubikObjectList;
-
-import java.io.InputStream;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class PurchaseActivity extends BaseActivity
-{
-    private static final int ACTIVITY_NUMBER = 3;
-    private static final float RATIO_UBAR = 0.14f;
-    private static final float RATIO_LBAR = 0.10f;
-    private static final float RATIO_VIEW = 0.50f;
-
-    private PurchaseScreen mScreen;
-    private int mObjectOrdinal;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    protected void onCreate(Bundle savedState)
-      {
-      super.onCreate(savedState);
-      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
-      setContentView(R.layout.purchase);
-
-      Bundle b = getIntent().getExtras();
-      if(b != null) mObjectOrdinal = b.getInt("obj");
-
-      computeScreenDimensions();
-      hideNavigationBar();
-      cutoutHack();
-      setHeights();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void setViewHeight(int id, int height)
-      {
-      View view = findViewById(id);
-
-      if( view!=null )
-        {
-        ViewGroup.LayoutParams params = view.getLayoutParams();
-        params.height = height;
-        view.setLayoutParams(params);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this does not include possible insets
-
-    private void setHeights()
-      {
-      int ubarHeight= (int)(mScreenHeight*RATIO_UBAR);
-      int lbarHeight= (int)(mScreenHeight*RATIO_LBAR);
-      int viewHeight= (int)(mScreenHeight*RATIO_VIEW);
-      int layHeight = (int)(mScreenHeight*(1-RATIO_UBAR-RATIO_LBAR-RATIO_VIEW)*0.5f);
-
-      setViewHeight(R.id.upperBar           , ubarHeight);
-      setViewHeight(R.id.lowerBar           , lbarHeight);
-      setViewHeight(R.id.purchaseSurfaceView, viewHeight);
-      setViewHeight(R.id.purchaseLayoutOne  , layHeight);
-      setViewHeight(R.id.purchaseLayoutAll  , layHeight);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onPause() 
-      {
-      super.onPause();
-      PurchaseSurfaceView view = findViewById(R.id.purchaseSurfaceView);
-      view.onPause();
-      DistortedLibrary.onPause(ACTIVITY_NUMBER);
-      savePreferences();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onResume() 
-      {
-      super.onResume();
-      DistortedLibrary.onResume(ACTIVITY_NUMBER);
-      PurchaseSurfaceView view = findViewById(R.id.purchaseSurfaceView);
-      view.onResume();
-
-      if( mScreen==null ) mScreen = new PurchaseScreen();
-      mScreen.onAttachedToWindow(this,mObjectOrdinal);
-
-      if( mObjectOrdinal>=0 && mObjectOrdinal< RubikObjectList.getNumObjects() )
-        {
-        RubikObject object = RubikObjectList.getObject(mObjectOrdinal);
-        changeIfDifferent(object,mObjectOrdinal,view.getObjectControl());
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onDestroy() 
-      {
-      super.onDestroy();
-      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void savePreferences()
-      {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      SharedPreferences.Editor editor = preferences.edit();
-      RubikScores scores = RubikScores.getInstance();
-      scores.savePreferencesMinimal(editor);
-
-      boolean success = editor.commit();
-      if( !success ) android.util.Log.e("D", "Failed to save preferences");
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void OpenGLError()
-      {
-      RubikDialogError errDiag = new RubikDialogError();
-      errDiag.show(getSupportFragmentManager(), null);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void changeIfDifferent(RubikObject object,int ordinal,ObjectControl control)
-      {
-      if( object!=null )
-        {
-        int iconMode           = TwistyObject.MODE_NORM;
-        InputStream jsonStream = object.getObjectStream(this);
-        InputStream meshStream = object.getMeshStream(this);
-        String name            = object.getUpperName();
-        InitAssets asset       = new InitAssets(jsonStream,meshStream,null);
-        control.changeIfDifferent(ordinal,name,iconMode,asset);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public ObjectControl getControl()
-      {
-      PurchaseSurfaceView view = findViewById(R.id.purchaseSurfaceView);
-      return view.getObjectControl();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    PurchaseRenderer getRenderer()
-      {
-      PurchaseSurfaceView view = findViewById(R.id.purchaseSurfaceView);
-      return view.getRenderer();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void blockUI()
-      {
-      mScreen.blockUI();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    int getObjectPrice()
-      {
-      if( mObjectOrdinal>=0 && mObjectOrdinal< RubikObjectList.getNumObjects() )
-        {
-        RubikObject object = RubikObjectList.getObject(mObjectOrdinal);
-        return object==null ? 0 : object.getPrice();
-        }
-
-      return 0;
-      }
-}
diff --git a/src/main/java/org/distorted/purchase/PurchaseObjectLibInterface.java b/src/main/java/org/distorted/purchase/PurchaseObjectLibInterface.java
deleted file mode 100644
index 8330bfd6..00000000
--- a/src/main/java/org/distorted/purchase/PurchaseObjectLibInterface.java
+++ /dev/null
@@ -1,57 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2021 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is proprietary software licensed under an EULA which you should have received      //
-// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.purchase;
-
-import com.google.firebase.crashlytics.FirebaseCrashlytics;
-
-import org.distorted.main.BuildConfig;
-import org.distorted.objectlib.helpers.ObjectLibInterface;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class PurchaseObjectLibInterface implements ObjectLibInterface
-{
-  public void onWinEffectFinished(long startTime, long endTime, String debug, int scrambleNum) { }
-  public void onScrambleEffectFinished() { }
-  public void onBeginRotation() { }
-  public void onSolved() { }
-  public void onObjectCreated(long time) { }
-  public void onReplaceModeDown(int cubit, int face) { }
-  public void onReplaceModeUp() { }
-  public void onRemoveRotation(int axis, int row, int angle) { }
-  public void failedToDrag() { }
-  public void reportJSONError(String error, int ordinal) { }
-  public void reportBlockProblem(int type, int place, long pause, long resume, long time) { }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void reportProblem(String problem, boolean reportException)
-    {
-    if( BuildConfig.DEBUG )
-      {
-      android.util.Log.e("interface", problem);
-      }
-    else
-      {
-      if( reportException )
-        {
-        Exception ex = new Exception(problem);
-        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-        crashlytics.setCustomKey("problem" , problem);
-        crashlytics.recordException(ex);
-        }
-      else
-        {
-        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-        crashlytics.log(problem);
-        }
-      }
-    }
-}
diff --git a/src/main/java/org/distorted/purchase/PurchaseRenderer.java b/src/main/java/org/distorted/purchase/PurchaseRenderer.java
deleted file mode 100644
index 979756f8..00000000
--- a/src/main/java/org/distorted/purchase/PurchaseRenderer.java
+++ /dev/null
@@ -1,129 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is proprietary software licensed under an EULA which you should have received      //
-// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.purchase;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ConfigurationInfo;
-import android.content.res.Resources;
-import android.opengl.GLSurfaceView;
-
-import org.distorted.library.effect.EffectType;
-import org.distorted.library.effect.VertexEffectQuaternion;
-import org.distorted.library.effect.VertexEffectRotate;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.library.mesh.MeshBase;
-import org.distorted.objectlib.effects.BaseEffect;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.overlays.OverlayGeneric;
-
-import java.io.InputStream;
-
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class PurchaseRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
-{
-   private static final int NUM_SCRAMBLES = 5;
-   private static final int DURATION = NUM_SCRAMBLES*2*1000;
-
-   private final PurchaseSurfaceView mView;
-   private final Resources mResources;
-   private final DistortedScreen mScreen;
-
-   private boolean mFirstRender;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   PurchaseRenderer(PurchaseSurfaceView v)
-     {
-     final float BRIGHTNESS = 0.333f;
-
-     mView = v;
-     mResources = v.getResources();
-
-     mFirstRender = true;
-     mScreen = new DistortedScreen();
-     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @Override
-   public void onDrawFrame(GL10 glUnused)
-     {
-     long time = System.currentTimeMillis();
-     mView.getObjectControl().preRender();
-     mScreen.render(time);
-
-     if( mFirstRender )
-       {
-       mFirstRender=false;
-       mView.getObjectControl().presentObject(NUM_SCRAMBLES, DURATION);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @Override
-   public void onSurfaceChanged(GL10 glUnused, int width, int height)
-      {
-      mScreen.resize(width,height);
-      mView.setScreenSize(width,height);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   DistortedScreen getScreen()
-     {
-     return mScreen;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @Override
-   public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
-      {
-      DistortedLibrary.setMax(EffectType.VERTEX,ObjectControl.MAX_QUATS+1);
-      MeshBase.setMaxEffComponents(ObjectControl.MAX_MOVING_PARTS);
-
-      VertexEffectRotate.enable();
-      VertexEffectQuaternion.enable();
-      BaseEffect.Type.enableEffects();
-      OverlayGeneric.enableEffects();
-
-      DistortedLibrary.onSurfaceCreated(this,1);
-      DistortedLibrary.setCull(true);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void distortedException(Exception ex)
-     {
-     android.util.Log.e("Purchase", "unexpected exception: "+ex.getMessage() );
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public InputStream localFile(int fileID)
-      {
-      return mResources.openRawResource(fileID);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void logMessage(String message)
-      {
-      android.util.Log.e("Purchase", message );
-      }
-}
diff --git a/src/main/java/org/distorted/purchase/PurchaseScreen.java b/src/main/java/org/distorted/purchase/PurchaseScreen.java
deleted file mode 100644
index c760ba04..00000000
--- a/src/main/java/org/distorted/purchase/PurchaseScreen.java
+++ /dev/null
@@ -1,80 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is proprietary software licensed under an EULA which you should have received      //
-// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.purchase;
-
-import android.view.View;
-import android.widget.LinearLayout;
-
-import org.distorted.helpers.TransparentImageButton;
-import org.distorted.main.R;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class PurchaseScreen
-{
-  private TransparentImageButton mBackButton;
-  private boolean mBlocked;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void blockUI()
-    {
-    mBlocked = true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final PurchaseActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
-    mBackButton = new TransparentImageButton(act,R.drawable.ui_smallback,params);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        if( !mBlocked ) act.finish();
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void onAttachedToWindow(final PurchaseActivity act, final int ordinal)
-    {
-    mBlocked = false;
-    int width = act.getScreenWidthInPixels();
-
-    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
-    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams(width/2, LinearLayout.LayoutParams.MATCH_PARENT);
-    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
-
-    LinearLayout layoutLeft = new LinearLayout(act);
-    layoutLeft.setLayoutParams(paramsL);
-    LinearLayout layoutMid  = new LinearLayout(act);
-    layoutMid.setLayoutParams(paramsM);
-    LinearLayout layoutRight= new LinearLayout(act);
-    layoutRight.setLayoutParams(paramsR);
-
-    setupBackButton(act);
-
-    layoutRight.addView(mBackButton);
-
-    LinearLayout layout = act.findViewById(R.id.lowerBar);
-    layout.removeAllViews();
-    layout.addView(layoutLeft);
-    layout.addView(layoutMid);
-    layout.addView(layoutRight);
-
-    PurchaseScreenPane pane = new PurchaseScreenPane(act);
-    pane.updatePane(act,ordinal);
-    }
-}
diff --git a/src/main/java/org/distorted/purchase/PurchaseScreenPane.java b/src/main/java/org/distorted/purchase/PurchaseScreenPane.java
deleted file mode 100644
index ae5e24d6..00000000
--- a/src/main/java/org/distorted/purchase/PurchaseScreenPane.java
+++ /dev/null
@@ -1,279 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is proprietary software licensed under an EULA which you should have received      //
-// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.purchase;
-
-import android.graphics.drawable.Drawable;
-import android.util.TypedValue;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.core.content.res.ResourcesCompat;
-
-import org.distorted.dialogs.RubikDialogStarsStatus;
-import org.distorted.external.RubikScores;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.main.R;
-import org.distorted.objectlib.json.JsonReader;
-import org.distorted.objects.RubikObject;
-import org.distorted.objects.RubikObjectList;
-import org.distorted.overlays.DataStars;
-import org.distorted.overlays.ListenerOverlay;
-import org.distorted.overlays.OverlayStars;
-
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class PurchaseScreenPane implements ListenerOverlay
-{
-  public static final int UNLOCK_ALL_PRICE = 600;
-
-  private static final int[] IMAGES =
-    {
-    R.drawable.difficulty1,
-    R.drawable.difficulty2,
-    R.drawable.difficulty3,
-    R.drawable.difficulty4,
-    R.drawable.difficulty5,
-    };
-
-  private static final int NUM_IMAGES      = IMAGES.length;
-  public  static final float PADDING_RATIO = 0.017f;
-  private static final float TEXT_RATIO_1  = 0.032f;
-  private static final float TEXT_RATIO_2  = 0.020f;
-
-  private final WeakReference<PurchaseActivity> mAct;
-  private RubikObject mObject;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean chargeUser(int amount)
-    {
-    RubikScores scores = RubikScores.getInstance();
-    int numStars = scores.getNumStars();
-
-    if( numStars>=amount )
-      {
-      scores.changeNumStars(-amount);
-      return true;
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void showStatus(PurchaseActivity act)
-    {
-    RubikDialogStarsStatus d = new RubikDialogStarsStatus();
-    d.show(act.getSupportFragmentManager(), null);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void showSuccess(PurchaseActivity act, RubikObject object)
-    {
-    act.blockUI();
-    RubikScores scores = RubikScores.getInstance();
-    int totStars = scores.getNumStars();
-    int price = object==null ? UNLOCK_ALL_PRICE:object.getPrice();
-    PurchaseRenderer renderer = act.getRenderer();
-    DistortedScreen screen = renderer.getScreen();
-    OverlayStars stars = new OverlayStars();
-    DataStars data = new DataStars(totStars+price,-price,act.getResources());
-    stars.startOverlay(screen,this,data);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void oneButtonClicked(PurchaseActivity act)
-    {
-    if( mObject!=null )
-      {
-      int price = mObject.getPrice();
-
-      if( chargeUser(price) )
-        {
-        RubikObjectList.buyObject(mObject);
-        showSuccess(act,mObject);
-        }
-      else
-        {
-        showStatus(act);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void allButtonClicked(PurchaseActivity act)
-    {
-    if( chargeUser(UNLOCK_ALL_PRICE) )
-      {
-      RubikObjectList.buyAll();
-      showSuccess(act,null);
-      }
-    else
-      {
-      showStatus(act);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setUpButtons(PurchaseActivity act, LinearLayout one, LinearLayout all)
-    {
-    ImageButton butO = one.findViewById(R.id.purchaseButtonOne);
-    ImageButton butA = all.findViewById(R.id.purchaseButtonAll);
-
-    int id,price = act.getObjectPrice();
-
-         if( price<=10 ) id = R.drawable.price_10;
-    else if( price<=20 ) id = R.drawable.price_20;
-    else if( price<=30 ) id = R.drawable.price_30;
-    else if( price<=40 ) id = R.drawable.price_40;
-    else if( price<=50 ) id = R.drawable.price_50;
-    else if( price<=60 ) id = R.drawable.price_60;
-    else if( price<=70 ) id = R.drawable.price_70;
-    else if( price<=80 ) id = R.drawable.price_80;
-    else if( price<=90 ) id = R.drawable.price_90;
-    else                 id = R.drawable.price_100;
-
-    Drawable drawable = ResourcesCompat.getDrawable(act.getResources(), id, null);
-    butO.setImageDrawable(drawable);
-
-    butO.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        oneButtonClicked(act);
-        }
-      });
-
-    butA.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        allButtonClicked(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void updatePane(PurchaseActivity act, int objectOrdinal)
-    {
-    mObject = RubikObjectList.getObject(objectOrdinal);
-
-    if( mObject!=null )
-      {
-      InputStream stream = mObject.getObjectStream(act);
-      JsonReader reader = new JsonReader();
-      String author, name;
-      int year, difficulty;
-
-      try
-        {
-        reader.parseJsonFileMetadata(stream);
-        name       = reader.getObjectName();
-        author     = reader.getAuthor();
-        year       = reader.getYearOfInvention();
-        difficulty = (int)reader.getDifficulty();
-        }
-      catch(Exception ex)
-        {
-        name = "?";
-        author = "?";
-        year = 0;
-        difficulty = 0;
-        }
-
-      if( difficulty<0           ) difficulty=0;
-      if( difficulty>=NUM_IMAGES ) difficulty=NUM_IMAGES-1;
-
-      String both = year>0 ? author+" "+year : author;
-
-      TextView v1 = act.findViewById(R.id.purchaseUpperName);
-      v1.setText(name);
-      TextView v2 = act.findViewById(R.id.purchaseUpperAuthor);
-      v2.setText(both);
-      ImageView image = act.findViewById(R.id.purchaseDifficulty);
-      image.setImageResource(IMAGES[difficulty]);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  PurchaseScreenPane(final PurchaseActivity act)
-    {
-    mAct = new WeakReference<>(act);
-    int height = act.getScreenHeightInPixels();
-    float textSize1 = height*TEXT_RATIO_1;
-    float textSize2 = height*TEXT_RATIO_2;
-    int margin = (int)(height*PADDING_RATIO);
-    int padding = margin/3;
-
-    LinearLayout upperBar  = act.findViewById(R.id.upperBar);
-    LinearLayout oneLayout = act.findViewById(R.id.purchaseLayoutOne);
-    LinearLayout allLayout = act.findViewById(R.id.purchaseLayoutAll);
-
-    upperBar.setPadding(   margin,  margin,  margin,  margin);
-    oneLayout.setPadding( padding, padding, padding, padding);
-    allLayout.setPadding( padding, padding, padding, padding);
-
-    LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.00f);
-    params1.bottomMargin = 0;
-    params1.topMargin    = margin;
-    params1.leftMargin   = margin;
-    params1.rightMargin  = margin;
-
-    LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.00f);
-    params2.bottomMargin = margin;
-    params2.topMargin    = margin;
-    params2.leftMargin   = margin;
-    params2.rightMargin  = margin;
-
-    LinearLayout.LayoutParams params3 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.00f);
-    params3.bottomMargin = 0;
-    params3.topMargin    = 0;
-    params3.leftMargin   = margin;
-    params3.rightMargin  = margin;
-
-    upperBar.setLayoutParams(params1);
-    oneLayout.setLayoutParams(params3);
-    allLayout.setLayoutParams(params2);
-
-    TextView text;
-    text = upperBar.findViewById(R.id.purchaseUpperName);
-    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize1);
-    text = upperBar.findViewById(R.id.purchaseUpperAuthor);
-    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize2);
-    text = oneLayout.findViewById(R.id.purchaseTextOne);
-    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize1);
-    text = allLayout.findViewById(R.id.purchaseTextAll);
-    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize1);
-
-    setUpButtons(act,oneLayout,allLayout);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void overlayFinished(long id)
-    {
-    PurchaseActivity act = mAct.get();
-    if( act!=null ) act.finish();
-    }
-}
diff --git a/src/main/java/org/distorted/purchase/PurchaseSurfaceView.java b/src/main/java/org/distorted/purchase/PurchaseSurfaceView.java
deleted file mode 100644
index 985cf8b8..00000000
--- a/src/main/java/org/distorted/purchase/PurchaseSurfaceView.java
+++ /dev/null
@@ -1,129 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is proprietary software licensed under an EULA which you should have received      //
-// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.purchase;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ConfigurationInfo;
-import android.opengl.GLES30;
-import android.opengl.GLSurfaceView;
-import android.util.AttributeSet;
-
-import com.google.firebase.crashlytics.FirebaseCrashlytics;
-
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.objectlib.main.TwistyObjectNode;
-import org.distorted.os.OSInterface;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class PurchaseSurfaceView extends GLSurfaceView
-{
-    private ObjectControl mObjectController;
-    private OSInterface mInterface;
-    private PurchaseRenderer mRenderer;
-    private boolean mCreated;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setScreenSize(int width, int height)
-      {
-      mObjectController.setScreenSizeAndScaling(width,height, Math.min(width,height));
-      mObjectController.setObjectScale(1.00f);
-
-      if( !mCreated )
-        {
-        mCreated = true;
-        mObjectController.createNode(width,height);
-        TwistyObjectNode objectNode = mObjectController.getNode();
-        mRenderer.getScreen().attach(objectNode);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    ObjectControl getObjectControl()
-      {
-      return mObjectController;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    PurchaseRenderer getRenderer()
-      {
-      return mRenderer;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public PurchaseSurfaceView(Context context, AttributeSet attrs)
-      {
-      super(context,attrs);
-
-      mCreated = false;
-
-      if(!isInEditMode())
-        {
-        PurchaseActivity act = (PurchaseActivity)context;
-        PurchaseObjectLibInterface ref = new PurchaseObjectLibInterface();
-        mInterface = new OSInterface(act,ref);
-        mObjectController = new ObjectControl(mInterface);
-        mObjectController.setRotateOnCreation(true);
-        mRenderer = new PurchaseRenderer(this);
-
-        final ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-
-        try
-          {
-          final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
-          int esVersion = configurationInfo.reqGlEsVersion>>16;
-          setEGLContextClientVersion(esVersion);
-          setRenderer(mRenderer);
-          }
-        catch(Exception ex)
-          {
-          act.OpenGLError();
-
-          String shading = GLES30.glGetString(GLES30.GL_SHADING_LANGUAGE_VERSION);
-          String version = GLES30.glGetString(GLES30.GL_VERSION);
-          String vendor  = GLES30.glGetString(GLES30.GL_VENDOR);
-          String renderer= GLES30.glGetString(GLES30.GL_RENDERER);
-
-          FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-          crashlytics.setCustomKey("GLSL Version"  , shading );
-          crashlytics.setCustomKey("GL version"    , version );
-          crashlytics.setCustomKey("GL Vendor "    , vendor  );
-          crashlytics.setCustomKey("GLSL renderer" , renderer);
-          crashlytics.recordException(ex);
-          }
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void onPause()
-      {
-      super.onPause();
-      mObjectController.onPause();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void onResume()
-      {
-      super.onResume();
-      mObjectController.onResume();
-      }
-}
-
diff --git a/src/main/java/org/distorted/solverui/SolverActivity.java b/src/main/java/org/distorted/solverui/SolverActivity.java
index 172410c6..c80a1123 100644
--- a/src/main/java/org/distorted/solverui/SolverActivity.java
+++ b/src/main/java/org/distorted/solverui/SolverActivity.java
@@ -16,8 +16,6 @@ import android.view.DisplayCutout;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
-import androidx.preference.PreferenceManager;
-
 import org.distorted.dialogs.RubikDialogError;
 import org.distorted.dialogs.RubikDialogMessage;
 import org.distorted.helpers.BaseActivity;
@@ -116,8 +114,7 @@ public class SolverActivity extends BaseActivity
 
       createObject();
 
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      restorePreferences(preferences);
+      restorePreferences();
       ScreenList.setScreen(this);
       }
 
@@ -134,8 +131,7 @@ public class SolverActivity extends BaseActivity
 
     private void savePreferences()
       {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      SharedPreferences.Editor editor = preferences.edit();
+      SharedPreferences.Editor editor = mPreferences.edit();
 
       for( int i=0; i< ScreenList.LENGTH; i++ )
         {
@@ -152,16 +148,16 @@ public class SolverActivity extends BaseActivity
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    private void restorePreferences(SharedPreferences preferences)
+    private void restorePreferences()
       {
       for( int i=0; i<ScreenList.LENGTH; i++ )
         {
-        ScreenList.getScreen(i).getScreenClass().restorePreferences(preferences);
+        ScreenList.getScreen(i).getScreenClass().restorePreferences(mPreferences);
         }
 
-      ScreenList.restorePreferences(preferences);
+      ScreenList.restorePreferences(mPreferences);
 
-      mDisplayMessageDialog = preferences.getBoolean("solverDisplayDialog",true);
+      mDisplayMessageDialog = mPreferences.getBoolean("solverDisplayDialog",true);
 
       if( mDisplayMessageDialog )
         {
diff --git a/src/main/res/layout/main.xml b/src/main/res/layout/main.xml
index ab71e90c..6a64899e 100644
--- a/src/main/res/layout/main.xml
+++ b/src/main/res/layout/main.xml
@@ -13,7 +13,7 @@
         android:layout_weight="0.00"
         android:gravity="center"
         android:orientation="horizontal"
-        android:background="@color/dark_grey">
+        android:background="?darkBackground">
     </LinearLayout>
 
     <LinearLayout
@@ -24,7 +24,7 @@
         android:gravity="center"
         android:weightSum="1.0"
         android:orientation="horizontal"
-        android:background="@color/dark_grey"
+        android:background="?darkBackground"
         android:baselineAligned="false">
 
         <LinearLayout
@@ -94,7 +94,7 @@
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="0.84"
-        android:background="@color/grey">
+        android:background="?lightBackground">
     </ScrollView>
 
     <LinearLayout
@@ -103,7 +103,7 @@
         android:layout_height="0dp"
         android:layout_weight="0.08"
         android:orientation="horizontal"
-        android:background="@color/dark_grey">
+        android:background="?darkBackground">
 
         <RelativeLayout
             android:id="@+id/bottomLayout"
diff --git a/src/main/res/layout/purchase.xml b/src/main/res/layout/purchase.xml
deleted file mode 100644
index 6e8dd63e..00000000
--- a/src/main/res/layout/purchase.xml
+++ /dev/null
@@ -1,128 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/mainLayout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/light_grey"
-    android:orientation="vertical">
-
-    <LinearLayout
-         android:id="@+id/upperBar"
-         android:layout_width="fill_parent"
-         android:layout_height="100dp"
-         android:paddingLeft="5dp"
-         android:paddingRight="5dp"
-         android:background="@color/grey"
-         android:orientation="horizontal">
-
-         <LinearLayout
-             android:id="@+id/upperBarLeft"
-             android:layout_width="0dp"
-             android:layout_height="match_parent"
-             android:layout_weight="3.0"
-             android:gravity="center_vertical|start"
-             android:background="@color/grey"
-             android:orientation="vertical">
-
-             <TextView
-                 android:id="@+id/purchaseUpperName"
-                 android:layout_width="match_parent"
-                 android:layout_height="wrap_content"
-                 android:paddingStart="5dp"
-                 android:textSize="26sp"
-                 android:singleLine="true"
-                 android:maxLines="1"/>
-             <TextView
-                 android:id="@+id/purchaseUpperAuthor"
-                 android:layout_width="match_parent"
-                 android:layout_height="wrap_content"
-                 android:paddingStart="5dp"
-                 android:textSize="26sp"
-                 android:singleLine="true"
-                 android:maxLines="1"/>
-         </LinearLayout>
-
-         <ImageView
-             android:id="@+id/purchaseDifficulty"
-             android:layout_width="0dp"
-             android:layout_height="match_parent"
-             android:layout_weight="1.0"/>
-
-    </LinearLayout>
-
-    <org.distorted.purchase.PurchaseSurfaceView
-        android:id="@+id/purchaseSurfaceView"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
-
-    <LinearLayout
-        android:id="@+id/purchaseLayoutOne"
-        android:layout_width="fill_parent"
-        android:layout_height="10dp"
-        android:paddingLeft="5dp"
-        android:paddingRight="5dp"
-        android:background="@color/grey"
-        android:orientation="horizontal">
-
-        <TextView
-            android:id="@+id/purchaseTextOne"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="2.0"
-            android:gravity="center_vertical|start"
-            android:paddingStart="5dp"
-            android:textSize="26sp"
-            android:text="@string/buy_one"/>
-
-        <ImageButton
-            android:id="@+id/purchaseButtonOne"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1.0"
-            android:gravity="center_vertical|end"
-            android:backgroundTint="@color/dark_white"
-            android:scaleType="fitCenter"
-            android:src="@drawable/price_50"/>
-
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/purchaseLayoutAll"
-        android:layout_width="fill_parent"
-        android:layout_height="10dp"
-        android:paddingLeft="5dp"
-        android:paddingRight="5dp"
-        android:background="@color/grey"
-        android:orientation="horizontal">
-
-        <TextView
-            android:id="@+id/purchaseTextAll"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="2.0"
-            android:gravity="center_vertical|start"
-            android:paddingStart="5dp"
-            android:textSize="26sp"
-            android:text="@string/buy_all"/>
-
-        <ImageButton
-            android:id="@+id/purchaseButtonAll"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1.0"
-            android:gravity="center_vertical|end"
-            android:backgroundTint="@color/dark_white"
-            android:scaleType="fitCenter"
-            android:src="@drawable/price_600"/>
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/lowerBar"
-        android:layout_width="match_parent"
-        android:layout_height="100dp"
-        android:layout_gravity="end"
-        android:orientation="horizontal"
-        android:background="@color/light_grey">
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/src/main/res/layout/settings_popup.xml b/src/main/res/layout/settings_popup.xml
index 0d32a3d9..44aadfcc 100644
--- a/src/main/res/layout/settings_popup.xml
+++ b/src/main/res/layout/settings_popup.xml
@@ -7,20 +7,20 @@
    android:orientation="vertical">
 
    <TextView
-        android:id="@+id/sortTitle"
+        android:id="@+id/settingsTitle"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="@string/settings_title"
         android:background="@color/light_grey"
         android:gravity="center"/>
 
-   <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:orientation="horizontal">
+   <GridLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:rowCount="2"
+        android:columnCount="2">
 
-       <TextView
+        <TextView
             android:id="@+id/sortText"
             android:layout_marginStart="5dp"
             android:layout_marginEnd="25dp"
@@ -29,11 +29,26 @@
             android:text="@string/sort_by"
             android:gravity="start|center_vertical"/>
 
-       <Spinner
+        <Spinner
             android:id="@+id/sortMethod"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
-            android:gravity="end|center_vertical"/>
+            android:gravity="center|center_vertical"/>
+
+        <TextView
+            android:id="@+id/themeText"
+            android:layout_marginStart="5dp"
+            android:layout_marginEnd="25dp"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:text="@string/theme"
+            android:gravity="start|center_vertical"/>
+
+        <Spinner
+            android:id="@+id/themeValue"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:gravity="center|center_vertical"/>
 
-   </LinearLayout>
+   </GridLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/src/main/res/layout/settings_popup_android25.xml b/src/main/res/layout/settings_popup_android25.xml
index daf5dafc..45e64554 100644
--- a/src/main/res/layout/settings_popup_android25.xml
+++ b/src/main/res/layout/settings_popup_android25.xml
@@ -7,7 +7,7 @@
    android:orientation="vertical">
 
    <TextView
-        android:id="@+id/sortTitle"
+        android:id="@+id/settingsTitle"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="@string/settings_title"
@@ -37,4 +37,27 @@
             android:gravity="end|center_vertical"/>
 
    </LinearLayout>
+
+   <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+       <TextView
+            android:id="@+id/themeText"
+            android:layout_marginStart="5dp"
+            android:layout_marginEnd="25dp"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:text="@string/theme"
+            android:gravity="start|center_vertical"/>
+
+       <Spinner
+            android:id="@+id/themeValue"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:gravity="end|center_vertical"/>
+
+   </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/src/main/res/values/attr.xml b/src/main/res/values/attr.xml
new file mode 100644
index 00000000..680ed657
--- /dev/null
+++ b/src/main/res/values/attr.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+<attr name="darkBackground" format="reference|color" />
+<attr name="lightBackground" format="reference|color" />
+
+</resources>
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
index 94f69b87..93625bfa 100644
--- a/src/main/res/values/colors.xml
+++ b/src/main/res/values/colors.xml
@@ -4,7 +4,8 @@
     <color name="colorPrimaryDark">#00574B</color>
     <color name="colorAccent">#D81B60</color>
     <color name="red">#ffff0000</color>
-    <color name="green">#ff00bb00</color>
+    <color name="dark_green">#ff009900</color>
+    <color name="green">#ff00cc00</color>
     <color name="dark_grey">#ff222222</color>
     <color name="grey">#ff333333</color>
     <color name="light_grey">#ff555555</color>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 877354d5..0537dd56 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -55,8 +55,6 @@
     <string name="contact">Contact us</string>
     <string name="email">Report a bug, suggest a feature:</string>
     <string name="exit_app">Exit App?</string>
-    <string name="sort_by">Sort by</string>
-    <string name="settings_title">Settings</string>
 
     <string name="stars">Stars</string>
     <string name="scores">High Scores</string>
@@ -118,6 +116,14 @@
     <string name="bandage_message">Bandage the cube by touching it.</string>
     <string name="solver_message">Set up scrambled position by touching the stickers.</string>
 
+    <string name="theme">Theme</string>
+    <string name="theme_white">White</string>
+    <string name="theme_grey">Grey</string>
+    <string name="theme_green">Green</string>
+
+    <string name="sort_by">Sort by</string>
+    <string name="settings_title">Settings</string>
+
     <string name="sort_classic">classic</string>
     <string name="sort_shape">shape</string>
     <string name="sort_difficulty">difficulty</string>
diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml
index 3f800ec5..b8684057 100644
--- a/src/main/res/values/styles.xml
+++ b/src/main/res/values/styles.xml
@@ -1,6 +1,6 @@
 <resources>
 
-   <style name="MaterialThemeNoActionBar" parent="@style/Theme.MaterialComponents.NoActionBar">
+   <style name="BaseMaterialThemeNoActionBar" parent="@style/Theme.MaterialComponents.NoActionBar">
         <item name="tabBackground">@drawable/tab_background</item>
         <item name="tabIndicatorHeight">0dp</item>
         <item name="android:windowNoTitle">true</item>
@@ -12,6 +12,21 @@
         <item name="buttonBarPositiveButtonStyle">@style/PositiveButtonStyle</item>
    </style>
 
+   <style name="GreyTheme" parent="@style/BaseMaterialThemeNoActionBar">
+        <item name="darkBackground">@color/dark_grey</item>
+        <item name="lightBackground">@color/grey</item>
+   </style>
+
+   <style name="WhiteTheme" parent="@style/BaseMaterialThemeNoActionBar">
+        <item name="darkBackground">@color/dark_white</item>
+        <item name="lightBackground">@color/white</item>
+   </style>
+
+   <style name="GreenTheme" parent="@style/BaseMaterialThemeNoActionBar">
+        <item name="darkBackground">@color/dark_green</item>
+        <item name="lightBackground">@color/green</item>
+   </style>
+
    <style name="NegativeButtonStyle" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
       <item name="android:textColor">#ffffff</item>
    </style>
