commit 1c04d05486f7f1ada942e72cba2b1be4bfe01d97
Author: leszek <leszek@koltunski.pl>
Date:   Sat Oct 28 00:01:27 2023 +0200

    Major progress to version 2.0.0.

diff --git a/build.gradle b/build.gradle
index e997a5b7..9308a96e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,8 +15,8 @@ android {
         applicationId "org.distorted.magic"
         minSdkVersion 21
         targetSdkVersion 34
-        versionCode 86
-        versionName "1.13.4"
+        versionCode 87
+        versionName "2.0.0"
     }
 
     buildTypes {
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 1bf2e80b..359f9f35 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -20,7 +20,7 @@
             android:value="${crashlyticsCollectionEnabled}"
         />
 
-        <activity android:name="org.distorted.main.RubikActivity" android:exported="true" android:screenOrientation="portrait">
+        <activity android:name="org.distorted.main.MainActivity" android:exported="true" android:screenOrientation="portrait">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java b/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java
index ee267e39..f46b8fb0 100644
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java
@@ -31,8 +31,9 @@ import androidx.preference.PreferenceManager;
 import org.distorted.dialogs.RubikDialogError;
 import org.distorted.external.RubikFiles;
 import org.distorted.library.main.DistortedLibrary;
+import org.distorted.main.MainActivity;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.main.InitAssets;
 import org.distorted.objectlib.main.TwistyJson;
 import org.distorted.objectlib.main.TwistyObject;
@@ -43,7 +44,7 @@ import org.distorted.os.OSInterface;
 public class BandagedCreatorActivity extends AppCompatActivity
 {
     private static final int ACTIVITY_NUMBER    = 3;
-    private static final float RATIO_BAR        = 0.10f;
+    private static final float RATIO_BAR        = MainActivity.RATIO_BAR;
     private static final float RATIO_BUT        = 0.07f;
     static final float RATIO_SCROLL             = 0.30f;
     public static final float SPINNER_TEXT_SIZE = 0.03f;
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorObjectView.java b/src/main/java/org/distorted/bandaged/BandagedCreatorObjectView.java
index 2b9a8cf4..11b98323 100644
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorObjectView.java
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorObjectView.java
@@ -18,7 +18,6 @@ import org.distorted.dialogs.RubikDialogBandagedDelete;
 import org.distorted.helpers.TransparentButton;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java b/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
index 82ac67a6..ab98a725 100644
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
+++ b/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
@@ -29,7 +29,7 @@ import android.widget.TextView;
 import org.distorted.external.RubikFiles;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.bandaged.LocallyBandagedList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java b/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java
index 301d5dcd..94e79110 100644
--- a/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java
+++ b/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java
@@ -25,8 +25,9 @@ import androidx.preference.PreferenceManager;
 import org.distorted.dialogs.RubikDialogError;
 import org.distorted.external.RubikFiles;
 import org.distorted.library.main.DistortedLibrary;
+import org.distorted.main.MainActivity;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.main.InitAssets;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objectlib.main.TwistyObject;
@@ -38,7 +39,7 @@ import java.io.InputStream;
 public class BandagedPlayActivity extends AppCompatActivity
 {
     private static final int ACTIVITY_NUMBER = 4;
-    private static final float RATIO_BAR     = 0.10f;
+    private static final float RATIO_BAR     = MainActivity.RATIO_BAR;
     private static final float RATIO_INSET   = 0.09f;
     public static final int FLAGS            = RubikActivity.FLAGS;
 
diff --git a/src/main/java/org/distorted/config/ConfigActivity.java b/src/main/java/org/distorted/config/ConfigActivity.java
index 2f3ab744..0994c931 100644
--- a/src/main/java/org/distorted/config/ConfigActivity.java
+++ b/src/main/java/org/distorted/config/ConfigActivity.java
@@ -21,7 +21,8 @@ import android.widget.LinearLayout;
 import androidx.appcompat.app.AppCompatActivity;
 
 import org.distorted.library.main.DistortedLibrary;
-import org.distorted.main.RubikActivity;
+import org.distorted.main.MainActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.main.InitAssets;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.main.R;
@@ -35,7 +36,7 @@ import org.distorted.objects.RubikObjectList;
 public class ConfigActivity extends AppCompatActivity
 {
     private static final int ACTIVITY_NUMBER = 2;
-    private static final float RATIO_BAR  = 0.10f;
+    private static final float RATIO_BAR  = MainActivity.RATIO_BAR;
     public static final int FLAGS = RubikActivity.FLAGS;
 
     private static int mScreenWidth, mScreenHeight;
diff --git a/src/main/java/org/distorted/config/ConfigScreen.java b/src/main/java/org/distorted/config/ConfigScreen.java
index 084c8286..8d69f260 100644
--- a/src/main/java/org/distorted/config/ConfigScreen.java
+++ b/src/main/java/org/distorted/config/ConfigScreen.java
@@ -24,7 +24,7 @@ import org.distorted.helpers.PopupCreator;
 
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogAbandon.java b/src/main/java/org/distorted/dialogs/RubikDialogAbandon.java
index 3288af94..7a94d836 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogAbandon.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogAbandon.java
@@ -15,7 +15,7 @@ import android.view.View;
 import androidx.fragment.app.FragmentActivity;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.screens.ScreenList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogAbout.java b/src/main/java/org/distorted/dialogs/RubikDialogAbout.java
index 2a5e3474..ac222ba5 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogAbout.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogAbout.java
@@ -9,6 +9,7 @@
 
 package org.distorted.dialogs;
 
+import android.app.Activity;
 import android.app.Dialog;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
@@ -26,17 +27,16 @@ import androidx.fragment.app.FragmentActivity;
 
 import org.distorted.main.BuildConfig;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class RubikDialogAbout extends RubikDialogAbstract
   {
   private static final String WHATS_NEW =
-      "1. Seriously lower memory usage - we were getting an increasing amount of crashes due to phones running out of memory.\n";
+      "1. New puzzle-centric UI. All actions one can do to a given puzzle in one context menu. ";
 
   private static final String WHATS_COMING =
-      "1. Next: version 2.0 with a completely new main screen and new UI.\n" +
+      "1. Improvements to the new UI - possibility to sort the puzzles by various criteria.\n" +
       "2. Support for adjustable stickers (not only the colors, but support for texturing the whole cube with images of your choice).\n"  +
       "3. Algorithmic solvers. (sub-optimal solvers for larger puzzles such as the 4x4)\n" +
       "4. iOS version (no time for this, anyone can help? Code is open-source)\n" +
@@ -55,7 +55,7 @@ public class RubikDialogAbout extends RubikDialogAbstract
       {
       WindowManager.LayoutParams params = window.getAttributes();
       params.width  = (int)Math.min( mHeight*0.60f,mWidth*0.90f );
- //     params.height = (int)(mHeight*0.85f);
+      params.height = (int)(mHeight*0.90f);
       window.setAttributes(params);
       }
     }
@@ -72,7 +72,7 @@ public class RubikDialogAbout extends RubikDialogAbstract
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private String findCurrentVersion(RubikActivity act)
+  private String findCurrentVersion(Activity act)
     {
     String version;
     try
@@ -91,11 +91,11 @@ public class RubikDialogAbout extends RubikDialogAbstract
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   @Override
-  String getTitleString(FragmentActivity act)
+  String getTitleString(FragmentActivity fact)
     {
     Resources res= getResources();
-    RubikActivity ract= (RubikActivity) getContext();
-    String version= ract!=null ? findCurrentVersion(ract) : "unknown";
+    Activity act= (Activity) getContext();
+    String version= act!=null ? findCurrentVersion(act) : "unknown";
     return res.getString(R.string.ab_placeholder,version);
     }
 
@@ -170,7 +170,7 @@ public class RubikDialogAbout extends RubikDialogAbstract
     {
     Resources res = act.getResources();
     String[] email = { res.getString(R.string.email_address) };
-    String version = findCurrentVersion((RubikActivity)act);
+    String version = findCurrentVersion((Activity)act);
     String name = res.getString(R.string.app_name);
 
     Intent intent = new Intent(Intent.ACTION_SENDTO);
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogAbstract.java b/src/main/java/org/distorted/dialogs/RubikDialogAbstract.java
index 105b6fec..5349d3b2 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogAbstract.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogAbstract.java
@@ -26,7 +26,7 @@ import androidx.appcompat.app.AppCompatDialogFragment;
 import androidx.fragment.app.FragmentActivity;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogCreatorView.java b/src/main/java/org/distorted/dialogs/RubikDialogCreatorView.java
index 68812b9a..49b0f07d 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogCreatorView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogCreatorView.java
@@ -16,8 +16,8 @@ import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import org.distorted.main.MainActivity;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -27,7 +27,7 @@ public class RubikDialogCreatorView
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public RubikDialogCreatorView(final RubikActivity act, final RubikDialogCreators dialog,
+  public RubikDialogCreatorView(final MainActivity act, final RubikDialogCreators dialog,
                                 final int index, int icon, int title, int desc, int padding, int fontSize,
                                 LinearLayout.LayoutParams pView, LinearLayout.LayoutParams pText, LinearLayout.LayoutParams pButt )
     {
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogCreators.java b/src/main/java/org/distorted/dialogs/RubikDialogCreators.java
index 5b16bac5..32648bd3 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogCreators.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogCreators.java
@@ -18,8 +18,8 @@ import android.widget.TextView;
 
 import androidx.fragment.app.FragmentActivity;
 
+import org.distorted.main.MainActivity;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -88,6 +88,7 @@ public class RubikDialogCreators extends RubikDialogAbstract
       {
       WindowManager.LayoutParams params = window.getAttributes();
       params.width  = (int)Math.min( mHeight*0.65f,mWidth*0.98f );
+      params.height = (int)( mHeight*0.90f);
       window.setAttributes(params);
       }
     }
@@ -125,7 +126,7 @@ public class RubikDialogCreators extends RubikDialogAbstract
     pB.setMargins(0,2*margin,0,0);
 
     int num = BandagedObjectDescription.NUM_OBJECTS;
-    RubikActivity ract = (RubikActivity) getContext();
+    MainActivity ract = (MainActivity) getContext();
 
     for(int i=0; i<num; i++)
       {
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogExit.java b/src/main/java/org/distorted/dialogs/RubikDialogExit.java
new file mode 100644
index 00000000..d766c5d4
--- /dev/null
+++ b/src/main/java/org/distorted/dialogs/RubikDialogExit.java
@@ -0,0 +1,39 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 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.dialogs;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.view.View;
+
+import androidx.fragment.app.FragmentActivity;
+
+import org.distorted.main.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikDialogExit extends RubikDialogAbstract
+  {
+  public int getResource()      { return R.layout.exit_app; }
+  public int getTitleResource() { return -1; }
+  public boolean hasArgument()  { return false; }
+  public int getPositive()      { return R.string.yes; }
+  public int getNegative()      { return R.string.no; }
+  public void negativeAction()  { }
+  public void prepareBody(Dialog dialog, View view, FragmentActivity act, float size) { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void positiveAction()
+    {
+    final Activity act = (Activity)getContext();
+    if( act!=null ) act.finish();
+    }
+  }
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogNewRecord.java b/src/main/java/org/distorted/dialogs/RubikDialogNewRecord.java
index 29d32c6e..23c81fc2 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogNewRecord.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogNewRecord.java
@@ -18,7 +18,7 @@ import android.view.View;
 import android.widget.TextView;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.external.RubikScores;
 import org.distorted.screens.ScreenList;
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPattern.java b/src/main/java/org/distorted/dialogs/RubikDialogPattern.java
index 27e3baa4..057c5720 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogPattern.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPattern.java
@@ -21,7 +21,7 @@ import android.view.WindowManager;
 import android.widget.ImageView;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 import org.distorted.objectlib.patterns.RubikPatternList;
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPatternListAdapter.java b/src/main/java/org/distorted/dialogs/RubikDialogPatternListAdapter.java
index da45fb01..26e29161 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogPatternListAdapter.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPatternListAdapter.java
@@ -17,7 +17,7 @@ import android.view.ViewGroup;
 import android.widget.BaseExpandableListAdapter;
 import android.widget.TextView;
 
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.patterns.RubikPattern;
 import org.distorted.main.R;
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java b/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
index 2444c23a..040874f3 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPatternView.java
@@ -21,7 +21,7 @@ import org.distorted.objectlib.patterns.RubikPattern;
 import org.distorted.objectlib.patterns.RubikPatternList;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.screens.ScreenList;
 import org.distorted.screens.RubikScreenPattern;
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPrivacy.java b/src/main/java/org/distorted/dialogs/RubikDialogPrivacy.java
index 6d858106..c6fbc5bf 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogPrivacy.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPrivacy.java
@@ -18,7 +18,6 @@ import android.widget.TextView;
 import androidx.fragment.app.FragmentActivity;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogScores.java b/src/main/java/org/distorted/dialogs/RubikDialogScores.java
index 52c8a3a3..ec682f70 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogScores.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogScores.java
@@ -21,7 +21,7 @@ import android.view.WindowManager;
 import android.widget.ImageView;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 
@@ -44,7 +44,7 @@ public class RubikDialogScores extends RubikDialogAbstract
       {
       WindowManager.LayoutParams params = window.getAttributes();
       params.width  = (int)Math.min( mHeight*0.65f,mWidth*0.98f );
-      params.height = (int)( mHeight*0.77f);
+      params.height = (int)( mHeight*0.90f);
       window.setAttributes(params);
       }
     }
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogScoresView.java b/src/main/java/org/distorted/dialogs/RubikDialogScoresView.java
index 0a4da862..c1802d34 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogScoresView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogScoresView.java
@@ -21,7 +21,7 @@ import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.external.RubikScores;
 
 import static org.distorted.external.RubikNetwork.MAX_PLACES;
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogSetName.java b/src/main/java/org/distorted/dialogs/RubikDialogSetName.java
index b944b786..94d091e1 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogSetName.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogSetName.java
@@ -23,7 +23,7 @@ import android.widget.EditText;
 import android.widget.TextView;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.external.RubikScores;
 import org.distorted.screens.ScreenList;
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogSolved.java b/src/main/java/org/distorted/dialogs/RubikDialogSolved.java
index f20ff744..9643de2b 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogSolved.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogSolved.java
@@ -17,7 +17,7 @@ import android.view.View;
 import android.widget.TextView;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.screens.ScreenList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogSolverView.java b/src/main/java/org/distorted/dialogs/RubikDialogSolverView.java
index 3eedde46..00b41f69 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogSolverView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogSolverView.java
@@ -17,7 +17,7 @@ import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 import org.distorted.screens.ScreenList;
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogSolvers.java b/src/main/java/org/distorted/dialogs/RubikDialogSolvers.java
index 395062ec..580c9ad8 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogSolvers.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogSolvers.java
@@ -19,7 +19,7 @@ import android.widget.TextView;
 import androidx.fragment.app.FragmentActivity;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.solvers.ImplementedSolversList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogTutorial.java b/src/main/java/org/distorted/dialogs/RubikDialogTutorial.java
index 7b12031c..728536b9 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogTutorial.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogTutorial.java
@@ -22,7 +22,7 @@ import androidx.viewpager.widget.ViewPager;
 import com.google.android.material.tabs.TabLayout;
 
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogTutorialView.java b/src/main/java/org/distorted/dialogs/RubikDialogTutorialView.java
index 1d696493..408b83f2 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogTutorialView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogTutorialView.java
@@ -27,7 +27,7 @@ import com.google.firebase.analytics.FirebaseAnalytics;
 
 import org.distorted.main.BuildConfig;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.json.JsonReader;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogUpdateView.java b/src/main/java/org/distorted/dialogs/RubikDialogUpdateView.java
index f89412c3..cf77e074 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogUpdateView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogUpdateView.java
@@ -31,10 +31,8 @@ import org.distorted.external.RubikNetwork;
 import org.distorted.external.RubikUpdates;
 import org.distorted.objectlib.json.JsonReader;
 import org.distorted.objects.RubikObjectList;
-import org.distorted.screens.RubikScreenPlay;
-import org.distorted.screens.ScreenList;
 
-import static org.distorted.main.RubikActivity.SHOW_DOWNLOADED_DEBUG;
+import static org.distorted.main_old.RubikActivity.SHOW_DOWNLOADED_DEBUG;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -231,14 +229,9 @@ public class RubikDialogUpdateView implements RubikNetwork.Downloadee
 
               if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "3");
 
-              RubikScreenPlay play = (RubikScreenPlay)ScreenList.PLAY.getScreenClass();
-              play.recreatePopup();
-
-              if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "4");
-
               makeProgress(100,R.string.success);
 
-              if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "5");
+              if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "4");
               }
             else
               {
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogUpdates.java b/src/main/java/org/distorted/dialogs/RubikDialogUpdates.java
index 0fc3da67..5705d220 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogUpdates.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogUpdates.java
@@ -170,6 +170,20 @@ public class RubikDialogUpdates extends RubikDialogAbstract implements RubikNetw
     if( act!=null ) mText.setText(act.getString(R.string.networkError));
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void objectDownloaded(String shortName)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getType()
+    {
+    return 1;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void iconDownloaded(int ordinal, Bitmap icon, boolean downloaded)
diff --git a/src/main/java/org/distorted/external/RubikNetwork.java b/src/main/java/org/distorted/external/RubikNetwork.java
index b54c9c4e..885701b2 100644
--- a/src/main/java/org/distorted/external/RubikNetwork.java
+++ b/src/main/java/org/distorted/external/RubikNetwork.java
@@ -18,6 +18,7 @@ import java.net.URL;
 import java.net.UnknownHostException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
 
 import android.app.Activity;
 import android.content.Context;
@@ -26,11 +27,10 @@ import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 
-import org.distorted.library.main.DistortedLibrary;
 import org.distorted.objectlib.json.JsonWriter;
 import org.distorted.objects.RubikObjectList;
 
-import static org.distorted.main.RubikActivity.SHOW_DOWNLOADED_DEBUG;
+import static org.distorted.main_old.RubikActivity.SHOW_DOWNLOADED_DEBUG;
 import static org.distorted.screens.RubikScreenPlay.LEVELS_SHOWN;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -51,7 +51,9 @@ public class RubikNetwork
 
   public interface Updatee
     {
+    int getType();
     void receiveUpdate(RubikUpdates update);
+    void objectDownloaded(String shortName);
     void errorUpdate();
     }
 
@@ -62,14 +64,9 @@ public class RubikNetwork
 
   public static final int MAX_PLACES = 10;
 
-  private static final int REND_ADRENO= 0;
-  private static final int REND_MALI  = 1;
-  private static final int REND_POWER = 2;
-  private static final int REND_OTHER = 3;
-
-  private static final int DEBUG_RUNNING = 1;
-  private static final int DEBUG_SUCCESS = 2;
-  private static final int DEBUG_FAILURE = 3;
+  private static final int UPDATES_RUNNING = 1;
+  private static final int UPDATES_SUCCESS = 2;
+  private static final int UPDATES_FAILURE = 3;
 
   private final String[] hex = {
     "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
@@ -116,11 +113,11 @@ public class RubikNetwork
   private static RubikNetwork mThis;
   private static String mScores = "";
   private static boolean mRunning = false;
-  private static Updatee mUpdatee;
+  private static ArrayList<Updatee> mUpdateeList;
   private static String mVersion;
   private static int mNumObjects;
   private static RubikUpdates mUpdates;
-  private static int mDebugState;
+  private static int mUpdatesState;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -285,60 +282,6 @@ public class RubikNetwork
       }
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getRendererType(String renderer)
-    {
-    if( renderer.contains("Adreno")  ) return REND_ADRENO;
-    if( renderer.contains("Mali")    ) return REND_MALI;
-    if( renderer.contains("PowerVR") ) return REND_POWER;
-
-    return REND_OTHER;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String parseRenderer(final int type, String renderer)
-    {
-    if( type==REND_ADRENO || type==REND_POWER )
-      {
-      int lastSpace = renderer.lastIndexOf(' ');
-      String ret = renderer.substring(lastSpace+1);
-      return URLencode(ret);
-      }
-
-    if( type==REND_MALI )
-      {
-      int firstHyphen = renderer.indexOf('-');
-      String ret = renderer.substring(firstHyphen+1);
-      return URLencode(ret);
-      }
-
-    return "other";
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String parseVersion(final int type, String version)
-    {
-    switch(type)
-      {
-      case REND_ADRENO: int aMonkey = version.indexOf('@');
-                        int aDot = version.indexOf('.', aMonkey);
-                        String ret1 = aDot>=3 ? version.substring(aDot-3,aDot) : "";
-                        return URLencode(ret1);
-      case REND_MALI  : int mV1 = version.indexOf("v1");
-                        int mHyphen = version.indexOf('-', mV1);
-                        String ret2 = mHyphen>mV1+3 && mV1>=0 ? version.substring(mV1+3,mHyphen) : "";
-                        return URLencode(ret2);
-      case REND_POWER : int pMonkey = version.indexOf('@');
-                        int pSpace  = version.lastIndexOf(' ');
-                        String ret3 = pSpace>=0 && pMonkey>pSpace+1 ? version.substring(pSpace+1,pMonkey) : "";
-                        return URLencode(ret3);
-      default         : return "";
-      }
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   private String URLencode(String s)
@@ -456,25 +399,18 @@ public class RubikNetwork
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private String constructDebugURL()
+  private String constructUpdatesURL()
     {
-    RubikScores scores = RubikScores.getInstance();
-    String name = URLencode(scores.getName());
-    int numRuns = scores.getNumRuns();
-    int numPlay = scores.getNumPlays();
-    String country = scores.getCountry();
-    String renderer = DistortedLibrary.getDriverRenderer();
-    String version  = DistortedLibrary.getDriverVersion();
+    RubikScores sco = RubikScores.getInstance();
+    String name     = URLencode(sco.getName());
+    int numRuns     = sco.getNumRuns();
+    int numPlay     = sco.getNumPlays();
+    String country  = sco.getCountry();
     int objectAPI   = JsonWriter.VERSION_OBJECT_APP;
     int tutorialAPI = JsonWriter.VERSION_EXTRAS_APP;
-    int numStars    = scores.getNumStars();
-
-    renderer = URLencode(renderer);
-    version  = URLencode(version);
 
-    String url=SERVER+"debugs.cgi";
-    url += "?n="+name+"&r="+numRuns+"&p="+numPlay+"&c="+country+"&e="+mVersion+"d";
-    url += "&d="+renderer+"&v="+version+"&a="+objectAPI+"&b="+tutorialAPI+"&s="+numStars;
+    String url=SERVER+"updates.cgi";
+    url += "?n="+name+"&r="+numRuns+"&p="+numPlay+"&c="+country+"&e="+mVersion+"d"+"&a="+objectAPI+"&b="+tutorialAPI;
 
     return url;
     }
@@ -507,16 +443,9 @@ public class RubikNetwork
     long epoch = System.currentTimeMillis();
     String salt = "cuboid";
 
-    String renderer = DistortedLibrary.getDriverRenderer();
-    String version  = DistortedLibrary.getDriverVersion();
-
-    int type = getRendererType(renderer);
-    renderer = parseRenderer(type,renderer);
-    version  = parseVersion(type,version);
-
     String url1=SERVER+"submit.cgi";
     String url2 = "n="+name+"&v="+veri+"&r="+numRuns+"&p="+numPlay+"&i="+deviceID+"&e="+mVersion;
-    url2 += "&d="+renderer+"&s="+version+reclist+"&c="+country+"&f="+epoch;
+    url2 += reclist+"&c="+country+"&f="+epoch;
     String hash = computeHash( url2, salt.getBytes() );
 
     return url1 + "?" + url2 + "&h=" + hash;
@@ -608,9 +537,9 @@ public class RubikNetwork
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void debugThread()
+  private void updatesThread()
     {
-    String url = constructDebugURL();
+    String url = constructUpdatesURL();
 
     try
       {
@@ -628,7 +557,7 @@ public class RubikNetwork
       BufferedReader r = new BufferedReader(new InputStreamReader(is));
       StringBuilder answer = new StringBuilder();
 
-      for (String line; (line = r.readLine()) != null; )
+      for( String line; (line = r.readLine()) != null; )
         {
         answer.append(line).append('\n');
         }
@@ -637,13 +566,33 @@ public class RubikNetwork
       conn.disconnect();
       mUpdates.parse(updates);
 
-      if( mUpdatee!=null ) mUpdatee.receiveUpdate(mUpdates);
-      mDebugState = DEBUG_SUCCESS;
+      if( mUpdateeList!=null )
+        {
+        int numUpdatees = mUpdateeList.size();
+
+        for(int u=0; u<numUpdatees; u++)
+          {
+          Updatee upd = mUpdateeList.get(u);
+          upd.receiveUpdate(mUpdates);
+          }
+        }
+
+      mUpdatesState = UPDATES_SUCCESS;
       }
     catch( final Exception e )
       {
-      if( mUpdatee!=null ) mUpdatee.errorUpdate();
-      mDebugState = DEBUG_FAILURE;
+      if( mUpdateeList!=null )
+        {
+        int numUpdatees = mUpdateeList.size();
+
+        for(int u=0; u<numUpdatees; u++)
+          {
+          Updatee upd = mUpdateeList.get(u);
+          upd.errorUpdate();
+          }
+        }
+
+      mUpdatesState = UPDATES_FAILURE;
       }
     }
 
@@ -846,6 +795,8 @@ public class RubikNetwork
   public static void onPause()
     {
     mRunning = false;
+    mUpdateeList.clear();
+    mUpdatesState = UPDATES_RUNNING;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -894,17 +845,17 @@ public class RubikNetwork
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void debug(final Activity act)
+  public void downloadUpdates(final Activity act)
     {
     initializeStatics();
     figureOutVersion(act);
-    mDebugState = DEBUG_RUNNING;
+    mUpdatesState = UPDATES_RUNNING;
 
     Thread thread = new Thread()
       {
       public void run()
         {
-        debugThread();
+        updatesThread();
         }
       };
 
@@ -947,20 +898,35 @@ public class RubikNetwork
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// Yes it can happen that the second Updatee registers before we sent an update to the first one
-// and, as a result, the update never gets sent to the first one. This is not a problem (now, when
-// there are only two updatees - the RubikStatePlay and the UpdateDialog)
-//
-// Yes, there is also a remote possibility that the two threads executing this function and executing
-// the sendDebug() get swapped exactly in unlucky moment and the update never gets to the updatee.
-// We don't care about such remote possibility, then the app simply would signal that there are no
-// updates available.
 
   public void signUpForUpdates(Updatee updatee)
     {
-         if( mDebugState==DEBUG_SUCCESS ) updatee.receiveUpdate(mUpdates);
-    else if( mDebugState==DEBUG_FAILURE ) updatee.errorUpdate();
-    else mUpdatee = updatee;
+    if( mUpdateeList==null ) mUpdateeList = new ArrayList<>();
+
+    int numUpdatees = mUpdateeList.size();
+    int type = updatee.getType();
+
+    for(int u=0; u<numUpdatees; u++)
+      {
+      Updatee upd = mUpdateeList.get(u);
+
+      if( upd.getType()==type )
+        {
+        mUpdateeList.remove(u);
+        break;
+        }
+      }
+
+    mUpdateeList.add(updatee);
+
+    if( mUpdatesState==UPDATES_SUCCESS )
+      {
+      updatee.receiveUpdate(mUpdates);
+      }
+    else if( mUpdatesState==UPDATES_FAILURE )
+      {
+      updatee.errorUpdate();
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1003,5 +969,16 @@ public class RubikNetwork
     {
     mUpdates.updateDone(shortName);
     mScores = "";
+
+    if( mUpdateeList!=null )
+      {
+      int numUpdatees = mUpdateeList.size();
+
+      for(int u=0; u<numUpdatees; u++)
+        {
+        Updatee upd = mUpdateeList.get(u);
+        upd.objectDownloaded(shortName);
+        }
+      }
     }
 }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/external/RubikUpdates.java b/src/main/java/org/distorted/external/RubikUpdates.java
index 027a979e..9795c10d 100644
--- a/src/main/java/org/distorted/external/RubikUpdates.java
+++ b/src/main/java/org/distorted/external/RubikUpdates.java
@@ -17,7 +17,7 @@ import android.content.Context;
 import android.graphics.Bitmap;
 import org.distorted.objects.RubikObjectList;
 
-import static org.distorted.main.RubikActivity.SHOW_DOWNLOADED_DEBUG;
+import static org.distorted.main_old.RubikActivity.SHOW_DOWNLOADED_DEBUG;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/main/MainActivity.java b/src/main/java/org/distorted/main/MainActivity.java
new file mode 100644
index 00000000..8e461c42
--- /dev/null
+++ b/src/main/java/org/distorted/main/MainActivity.java
@@ -0,0 +1,496 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 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.main;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.DisplayCutout;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceManager;
+
+import com.google.firebase.analytics.FirebaseAnalytics;
+import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
+
+import org.distorted.bandaged.BandagedCreatorActivity;
+import org.distorted.config.ConfigActivity;
+import org.distorted.dialogs.RubikDialogAbout;
+import org.distorted.dialogs.RubikDialogCreators;
+import org.distorted.dialogs.RubikDialogExit;
+import org.distorted.dialogs.RubikDialogScores;
+import org.distorted.dialogs.RubikDialogUpdates;
+import org.distorted.external.RubikNetwork;
+import org.distorted.external.RubikScores;
+import org.distorted.external.RubikUpdates;
+import org.distorted.messaging.RubikInAppMessanging;
+import org.distorted.objects.RubikObjectList;
+import org.distorted.purchase.PurchaseActivity;
+import org.distorted.tutorials.TutorialActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class MainActivity extends AppCompatActivity implements RubikNetwork.Updatee
+{
+    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
+                                   | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                                   | View.SYSTEM_UI_FLAG_FULLSCREEN
+                                   | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+
+    private static final float RATIO_INSET= 0.09f;
+
+    private boolean mJustStarted;
+    private FirebaseAnalytics mFirebaseAnalytics;
+    private static int mScreenWidth, mScreenHeight;
+    private int mCurrentApiVersion;
+    private int mHeightUpperBar;
+    private String mOldVersion, mCurrVersion;
+    private int mSolverIndex;
+    private TextView mBubbleUpdates;
+    private int mNumUpdates;
+    private MainScrollGrid mGrid;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void onCreate(Bundle savedState)
+      {
+      super.onCreate(savedState);
+      setTheme(R.style.MaterialThemeNoActionBar);
+      setContentView(R.layout.new_main);
+      hideNavigationBar();
+
+      mJustStarted = true;
+      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
+
+      DisplayMetrics displaymetrics = new DisplayMetrics();
+      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
+      mScreenWidth =displaymetrics.widthPixels;
+      mScreenHeight=displaymetrics.heightPixels;
+
+      cutoutHack();
+      computeHeights();
+
+      mCurrVersion = getAppVers();
+      mSolverIndex = 0;
+
+      Thread thread = new Thread()
+        {
+        public void run()
+          {
+          RubikInAppMessanging listener = new RubikInAppMessanging();
+          FirebaseInAppMessaging.getInstance().addClickListener(listener);
+          }
+        };
+
+      thread.start();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this does not include possible insets
+
+    private void computeHeights()
+      {
+      int barHeight    = (int)(mScreenHeight*RATIO_BAR);
+      int scrollHeight = (int)(mScreenHeight*(1-2*RATIO_BAR));
+      mHeightUpperBar  = barHeight;
+
+      LinearLayout layoutTop = findViewById(R.id.upperBar);
+      ViewGroup.LayoutParams paramsTop = layoutTop.getLayoutParams();
+      paramsTop.height = barHeight;
+      layoutTop.setLayoutParams(paramsTop);
+
+      LinearLayout layoutBot = findViewById(R.id.lowerBar);
+      ViewGroup.LayoutParams paramsBot = layoutBot.getLayoutParams();
+      paramsBot.height = barHeight;
+      layoutBot.setLayoutParams(paramsBot);
+
+      ScrollView scroll = findViewById(R.id.objectScroll);
+      ViewGroup.LayoutParams paramsScroll = scroll.getLayoutParams();
+      paramsScroll.height = scrollHeight;
+      scroll.setLayoutParams(paramsScroll);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void hideNavigationBar()
+      {
+      mCurrentApiVersion = Build.VERSION.SDK_INT;
+
+      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT)
+        {
+        final View decorView = getWindow().getDecorView();
+
+        decorView.setSystemUiVisibility(FLAGS);
+
+        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
+          {
+          @Override
+          public void onSystemUiVisibilityChange(int visibility)
+            {
+            if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
+              {
+              decorView.setSystemUiVisibility(FLAGS);
+              }
+            }
+          });
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onAttachedToWindow()
+      {
+      super.onAttachedToWindow();
+
+      if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.P )
+        {
+        DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
+        int insetHeight = cutout!=null ? cutout.getSafeInsetTop() : 0;
+
+        LinearLayout layoutHid = findViewById(R.id.hiddenBar);
+        ViewGroup.LayoutParams paramsHid = layoutHid.getLayoutParams();
+        paramsHid.height = (int)(insetHeight*RATIO_INSET);
+        layoutHid.setLayoutParams(paramsHid);
+        mHeightUpperBar += paramsHid.height;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// do not avoid cutouts
+
+    private void cutoutHack()
+      {
+      if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.P )
+        {
+        getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus)
+      {
+      super.onWindowFocusChanged(hasFocus);
+
+      if( mCurrentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus )
+        {
+        getWindow().getDecorView().setSystemUiVisibility(FLAGS);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onPause() 
+      {
+      super.onPause();
+      RubikNetwork.onPause();
+      savePreferences();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      restorePreferences(preferences,mJustStarted);
+
+      mBubbleUpdates = findViewById(R.id.bubbleUpdates);
+      mBubbleUpdates.setVisibility(View.INVISIBLE);
+      mNumUpdates = 0;
+
+      RubikNetwork network = RubikNetwork.getInstance();
+      network.signUpForUpdates(this);
+      network.downloadUpdates(this);
+
+      mGrid = new MainScrollGrid();
+      mGrid.createGrid(this,mScreenWidth,mScreenHeight);
+
+      if( mJustStarted )
+        {
+        mJustStarted = false;
+        RubikScores scores = RubikScores.getInstance();
+        scores.incrementNumRuns();
+        scores.setCountry(this);
+        RubikObjectList.restoreMeshState(preferences);
+        }
+
+      if( !mOldVersion.equals(mCurrVersion) ) displayNovelties();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void displayNovelties()
+      {
+      Bundle bundle = new Bundle();
+      bundle.putString("argument",mOldVersion);
+      RubikDialogAbout newDialog = new RubikDialogAbout();
+      newDialog.setArguments(bundle);
+      newDialog.show(getSupportFragmentManager(), RubikDialogAbout.getDialogTag() );
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      super.onDestroy();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private String getAppVers()
+      {
+      try
+        {
+        PackageInfo pInfo = getPackageManager().getPackageInfo( getPackageName(), 0);
+        return pInfo.versionName;
+        }
+      catch( PackageManager.NameNotFoundException e )
+        {
+        return "unknown";
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void savePreferences()
+      {
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      SharedPreferences.Editor editor = preferences.edit();
+
+      editor.putString("appVersion", mCurrVersion );
+      editor.putInt("solverIndex", mSolverIndex );
+
+      RubikObjectList.savePreferences(editor);
+      RubikObjectList.saveMeshState(editor);
+
+      boolean success = editor.commit();
+      if( !success ) android.util.Log.e("D", "Failed to save preferences");
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void restorePreferences(SharedPreferences preferences, boolean justStarted)
+      {
+      mOldVersion = preferences.getString("appVersion","");
+      mSolverIndex = preferences.getInt("solverIndex",0);
+
+      RubikObjectList.restorePreferences(this,preferences,justStarted);
+      RubikScores scores = RubikScores.getInstance();
+
+      if( scores.isVerified() )
+        {
+        FirebaseAnalytics analytics = getAnalytics();
+        analytics.setUserId(scores.getName());
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public FirebaseAnalytics getAnalytics()
+      {
+      return mFirebaseAnalytics;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getHeightUpperBar()
+      {
+      return mHeightUpperBar;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenWidthInPixels()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenHeightInPixels()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToTutorial(String url, int objectOrdinal)
+      {
+      Intent intent = new Intent(this, TutorialActivity.class);
+      intent.putExtra("url", url);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToConfig(int objectOrdinal)
+      {
+      Intent intent = new Intent(this, ConfigActivity.class);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToBandagedCreator(int objectOrdinal)
+      {
+      Intent intent = new Intent(this, BandagedCreatorActivity.class);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToPurchase(int objectOrdinal)
+      {
+      Intent intent = new Intent(this, PurchaseActivity.class);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void setSolverIndex(int index)
+      {
+      mSolverIndex = index;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getSolverIndex()
+      {
+      return mSolverIndex;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void onScores(View v)
+      {
+      Bundle sBundle = new Bundle();
+      sBundle.putString("argument", "false");
+      RubikDialogScores scores = new RubikDialogScores();
+      scores.setArguments(sBundle);
+      scores.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void onUpdates(View v)
+      {
+      RubikDialogUpdates diag = new RubikDialogUpdates();
+      diag.show( getSupportFragmentManager(), RubikDialogUpdates.getDialogTag() );
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void onExit(View v)
+      {
+      RubikDialogExit diag = new RubikDialogExit();
+      diag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void onAbout(View v)
+      {
+      RubikDialogAbout diag = new RubikDialogAbout();
+      diag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void onBandage(View v)
+      {
+      RubikDialogCreators diag = new RubikDialogCreators();
+      diag.show(getSupportFragmentManager(), RubikDialogCreators.getDialogTag() );
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void updateBubble(int num)
+      {
+      runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          mNumUpdates = num;
+
+          if( num>0 )
+            {
+            String shownNum = String.valueOf(num);
+            mBubbleUpdates.setText(shownNum);
+            mBubbleUpdates.setVisibility(View.VISIBLE);
+            int height = (int)(0.05f*mScreenWidth);
+            mBubbleUpdates.setTextSize(TypedValue.COMPLEX_UNIT_PX,height);
+            }
+          else
+            {
+            mBubbleUpdates.setVisibility(View.INVISIBLE);
+            }
+          }
+        });
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void receiveUpdate(RubikUpdates updates)
+      {
+      int num = updates.getCompletedNumber();
+      updateBubble(num);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void objectDownloaded(String shortName)
+      {
+      mNumUpdates--;
+      updateBubble(mNumUpdates);
+      mGrid.updateGrid(this,mScreenWidth,mScreenHeight);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getType()
+      {
+      return 0;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void errorUpdate()
+      {
+      android.util.Log.e("D", "NewRubikActivity: Error receiving downloaded objects update");
+      }
+}
diff --git a/src/main/java/org/distorted/main/MainScrollGrid.java b/src/main/java/org/distorted/main/MainScrollGrid.java
new file mode 100644
index 00000000..b9f526c6
--- /dev/null
+++ b/src/main/java/org/distorted/main/MainScrollGrid.java
@@ -0,0 +1,74 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 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.main;
+
+import android.view.View;
+import android.widget.GridLayout;
+import android.widget.ImageButton;
+
+import org.distorted.helpers.PopupCreator;
+import org.distorted.main_old.RubikActivity;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class MainScrollGrid
+  {
+  public static final int NUM_COLUMNS  = 5;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void createGrid(final MainActivity act, int scrW, int scrH)
+    {
+    int numObjects = RubikObjectList.getNumObjects();
+    int rowCount   = (numObjects + NUM_COLUMNS-1) / NUM_COLUMNS;
+    int colCount   = NUM_COLUMNS;
+    int objectSize = scrW / NUM_COLUMNS;
+    int margin     = (int)(scrH*RubikActivity.POPUP_MARGIN);
+    int padding    = (int)(scrH*RubikActivity.POPUP_PADDING);
+    int cubeSize   = objectSize - 2*margin;
+
+    GridLayout objectGrid = act.findViewById(R.id.objectGrid);
+    PopupCreator.createObjectGrid(objectGrid,act,rowCount,colCount,numObjects,margin,cubeSize,padding);
+
+    for(int child=0; child<numObjects; child++)
+      {
+      final RubikObject obj = RubikObjectList.getObject(child);
+      View v = objectGrid.getChildAt(child);
+      ImageButton button = PopupCreator.getButton(obj,v);
+      final int ordinal = child;
+
+      button.setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View v)
+          {
+          android.util.Log.e("D", "object "+ordinal+" clicked!");
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void updateGrid(final MainActivity act, int scrW, int scrH)
+    {
+    act.runOnUiThread(new Runnable()
+      {
+      @Override
+      public void run()
+        {
+        createGrid(act,scrW,scrH);
+        }
+      });
+    }
+  }
+
diff --git a/src/main/java/org/distorted/main/RubikActivity.java b/src/main/java/org/distorted/main/RubikActivity.java
deleted file mode 100644
index 838f7894..00000000
--- a/src/main/java/org/distorted/main/RubikActivity.java
+++ /dev/null
@@ -1,670 +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.main;
-
-import java.io.InputStream;
-import java.util.Locale;
-
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Bundle;
-
-import android.util.DisplayMetrics;
-import android.view.DisplayCutout;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.LinearLayout;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.preference.PreferenceManager;
-
-import com.google.firebase.analytics.FirebaseAnalytics;
-import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
-
-import org.distorted.config.ConfigActivity;
-import org.distorted.bandaged.BandagedCreatorActivity;
-import org.distorted.dialogs.RubikDialogAbout;
-import org.distorted.library.main.DistortedLibrary;
-
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.messaging.RubikInAppMessanging;
-import org.distorted.objectlib.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.main.InitAssets;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.objectlib.main.TwistyObject;
-
-import org.distorted.dialogs.RubikDialogError;
-import org.distorted.external.RubikScores;
-import org.distorted.external.RubikNetwork;
-import org.distorted.objects.RubikObject;
-import org.distorted.objects.RubikObjectList;
-import org.distorted.os.OSInterface;
-import org.distorted.purchase.PurchaseActivity;
-import org.distorted.screens.RubikScreenSolving;
-import org.distorted.screens.ScreenList;
-import org.distorted.screens.RubikScreenPlay;
-import org.distorted.tutorials.TutorialActivity;
-
-import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikActivity extends AppCompatActivity
-{
-    public static final boolean SHOW_DOWNLOADED_DEBUG = false;
-    public static final boolean SHOW_IAP_DEBUG        = false;
-    public static final boolean USE_IAP               = false;
-
-    public static final float PADDING             = 0.010f;
-    public static final float SMALL_MARGIN        = 0.004f;
-    public static final float BUTTON_TEXT_SIZE    = 0.050f;
-    public static final float TITLE_TEXT_SIZE     = 0.060f;
-    public static final float PATTERN_GROUP_TEXT  = 0.030f;
-    public static final float PATTERN_CHILD_TEXT  = 0.020f;
-    public static final float SCORES_LEVEL_TEXT   = 0.035f;
-    public static final float SCORES_ITEM_TEXT    = 0.025f;
-    public static final float TAB_WIDTH           = 0.066f;
-    public static final float TAB_HEIGHT          = 0.066f;
-    public static final float POPUP_PADDING       = 0.028f;
-    public static final float POPUP_MARGIN        = 0.016f;
-    public static final float POPUP_BOTTOM        = 0.090f;
-    public static final float RATIO_BAR           = 0.100f;
-
-    public static final int FLAGS =  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                                   | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                                   | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
-                                   | View.SYSTEM_UI_FLAG_FULLSCREEN
-                                   | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
-
-    private static final int ACTIVITY_NUMBER = 0;
-    private static final float RATIO_INSET= 0.09f;
-
-    private static final String KEY_PLAY = "movesController_play";
-    private static final String KEY_SOLV = "movesController_solv";
-
-    private boolean mJustStarted;
-    private FirebaseAnalytics mFirebaseAnalytics;
-    private static int mScreenWidth, mScreenHeight;
-    private int mCurrentApiVersion;
-    private int mHeightUpperBar;
-    private int mOldVersion1, mOldVersion2, mOldVersion3;
-    private String mOldVersion, mCurrVersion;
-    private int mSolverIndex;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    protected void onCreate(Bundle savedState)
-      {
-      super.onCreate(savedState);
-      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
-      setTheme(R.style.MaterialThemeNoActionBar);
-      setContentView(R.layout.main);
-      hideNavigationBar();
-
-      mJustStarted = true;
-      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
-
-      DisplayMetrics displaymetrics = new DisplayMetrics();
-      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
-      mScreenWidth =displaymetrics.widthPixels;
-      mScreenHeight=displaymetrics.heightPixels;
-
-      cutoutHack();
-      computeBarHeights();
-
-      mCurrVersion = getAppVers();
-      mSolverIndex = 0;
-
-      Thread thread = new Thread()
-        {
-        public void run()
-          {
-          RubikInAppMessanging listener = new RubikInAppMessanging();
-          FirebaseInAppMessaging.getInstance().addClickListener(listener);
-          }
-        };
-
-      thread.start();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this does not include possible insets
-
-    private void computeBarHeights()
-      {
-      int barHeight = (int)(mScreenHeight*RATIO_BAR);
-      mHeightUpperBar = barHeight;
-
-      LinearLayout layoutTop = findViewById(R.id.upperBar);
-      LinearLayout layoutBot = findViewById(R.id.lowerBar);
-
-      ViewGroup.LayoutParams paramsTop = layoutTop.getLayoutParams();
-      paramsTop.height = mHeightUpperBar;
-      layoutTop.setLayoutParams(paramsTop);
-      ViewGroup.LayoutParams paramsBot = layoutBot.getLayoutParams();
-      paramsBot.height = barHeight;
-      layoutBot.setLayoutParams(paramsBot);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void hideNavigationBar()
-      {
-      mCurrentApiVersion = Build.VERSION.SDK_INT;
-
-      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT)
-        {
-        final View decorView = getWindow().getDecorView();
-
-        decorView.setSystemUiVisibility(FLAGS);
-
-        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
-          {
-          @Override
-          public void onSystemUiVisibilityChange(int visibility)
-            {
-            if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
-              {
-              decorView.setSystemUiVisibility(FLAGS);
-              }
-            }
-          });
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void onAttachedToWindow()
-      {
-      super.onAttachedToWindow();
-
-      if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.P )
-        {
-        DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
-        int insetHeight = cutout!=null ? cutout.getSafeInsetTop() : 0;
-
-        LinearLayout layoutHid = findViewById(R.id.hiddenBar);
-        ViewGroup.LayoutParams paramsHid = layoutHid.getLayoutParams();
-        paramsHid.height = (int)(insetHeight*RATIO_INSET);
-        layoutHid.setLayoutParams(paramsHid);
-        mHeightUpperBar += paramsHid.height;
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// do not avoid cutouts
-
-    private void cutoutHack()
-      {
-      if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.P )
-        {
-        getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus)
-      {
-      super.onWindowFocusChanged(hasFocus);
-
-      if( mCurrentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus )
-        {
-        getWindow().getDecorView().setSystemUiVisibility(FLAGS);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onPause() 
-      {
-      super.onPause();
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      view.onPause();
-      DistortedLibrary.onPause(ACTIVITY_NUMBER);
-      RubikNetwork.onPause();
-      savePreferences();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onResume() 
-      {
-      super.onResume();
-      DistortedLibrary.onResume(ACTIVITY_NUMBER);
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      view.onResume();
-
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      restorePreferences(preferences,mJustStarted);
-      ScreenList.setScreen(this);
-      restoreMoves(preferences);
-
-      if( mJustStarted )
-        {
-        mJustStarted = false;
-        RubikScores scores = RubikScores.getInstance();
-        scores.incrementNumRuns();
-        scores.setCountry(this);
-        RubikObjectList.restoreMeshState(preferences);
-        }
-
-      int object = RubikObjectList.getCurrObject();
-      changeIfDifferent(object,view.getObjectControl());
-
-      if( !mOldVersion.equals(mCurrVersion) ) displayNovelties();
-      else if( USE_IAP ) view.setShowStars();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void displayNovelties()
-      {
-      Bundle bundle = new Bundle();
-      bundle.putString("argument",mOldVersion);
-      RubikDialogAbout newDialog = new RubikDialogAbout();
-      newDialog.setArguments(bundle);
-      newDialog.show(getSupportFragmentManager(), RubikDialogAbout.getDialogTag() );
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onDestroy() 
-      {
-      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
-      super.onDestroy();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private String getAppVers()
-      {
-      try
-        {
-        PackageInfo pInfo = getPackageManager().getPackageInfo( getPackageName(), 0);
-        return pInfo.versionName;
-        }
-      catch (PackageManager.NameNotFoundException e)
-        {
-        return "unknown";
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void savePreferences()
-      {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      SharedPreferences.Editor editor = preferences.edit();
-
-      editor.putString("appVersion", mCurrVersion );
-      editor.putInt("solverIndex", mSolverIndex );
-
-      for( int i=0; i< ScreenList.LENGTH; i++ )
-        {
-        ScreenList.getScreen(i).getScreenClass().savePreferences(editor);
-        }
-
-      RubikObjectList.savePreferences(editor);
-      RubikObjectList.saveMeshState(editor);
-      ScreenList.savePreferences(editor);
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      OSInterface os = view.getInterface();
-      os.setEditor(editor);
-      view.getObjectControl().savePreferences();
-
-      ScreenList curr = ScreenList.getCurrentScreen();
-
-      if( curr==ScreenList.PLAY )
-        {
-        RubikScreenPlay play = (RubikScreenPlay)ScreenList.PLAY.getScreenClass();
-        play.saveMovePreferences(KEY_PLAY,editor);
-        }
-      if( curr==ScreenList.SOLV )
-        {
-        RubikScreenSolving solv = (RubikScreenSolving)ScreenList.SOLV.getScreenClass();
-        solv.saveMovePreferences(KEY_SOLV,editor);
-        }
-
-      boolean success = editor.commit();
-      if( !success ) android.util.Log.e("D", "Failed to save preferences");
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void restorePreferences(SharedPreferences preferences, boolean justStarted)
-      {
-      mOldVersion = preferences.getString("appVersion","");
-      mSolverIndex = preferences.getInt("solverIndex",0);
-
-      parseOldVersion(mOldVersion);
-
-      RubikObjectList.restorePreferences(this,preferences,justStarted);
-
-      for (int i=0; i<ScreenList.LENGTH; i++)
-        {
-        ScreenList.getScreen(i).getScreenClass().restorePreferences(preferences);
-        }
-
-      RubikScores scores = RubikScores.getInstance();
-
-      if( scores.isVerified() )
-        {
-        FirebaseAnalytics analytics = getAnalytics();
-        analytics.setUserId(scores.getName());
-        }
-
-      // all old users upgrading from version <1.11.4, where there was no concept of 'stars',
-      // get all the stars they have earned
-      int numStars = scores.getNumStars();
-
-      if( justStarted && numStars==0 && oldVersionLessThan(1,11,4) )
-        {
-        scores.correctNumStars();
-        }
-
-      // in 1.11.5 we have introduced IAP. When upgrading from something less than 1.11.5 to
-      // something at least 1.11.5, mark all solved objects as free.
-      // this needs to be after the above ScreenList.getScreen(i).getScreenClass().restorePreferences()
-      // as that restores the Records which we need here
-      // also needs to be after RubikObjectList.restorePreferences()
-      if( USE_IAP && justStarted && oldVersionLessThan(1,11,5) && !mCurrVersion.equals("1.11.4") )
-        {
-        RubikObjectList.firstUpgradeMarkAllSolvedAsFree();
-        }
-
-      ScreenList.restorePreferences(preferences);
-
-      // Versions <= 1.8.6 did not save their 'appVersion' to preferences, therefore in their
-      // case the 'mOldVersion' - version of the app which saved the preferences on the last
-      // go - is empty.
-      // Between versions 1.8.6 and 1.9.0, the order of the cubits in TwistyCube has changed.
-      // If someone has scrambled the cube with v. 1.8.6, then upgraded to 1.9.0 and re-started
-      // the app, because of the different order of the cubits - his cube would be messed up.
-      // So in such case, i.e. on fresh upgrade from version<=1.8.6 to version>=1.9.0, do not
-      // restore the object scrambling.
-
-      if( !mOldVersion.equals("") )
-        {
-        RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-        OSInterface os = view.getInterface();
-        os.setPreferences(preferences);
-        view.getObjectControl().restorePreferences();
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void parseOldVersion(String version)
-      {
-      if( version==null ) return;
-
-      try
-        {
-        String[] parts = version.split("\\.");
-
-        if( parts.length==3 )
-          {
-          mOldVersion1 = Integer.parseInt(parts[0]);
-          mOldVersion2 = Integer.parseInt(parts[1]);
-          mOldVersion3 = Integer.parseInt(parts[2]);
-          }
-        }
-      catch(Exception ignored)
-        {
-        mOldVersion1 = 0;
-        mOldVersion2 = 0;
-        mOldVersion3 = 0;
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private boolean oldVersionLessThan(int v1, int v2, int v3)
-      {
-      if( mOldVersion1<v1 ) return true;
-      if( mOldVersion1>v1 ) return false;
-      if( mOldVersion2<v2 ) return true;
-      if( mOldVersion2>v2 ) return false;
-      return mOldVersion3<v3;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void restoreMoves(SharedPreferences preferences)
-      {
-      ScreenList curr = ScreenList.getCurrentScreen();
-
-      if( curr==ScreenList.PLAY )
-        {
-        RubikScreenPlay play = (RubikScreenPlay)ScreenList.PLAY.getScreenClass();
-        play.restoreMovePreferences(this,KEY_PLAY,preferences);
-        }
-      if( curr==ScreenList.SOLV )
-        {
-        RubikScreenSolving solv = (RubikScreenSolving)ScreenList.SOLV.getScreenClass();
-        solv.restoreMovePreferences(this,KEY_SOLV,preferences);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void OpenGLError()
-      {
-      RubikDialogError errDiag = new RubikDialogError();
-      errDiag.show(getSupportFragmentManager(), null);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public FirebaseAnalytics getAnalytics()
-      {
-      return mFirebaseAnalytics;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getHeightUpperBar()
-      {
-      return mHeightUpperBar;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public TwistyObject getObject()
-      {
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      return view.getObjectControl().getObject();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public DistortedScreen getScreen()
-      {
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      return view.getRenderer().getScreen();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public ObjectControl getControl()
-      {
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      return view.getObjectControl();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getScreenWidthInPixels()
-      {
-      return mScreenWidth;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getScreenHeightInPixels()
-      {
-      return mScreenHeight;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void changeObject(int newObject, boolean reportChange)
-      {
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      ObjectControl control = view.getObjectControl();
-      TwistyObject oldObject = control.getObject();
-      changeIfDifferent(newObject,control);
-
-      if( reportChange && oldObject!=null )
-        {
-        RubikObject robject = RubikObjectList.getObject(newObject);
-        String newName = robject==null ? "NULL" : robject.getUpperName();
-        float fps = view.getRenderer().getFPS();
-        fps = (int)(fps+0.5f);
-        StringBuilder name = new StringBuilder();
-        name.append(oldObject.getShortName());
-        name.append(' ');
-        name.append(fps);
-        name.append(" --> ");
-        name.append(newName);
-
-        if( BuildConfig.DEBUG )
-          {
-          android.util.Log.e("rubik", name.toString());
-          }
-        else
-          {
-          FirebaseAnalytics analytics = getAnalytics();
-
-          if( analytics!=null )
-            {
-            Bundle bundle = new Bundle();
-            bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, name.toString());
-            analytics.logEvent(FirebaseAnalytics.Event.SELECT_ITEM, bundle);
-            }
-          }
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void changeIfDifferent(int ordinal, ObjectControl control)
-      {
-      RubikObject object = RubikObjectList.getObject(ordinal);
-      int meshState = object!=null ? object.getMeshState() : MESH_NICE;
-      int iconMode  = TwistyObject.MODE_NORM;
-      InputStream jsonStream = object==null ? null : object.getObjectStream(this);
-      InputStream meshStream = object==null ? null : object.getMeshStream(this);
-      String name = object==null ? "NULL" : object.getUpperName();
-      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
-      OSInterface os = view.getInterface();
-      InitAssets asset = new InitAssets(jsonStream,meshStream,os);
-
-      control.changeIfDifferent(ordinal,name,meshState,iconMode,asset);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void switchToTutorial(String url, int objectOrdinal)
-      {
-      Intent intent = new Intent(this, TutorialActivity.class);
-      intent.putExtra("url", url);
-      intent.putExtra("obj", objectOrdinal);
-      startActivity(intent);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void switchToConfig(int objectOrdinal)
-      {
-      Intent intent = new Intent(this, ConfigActivity.class);
-      intent.putExtra("obj", objectOrdinal);
-      startActivity(intent);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void switchToBandagedCreator(int objectOrdinal)
-      {
-      Intent intent = new Intent(this, BandagedCreatorActivity.class);
-      intent.putExtra("obj", objectOrdinal);
-      startActivity(intent);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void switchToPurchase(int objectOrdinal)
-      {
-      Intent intent = new Intent(this, PurchaseActivity.class);
-      intent.putExtra("obj", objectOrdinal);
-      startActivity(intent);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void reloadObject(String shortName)
-      {
-      TwistyObject currObject = getObject();
-      String name = currObject==null ? "" : currObject.getShortName();
-
-      if( name.toLowerCase(Locale.ENGLISH).equals(shortName) )
-        {
-        RubikObject object = RubikObjectList.getObject(name);
-
-        if( object!=null )
-          {
-          int meshState = object.getMeshState();
-          int iconMode  = TwistyObject.MODE_NORM;
-          InputStream jsonStream = object.getObjectStream(this);
-          InputStream meshStream = object.getMeshStream(this);
-          RubikSurfaceView view  = findViewById(R.id.rubikSurfaceView);
-          OSInterface os         = view.getInterface();
-          InitAssets asset       = new InitAssets(jsonStream,meshStream,os);
-          ObjectControl control  = getControl();
-          control.changeObject(-1,meshState,iconMode,asset);
-          }
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void setSolverIndex(int index)
-      {
-      mSolverIndex = index;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getSolverIndex()
-      {
-      return mSolverIndex;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public OperatingSystemInterface getInterface()
-     {
-     RubikSurfaceView view  = findViewById(R.id.rubikSurfaceView);
-     return view.getInterface();
-     }
-}
diff --git a/src/main/java/org/distorted/main/RubikObjectLibInterface.java b/src/main/java/org/distorted/main/RubikObjectLibInterface.java
deleted file mode 100644
index 0ddfc26d..00000000
--- a/src/main/java/org/distorted/main/RubikObjectLibInterface.java
+++ /dev/null
@@ -1,538 +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.main;
-
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-
-import com.google.android.play.core.review.ReviewInfo;
-import com.google.android.play.core.review.ReviewManager;
-import com.google.android.play.core.review.ReviewManagerFactory;
-import com.google.android.play.core.tasks.OnCompleteListener;
-import com.google.android.play.core.tasks.OnFailureListener;
-import com.google.android.play.core.tasks.Task;
-import com.google.firebase.analytics.FirebaseAnalytics;
-import com.google.firebase.crashlytics.FirebaseCrashlytics;
-
-import org.distorted.dialogs.RubikDialogScoresView;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.library.message.EffectMessageSender;
-
-import org.distorted.external.RubikNetwork;
-import org.distorted.objectlib.helpers.BlockController;
-import org.distorted.objectlib.helpers.ObjectLibInterface;
-import org.distorted.objectlib.main.ObjectControl;
-
-import org.distorted.dialogs.RubikDialogNewRecord;
-import org.distorted.dialogs.RubikDialogSolved;
-import org.distorted.external.RubikScores;
-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 org.distorted.screens.RubikScreenPlay;
-import org.distorted.screens.RubikScreenReady;
-import org.distorted.screens.RubikScreenSolver;
-import org.distorted.screens.RubikScreenSolving;
-import org.distorted.screens.ScreenList;
-import org.distorted.solvers.SolverMain;
-
-import java.lang.ref.WeakReference;
-
-import static org.distorted.external.RubikScores.RECORD_FIRST;
-import static org.distorted.external.RubikScores.RECORD_NEW;
-import static org.distorted.external.RubikScores.RECORD_NOT_NEW;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikObjectLibInterface implements ObjectLibInterface, ListenerOverlay
-{
-  private final WeakReference<RubikActivity> mAct;
-  private int mIsNewRecord;
-  private int mNewRecord;
-  private int mLastCubitColor, mLastCubit, mLastCubitFace;
-  private boolean mReviewAsked;
-  private int mNumRotations, mNumScrambles;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RubikObjectLibInterface(RubikActivity act)
-    {
-    mAct = new WeakReference<>(act);
-    mLastCubitColor = -1;
-    mReviewAsked = false;
-    mNumRotations = 0;
-    mNumScrambles = 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void analyticsReport(RubikActivity act, String message, String name, long timeBegin)
-    {
-    long elapsed = System.currentTimeMillis() - timeBegin;
-    String msg = message+" startTime: "+timeBegin+" elapsed: "+elapsed+" name: "+name;
-
-    if( BuildConfig.DEBUG )
-       {
-       android.util.Log.d("libInterface", msg);
-       }
-    else
-      {
-      FirebaseAnalytics analytics = act.getAnalytics();
-
-      if( analytics!=null )
-        {
-        Bundle bundle = new Bundle();
-        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, msg);
-        analytics.logEvent(FirebaseAnalytics.Event.SHARE, bundle);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void reportRecord(RubikActivity act, long startTime, long endTime, String debug, int scrambleNum)
-    {
-    RubikScreenPlay play= (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-    RubikScores scores  = RubikScores.getInstance();
-    int object  = RubikObjectList.getCurrObject();
-    int level   = play.getLevel();
-    String name = scores.getName();
-    RubikObject obj = RubikObjectList.getObject(object);
-    String objName = obj==null ? "NULL" : obj.getUpperName();
-
-    String record = objName+" level "+level+" time "+mNewRecord+" isNew: "+mIsNewRecord+" scrambleNum: "+scrambleNum;
-
-    if( BuildConfig.DEBUG )
-       {
-       android.util.Log.e("libInterface", debug);
-       android.util.Log.e("libInterface", name);
-       android.util.Log.e("libInterface", record);
-       }
-    else
-      {
-      if( level>=9 && mNewRecord<300*level )
-        {
-        long timeNow = System.currentTimeMillis();
-        long elapsed = timeNow - startTime;
-        String suspicious ="start"+startTime+"end"+endTime+"elapsed"+elapsed+"obj"+objName+"level"+level+"record"+mNewRecord+"scrambles"+scrambleNum+debug;
-        RubikNetwork network = RubikNetwork.getInstance();
-        network.suspicious(suspicious,act);
-        }
-
-      FirebaseAnalytics analytics = act.getAnalytics();
-
-      if( analytics!=null )
-        {
-        Bundle bundle = new Bundle();
-        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, debug);
-        bundle.putString(FirebaseAnalytics.Param.CHARACTER, name);
-        bundle.putString(FirebaseAnalytics.Param.LEVEL, record);
-        analytics.logEvent(FirebaseAnalytics.Event.LEVEL_UP, bundle);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private Bundle createDialogBundle()
-    {
-    Bundle bundle = new Bundle();
-    String arg = RubikDialogScoresView.formatRecord(mNewRecord);
-    bundle.putString("argument", arg );
-    return bundle;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void requestReview(RubikActivity act)
-    {
-    android.util.Log.e("D", "ASKING FOR REVIEW");
-
-    mReviewAsked = true;
-    final String name = RubikScores.getInstance().getName();
-    final long timeBegin = System.currentTimeMillis();
-    final ReviewManager manager = ReviewManagerFactory.create(act);
-    Task<ReviewInfo> request = manager.requestReviewFlow();
-
-    request.addOnCompleteListener(new OnCompleteListener<ReviewInfo>()
-      {
-      @Override
-      public void onComplete (@NonNull Task<ReviewInfo> task)
-        {
-        if (task.isSuccessful())
-          {
-          ReviewInfo reviewInfo = task.getResult();
-          Task<Void> flow = manager.launchReviewFlow(act, reviewInfo);
-
-          flow.addOnFailureListener(new OnFailureListener()
-            {
-            @Override
-            public void onFailure(Exception e)
-              {
-              analyticsReport(act,"Failed", name, timeBegin);
-              }
-            });
-
-          flow.addOnCompleteListener(new OnCompleteListener<Void>()
-            {
-            @Override
-            public void onComplete(@NonNull Task<Void> task)
-              {
-              analyticsReport(act,"Complete", name, timeBegin);
-              }
-            });
-          }
-        else analyticsReport(act,"Not Successful", name, timeBegin);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onScrambleEffectFinished()
-    {
-    RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-
-    if( play.shouldReactToEndOfScrambling() )
-      {
-      RubikActivity act = mAct.get();
-      RubikScores.getInstance().incrementNumPlays();
-
-      act.runOnUiThread(new Runnable()
-        {
-        @Override
-        public void run()
-          {
-          ScreenList.switchScreen( act, ScreenList.READ);
-          ObjectControl control = act.getControl();
-          control.unblockEverything();
-          }
-        });
-      }
-    else
-      {
-      mNumScrambles++;
-
-      if( mNumScrambles==10 && !mReviewAsked )
-        {
-        RubikActivity act = mAct.get();
-        requestReview(act);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onFinishRotation(int axis, int row, int angle)
-    {
-    mNumRotations++;
-
-    if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
-      {
-      RubikScreenSolving solv = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
-      solv.addMove(mAct.get(), axis, row, angle);
-      }
-    if( ScreenList.getCurrentScreen()== ScreenList.PLAY )
-      {
-      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-      play.addMove(mAct.get(), axis, row, angle);
-      }
-
-    if( mNumRotations==40 && !mReviewAsked )
-      {
-      RubikActivity act = mAct.get();
-      requestReview(act);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onBeginRotation()
-    {
-    if( ScreenList.getCurrentScreen()== ScreenList.READ )
-      {
-      RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
-      solving.resetElapsed();
-      RubikActivity act = mAct.get();
-
-      act.runOnUiThread(new Runnable()
-        {
-        @Override
-        public void run()
-          {
-          ScreenList.switchScreen( act, ScreenList.SOLV);
-          }
-        });
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void failedToDrag()
-    {
-    ScreenList curr = ScreenList.getCurrentScreen();
-
-    if( curr==ScreenList.PLAY )
-      {
-      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-      play.reddenLock(mAct.get());
-      }
-    else if( curr==ScreenList.READ )
-      {
-      RubikScreenReady read = (RubikScreenReady) ScreenList.READ.getScreenClass();
-      read.reddenLock(mAct.get());
-      }
-    else if( curr==ScreenList.SOLV )
-      {
-      RubikScreenSolving solv = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
-      solv.reddenLock(mAct.get());
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onSolved()
-    {
-    if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
-      {
-      RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
-      mNewRecord = solving.stopTimerAndGetRecord();
-      mIsNewRecord = solving.setRecord();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onObjectCreated(long time)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void reportProblem(String problem, boolean recordException)
-    {
-    if( BuildConfig.DEBUG )
-      {
-      android.util.Log.e("libInterface", problem);
-      }
-    else
-      {
-      if( recordException )
-        {
-        Exception ex = new Exception(problem);
-        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-        crashlytics.setCustomKey("problem" , problem);
-        crashlytics.recordException(ex);
-        }
-      else
-        {
-        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-        crashlytics.log(problem);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void reportScramblingProblem(int place, long pause, long resume, long time)
-    {
-    String error = "SCRAMBLING BLOCK "+place+" blocked for "+time;
-
-    if( BuildConfig.DEBUG )
-       {
-       android.util.Log.e("libInterface", error);
-       }
-    else
-      {
-      Exception ex = new Exception(error);
-      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-      crashlytics.setCustomKey("pause" , pause );
-      crashlytics.setCustomKey("resume", resume );
-      crashlytics.recordException(ex);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void reportRotationProblem(int place, long pause, long resume, long time)
-    {
-    String error = "ROTATION BLOCK "+place+" blocked for "+time;
-
-    if( BuildConfig.DEBUG )
-       {
-       android.util.Log.e("libInterface", error);
-       }
-    else
-      {
-      Exception ex = new Exception(error);
-      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-      crashlytics.setCustomKey("pause" , pause );
-      crashlytics.setCustomKey("resume", resume);
-      crashlytics.recordException(ex);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void reportThreadProblem(int place, long pause, long resume, long time)
-    {
-    String error = EffectMessageSender.reportState();
-
-    if( BuildConfig.DEBUG )
-       {
-       android.util.Log.e("libInterface", error);
-       }
-    else
-      {
-      Exception ex = new Exception(error);
-      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-      crashlytics.setCustomKey("pause" , pause  );
-      crashlytics.setCustomKey("resume", resume );
-      crashlytics.recordException(ex);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void reportBlockProblem(int type, int place, long pause, long resume, long time)
-    {
-    switch(type)
-      {
-      case BlockController.TYPE_SCRAMBLING: reportScramblingProblem(place,pause,resume,time); break;
-      case BlockController.TYPE_ROTATION  : reportRotationProblem(place,pause,resume,time); break;
-      case BlockController.TYPE_THREAD    : reportThreadProblem(place,pause,resume,time); break;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void reportJSONError(String error, int ordinal)
-    {
-    RubikObject object = RubikObjectList.getObject(ordinal);
-    String name = object==null ? "NULL" : object.getUpperName();
-
-    if( BuildConfig.DEBUG )
-       {
-       android.util.Log.e("libInterface", "name="+name+" JSON error: "+error);
-       }
-    else
-      {
-      Exception ex = new Exception(error);
-      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-      crashlytics.setCustomKey("name" , name );
-      crashlytics.setCustomKey("JSONerror", error );
-      crashlytics.recordException(ex);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onReplaceModeDown(int cubit, int face)
-    {
-    RubikScreenSolver solver = (RubikScreenSolver) ScreenList.SVER.getScreenClass();
-    int color = solver.getCurrentColor();
-    int currObject = RubikObjectList.getCurrObject();
-    mLastCubitColor = SolverMain.cubitIsLocked(currObject,cubit);
-    mLastCubit = cubit;
-    mLastCubitFace = face;
-    ObjectControl control = mAct.get().getControl();
-    control.setTextureMap( cubit, face, color );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onReplaceModeUp()
-    {
-    if( mLastCubitColor>=0 )
-      {
-      ObjectControl control = mAct.get().getControl();
-      control.setTextureMap( mLastCubit, mLastCubitFace, mLastCubitColor );
-      mLastCubitColor = -1;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onWinEffectFinished(long startTime, long endTime, String debug, int scrambleNum)
-    {
-    if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
-      {
-      RubikActivity act = mAct.get();
-      reportRecord(act,startTime,endTime,debug,scrambleNum);
-
-      RubikScores scores = RubikScores.getInstance();
-      int numWins = scores.incrementNumWins();
-      int numRuns = scores.getNumRuns();
-
-      if( numRuns==3 || numRuns==6 || numWins==4 || numWins==20 || numWins==50 || numWins==80 || numWins==100)
-        {
-        requestReview(act);
-        }
-
-      switch(mIsNewRecord)
-        {
-        case RECORD_FIRST  : if( RubikActivity.USE_IAP )
-                                {
-                                RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-                                int level = play.getLevel();
-                                int newStars = scores.computeNumStars(level);
-                                int totStars = scores.getNumStars();
-                                scores.changeNumStars(newStars);
-                                DistortedScreen screen = act.getScreen();
-                                OverlayStars stars = new OverlayStars();
-                                DataStars data  = new DataStars(totStars,newStars,act.getResources());
-                                stars.startOverlay(screen,this,data);
-                                }
-                             else
-                                {
-                                Bundle bundle = createDialogBundle();
-                                RubikDialogNewRecord d2 = new RubikDialogNewRecord();
-                                d2.setArguments(bundle);
-                                d2.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
-                                }
-                             break;
-        case RECORD_NEW    : Bundle byes = createDialogBundle();
-                             RubikDialogNewRecord dyes = new RubikDialogNewRecord();
-                             dyes.setArguments(byes);
-                             dyes.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
-                             break;
-        case RECORD_NOT_NEW: Bundle bno = createDialogBundle();
-                             RubikDialogSolved dno = new RubikDialogSolved();
-                             dno.setArguments(bno);
-                             dno.show( act.getSupportFragmentManager(), RubikDialogSolved.getDialogTag() );
-                             break;
-        }
-
-      act.runOnUiThread(new Runnable()
-        {
-        @Override
-        public void run()
-          {
-          ScreenList.switchScreen( act, ScreenList.DONE);
-          }
-        });
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void overlayFinished(long id)
-    {
-    RubikActivity act = mAct.get();
-    Bundle bundle = createDialogBundle();
-    RubikDialogNewRecord d = new RubikDialogNewRecord();
-    d.setArguments(bundle);
-    d.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
-    }
-}
diff --git a/src/main/java/org/distorted/main/RubikRenderer.java b/src/main/java/org/distorted/main/RubikRenderer.java
deleted file mode 100644
index 4a5a9dae..00000000
--- a/src/main/java/org/distorted/main/RubikRenderer.java
+++ /dev/null
@@ -1,217 +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.main;
-
-import android.app.Activity;
-import android.content.res.Resources;
-import android.opengl.GLES30;
-import android.opengl.GLSurfaceView;
-
-import org.distorted.library.main.InternalOutputSurface;
-import org.distorted.objectlib.effects.BaseEffect;
-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.external.RubikNetwork;
-import org.distorted.objectlib.main.ObjectControl;
-
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
-import com.google.firebase.crashlytics.FirebaseCrashlytics;
-
-import java.io.InputStream;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
-{
-   public static final float BRIGHTNESS = 0.30f;
-
-   private final RubikSurfaceView mView;
-   private final Resources mResources;
-   private final DistortedScreen mScreen;
-   private final ObjectControl mControl;
-   private final Fps mFPS;
-   private boolean mErrorShown;
-   private boolean mDebugSent;
-
-   private static class Fps
-     {
-     private static final int NUM_FRAMES  = 100;
-
-     private long lastTime=0;
-     private final long[] durations;
-     private int currDuration;
-     private float currFPS;
-
-     Fps()
-       {
-       durations = new long[NUM_FRAMES+1];
-       currDuration = 0;
-
-       for (int i=0; i<NUM_FRAMES+1; i++) durations[i] = 16;
-       durations[NUM_FRAMES] = NUM_FRAMES * 16;
-       }
-
-     void onRender(long time)
-       {
-       if( lastTime==0 ) lastTime = time;
-
-       currDuration++;
-       if (currDuration >= NUM_FRAMES) currDuration = 0;
-       durations[NUM_FRAMES] += ((time - lastTime) - durations[currDuration]);
-       durations[currDuration] = time - lastTime;
-
-       currFPS = ((int)(10000.0f*NUM_FRAMES/durations[NUM_FRAMES]))/10.0f;
-
-       lastTime = time;
-       }
-
-     float getFPS()
-       {
-       return currFPS;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   RubikRenderer(RubikSurfaceView v)
-     {
-     mView = v;
-     mResources = v.getResources();
-
-     mErrorShown = false;
-     mControl = v.getObjectControl();
-     mFPS = new Fps();
-     mScreen = new DistortedScreen();
-     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
-     mScreen.enableDepthStencil(InternalOutputSurface.DEPTH_NO_STENCIL);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   float getFPS()
-     {
-     return mFPS.getFPS();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   DistortedScreen getScreen()
-     {
-     return mScreen;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// various things are done here delayed, 'after the next render' as not to be done mid-render and
-// cause artifacts.
-
-   @Override
-   public void onDrawFrame(GL10 glUnused)
-     {
-     long time = System.currentTimeMillis();
-     mFPS.onRender(time);
-     mControl.preRender();
-     mScreen.render(time);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @Override
-   public void onSurfaceChanged(GL10 glUnused, int width, int height)
-      {
-      mScreen.resize(width,height);
-      mView.setScreenSize(width,height);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @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);
-
-      if( !mDebugSent )
-        {
-        mDebugSent= true;
-        Activity act = (Activity)mView.getContext();
-        RubikNetwork network = RubikNetwork.getInstance();
-        network.debug(act);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void distortedException(Exception ex)
-     {
-     String message = ex.getMessage();
-     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);
-
-     if( message==null ) message = "exception NULL";
-
-     if( BuildConfig.DEBUG )
-       {
-       android.util.Log.e("DISTORTED", message );
-       android.util.Log.e("DISTORTED", "GLSL Version "+shading);
-       android.util.Log.e("DISTORTED", "GL Version "  +version);
-       android.util.Log.e("DISTORTED", "GL Vendor "   +vendor);
-       android.util.Log.e("DISTORTED", "GL Renderer " +renderer);
-       }
-     else
-       {
-       FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-       crashlytics.setCustomKey("DistortedError", message );
-       crashlytics.setCustomKey("GLSL Version"  , shading );
-       crashlytics.setCustomKey("GLversion"     , version );
-       crashlytics.setCustomKey("GL Vendor "    , vendor  );
-       crashlytics.setCustomKey("GLSLrenderer"  , renderer);
-       crashlytics.recordException(ex);
-       }
-
-     int glsl = DistortedLibrary.getGLSL();
-
-     if( glsl< 300 && !mErrorShown )
-       {
-       mErrorShown = true;
-       RubikActivity act = (RubikActivity)mView.getContext();
-       act.OpenGLError();
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public InputStream localFile(int fileID)
-     {
-     return mResources.openRawResource(fileID);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void logMessage(String message)
-     {
-     android.util.Log.e("Rubik", message );
-     }
-}
diff --git a/src/main/java/org/distorted/main/RubikSurfaceView.java b/src/main/java/org/distorted/main/RubikSurfaceView.java
deleted file mode 100644
index 1ecb1558..00000000
--- a/src/main/java/org/distorted/main/RubikSurfaceView.java
+++ /dev/null
@@ -1,174 +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.main;
-
-import android.annotation.SuppressLint;
-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 android.view.MotionEvent;
-
-import com.google.firebase.crashlytics.FirebaseCrashlytics;
-
-import org.distorted.external.RubikScores;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.objectlib.main.TwistyObjectNode;
-import org.distorted.os.OSInterface;
-import org.distorted.overlays.DataStars;
-import org.distorted.overlays.OverlayStars;
-import org.distorted.screens.ScreenList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikSurfaceView extends GLSurfaceView
-{
-    private ObjectControl mObjectController;
-    private OSInterface mInterface;
-    private RubikRenderer mRenderer;
-    private boolean mCreated;
-    private boolean mShowStars;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setScreenSize(int width, int height)
-      {
-      mObjectController.setScreenSizeAndScaling(width,height, Math.min(width, (int)(0.75f*height)));
-
-      if( !mCreated )
-        {
-        mCreated = true;
-        mObjectController.createNode(width,height);
-        TwistyObjectNode objectNode = mObjectController.getNode();
-        objectNode.glDepthMask(false);
-        objectNode.glStencilMask(0);
-        mRenderer.getScreen().attach(objectNode);
-
-        if( mShowStars )
-          {
-          mShowStars = false;
-          RubikScores scores = RubikScores.getInstance();
-          int totStars = scores.getNumStars();
-          DistortedScreen screen = mRenderer.getScreen();
-          OverlayStars stars = new OverlayStars();
-          DataStars data = new DataStars(totStars,0,getResources());
-          stars.startOverlay(screen,null,data);
-          }
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setShowStars()
-      {
-      mShowStars = true;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    RubikRenderer getRenderer()
-      {
-      return mRenderer;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    OSInterface getInterface()
-      {
-      return mInterface;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    ObjectControl getObjectControl()
-      {
-      return mObjectController;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public RubikSurfaceView(Context context, AttributeSet attrs)
-      {
-      super(context,attrs);
-
-      mCreated = false;
-      mShowStars = false;
-
-      if(!isInEditMode())
-        {
-        RubikActivity act = (RubikActivity)context;
-        RubikObjectLibInterface ref = new RubikObjectLibInterface(act);
-        mInterface = new OSInterface(act,ref);
-        mObjectController = new ObjectControl(mInterface);
-        mRenderer = new RubikRenderer(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();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouchEvent(MotionEvent event)
-      {
-      mInterface.setMotionEvent(event);
-      int mode = ScreenList.getMode();
-      return mObjectController.onTouchEvent(mode);
-      }
-}
-
diff --git a/src/main/java/org/distorted/main_old/RubikActivity.java b/src/main/java/org/distorted/main_old/RubikActivity.java
new file mode 100644
index 00000000..39c7da01
--- /dev/null
+++ b/src/main/java/org/distorted/main_old/RubikActivity.java
@@ -0,0 +1,672 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main_old;
+
+import java.io.InputStream;
+import java.util.Locale;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+
+import android.util.DisplayMetrics;
+import android.view.DisplayCutout;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceManager;
+
+import com.google.firebase.analytics.FirebaseAnalytics;
+import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
+
+import org.distorted.config.ConfigActivity;
+import org.distorted.bandaged.BandagedCreatorActivity;
+import org.distorted.dialogs.RubikDialogAbout;
+import org.distorted.library.main.DistortedLibrary;
+
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.main.BuildConfig;
+import org.distorted.main.R;
+import org.distorted.messaging.RubikInAppMessanging;
+import org.distorted.objectlib.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.main.InitAssets;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObject;
+
+import org.distorted.dialogs.RubikDialogError;
+import org.distorted.external.RubikScores;
+import org.distorted.external.RubikNetwork;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+import org.distorted.os.OSInterface;
+import org.distorted.purchase.PurchaseActivity;
+import org.distorted.screens.RubikScreenSolving;
+import org.distorted.screens.ScreenList;
+import org.distorted.screens.RubikScreenPlay;
+import org.distorted.tutorials.TutorialActivity;
+
+import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikActivity extends AppCompatActivity
+{
+    public static final boolean SHOW_DOWNLOADED_DEBUG = false;
+    public static final boolean SHOW_IAP_DEBUG        = false;
+    public static final boolean USE_IAP               = false;
+
+    public static final float PADDING             = 0.010f;
+    public static final float SMALL_MARGIN        = 0.004f;
+    public static final float BUTTON_TEXT_SIZE    = 0.050f;
+    public static final float TITLE_TEXT_SIZE     = 0.060f;
+    public static final float PATTERN_GROUP_TEXT  = 0.030f;
+    public static final float PATTERN_CHILD_TEXT  = 0.020f;
+    public static final float SCORES_LEVEL_TEXT   = 0.035f;
+    public static final float SCORES_ITEM_TEXT    = 0.025f;
+    public static final float TAB_WIDTH           = 0.066f;
+    public static final float TAB_HEIGHT          = 0.066f;
+    public static final float POPUP_PADDING       = 0.028f;
+    public static final float POPUP_MARGIN        = 0.016f;
+    public static final float POPUP_BOTTOM        = 0.090f;
+    public static final float RATIO_BAR           = 0.100f;
+
+    public static final int FLAGS =  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                                   | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                                   | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                                   | View.SYSTEM_UI_FLAG_FULLSCREEN
+                                   | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+
+    private static final int ACTIVITY_NUMBER = 0;
+    private static final float RATIO_INSET= 0.09f;
+
+    private static final String KEY_PLAY = "movesController_play";
+    private static final String KEY_SOLV = "movesController_solv";
+
+    private boolean mJustStarted;
+    private FirebaseAnalytics mFirebaseAnalytics;
+    private static int mScreenWidth, mScreenHeight;
+    private int mCurrentApiVersion;
+    private int mHeightUpperBar;
+    private int mOldVersion1, mOldVersion2, mOldVersion3;
+    private String mOldVersion, mCurrVersion;
+    private int mSolverIndex;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void onCreate(Bundle savedState)
+      {
+      super.onCreate(savedState);
+      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
+      setTheme(R.style.MaterialThemeNoActionBar);
+      setContentView(R.layout.main);
+      hideNavigationBar();
+
+      mJustStarted = true;
+      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
+
+      DisplayMetrics displaymetrics = new DisplayMetrics();
+      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
+      mScreenWidth =displaymetrics.widthPixels;
+      mScreenHeight=displaymetrics.heightPixels;
+
+      cutoutHack();
+      computeBarHeights();
+
+      mCurrVersion = getAppVers();
+      mSolverIndex = 0;
+
+      Thread thread = new Thread()
+        {
+        public void run()
+          {
+          RubikInAppMessanging listener = new RubikInAppMessanging();
+          FirebaseInAppMessaging.getInstance().addClickListener(listener);
+          }
+        };
+
+      thread.start();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this does not include possible insets
+
+    private void computeBarHeights()
+      {
+      int barHeight = (int)(mScreenHeight*RATIO_BAR);
+      mHeightUpperBar = barHeight;
+
+      LinearLayout layoutTop = findViewById(R.id.upperBar);
+      LinearLayout layoutBot = findViewById(R.id.lowerBar);
+
+      ViewGroup.LayoutParams paramsTop = layoutTop.getLayoutParams();
+      paramsTop.height = mHeightUpperBar;
+      layoutTop.setLayoutParams(paramsTop);
+      ViewGroup.LayoutParams paramsBot = layoutBot.getLayoutParams();
+      paramsBot.height = barHeight;
+      layoutBot.setLayoutParams(paramsBot);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void hideNavigationBar()
+      {
+      mCurrentApiVersion = Build.VERSION.SDK_INT;
+
+      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT)
+        {
+        final View decorView = getWindow().getDecorView();
+
+        decorView.setSystemUiVisibility(FLAGS);
+
+        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
+          {
+          @Override
+          public void onSystemUiVisibilityChange(int visibility)
+            {
+            if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
+              {
+              decorView.setSystemUiVisibility(FLAGS);
+              }
+            }
+          });
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onAttachedToWindow()
+      {
+      super.onAttachedToWindow();
+
+      if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.P )
+        {
+        DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
+        int insetHeight = cutout!=null ? cutout.getSafeInsetTop() : 0;
+
+        LinearLayout layoutHid = findViewById(R.id.hiddenBar);
+        ViewGroup.LayoutParams paramsHid = layoutHid.getLayoutParams();
+        paramsHid.height = (int)(insetHeight*RATIO_INSET);
+        layoutHid.setLayoutParams(paramsHid);
+        mHeightUpperBar += paramsHid.height;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// do not avoid cutouts
+
+    private void cutoutHack()
+      {
+      if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.P )
+        {
+        getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus)
+      {
+      super.onWindowFocusChanged(hasFocus);
+
+      if( mCurrentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus )
+        {
+        getWindow().getDecorView().setSystemUiVisibility(FLAGS);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onPause() 
+      {
+      super.onPause();
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      view.onPause();
+      DistortedLibrary.onPause(ACTIVITY_NUMBER);
+      RubikNetwork.onPause();
+      savePreferences();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+      DistortedLibrary.onResume(ACTIVITY_NUMBER);
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      view.onResume();
+
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      restorePreferences(preferences,mJustStarted);
+      ScreenList.setScreen(this);
+      restoreMoves(preferences);
+
+      if( mJustStarted )
+        {
+        mJustStarted = false;
+        RubikScores scores = RubikScores.getInstance();
+        scores.incrementNumRuns();
+        scores.setCountry(this);
+        RubikObjectList.restoreMeshState(preferences);
+        }
+
+      int object = RubikObjectList.getCurrObject();
+      changeIfDifferent(object,view.getObjectControl());
+
+      if( !mOldVersion.equals(mCurrVersion) ) displayNovelties();
+      else if( USE_IAP ) view.setShowStars();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void displayNovelties()
+      {
+      Bundle bundle = new Bundle();
+      bundle.putString("argument",mOldVersion);
+      RubikDialogAbout newDialog = new RubikDialogAbout();
+      newDialog.setArguments(bundle);
+      newDialog.show(getSupportFragmentManager(), RubikDialogAbout.getDialogTag() );
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
+      super.onDestroy();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private String getAppVers()
+      {
+      try
+        {
+        PackageInfo pInfo = getPackageManager().getPackageInfo( getPackageName(), 0);
+        return pInfo.versionName;
+        }
+      catch (PackageManager.NameNotFoundException e)
+        {
+        return "unknown";
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void savePreferences()
+      {
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      SharedPreferences.Editor editor = preferences.edit();
+
+      editor.putString("appVersion", mCurrVersion );
+      editor.putInt("solverIndex", mSolverIndex );
+
+      for( int i=0; i< ScreenList.LENGTH; i++ )
+        {
+        ScreenList.getScreen(i).getScreenClass().savePreferences(editor);
+        }
+
+      RubikObjectList.savePreferences(editor);
+      RubikObjectList.saveMeshState(editor);
+      ScreenList.savePreferences(editor);
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      OSInterface os = view.getInterface();
+      os.setEditor(editor);
+      view.getObjectControl().savePreferences();
+
+      ScreenList curr = ScreenList.getCurrentScreen();
+
+      if( curr==ScreenList.PLAY )
+        {
+        RubikScreenPlay play = (RubikScreenPlay)ScreenList.PLAY.getScreenClass();
+        play.saveMovePreferences(KEY_PLAY,editor);
+        }
+      if( curr==ScreenList.SOLV )
+        {
+        RubikScreenSolving solv = (RubikScreenSolving)ScreenList.SOLV.getScreenClass();
+        solv.saveMovePreferences(KEY_SOLV,editor);
+        }
+
+      boolean success = editor.commit();
+      if( !success ) android.util.Log.e("D", "Failed to save preferences");
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void restorePreferences(SharedPreferences preferences, boolean justStarted)
+      {
+      mOldVersion = preferences.getString("appVersion","");
+      mSolverIndex = preferences.getInt("solverIndex",0);
+
+      parseOldVersion(mOldVersion);
+
+      RubikObjectList.restorePreferences(this,preferences,justStarted);
+
+      for (int i=0; i<ScreenList.LENGTH; i++)
+        {
+        ScreenList.getScreen(i).getScreenClass().restorePreferences(preferences);
+        }
+
+      RubikScores scores = RubikScores.getInstance();
+
+      if( scores.isVerified() )
+        {
+        FirebaseAnalytics analytics = getAnalytics();
+        analytics.setUserId(scores.getName());
+        }
+
+      // all old users upgrading from version <1.11.4, where there was no concept of 'stars',
+      // get all the stars they have earned
+      int numStars = scores.getNumStars();
+
+      if( justStarted && numStars==0 && oldVersionLessThan(1,11,4) )
+        {
+        scores.correctNumStars();
+        }
+
+      // in 1.11.5 we have introduced IAP. When upgrading from something less than 1.11.5 to
+      // something at least 1.11.5, mark all solved objects as free.
+      // this needs to be after the above ScreenList.getScreen(i).getScreenClass().restorePreferences()
+      // as that restores the Records which we need here
+      // also needs to be after RubikObjectList.restorePreferences()
+      if( USE_IAP && justStarted && oldVersionLessThan(1,11,5) && !mCurrVersion.equals("1.11.4") )
+        {
+        RubikObjectList.firstUpgradeMarkAllSolvedAsFree();
+        }
+
+      ScreenList.restorePreferences(preferences);
+
+      // Versions <= 1.8.6 did not save their 'appVersion' to preferences, therefore in their
+      // case the 'mOldVersion' - version of the app which saved the preferences on the last
+      // go - is empty.
+      // Between versions 1.8.6 and 1.9.0, the order of the cubits in TwistyCube has changed.
+      // If someone has scrambled the cube with v. 1.8.6, then upgraded to 1.9.0 and re-started
+      // the app, because of the different order of the cubits - his cube would be messed up.
+      // So in such case, i.e. on fresh upgrade from version<=1.8.6 to version>=1.9.0, do not
+      // restore the object scrambling.
+
+      if( !mOldVersion.equals("") )
+        {
+        RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+        OSInterface os = view.getInterface();
+        os.setPreferences(preferences);
+        view.getObjectControl().restorePreferences();
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void parseOldVersion(String version)
+      {
+      if( version==null ) return;
+
+      try
+        {
+        String[] parts = version.split("\\.");
+
+        if( parts.length==3 )
+          {
+          mOldVersion1 = Integer.parseInt(parts[0]);
+          mOldVersion2 = Integer.parseInt(parts[1]);
+          mOldVersion3 = Integer.parseInt(parts[2]);
+          }
+        }
+      catch(Exception ignored)
+        {
+        mOldVersion1 = 0;
+        mOldVersion2 = 0;
+        mOldVersion3 = 0;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private boolean oldVersionLessThan(int v1, int v2, int v3)
+      {
+      if( mOldVersion1<v1 ) return true;
+      if( mOldVersion1>v1 ) return false;
+      if( mOldVersion2<v2 ) return true;
+      if( mOldVersion2>v2 ) return false;
+      return mOldVersion3<v3;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void restoreMoves(SharedPreferences preferences)
+      {
+      ScreenList curr = ScreenList.getCurrentScreen();
+
+      if( curr==ScreenList.PLAY )
+        {
+        RubikScreenPlay play = (RubikScreenPlay)ScreenList.PLAY.getScreenClass();
+        play.restoreMovePreferences(this,KEY_PLAY,preferences);
+        }
+      if( curr==ScreenList.SOLV )
+        {
+        RubikScreenSolving solv = (RubikScreenSolving)ScreenList.SOLV.getScreenClass();
+        solv.restoreMovePreferences(this,KEY_SOLV,preferences);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void OpenGLError()
+      {
+      RubikDialogError errDiag = new RubikDialogError();
+      errDiag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public FirebaseAnalytics getAnalytics()
+      {
+      return mFirebaseAnalytics;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getHeightUpperBar()
+      {
+      return mHeightUpperBar;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public TwistyObject getObject()
+      {
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      return view.getObjectControl().getObject();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public DistortedScreen getScreen()
+      {
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      return view.getRenderer().getScreen();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public ObjectControl getControl()
+      {
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      return view.getObjectControl();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenWidthInPixels()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenHeightInPixels()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void changeObject(int newObject, boolean reportChange)
+      {
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      ObjectControl control = view.getObjectControl();
+      TwistyObject oldObject = control.getObject();
+      changeIfDifferent(newObject,control);
+
+      if( reportChange && oldObject!=null )
+        {
+        RubikObject robject = RubikObjectList.getObject(newObject);
+        String newName = robject==null ? "NULL" : robject.getUpperName();
+        float fps = view.getRenderer().getFPS();
+        fps = (int)(fps+0.5f);
+        StringBuilder name = new StringBuilder();
+        name.append(oldObject.getShortName());
+        name.append(' ');
+        name.append(fps);
+        name.append(" --> ");
+        name.append(newName);
+
+        if( BuildConfig.DEBUG )
+          {
+          android.util.Log.e("rubik", name.toString());
+          }
+        else
+          {
+          FirebaseAnalytics analytics = getAnalytics();
+
+          if( analytics!=null )
+            {
+            Bundle bundle = new Bundle();
+            bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, name.toString());
+            analytics.logEvent(FirebaseAnalytics.Event.SELECT_ITEM, bundle);
+            }
+          }
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void changeIfDifferent(int ordinal, ObjectControl control)
+      {
+      RubikObject object = RubikObjectList.getObject(ordinal);
+      int meshState = object!=null ? object.getMeshState() : MESH_NICE;
+      int iconMode  = TwistyObject.MODE_NORM;
+      InputStream jsonStream = object==null ? null : object.getObjectStream(this);
+      InputStream meshStream = object==null ? null : object.getMeshStream(this);
+      String name = object==null ? "NULL" : object.getUpperName();
+      RubikSurfaceView view = findViewById(R.id.rubikSurfaceView);
+      OSInterface os = view.getInterface();
+      InitAssets asset = new InitAssets(jsonStream,meshStream,os);
+
+      control.changeIfDifferent(ordinal,name,meshState,iconMode,asset);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToTutorial(String url, int objectOrdinal)
+      {
+      Intent intent = new Intent(this, TutorialActivity.class);
+      intent.putExtra("url", url);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToConfig(int objectOrdinal)
+      {
+      Intent intent = new Intent(this, ConfigActivity.class);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToBandagedCreator(int objectOrdinal)
+      {
+      Intent intent = new Intent(this, BandagedCreatorActivity.class);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToPurchase(int objectOrdinal)
+      {
+      Intent intent = new Intent(this, PurchaseActivity.class);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void reloadObject(String shortName)
+      {
+      TwistyObject currObject = getObject();
+      String name = currObject==null ? "" : currObject.getShortName();
+
+      if( name.toLowerCase(Locale.ENGLISH).equals(shortName) )
+        {
+        RubikObject object = RubikObjectList.getObject(name);
+
+        if( object!=null )
+          {
+          int meshState = object.getMeshState();
+          int iconMode  = TwistyObject.MODE_NORM;
+          InputStream jsonStream = object.getObjectStream(this);
+          InputStream meshStream = object.getMeshStream(this);
+          RubikSurfaceView view  = findViewById(R.id.rubikSurfaceView);
+          OSInterface os         = view.getInterface();
+          InitAssets asset       = new InitAssets(jsonStream,meshStream,os);
+          ObjectControl control  = getControl();
+          control.changeObject(-1,meshState,iconMode,asset);
+          }
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void setSolverIndex(int index)
+      {
+      mSolverIndex = index;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getSolverIndex()
+      {
+      return mSolverIndex;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public OperatingSystemInterface getInterface()
+     {
+     RubikSurfaceView view  = findViewById(R.id.rubikSurfaceView);
+     return view.getInterface();
+     }
+}
diff --git a/src/main/java/org/distorted/main_old/RubikObjectLibInterface.java b/src/main/java/org/distorted/main_old/RubikObjectLibInterface.java
new file mode 100644
index 00000000..0a9c28f6
--- /dev/null
+++ b/src/main/java/org/distorted/main_old/RubikObjectLibInterface.java
@@ -0,0 +1,539 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main_old;
+
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.play.core.review.ReviewInfo;
+import com.google.android.play.core.review.ReviewManager;
+import com.google.android.play.core.review.ReviewManagerFactory;
+import com.google.android.play.core.tasks.OnCompleteListener;
+import com.google.android.play.core.tasks.OnFailureListener;
+import com.google.android.play.core.tasks.Task;
+import com.google.firebase.analytics.FirebaseAnalytics;
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import org.distorted.dialogs.RubikDialogScoresView;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.message.EffectMessageSender;
+
+import org.distorted.external.RubikNetwork;
+import org.distorted.main.BuildConfig;
+import org.distorted.objectlib.helpers.BlockController;
+import org.distorted.objectlib.helpers.ObjectLibInterface;
+import org.distorted.objectlib.main.ObjectControl;
+
+import org.distorted.dialogs.RubikDialogNewRecord;
+import org.distorted.dialogs.RubikDialogSolved;
+import org.distorted.external.RubikScores;
+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 org.distorted.screens.RubikScreenPlay;
+import org.distorted.screens.RubikScreenReady;
+import org.distorted.screens.RubikScreenSolver;
+import org.distorted.screens.RubikScreenSolving;
+import org.distorted.screens.ScreenList;
+import org.distorted.solvers.SolverMain;
+
+import java.lang.ref.WeakReference;
+
+import static org.distorted.external.RubikScores.RECORD_FIRST;
+import static org.distorted.external.RubikScores.RECORD_NEW;
+import static org.distorted.external.RubikScores.RECORD_NOT_NEW;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikObjectLibInterface implements ObjectLibInterface, ListenerOverlay
+{
+  private final WeakReference<RubikActivity> mAct;
+  private int mIsNewRecord;
+  private int mNewRecord;
+  private int mLastCubitColor, mLastCubit, mLastCubitFace;
+  private boolean mReviewAsked;
+  private int mNumRotations, mNumScrambles;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikObjectLibInterface(RubikActivity act)
+    {
+    mAct = new WeakReference<>(act);
+    mLastCubitColor = -1;
+    mReviewAsked = false;
+    mNumRotations = 0;
+    mNumScrambles = 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void analyticsReport(RubikActivity act, String message, String name, long timeBegin)
+    {
+    long elapsed = System.currentTimeMillis() - timeBegin;
+    String msg = message+" startTime: "+timeBegin+" elapsed: "+elapsed+" name: "+name;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.d("libInterface", msg);
+       }
+    else
+      {
+      FirebaseAnalytics analytics = act.getAnalytics();
+
+      if( analytics!=null )
+        {
+        Bundle bundle = new Bundle();
+        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, msg);
+        analytics.logEvent(FirebaseAnalytics.Event.SHARE, bundle);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportRecord(RubikActivity act, long startTime, long endTime, String debug, int scrambleNum)
+    {
+    RubikScreenPlay play= (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
+    RubikScores scores  = RubikScores.getInstance();
+    int object  = RubikObjectList.getCurrObject();
+    int level   = play.getLevel();
+    String name = scores.getName();
+    RubikObject obj = RubikObjectList.getObject(object);
+    String objName = obj==null ? "NULL" : obj.getUpperName();
+
+    String record = objName+" level "+level+" time "+mNewRecord+" isNew: "+mIsNewRecord+" scrambleNum: "+scrambleNum;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("libInterface", debug);
+       android.util.Log.e("libInterface", name);
+       android.util.Log.e("libInterface", record);
+       }
+    else
+      {
+      if( level>=9 && mNewRecord<300*level )
+        {
+        long timeNow = System.currentTimeMillis();
+        long elapsed = timeNow - startTime;
+        String suspicious ="start"+startTime+"end"+endTime+"elapsed"+elapsed+"obj"+objName+"level"+level+"record"+mNewRecord+"scrambles"+scrambleNum+debug;
+        RubikNetwork network = RubikNetwork.getInstance();
+        network.suspicious(suspicious,act);
+        }
+
+      FirebaseAnalytics analytics = act.getAnalytics();
+
+      if( analytics!=null )
+        {
+        Bundle bundle = new Bundle();
+        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, debug);
+        bundle.putString(FirebaseAnalytics.Param.CHARACTER, name);
+        bundle.putString(FirebaseAnalytics.Param.LEVEL, record);
+        analytics.logEvent(FirebaseAnalytics.Event.LEVEL_UP, bundle);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private Bundle createDialogBundle()
+    {
+    Bundle bundle = new Bundle();
+    String arg = RubikDialogScoresView.formatRecord(mNewRecord);
+    bundle.putString("argument", arg );
+    return bundle;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void requestReview(RubikActivity act)
+    {
+    android.util.Log.e("D", "ASKING FOR REVIEW");
+
+    mReviewAsked = true;
+    final String name = RubikScores.getInstance().getName();
+    final long timeBegin = System.currentTimeMillis();
+    final ReviewManager manager = ReviewManagerFactory.create(act);
+    Task<ReviewInfo> request = manager.requestReviewFlow();
+
+    request.addOnCompleteListener(new OnCompleteListener<ReviewInfo>()
+      {
+      @Override
+      public void onComplete (@NonNull Task<ReviewInfo> task)
+        {
+        if (task.isSuccessful())
+          {
+          ReviewInfo reviewInfo = task.getResult();
+          Task<Void> flow = manager.launchReviewFlow(act, reviewInfo);
+
+          flow.addOnFailureListener(new OnFailureListener()
+            {
+            @Override
+            public void onFailure(Exception e)
+              {
+              analyticsReport(act,"Failed", name, timeBegin);
+              }
+            });
+
+          flow.addOnCompleteListener(new OnCompleteListener<Void>()
+            {
+            @Override
+            public void onComplete(@NonNull Task<Void> task)
+              {
+              analyticsReport(act,"Complete", name, timeBegin);
+              }
+            });
+          }
+        else analyticsReport(act,"Not Successful", name, timeBegin);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onScrambleEffectFinished()
+    {
+    RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
+
+    if( play.shouldReactToEndOfScrambling() )
+      {
+      RubikActivity act = mAct.get();
+      RubikScores.getInstance().incrementNumPlays();
+
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchScreen( act, ScreenList.READ);
+          ObjectControl control = act.getControl();
+          control.unblockEverything();
+          }
+        });
+      }
+    else
+      {
+      mNumScrambles++;
+
+      if( mNumScrambles==10 && !mReviewAsked )
+        {
+        RubikActivity act = mAct.get();
+        requestReview(act);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onFinishRotation(int axis, int row, int angle)
+    {
+    mNumRotations++;
+
+    if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
+      {
+      RubikScreenSolving solv = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
+      solv.addMove(mAct.get(), axis, row, angle);
+      }
+    if( ScreenList.getCurrentScreen()== ScreenList.PLAY )
+      {
+      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
+      play.addMove(mAct.get(), axis, row, angle);
+      }
+
+    if( mNumRotations==40 && !mReviewAsked )
+      {
+      RubikActivity act = mAct.get();
+      requestReview(act);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onBeginRotation()
+    {
+    if( ScreenList.getCurrentScreen()== ScreenList.READ )
+      {
+      RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
+      solving.resetElapsed();
+      RubikActivity act = mAct.get();
+
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchScreen( act, ScreenList.SOLV);
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void failedToDrag()
+    {
+    ScreenList curr = ScreenList.getCurrentScreen();
+
+    if( curr==ScreenList.PLAY )
+      {
+      RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
+      play.reddenLock(mAct.get());
+      }
+    else if( curr==ScreenList.READ )
+      {
+      RubikScreenReady read = (RubikScreenReady) ScreenList.READ.getScreenClass();
+      read.reddenLock(mAct.get());
+      }
+    else if( curr==ScreenList.SOLV )
+      {
+      RubikScreenSolving solv = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
+      solv.reddenLock(mAct.get());
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onSolved()
+    {
+    if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
+      {
+      RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
+      mNewRecord = solving.stopTimerAndGetRecord();
+      mIsNewRecord = solving.setRecord();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onObjectCreated(long time)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reportProblem(String problem, boolean recordException)
+    {
+    if( BuildConfig.DEBUG )
+      {
+      android.util.Log.e("libInterface", problem);
+      }
+    else
+      {
+      if( recordException )
+        {
+        Exception ex = new Exception(problem);
+        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+        crashlytics.setCustomKey("problem" , problem);
+        crashlytics.recordException(ex);
+        }
+      else
+        {
+        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+        crashlytics.log(problem);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportScramblingProblem(int place, long pause, long resume, long time)
+    {
+    String error = "SCRAMBLING BLOCK "+place+" blocked for "+time;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("libInterface", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause );
+      crashlytics.setCustomKey("resume", resume );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportRotationProblem(int place, long pause, long resume, long time)
+    {
+    String error = "ROTATION BLOCK "+place+" blocked for "+time;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("libInterface", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause );
+      crashlytics.setCustomKey("resume", resume);
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportThreadProblem(int place, long pause, long resume, long time)
+    {
+    String error = EffectMessageSender.reportState();
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("libInterface", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause  );
+      crashlytics.setCustomKey("resume", resume );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reportBlockProblem(int type, int place, long pause, long resume, long time)
+    {
+    switch(type)
+      {
+      case BlockController.TYPE_SCRAMBLING: reportScramblingProblem(place,pause,resume,time); break;
+      case BlockController.TYPE_ROTATION  : reportRotationProblem(place,pause,resume,time); break;
+      case BlockController.TYPE_THREAD    : reportThreadProblem(place,pause,resume,time); break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reportJSONError(String error, int ordinal)
+    {
+    RubikObject object = RubikObjectList.getObject(ordinal);
+    String name = object==null ? "NULL" : object.getUpperName();
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("libInterface", "name="+name+" JSON error: "+error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("name" , name );
+      crashlytics.setCustomKey("JSONerror", error );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onReplaceModeDown(int cubit, int face)
+    {
+    RubikScreenSolver solver = (RubikScreenSolver) ScreenList.SVER.getScreenClass();
+    int color = solver.getCurrentColor();
+    int currObject = RubikObjectList.getCurrObject();
+    mLastCubitColor = SolverMain.cubitIsLocked(currObject,cubit);
+    mLastCubit = cubit;
+    mLastCubitFace = face;
+    ObjectControl control = mAct.get().getControl();
+    control.setTextureMap( cubit, face, color );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onReplaceModeUp()
+    {
+    if( mLastCubitColor>=0 )
+      {
+      ObjectControl control = mAct.get().getControl();
+      control.setTextureMap( mLastCubit, mLastCubitFace, mLastCubitColor );
+      mLastCubitColor = -1;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onWinEffectFinished(long startTime, long endTime, String debug, int scrambleNum)
+    {
+    if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
+      {
+      RubikActivity act = mAct.get();
+      reportRecord(act,startTime,endTime,debug,scrambleNum);
+
+      RubikScores scores = RubikScores.getInstance();
+      int numWins = scores.incrementNumWins();
+      int numRuns = scores.getNumRuns();
+
+      if( numRuns==3 || numRuns==6 || numWins==4 || numWins==20 || numWins==50 || numWins==80 || numWins==100)
+        {
+        requestReview(act);
+        }
+
+      switch(mIsNewRecord)
+        {
+        case RECORD_FIRST  : if( RubikActivity.USE_IAP )
+                                {
+                                RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
+                                int level = play.getLevel();
+                                int newStars = scores.computeNumStars(level);
+                                int totStars = scores.getNumStars();
+                                scores.changeNumStars(newStars);
+                                DistortedScreen screen = act.getScreen();
+                                OverlayStars stars = new OverlayStars();
+                                DataStars data  = new DataStars(totStars,newStars,act.getResources());
+                                stars.startOverlay(screen,this,data);
+                                }
+                             else
+                                {
+                                Bundle bundle = createDialogBundle();
+                                RubikDialogNewRecord d2 = new RubikDialogNewRecord();
+                                d2.setArguments(bundle);
+                                d2.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
+                                }
+                             break;
+        case RECORD_NEW    : Bundle byes = createDialogBundle();
+                             RubikDialogNewRecord dyes = new RubikDialogNewRecord();
+                             dyes.setArguments(byes);
+                             dyes.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
+                             break;
+        case RECORD_NOT_NEW: Bundle bno = createDialogBundle();
+                             RubikDialogSolved dno = new RubikDialogSolved();
+                             dno.setArguments(bno);
+                             dno.show( act.getSupportFragmentManager(), RubikDialogSolved.getDialogTag() );
+                             break;
+        }
+
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchScreen( act, ScreenList.DONE);
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void overlayFinished(long id)
+    {
+    RubikActivity act = mAct.get();
+    Bundle bundle = createDialogBundle();
+    RubikDialogNewRecord d = new RubikDialogNewRecord();
+    d.setArguments(bundle);
+    d.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
+    }
+}
diff --git a/src/main/java/org/distorted/main_old/RubikRenderer.java b/src/main/java/org/distorted/main_old/RubikRenderer.java
new file mode 100644
index 00000000..2578c776
--- /dev/null
+++ b/src/main/java/org/distorted/main_old/RubikRenderer.java
@@ -0,0 +1,218 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main_old;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.opengl.GLES30;
+import android.opengl.GLSurfaceView;
+
+import org.distorted.library.main.InternalOutputSurface;
+import org.distorted.main.BuildConfig;
+import org.distorted.objectlib.effects.BaseEffect;
+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.external.RubikNetwork;
+import org.distorted.objectlib.main.ObjectControl;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
+{
+   public static final float BRIGHTNESS = 0.30f;
+
+   private final RubikSurfaceView mView;
+   private final Resources mResources;
+   private final DistortedScreen mScreen;
+   private final ObjectControl mControl;
+   private final Fps mFPS;
+   private boolean mErrorShown;
+   private boolean mDebugSent;
+
+   private static class Fps
+     {
+     private static final int NUM_FRAMES  = 100;
+
+     private long lastTime=0;
+     private final long[] durations;
+     private int currDuration;
+     private float currFPS;
+
+     Fps()
+       {
+       durations = new long[NUM_FRAMES+1];
+       currDuration = 0;
+
+       for (int i=0; i<NUM_FRAMES+1; i++) durations[i] = 16;
+       durations[NUM_FRAMES] = NUM_FRAMES * 16;
+       }
+
+     void onRender(long time)
+       {
+       if( lastTime==0 ) lastTime = time;
+
+       currDuration++;
+       if (currDuration >= NUM_FRAMES) currDuration = 0;
+       durations[NUM_FRAMES] += ((time - lastTime) - durations[currDuration]);
+       durations[currDuration] = time - lastTime;
+
+       currFPS = ((int)(10000.0f*NUM_FRAMES/durations[NUM_FRAMES]))/10.0f;
+
+       lastTime = time;
+       }
+
+     float getFPS()
+       {
+       return currFPS;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   RubikRenderer(RubikSurfaceView v)
+     {
+     mView = v;
+     mResources = v.getResources();
+
+     mErrorShown = false;
+     mControl = v.getObjectControl();
+     mFPS = new Fps();
+     mScreen = new DistortedScreen();
+     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+     mScreen.enableDepthStencil(InternalOutputSurface.DEPTH_NO_STENCIL);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   float getFPS()
+     {
+     return mFPS.getFPS();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   DistortedScreen getScreen()
+     {
+     return mScreen;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// various things are done here delayed, 'after the next render' as not to be done mid-render and
+// cause artifacts.
+
+   @Override
+   public void onDrawFrame(GL10 glUnused)
+     {
+     long time = System.currentTimeMillis();
+     mFPS.onRender(time);
+     mControl.preRender();
+     mScreen.render(time);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceChanged(GL10 glUnused, int width, int height)
+      {
+      mScreen.resize(width,height);
+      mView.setScreenSize(width,height);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @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);
+
+      if( !mDebugSent )
+        {
+        mDebugSent= true;
+        Activity act = (Activity)mView.getContext();
+        RubikNetwork network = RubikNetwork.getInstance();
+        network.downloadUpdates(act);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void distortedException(Exception ex)
+     {
+     String message = ex.getMessage();
+     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);
+
+     if( message==null ) message = "exception NULL";
+
+     if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("DISTORTED", message );
+       android.util.Log.e("DISTORTED", "GLSL Version "+shading);
+       android.util.Log.e("DISTORTED", "GL Version "  +version);
+       android.util.Log.e("DISTORTED", "GL Vendor "   +vendor);
+       android.util.Log.e("DISTORTED", "GL Renderer " +renderer);
+       }
+     else
+       {
+       FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+       crashlytics.setCustomKey("DistortedError", message );
+       crashlytics.setCustomKey("GLSL Version"  , shading );
+       crashlytics.setCustomKey("GLversion"     , version );
+       crashlytics.setCustomKey("GL Vendor "    , vendor  );
+       crashlytics.setCustomKey("GLSLrenderer"  , renderer);
+       crashlytics.recordException(ex);
+       }
+
+     int glsl = DistortedLibrary.getGLSL();
+
+     if( glsl< 300 && !mErrorShown )
+       {
+       mErrorShown = true;
+       RubikActivity act = (RubikActivity)mView.getContext();
+       act.OpenGLError();
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public InputStream localFile(int fileID)
+     {
+     return mResources.openRawResource(fileID);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void logMessage(String message)
+     {
+     android.util.Log.e("Rubik", message );
+     }
+}
diff --git a/src/main/java/org/distorted/main_old/RubikSurfaceView.java b/src/main/java/org/distorted/main_old/RubikSurfaceView.java
new file mode 100644
index 00000000..1696d870
--- /dev/null
+++ b/src/main/java/org/distorted/main_old/RubikSurfaceView.java
@@ -0,0 +1,174 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.main_old;
+
+import android.annotation.SuppressLint;
+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 android.view.MotionEvent;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import org.distorted.external.RubikScores;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObjectNode;
+import org.distorted.os.OSInterface;
+import org.distorted.overlays.DataStars;
+import org.distorted.overlays.OverlayStars;
+import org.distorted.screens.ScreenList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikSurfaceView extends GLSurfaceView
+{
+    private ObjectControl mObjectController;
+    private OSInterface mInterface;
+    private RubikRenderer mRenderer;
+    private boolean mCreated;
+    private boolean mShowStars;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void setScreenSize(int width, int height)
+      {
+      mObjectController.setScreenSizeAndScaling(width,height, Math.min(width, (int)(0.75f*height)));
+
+      if( !mCreated )
+        {
+        mCreated = true;
+        mObjectController.createNode(width,height);
+        TwistyObjectNode objectNode = mObjectController.getNode();
+        objectNode.glDepthMask(false);
+        objectNode.glStencilMask(0);
+        mRenderer.getScreen().attach(objectNode);
+
+        if( mShowStars )
+          {
+          mShowStars = false;
+          RubikScores scores = RubikScores.getInstance();
+          int totStars = scores.getNumStars();
+          DistortedScreen screen = mRenderer.getScreen();
+          OverlayStars stars = new OverlayStars();
+          DataStars data = new DataStars(totStars,0,getResources());
+          stars.startOverlay(screen,null,data);
+          }
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void setShowStars()
+      {
+      mShowStars = true;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    RubikRenderer getRenderer()
+      {
+      return mRenderer;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    OSInterface getInterface()
+      {
+      return mInterface;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    ObjectControl getObjectControl()
+      {
+      return mObjectController;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public RubikSurfaceView(Context context, AttributeSet attrs)
+      {
+      super(context,attrs);
+
+      mCreated = false;
+      mShowStars = false;
+
+      if(!isInEditMode())
+        {
+        RubikActivity act = (RubikActivity)context;
+        RubikObjectLibInterface ref = new RubikObjectLibInterface(act);
+        mInterface = new OSInterface(act,ref);
+        mObjectController = new ObjectControl(mInterface);
+        mRenderer = new RubikRenderer(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();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent event)
+      {
+      mInterface.setMotionEvent(event);
+      int mode = ScreenList.getMode();
+      return mObjectController.onTouchEvent(mode);
+      }
+}
+
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index 95c42f68..8a808830 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -26,12 +26,12 @@ import org.distorted.dmesh.ObjectMesh;
 import org.distorted.external.RubikFiles;
 import org.distorted.jsons.ObjectJson;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.main.ObjectType;
 import org.distorted.objectlib.patterns.RubikPatternList;
 
 import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
-import static org.distorted.main.RubikActivity.SHOW_DOWNLOADED_DEBUG;
+import static org.distorted.main_old.RubikActivity.SHOW_DOWNLOADED_DEBUG;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/objects/RubikObjectList.java b/src/main/java/org/distorted/objects/RubikObjectList.java
index 509baf0e..4cd076ba 100644
--- a/src/main/java/org/distorted/objects/RubikObjectList.java
+++ b/src/main/java/org/distorted/objects/RubikObjectList.java
@@ -17,15 +17,14 @@ import android.content.SharedPreferences;
 
 import org.distorted.external.RubikFiles;
 import org.distorted.external.RubikScores;
-import org.distorted.main.RubikActivity;
 import org.distorted.objectlib.signature.ObjectConstants;
 import org.distorted.objectlib.main.ObjectType;
 
-import static org.distorted.main.RubikActivity.SHOW_IAP_DEBUG;
-import static org.distorted.main.RubikActivity.USE_IAP;
+import static org.distorted.main_old.RubikActivity.SHOW_IAP_DEBUG;
+import static org.distorted.main_old.RubikActivity.USE_IAP;
 import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
 import static org.distorted.objectlib.main.ObjectType.NUM_OBJECTS;
-import static org.distorted.main.RubikActivity.SHOW_DOWNLOADED_DEBUG;
+import static org.distorted.main_old.RubikActivity.SHOW_DOWNLOADED_DEBUG;
 import static org.distorted.screens.RubikScreenPlay.LEVELS_SHOWN;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -208,16 +207,6 @@ public class RubikObjectList
 
         if( SHOW_DOWNLOADED_DEBUG ) android.util.Log.e("D", "Updating downloaded object "+shortName+" icon="+obj.icon+" object="+obj.object+" extras="+obj.extras);
 
-        try
-          {
-          RubikActivity ract = (RubikActivity)context;
-          ract.reloadObject(shortName);
-          }
-        catch(Exception ex)
-          {
-          android.util.Log.e("D", "exception trying to reload object: "+ex.getMessage() );
-          }
-
         return false;
         }
       }
diff --git a/src/main/java/org/distorted/purchase/PurchaseActivity.java b/src/main/java/org/distorted/purchase/PurchaseActivity.java
index 86c6b8e0..7ddf8b8d 100644
--- a/src/main/java/org/distorted/purchase/PurchaseActivity.java
+++ b/src/main/java/org/distorted/purchase/PurchaseActivity.java
@@ -24,7 +24,7 @@ import org.distorted.dialogs.RubikDialogError;
 import org.distorted.external.RubikScores;
 import org.distorted.library.main.DistortedLibrary;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.main.InitAssets;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objectlib.main.TwistyObject;
diff --git a/src/main/java/org/distorted/screens/RubikScreenAbstract.java b/src/main/java/org/distorted/screens/RubikScreenAbstract.java
index 8096316c..9c35b6f5 100644
--- a/src/main/java/org/distorted/screens/RubikScreenAbstract.java
+++ b/src/main/java/org/distorted/screens/RubikScreenAbstract.java
@@ -11,7 +11,7 @@ package org.distorted.screens;
 
 import android.content.SharedPreferences;
 
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/screens/RubikScreenBase.java b/src/main/java/org/distorted/screens/RubikScreenBase.java
index 87f6037f..8508082e 100644
--- a/src/main/java/org/distorted/screens/RubikScreenBase.java
+++ b/src/main/java/org/distorted/screens/RubikScreenBase.java
@@ -14,7 +14,7 @@ import android.content.SharedPreferences;
 import android.widget.LinearLayout;
 
 import org.distorted.helpers.TransparentImageButton;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.main.ObjectControl;
 
 import org.distorted.helpers.LockController;
diff --git a/src/main/java/org/distorted/screens/RubikScreenDone.java b/src/main/java/org/distorted/screens/RubikScreenDone.java
index 5b36efde..8bdbd227 100644
--- a/src/main/java/org/distorted/screens/RubikScreenDone.java
+++ b/src/main/java/org/distorted/screens/RubikScreenDone.java
@@ -22,7 +22,7 @@ import org.distorted.dialogs.RubikDialogNewRecord;
 import org.distorted.dialogs.RubikDialogSolved;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/screens/RubikScreenPattern.java b/src/main/java/org/distorted/screens/RubikScreenPattern.java
index 36c72c9c..0988086e 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPattern.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPattern.java
@@ -23,7 +23,7 @@ import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.main.R;
 import org.distorted.dialogs.RubikDialogPattern;
 import org.distorted.helpers.TransparentImageButton;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objects.RubikObjectList;
 import org.distorted.objectlib.patterns.RubikPattern;
 import org.distorted.objectlib.patterns.RubikPatternList;
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
index b0e9e713..f96da6c7 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -42,7 +42,7 @@ import org.distorted.external.RubikUpdates;
 
 import org.distorted.helpers.PopupCreator;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.dialogs.RubikDialogPattern;
 import org.distorted.dialogs.RubikDialogScores;
 import org.distorted.dialogs.RubikDialogTutorial;
@@ -54,7 +54,7 @@ import org.distorted.objects.RubikObjectList;
 
 import static android.view.View.GONE;
 import static android.view.View.inflate;
-import static org.distorted.main.RubikActivity.USE_IAP;
+import static org.distorted.main_old.RubikActivity.USE_IAP;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -697,4 +697,18 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
     {
     android.util.Log.e("D", "RubikScreenPlay: Error receiving downloaded objects update");
     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getType()
+    {
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void objectDownloaded(String shortName)
+    {
+    recreatePopup();
+    }
   }
diff --git a/src/main/java/org/distorted/screens/RubikScreenReady.java b/src/main/java/org/distorted/screens/RubikScreenReady.java
index 28c1c88f..8b65f76b 100644
--- a/src/main/java/org/distorted/screens/RubikScreenReady.java
+++ b/src/main/java/org/distorted/screens/RubikScreenReady.java
@@ -18,7 +18,7 @@ import android.widget.TextView;
 
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/main/java/org/distorted/screens/RubikScreenSolution.java b/src/main/java/org/distorted/screens/RubikScreenSolution.java
index 7c91f1eb..1dfe81ea 100644
--- a/src/main/java/org/distorted/screens/RubikScreenSolution.java
+++ b/src/main/java/org/distorted/screens/RubikScreenSolution.java
@@ -22,7 +22,7 @@ import org.distorted.objectlib.helpers.MovesFinished;
 
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.patterns.RubikPattern;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/screens/RubikScreenSolver.java b/src/main/java/org/distorted/screens/RubikScreenSolver.java
index c6bd1f49..1d8f058a 100644
--- a/src/main/java/org/distorted/screens/RubikScreenSolver.java
+++ b/src/main/java/org/distorted/screens/RubikScreenSolver.java
@@ -26,6 +26,7 @@ import android.widget.ImageButton;
 import android.widget.LinearLayout;
 
 import org.distorted.dialogs.RubikDialogSolverImpossible;
+import org.distorted.main.MainActivity;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objectlib.signature.ObjectConstants;
 import org.distorted.objectlib.main.TwistyObject;
@@ -33,7 +34,7 @@ import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.dialogs.RubikDialogSolverError;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.shape.ShapeDiamond;
 import org.distorted.objectlib.shape.ShapeDodecahedron;
 import org.distorted.objectlib.shape.ShapeHexahedron;
@@ -94,7 +95,7 @@ public class RubikScreenSolver extends RubikScreenAbstract
     generateFaceColors(currentObject);
 
     final float BUTTON_RATIO = 0.75f;
-    int sizeV = (int)(heigh*RubikActivity.RATIO_BAR*BUTTON_RATIO);
+    int sizeV = (int)(heigh*MainActivity.RATIO_BAR*BUTTON_RATIO);
     int sizeH = (int)((width/mNumColors)*BUTTON_RATIO);
     mBitmapSize = Math.min(sizeV,sizeH);
 
diff --git a/src/main/java/org/distorted/screens/RubikScreenSolving.java b/src/main/java/org/distorted/screens/RubikScreenSolving.java
index 5e6f1f49..7af8a923 100644
--- a/src/main/java/org/distorted/screens/RubikScreenSolving.java
+++ b/src/main/java/org/distorted/screens/RubikScreenSolving.java
@@ -22,7 +22,7 @@ import android.widget.TextView;
 import org.distorted.dialogs.RubikDialogAbandon;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.R;
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.external.RubikScores;
 import org.distorted.objects.RubikObjectList;
 
diff --git a/src/main/java/org/distorted/screens/ScreenList.java b/src/main/java/org/distorted/screens/ScreenList.java
index 2225232a..b9fc5b62 100644
--- a/src/main/java/org/distorted/screens/ScreenList.java
+++ b/src/main/java/org/distorted/screens/ScreenList.java
@@ -14,7 +14,7 @@ import android.os.Bundle;
 
 import com.google.firebase.analytics.FirebaseAnalytics;
 
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import static org.distorted.objectlib.main.ObjectControl.*;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/tutorials/TutorialActivity.java b/src/main/java/org/distorted/tutorials/TutorialActivity.java
index 1c17072d..d14d80ce 100644
--- a/src/main/java/org/distorted/tutorials/TutorialActivity.java
+++ b/src/main/java/org/distorted/tutorials/TutorialActivity.java
@@ -25,7 +25,7 @@ import androidx.appcompat.app.AppCompatActivity;
 
 import org.distorted.library.main.DistortedLibrary;
 
-import org.distorted.main.RubikActivity;
+import org.distorted.main_old.RubikActivity;
 import org.distorted.objectlib.main.InitAssets;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objectlib.main.TwistyObject;
diff --git a/src/main/res/drawable-nodpi/ui_bandaged.png b/src/main/res/drawable-nodpi/ui_bandaged.png
new file mode 100644
index 00000000..99bd0171
Binary files /dev/null and b/src/main/res/drawable-nodpi/ui_bandaged.png differ
diff --git a/src/main/res/drawable-nodpi/ui_download.png b/src/main/res/drawable-nodpi/ui_download.png
index b8afb355..7b1112e3 100644
Binary files a/src/main/res/drawable-nodpi/ui_download.png and b/src/main/res/drawable-nodpi/ui_download.png differ
diff --git a/src/main/res/drawable-nodpi/ui_exit.png b/src/main/res/drawable-nodpi/ui_exit.png
new file mode 100644
index 00000000..8d46b4c7
Binary files /dev/null and b/src/main/res/drawable-nodpi/ui_exit.png differ
diff --git a/src/main/res/drawable-nodpi/ui_info.png b/src/main/res/drawable-nodpi/ui_info.png
index 0d402b36..a32aa4c3 100644
Binary files a/src/main/res/drawable-nodpi/ui_info.png and b/src/main/res/drawable-nodpi/ui_info.png differ
diff --git a/src/main/res/drawable-nodpi/ui_podium.png b/src/main/res/drawable-nodpi/ui_podium.png
new file mode 100644
index 00000000..2e9c244a
Binary files /dev/null and b/src/main/res/drawable-nodpi/ui_podium.png differ
diff --git a/src/main/res/layout/exit_app.xml b/src/main/res/layout/exit_app.xml
new file mode 100644
index 00000000..508c7108
--- /dev/null
+++ b/src/main/res/layout/exit_app.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:layout_marginLeft="10dp"
+    android:layout_marginTop="0dp"
+    android:layout_marginRight="10dp"
+    android:background="@color/grey"
+    android:gravity="center|fill_horizontal">
+
+    <TextView
+        android:id="@+id/exit_app_string"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginLeft="10dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginRight="10dp"
+        android:layout_marginBottom="10dp"
+        android:gravity="center"
+        android:text="@string/exit_app"
+        android:textSize="24sp" />
+</LinearLayout>
diff --git a/src/main/res/layout/main.xml b/src/main/res/layout/main.xml
index 882c41d7..acc9e572 100644
--- a/src/main/res/layout/main.xml
+++ b/src/main/res/layout/main.xml
@@ -4,7 +4,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
 
-    <org.distorted.main.RubikSurfaceView
+    <org.distorted.main_old.RubikSurfaceView
         android:id="@+id/rubikSurfaceView"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/src/main/res/layout/new_main.xml b/src/main/res/layout/new_main.xml
new file mode 100644
index 00000000..0847d80a
--- /dev/null
+++ b/src/main/res/layout/new_main.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/relativeLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/hiddenBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@color/dark_grey">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/upperBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:weightSum="1.0"
+        android:orientation="horizontal"
+        android:background="@color/dark_grey">
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="0.25"
+            android:orientation="horizontal"
+            android:gravity="center"
+            android:background="@android:color/transparent">
+
+            <ImageButton
+                android:id="@+id/buttonScores"
+                android:adjustViewBounds="true"
+                android:scaleType="fitCenter"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:onClick="onScores"
+                android:background="@android:color/transparent"
+                android:src="@drawable/ui_podium"/>
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="0.5"
+            android:orientation="horizontal"
+            android:gravity="center"
+            android:background="@android:color/transparent">
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="0.25"
+            android:orientation="horizontal"
+            android:gravity="center"
+            android:background="@android:color/transparent">
+
+            <ImageButton
+                android:id="@+id/buttonBandage"
+                android:adjustViewBounds="true"
+                android:scaleType="fitCenter"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:onClick="onBandage"
+                android:background="@android:color/transparent"
+                android:src="@drawable/ui_bandaged"/>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <ScrollView
+        android:id="@+id/objectScroll"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/grey">
+
+        <GridLayout
+            android:id="@+id/objectGrid"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+        </GridLayout>
+
+    </ScrollView>
+
+    <LinearLayout
+        android:id="@+id/lowerBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="@color/dark_grey">
+
+        <RelativeLayout
+            android:id="@+id/bottomLayout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingEnd="10dp"
+            android:paddingStart="10dp">
+
+            <ImageButton
+                android:id="@+id/buttonAbout"
+                android:layout_alignParentStart="true"
+                android:adjustViewBounds="true"
+                android:scaleType="fitCenter"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:onClick="onAbout"
+                android:background="@android:color/transparent"
+                android:src="@drawable/ui_info"/>
+
+            <ImageButton
+                android:id="@+id/buttonUpdates"
+                android:layout_centerHorizontal="true"
+                android:adjustViewBounds="true"
+                android:scaleType="fitCenter"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:onClick="onUpdates"
+                android:background="@android:color/transparent"
+                android:src="@drawable/ui_download"/>
+
+            <TextView
+                android:id="@+id/bubbleUpdates"
+                android:adjustViewBounds="true"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignTop="@+id/buttonUpdates"
+                android:layout_alignEnd="@+id/buttonUpdates"
+                android:scaleType="fitCenter"
+                android:textColor="#FFF"
+                android:textSize="16sp"
+                android:textStyle="bold"
+                android:background="@drawable/badge_circle"/>
+
+            <ImageButton
+                android:id="@+id/buttonExit"
+                android:adjustViewBounds="true"
+                android:layout_alignParentEnd="true"
+                android:scaleType="fitCenter"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:onClick="onExit"
+                android:background="@android:color/transparent"
+                android:src="@drawable/ui_exit"/>
+
+        </RelativeLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index b7de215a..677d598e 100755
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">Teilen Sie diese App</string>
     <string name="contact">Kontakt</string>
     <string name="email">Melden Sie einen Fehler, schlagen Sie eine Funktion vor:</string>
+    <string name="exit_app">App beenden?</string>
 
     <string name="stars">Sterne</string>
     <string name="scores">Highscores</string>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index 8715b8fe..019b1b70 100755
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">Comparte esta app</string>
     <string name="contact">Contacto</string>
     <string name="email">Reportar un error, sugerir una función:</string>
+    <string name="exit_app">¿Salir de la aplicación?</string>
 
     <string name="stars">Estrellas</string>
     <string name="scores">Leaderboard</string>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index db532bed..ac17021c 100755
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">Partager cette app</string>
     <string name="contact">Contactez-nous</string>
     <string name="email">Signaler un bug, suggérer une fonctionnalité :</string>
+    <string name="exit_app">Quitter l\'application ?</string>
 
     <string name="stars">Étoiles</string>
     <string name="scores">Meilleurs scores</string>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index 626e6eeb..f012e170 100755
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">このアプリを共有する</string>
     <string name="contact">お問い合わせ</string>
     <string name="email">バグを報告し、機能を提案してください:</string>
+    <string name="exit_app">アプリを終了しますか?</string>
 
     <string name="stars">星</string>
     <string name="scores">ハイスコア</string>
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index bd0d4a58..e29954be 100755
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">이 애플리케이션 공유</string>
     <string name="contact">문의하기</string>
     <string name="email">버그 신고, 기능 제안:</string>
+    <string name="exit_app">앱을 종료하시겠습니까?</string>
 
     <string name="stars">별</string>
     <string name="scores">고득점</string>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 80c1486c..ab4c7b50 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">Udostępnij tę apkę</string>
     <string name="contact">Kontakt</string>
     <string name="email">Zaraportuj błąd, zadaj pytanie:</string>
+    <string name="exit_app">Wyjść z apki?</string>
 
     <string name="stars">Gwiazdki</string>
     <string name="scores">Lista najlepszych</string>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index 0d6e8a36..962fa508 100755
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">Поделись этим</string>
     <string name="contact">Контакт</string>
     <string name="email">Сообщить об ошибке, задать вопрос:</string>
+    <string name="exit_app">Выйти из приложения?</string>
 
     <string name="stars">Звезды</string>
     <string name="scores">Высокие баллы</string>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index d3715bac..bbebd536 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">分享这个应用程序</string>
     <string name="contact">联系我们</string>
     <string name="email">报告错误，提出问题：</string>
+    <string name="exit_app">退出应用程序？</string>
 
     <string name="stars">星星</string>
     <string name="scores">高分</string>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index 7fb71c0b..fea1a7c5 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -52,6 +52,7 @@
     <string name="share">分享這個應用程序</string>
     <string name="contact">聯繫我們</string>
     <string name="email">报告错误，提出问题：</string>
+    <string name="exit_app">退出應用程式？</string>
 
     <string name="stars">星星</string>
     <string name="scores">高分</string>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index d309178a..7ebd4c2e 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -54,6 +54,7 @@
     <string name="share">Share this app</string>
     <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="stars">Stars</string>
     <string name="scores">High Scores</string>
