commit cb30e768dc23fc3509cc6d1e46fa89e04c0fbb09
Author: leszek <leszek@koltunski.pl>
Date:   Tue Nov 7 17:47:07 2023 +0100

    Version 2.0: add Solver Activity.

diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 9eca4d61..76f5c79b 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -35,6 +35,7 @@
         <activity android:name="org.distorted.bandaged.BandagedCreatorActivity" android:exported="false" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.bandaged.BandagedPlayActivity" android:exported="false" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.purchase.PurchaseActivity" android:exported="false" android:screenOrientation="portrait"/>
+        <activity android:name="org.distorted.solverui.SolverActivity" android:exported="false" android:screenOrientation="portrait"/>
 
         <service
             android:name="org.distorted.messaging.RubikMessagingService"
diff --git a/src/main/java/org/distorted/main/MainActivity.java b/src/main/java/org/distorted/main/MainActivity.java
index 7816b33e..9c54015e 100644
--- a/src/main/java/org/distorted/main/MainActivity.java
+++ b/src/main/java/org/distorted/main/MainActivity.java
@@ -46,6 +46,7 @@ import org.distorted.external.RubikUpdates;
 import org.distorted.messaging.RubikInAppMessanging;
 import org.distorted.objects.RubikObjectList;
 import org.distorted.purchase.PurchaseActivity;
+import org.distorted.solverui.SolverActivity;
 import org.distorted.tutorials.TutorialActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -63,7 +64,6 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
     private FirebaseAnalytics mFirebaseAnalytics;
     private int mCurrentApiVersion;
     private String mOldVersion, mCurrVersion;
-    private int mSolverIndex;
     private int mScreenWidth;
     private TextView mBubbleUpdates;
     private int mNumUpdates;
@@ -76,7 +76,7 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       {
       super.onCreate(savedState);
       setTheme(R.style.MaterialThemeNoActionBar);
-      setContentView(R.layout.new_main);
+      setContentView(R.layout.main);
       hideNavigationBar();
 
       mJustStarted = true;
@@ -86,7 +86,6 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       computeHeights();
 
       mCurrVersion = getAppVers();
-      mSolverIndex = 0;
 
       Thread thread = new Thread()
         {
@@ -300,7 +299,6 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       SharedPreferences.Editor editor = preferences.edit();
 
       editor.putString("appVersion", mCurrVersion );
-      editor.putInt("solverIndex", mSolverIndex );
 
       RubikObjectList.savePreferences(editor);
       RubikObjectList.saveMeshState(editor);
@@ -314,7 +312,6 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
     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();
@@ -392,27 +389,13 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void switchToPurchase(int objectOrdinal)
+    public void switchToSolver(int objectOrdinal)
       {
-      Intent intent = new Intent(this, PurchaseActivity.class);
+      Intent intent = new Intent(this, SolverActivity.class);
       intent.putExtra("obj", objectOrdinal);
       startActivity(intent);
       }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void setSolverIndex(int index)
-      {
-      mSolverIndex = index;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getSolverIndex()
-      {
-      return mSolverIndex;
-      }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void onScores(View v)
diff --git a/src/main/java/org/distorted/main/MainObjectPopup.java b/src/main/java/org/distorted/main/MainObjectPopup.java
index c1b593b7..89c4aa0e 100644
--- a/src/main/java/org/distorted/main/MainObjectPopup.java
+++ b/src/main/java/org/distorted/main/MainObjectPopup.java
@@ -71,7 +71,8 @@ public class MainObjectPopup
         @Override
         public void onClick(View v)
           {
-
+          mPopup.dismiss();
+          act.switchToSolver(ordinal);
           }
         });
       }
diff --git a/src/main/java/org/distorted/main_old/RubikActivity.java b/src/main/java/org/distorted/main_old/RubikActivity.java
index 39c7da01..e4255c17 100644
--- a/src/main/java/org/distorted/main_old/RubikActivity.java
+++ b/src/main/java/org/distorted/main_old/RubikActivity.java
@@ -112,7 +112,7 @@ public class RubikActivity extends AppCompatActivity
       super.onCreate(savedState);
       DistortedLibrary.onCreate(ACTIVITY_NUMBER);
       setTheme(R.style.MaterialThemeNoActionBar);
-      setContentView(R.layout.main);
+      setContentView(R.layout.old_main);
       hideNavigationBar();
 
       mJustStarted = true;
diff --git a/src/main/java/org/distorted/solvers/SolverCube3.java b/src/main/java/org/distorted/solvers/SolverCube3.java
index 266827cc..9e29919d 100644
--- a/src/main/java/org/distorted/solvers/SolverCube3.java
+++ b/src/main/java/org/distorted/solvers/SolverCube3.java
@@ -13,7 +13,7 @@ import android.content.res.Resources;
 
 import org.distorted.main.R;
 import org.distorted.objectlib.helpers.OperatingSystemInterface;
-import org.distorted.screens.RubikScreenSolver;
+import org.distorted.solverui.ScreenSolver;
 import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.objectlib.kociemba.SolverSearch;
 
@@ -175,7 +175,7 @@ public class SolverCube3
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void solve(RubikScreenSolver solver)
+  public void solve(ScreenSolver solver)
     {
     String result;
 
diff --git a/src/main/java/org/distorted/solvers/SolverMain.java b/src/main/java/org/distorted/solvers/SolverMain.java
index 12042e92..5504f818 100644
--- a/src/main/java/org/distorted/solvers/SolverMain.java
+++ b/src/main/java/org/distorted/solvers/SolverMain.java
@@ -16,8 +16,8 @@ import org.distorted.objectlib.signature.ObjectConstants;
 import org.distorted.objectlib.main.TwistyObject;
 
 import org.distorted.main.R;
-import org.distorted.screens.ScreenList;
-import org.distorted.screens.RubikScreenSolver;
+import org.distorted.solverui.ScreenList;
+import org.distorted.solverui.ScreenSolver;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -72,7 +72,7 @@ public class SolverMain implements Runnable
 
   public void run()
     {
-    RubikScreenSolver screen = (RubikScreenSolver) ScreenList.SVER.getScreenClass();
+    ScreenSolver screen = (ScreenSolver) ScreenList.SVER.getScreenClass();
 
     if( mSignature==ObjectConstants.CUBE_3 )
       {
diff --git a/src/main/java/org/distorted/solvers/SolverTablebase.java b/src/main/java/org/distorted/solvers/SolverTablebase.java
index 1cdfd73d..6d2d5b9b 100644
--- a/src/main/java/org/distorted/solvers/SolverTablebase.java
+++ b/src/main/java/org/distorted/solvers/SolverTablebase.java
@@ -13,7 +13,7 @@ import android.content.res.Resources;
 
 import org.distorted.objectlib.helpers.OperatingSystemInterface;
 import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.screens.RubikScreenSolver;
+import org.distorted.solverui.ScreenSolver;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -42,7 +42,7 @@ public abstract class SolverTablebase
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void solve(RubikScreenSolver screen)
+  public void solve(ScreenSolver screen)
     {
     int index = tablebaseIndex(mObject);
 
diff --git a/src/main/java/org/distorted/solverui/ScreenAbstract.java b/src/main/java/org/distorted/solverui/ScreenAbstract.java
new file mode 100644
index 00000000..d4bf5266
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/ScreenAbstract.java
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+import android.content.SharedPreferences;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class ScreenAbstract
+  {
+  abstract void enterScreen(SolverActivity act);
+  abstract void leaveScreen(SolverActivity act);
+  public abstract void savePreferences(SharedPreferences.Editor editor);
+  public abstract void restorePreferences(SharedPreferences preferences);
+  }
diff --git a/src/main/java/org/distorted/solverui/ScreenList.java b/src/main/java/org/distorted/solverui/ScreenList.java
new file mode 100644
index 00000000..50b702da
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/ScreenList.java
@@ -0,0 +1,151 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+import static org.distorted.objectlib.main.ObjectControl.MODE_DRAG;
+import static org.distorted.objectlib.main.ObjectControl.MODE_REPLACE;
+import static org.distorted.objectlib.main.ObjectControl.MODE_ROTATE;
+
+import android.content.SharedPreferences;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public enum ScreenList
+  {
+  SVER ( null , MODE_REPLACE, new ScreenSolver()   ),
+  SOLU ( SVER , MODE_DRAG   , new ScreenSolution() ),
+  ;
+
+  public static final int LENGTH = values().length;
+  private static final ScreenList[] screens;
+  private final ScreenList mBack;
+  private final int mMode;
+  private final ScreenAbstract mClass;
+
+  private static ScreenList mCurrScreen;
+
+  static
+    {
+    int i = 0;
+    screens = new ScreenList[LENGTH];
+    for(ScreenList state: ScreenList.values()) screens[i++] = state;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getScreen(int ordinal)
+    {
+    return ordinal>=0 && ordinal<LENGTH ?  screens[ordinal] : SVER;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getScreenFromName(String name)
+    {
+    for(int i=0; i<LENGTH; i++)
+      {
+      if( name.equals(screens[i].name()) )
+        {
+        return screens[i];
+        }
+      }
+
+    return SVER;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScreenList getCurrentScreen()
+    {
+    return mCurrScreen;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getMode()
+    {
+    return mCurrScreen.mMode;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void savePreferences(SharedPreferences.Editor editor)
+    {
+    editor.putString("curr_state_name", mCurrScreen.name() );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void restorePreferences(SharedPreferences preferences)
+    {
+    String currScreenName = preferences.getString("curr_state_name", ScreenList.SVER.name() );
+    mCurrScreen = getScreenFromName(currScreenName);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void goBack(SolverActivity act)
+    {
+    switchScreen(act, mCurrScreen.mBack );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void setScreen(SolverActivity act)
+    {
+    mCurrScreen.enterScreen(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void switchScreen(SolverActivity act, ScreenList next)
+    {
+    if( next!=null )
+      {
+      if( mCurrScreen !=null ) mCurrScreen.leaveScreen(act);
+      next.enterScreen(act);
+      mCurrScreen = next;
+      }
+    else
+      {
+      act.finish();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  ScreenList(ScreenList back, int mode, ScreenAbstract clazz)
+    {
+    mBack = back;
+    mMode = mode;
+    mClass= clazz;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ScreenAbstract getScreenClass()
+    {
+    return mClass;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void leaveScreen(SolverActivity act)
+    {
+    mClass.leaveScreen(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void enterScreen(SolverActivity act)
+    {
+    mClass.enterScreen(act);
+    }
+  }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/solverui/ScreenSolution.java b/src/main/java/org/distorted/solverui/ScreenSolution.java
new file mode 100644
index 00000000..b182d3e9
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/ScreenSolution.java
@@ -0,0 +1,292 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.objectlib.helpers.MovesFinished;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.patterns.RubikPattern;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScreenSolution extends ScreenAbstract implements MovesFinished
+  {
+  private static final int MILLIS_PER_DEGREE = 6;
+
+  private TransparentImageButton mPrevButton, mNextButton, mBackButton;
+  private TextView mMovesText;
+  private int[][] mMoves;
+  private int mCurrMove, mNumMoves;
+  private boolean mCanRotate;
+  private float mButtonSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(SolverActivity act)
+    {
+    ObjectControl control = act.getControl();
+    control.solveOnly();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final SolverActivity act)
+    {
+    float width = act.getScreenWidthInPixels();
+    mButtonSize = width*SolverActivity.BUTTON_TEXT_SIZE;
+    float titleSize  = width*SolverActivity.TITLE_TEXT_SIZE;
+
+    LayoutInflater inflater = act.getLayoutInflater();
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+
+    final TextView text = (TextView)inflater.inflate(R.layout.upper_text, null);
+    text.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    text.setText(R.string.solution);
+    layoutTop.addView(text);
+
+    // BOT ////////////////////////////
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams((int)(width/2),LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams((int)(width/6),LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams((int)(width/3),LinearLayout.LayoutParams.MATCH_PARENT);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutMid = new LinearLayout(act);
+    layoutMid.setLayoutParams(paramsM);
+    LinearLayout layoutRight = new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
+
+    setupPrevButton(act);
+    setupNextButton(act);
+    setupTextView(act,width);
+
+    layoutLeft.addView(mPrevButton);
+    layoutLeft.addView(mMovesText);
+    layoutLeft.addView(mNextButton);
+
+    setupBackButton(act);
+
+    layoutRight.addView(mBackButton);
+
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutMid);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPrevButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mPrevButton = new TransparentImageButton(act,R.drawable.ui_left,params);
+
+    mPrevButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        backMove(control);
+        mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupNextButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mNextButton = new TransparentImageButton(act,R.drawable.ui_right,params);
+
+    mNextButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        makeMove(control);
+        mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupTextView(final SolverActivity act, final float width)
+    {
+    int padding = (int)(width*SolverActivity.PADDING);
+    int margin  = (int)(width*SolverActivity.SMALL_MARGIN);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,2.0f);
+    params.topMargin    = margin;
+    params.bottomMargin = margin;
+    params.leftMargin   = margin;
+    params.rightMargin  = margin;
+
+    mMovesText = new TextView(act);
+    mMovesText.setTextSize(20);
+    mMovesText.setLayoutParams(params);
+    mMovesText.setPadding(padding,0,padding,0);
+    mMovesText.setGravity(Gravity.CENTER);
+    mMovesText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
+    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mBackButton = new TransparentImageButton(act,R.drawable.ui_back,params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ScreenList.goBack(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void makeMove(ObjectControl control)
+    {
+    if( mCanRotate )
+      {
+      mCurrMove++;
+
+      if( mCurrMove>mNumMoves )
+        {
+        mCurrMove= 0;
+        control.initializeObject(null);
+        }
+      else
+        {
+        int axis      = mMoves[mCurrMove-1][0];
+		    int rowBitmap = mMoves[mCurrMove-1][1];
+		    int bareAngle = mMoves[mCurrMove-1][2];
+
+        if( bareAngle!=0 )
+          {
+          mCanRotate = false;
+          control.addRotation(this, axis, rowBitmap, bareAngle, MILLIS_PER_DEGREE);
+          }
+        else
+          {
+          android.util.Log.e("solution", "error: solution contains angle 0");
+          }
+        }
+      }
+    else
+      {
+      android.util.Log.e("solution", "failed to make move!");
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void backMove(ObjectControl control)
+    {
+    if( mCanRotate )
+      {
+      mCurrMove--;
+
+      if( mCurrMove<0 )
+        {
+        mCurrMove=mNumMoves;
+        control.initializeObject(mMoves);
+        }
+      else
+        {
+        int axis      = mMoves[mCurrMove][0];
+		    int rowBitmap = mMoves[mCurrMove][1];
+		    int bareAngle = mMoves[mCurrMove][2];
+
+        if( bareAngle!=0 )
+          {
+          mCanRotate = false;
+          control.addRotation(this, axis, rowBitmap, -bareAngle, MILLIS_PER_DEGREE);
+          }
+        else
+          {
+          android.util.Log.e("solution", "error: solution contains angle 0");
+          }
+        }
+      }
+    else
+      {
+      android.util.Log.e("solution", "failed to back move!");
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setupMoves(final SolverActivity act, String moves)
+    {
+    mCanRotate= true;
+    mCurrMove = 0;
+    mNumMoves = moves.length()/4;
+    mMoves    = new int[mNumMoves][3];
+
+    RubikPattern.parseMoves(mMoves,mNumMoves,moves);
+
+    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setupMoves(final SolverActivity act, int[][] moves)
+    {
+    mCanRotate= true;
+    mCurrMove = 0;
+    mNumMoves = moves==null ? 0 : moves.length;
+    mMoves    = moves;
+
+    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onActionFinished(final long effectID)
+    {
+    mCanRotate = true;
+    }
+  }
diff --git a/src/main/java/org/distorted/solverui/ScreenSolver.java b/src/main/java/org/distorted/solverui/ScreenSolver.java
new file mode 100644
index 00000000..01115ec9
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/ScreenSolver.java
@@ -0,0 +1,470 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+import static org.distorted.objectlib.main.TwistyObject.COLOR_BLUE;
+import static org.distorted.objectlib.main.TwistyObject.COLOR_RED;
+import static org.distorted.objectlib.main.TwistyObject.COLOR_WHITE;
+import static org.distorted.objectlib.main.TwistyObject.COLOR_YELLOW;
+
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.core.content.ContextCompat;
+
+import org.distorted.dialogs.RubikDialogSolverError;
+import org.distorted.dialogs.RubikDialogSolverImpossible;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.MainActivity;
+import org.distorted.main.R;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.shape.ShapeDiamond;
+import org.distorted.objectlib.shape.ShapeDodecahedron;
+import org.distorted.objectlib.shape.ShapeHexahedron;
+import org.distorted.objectlib.shape.ShapeIcosahedron;
+import org.distorted.objectlib.shape.ShapeOctahedron;
+import org.distorted.objectlib.shape.ShapeTetrahedron;
+import org.distorted.objectlib.signature.ObjectConstants;
+import org.distorted.solvers.ImplementedSolversList;
+import org.distorted.solvers.SolverMain;
+
+import java.lang.ref.WeakReference;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScreenSolver extends ScreenAbstract
+  {
+  private static final int RESET_DURATION = 1000;
+  private static final int MODE_NORMAL = 0;
+  private static final int MODE_DINO_4 = 1;
+
+  private static Bitmap[] mBitmap;
+  private ImageButton[] mColorButton;
+  private TransparentImageButton mResetButton,mBackButton, mSolveButton;
+  private boolean mSolving;
+  private int mCurrentColor, mCurrentButton;
+  private int[] mFaceColors;
+  private int mColorMode;
+  private int mNumColors;
+  private float mBitmapSize;
+  private WeakReference<SolverActivity> mWeakAct;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(SolverActivity act)
+    {
+    ObjectControl control = act.getControl();
+    control.unsetLock();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final SolverActivity act)
+    {
+    ObjectControl control = act.getControl();
+    control.setLock(false);
+
+    float width = act.getScreenWidthInPixels();
+    float heigh = act.getScreenHeightInPixels();
+
+    mWeakAct = new WeakReference<>(act);
+    mSolving = false;
+
+    int solverIndex= act.getSolverOrdinal();
+    ImplementedSolversList currentSolver = ImplementedSolversList.getSolver(solverIndex);
+    int currentObject = currentSolver.getObject();
+    control.solveOnly();
+    generateFaceColors(currentObject);
+
+    final float BUTTON_RATIO = 0.75f;
+    int sizeV = (int)(heigh*MainActivity.RATIO_BAR*BUTTON_RATIO);
+    int sizeH = (int)((width/mNumColors)*BUTTON_RATIO);
+    mBitmapSize = Math.min(sizeV,sizeH);
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+
+    if( mNumColors>0 )
+      {
+      setupBitmaps();
+      setupColorButtons(act,width);
+      markButton(act);
+      }
+
+    for(ImageButton button: mColorButton) layoutTop.addView(button);
+
+    // BOT ////////////////////////////
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1);
+
+    LinearLayout layoutL = new LinearLayout(act);
+    layoutL.setLayoutParams(params);
+    LinearLayout layoutM = new LinearLayout(act);
+    layoutM.setLayoutParams(params);
+    LinearLayout layoutR = new LinearLayout(act);
+    layoutR.setLayoutParams(params);
+
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    setupResetButton(act);
+    setupSolveButton(act);
+    setupBackButton(act);
+
+    layoutL.addView(mResetButton);
+    layoutM.addView(mSolveButton);
+    layoutR.addView(mBackButton);
+
+    layoutBot.addView(layoutL);
+    layoutBot.addView(layoutM);
+    layoutBot.addView(layoutR);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// This doesn't quite work in many cases, but in case of the solvers that will pop up in foreseeable
+// future it should be ok.
+
+  public void generateFaceColors(int object)
+    {
+    mColorMode = MODE_NORMAL;
+
+    if( object== ObjectConstants.PYRA_3 ||
+        object== ObjectConstants.PYRA_4 ||
+        object== ObjectConstants.PYRA_5 ||
+        object== ObjectConstants.PDUO_2 ||
+        object== ObjectConstants.JING_2 ||
+        object== ObjectConstants.MORP_2 ||
+        object== ObjectConstants.MORP_3 ||
+        object== ObjectConstants.MORP_4  )
+      {
+      mNumColors  = ShapeTetrahedron.NUM_FACES;
+      mFaceColors = ShapeTetrahedron.FACE_COLORS;
+      }
+    else if( object== ObjectConstants.DIAM_2 ||
+             object== ObjectConstants.DIAM_3 ||
+             object== ObjectConstants.DIAM_4 ||
+             object== ObjectConstants.TRAJ_3 ||
+             object== ObjectConstants.TRAJ_4 ||
+             object== ObjectConstants.PDIA_3  )
+      {
+      mNumColors  = ShapeOctahedron.NUM_FACES;
+      mFaceColors = ShapeOctahedron.FACE_COLORS;
+      }
+    else if( object== ObjectConstants.CRYS_3 ||
+             object== ObjectConstants.STAR_3 ||
+             object== ObjectConstants.PENT_2 ||
+             object== ObjectConstants.KILO_3 ||
+             object== ObjectConstants.KILO_5 ||
+             object== ObjectConstants.MEGA_3 ||
+             object== ObjectConstants.MEGA_5  )
+      {
+      mNumColors  = ShapeDodecahedron.NUM_FACES;
+      mFaceColors = ShapeDodecahedron.FACE_COLORS;
+      }
+    else if( object== ObjectConstants.BALL_4 )
+      {
+      mNumColors  = ShapeDiamond.NUM_FACES;
+      mFaceColors = ShapeDiamond.FACE_COLORS;
+      }
+    else if( object== ObjectConstants.ICOS_2 )
+      {
+      mNumColors  = ShapeIcosahedron.NUM_FACES;
+      mFaceColors = ShapeIcosahedron.FACE_COLORS;
+      }
+    else if( object== ObjectConstants.DIN4_3 )
+      {
+      mNumColors  = 4;
+      mFaceColors = new int[] { COLOR_YELLOW, COLOR_RED, COLOR_BLUE, COLOR_WHITE };
+      mColorMode  = MODE_DINO_4;
+      }
+    else
+      {
+      mNumColors  = ShapeHexahedron.NUM_FACES;
+      mFaceColors = ShapeHexahedron.FACE_COLORS;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBitmaps()
+    {
+    final int SIZE = (int)mBitmapSize;
+    final float R = SIZE*0.15f;
+    final float M = SIZE*0.08f;
+
+    mBitmap = new Bitmap[mNumColors];
+
+    Paint paint = new Paint();
+    paint.setColor(0xff008800);
+    paint.setStyle(Paint.Style.FILL);
+
+    paint.setAntiAlias(true);
+    paint.setTextAlign(Paint.Align.CENTER);
+    paint.setStyle(Paint.Style.FILL);
+
+    for(int i=0; i<mNumColors; i++)
+      {
+      mBitmap[i] = Bitmap.createBitmap(SIZE, SIZE, Bitmap.Config.ARGB_8888);
+      Canvas canvas = new Canvas(mBitmap[i]);
+
+      paint.setColor(0xff000000);
+      canvas.drawRect(0, 0, SIZE, SIZE, paint);
+
+      paint.setColor(mFaceColors[i]);
+      canvas.drawRoundRect( M, M, SIZE-M, SIZE-M, R, R, paint);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int translateColor(int color)
+    {
+    if( mColorMode==MODE_DINO_4 )
+      {
+      int realColor = mFaceColors[color];
+      int[] hexColors = ShapeHexahedron.FACE_COLORS;
+
+      for(int i=0; i<6; i++)
+        if( hexColors[i]==realColor ) return i;
+      }
+
+    return color;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupColorButtons(final SolverActivity act, final float width)
+    {
+    mColorButton = new ImageButton[mNumColors];
+    int padding = (int)(width*SolverActivity.PADDING);
+    int margin  = (int)(width*SolverActivity.SMALL_MARGIN);
+
+    for(int i=0; i<mNumColors; i++)
+      {
+      final int ii = i;
+      LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
+      params.topMargin    = margin;
+      params.bottomMargin = margin;
+      params.leftMargin   = margin;
+      params.rightMargin  = margin;
+
+      mColorButton[i] = new ImageButton(act);
+      mColorButton[i].setLayoutParams(params);
+      mColorButton[i].setPadding(padding,0,padding,0);
+      mColorButton[i].setImageBitmap(mBitmap[i]);
+
+      mColorButton[i].setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View view)
+          {
+          mCurrentColor = translateColor(ii);
+          mCurrentButton= ii;
+          markButton(act);
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupResetButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mResetButton = new TransparentImageButton(act, R.drawable.ui_reset, params);
+
+    mResetButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        control.resetTextureMapsEffect(RESET_DURATION);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupSolveButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mSolveButton = new TransparentImageButton(act,R.drawable.ui_solve,params);
+
+    mSolveButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        if( !mSolving )
+          {
+          mSolving = true;
+          TwistyObject object = act.getObject();
+          SolverMain solver = new SolverMain( act.getInterface(), act.getResources(), object );
+          solver.start();
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mBackButton = new TransparentImageButton(act,R.drawable.ui_back,params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        control.resetAllTextureMaps();
+        ScreenList.goBack(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void markButton(SolverActivity act)
+    {
+    if( mCurrentButton>=mNumColors )
+      {
+      mCurrentButton = 0;
+      mCurrentColor = translateColor(0);
+      }
+
+    for(int b=0; b<mNumColors; b++)
+      {
+      Drawable d = mColorButton[b].getBackground();
+
+      if( b==mCurrentButton )
+        {
+        d.setColorFilter(ContextCompat.getColor(act,R.color.red), PorterDuff.Mode.MULTIPLY);
+        }
+      else
+        {
+        d.clearColorFilter();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+    editor.putInt("stateSolver_color" , mCurrentColor );
+    editor.putInt("stateSolver_button", mCurrentButton);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+    mCurrentColor = preferences.getInt("stateSolver_color" , 0);
+    mCurrentButton= preferences.getInt("stateSolver_button", 0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCurrentColor()
+    {
+    return mCurrentColor;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setSolved(final String moves)
+    {
+    mSolving = false;
+    final SolverActivity act = mWeakAct.get();
+
+    if( act!=null )
+      {
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchScreen(act, ScreenList.SOLU);
+          ScreenSolution solution = (ScreenSolution) ScreenList.SOLU.getScreenClass();
+          solution.setupMoves(act, moves);
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setSolved(final int[][] moves)
+    {
+    mSolving = false;
+    final SolverActivity act = mWeakAct.get();
+
+    if( act!=null )
+      {
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchScreen(act, ScreenList.SOLU);
+          ScreenSolution solution = (ScreenSolution) ScreenList.SOLU.getScreenClass();
+          solution.setupMoves(act, moves);
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void displayErrorDialog(String message)
+    {
+    mSolving = false;
+    SolverActivity act = mWeakAct.get();
+
+    if( act!=null )
+      {
+      RubikDialogSolverError dialog = new RubikDialogSolverError();
+      Bundle bundle = new Bundle();
+      bundle.putString("argument", message );
+      dialog.setArguments(bundle);
+      dialog.show( act.getSupportFragmentManager(), null);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void displayImpossibleDialog(String message)
+    {
+    mSolving = false;
+    SolverActivity act = mWeakAct.get();
+
+    if( act!=null )
+      {
+      RubikDialogSolverImpossible dialog = new RubikDialogSolverImpossible();
+      Bundle bundle = new Bundle();
+      bundle.putString("argument", message );
+      dialog.setArguments(bundle);
+      dialog.show( act.getSupportFragmentManager(), null);
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/solverui/SolverActivity.java b/src/main/java/org/distorted/solverui/SolverActivity.java
new file mode 100644
index 00000000..0f95b95c
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/SolverActivity.java
@@ -0,0 +1,348 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+import static org.distorted.objectlib.main.TwistyObject.MESH_NICE;
+
+import android.content.SharedPreferences;
+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 org.distorted.dialogs.RubikDialogError;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.main.MainActivity;
+import org.distorted.main.R;
+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.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+import org.distorted.os.OSInterface;
+import org.distorted.solvers.ImplementedSolversList;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverActivity extends AppCompatActivity
+{
+    public static final float RATIO_BAR       = 0.100f;
+    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;
+
+    private static final int ACTIVITY_NUMBER = 0;
+    private static final float RATIO_INSET= 0.09f;
+
+    private boolean mJustStarted;
+    private static int mScreenWidth, mScreenHeight;
+    private int mCurrentApiVersion;
+    private int mHeightUpperBar;
+    private int mSolverOrdinal;
+    private int mObjectOrdinal;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void onCreate(Bundle savedState)
+      {
+      super.onCreate(savedState);
+      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
+      setTheme(R.style.MaterialThemeNoActionBar);
+      setContentView(R.layout.solver);
+      hideNavigationBar();
+
+      mJustStarted = true;
+
+      Bundle b = getIntent().getExtras();
+      mObjectOrdinal = b!=null ? b.getInt("obj") : 0;
+      mSolverOrdinal = ImplementedSolversList.getSolverOrdinal(mObjectOrdinal);
+
+      DisplayMetrics displaymetrics = new DisplayMetrics();
+      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
+      mScreenWidth =displaymetrics.widthPixels;
+      mScreenHeight=displaymetrics.heightPixels;
+
+      cutoutHack();
+      computeBarHeights();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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(MainActivity.FLAGS);
+
+        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
+          {
+          @Override
+          public void onSystemUiVisibilityChange(int visibility)
+            {
+            if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
+              {
+              decorView.setSystemUiVisibility(MainActivity.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(MainActivity.FLAGS);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onPause() 
+      {
+      super.onPause();
+      SolverSurfaceView view = findViewById(R.id.solverSurfaceView);
+      view.onPause();
+      DistortedLibrary.onPause(ACTIVITY_NUMBER);
+      savePreferences();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+      DistortedLibrary.onResume(ACTIVITY_NUMBER);
+      SolverSurfaceView view = findViewById(R.id.solverSurfaceView);
+      view.onResume();
+
+      createObject();
+
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      restorePreferences(preferences,mJustStarted);
+      ScreenList.setScreen(this);
+
+      if( mJustStarted )
+        {
+        mJustStarted = false;
+        RubikObjectList.restoreMeshState(preferences);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
+      super.onDestroy();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void savePreferences()
+      {
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      SharedPreferences.Editor editor = preferences.edit();
+
+      editor.putInt("solverIndex", mSolverOrdinal );
+
+      for( int i=0; i< ScreenList.LENGTH; i++ )
+        {
+        ScreenList.getScreen(i).getScreenClass().savePreferences(editor);
+        }
+
+      RubikObjectList.savePreferences(editor);
+      RubikObjectList.saveMeshState(editor);
+      ScreenList.savePreferences(editor);
+      SolverSurfaceView view = findViewById(R.id.solverSurfaceView);
+      OSInterface os = view.getInterface();
+      os.setEditor(editor);
+      view.getObjectControl().savePreferences();
+
+      boolean success = editor.commit();
+      if( !success ) android.util.Log.e("D", "Failed to save preferences");
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void restorePreferences(SharedPreferences preferences, boolean justStarted)
+      {
+      mSolverOrdinal = preferences.getInt("solverIndex",0);
+
+      RubikObjectList.restorePreferences(this,preferences,justStarted);
+
+      for (int i=0; i<ScreenList.LENGTH; i++)
+        {
+        ScreenList.getScreen(i).getScreenClass().restorePreferences(preferences);
+        }
+
+      ScreenList.restorePreferences(preferences);
+
+      SolverSurfaceView view = findViewById(R.id.solverSurfaceView);
+      OSInterface os = view.getInterface();
+      os.setPreferences(preferences);
+      view.getObjectControl().restorePreferences();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void OpenGLError()
+      {
+      RubikDialogError errDiag = new RubikDialogError();
+      errDiag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public TwistyObject getObject()
+      {
+      SolverSurfaceView view = findViewById(R.id.solverSurfaceView);
+      return view.getObjectControl().getObject();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public DistortedScreen getScreen()
+      {
+      SolverSurfaceView view = findViewById(R.id.solverSurfaceView);
+      return view.getRenderer().getScreen();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public ObjectControl getControl()
+      {
+      SolverSurfaceView view = findViewById(R.id.solverSurfaceView);
+      return view.getObjectControl();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenWidthInPixels()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenHeightInPixels()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getSolverOrdinal()
+      {
+      return mSolverOrdinal;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void createObject()
+      {
+      SolverSurfaceView view = findViewById(R.id.solverSurfaceView);
+      ObjectControl control = view.getObjectControl();
+      RubikObject object = RubikObjectList.getObject(mObjectOrdinal);
+      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();
+      OSInterface os = view.getInterface();
+      InitAssets asset = new InitAssets(jsonStream,meshStream,os);
+
+      control.changeIfDifferent(mObjectOrdinal,name,MESH_NICE,iconMode,asset);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public OperatingSystemInterface getInterface()
+      {
+      SolverSurfaceView view  = findViewById(R.id.solverSurfaceView);
+      return view.getInterface();
+      }
+}
diff --git a/src/main/java/org/distorted/solverui/SolverObjectLibInterface.java b/src/main/java/org/distorted/solverui/SolverObjectLibInterface.java
new file mode 100644
index 00000000..35325e04
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/SolverObjectLibInterface.java
@@ -0,0 +1,195 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import org.distorted.library.message.EffectMessageSender;
+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.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+import org.distorted.overlays.ListenerOverlay;
+import org.distorted.solvers.SolverMain;
+
+import java.lang.ref.WeakReference;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverObjectLibInterface implements ObjectLibInterface, ListenerOverlay
+{
+  private final WeakReference<SolverActivity> mAct;
+  private int mLastCubitColor, mLastCubit, mLastCubitFace;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  SolverObjectLibInterface(SolverActivity act)
+    {
+    mAct = new WeakReference<>(act);
+    mLastCubitColor = -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onScrambleEffectFinished() { }
+  public void onFinishRotation(int axis, int row, int angle) { }
+  public void onBeginRotation() { }
+  public void failedToDrag() { }
+  public void onSolved() { }
+  public void onObjectCreated(long time) { }
+  public void onWinEffectFinished(long startTime, long endTime, String debug, int scrambleNum) { }
+  public void overlayFinished(long id) { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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)
+    {
+    ScreenSolver solver = (ScreenSolver) 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;
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/solverui/SolverRenderer.java b/src/main/java/org/distorted/solverui/SolverRenderer.java
new file mode 100644
index 00000000..82e071e1
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/SolverRenderer.java
@@ -0,0 +1,158 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+import android.content.res.Resources;
+import android.opengl.GLES30;
+import android.opengl.GLSurfaceView;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+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.main.InternalOutputSurface;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.main.BuildConfig;
+import org.distorted.objectlib.effects.BaseEffect;
+import org.distorted.objectlib.main.ObjectControl;
+
+import java.io.InputStream;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
+{
+   public static final float BRIGHTNESS = 0.30f;
+
+   private final SolverSurfaceView mView;
+   private final Resources mResources;
+   private final DistortedScreen mScreen;
+   private final ObjectControl mControl;
+   private boolean mErrorShown;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   SolverRenderer(SolverSurfaceView v)
+     {
+     mView = v;
+     mResources = v.getResources();
+
+     mErrorShown = false;
+     mControl = v.getObjectControl();
+     mScreen = new DistortedScreen();
+     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+     mScreen.enableDepthStencil(InternalOutputSurface.DEPTH_NO_STENCIL);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   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();
+     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();
+
+      DistortedLibrary.onSurfaceCreated(this,1);
+      DistortedLibrary.setCull(true);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   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;
+       SolverActivity act = (SolverActivity)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/solverui/SolverSurfaceView.java b/src/main/java/org/distorted/solverui/SolverSurfaceView.java
new file mode 100644
index 00000000..455e5a45
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/SolverSurfaceView.java
@@ -0,0 +1,149 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+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.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObjectNode;
+import org.distorted.os.OSInterface;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverSurfaceView extends GLSurfaceView
+{
+    private ObjectControl mObjectController;
+    private OSInterface mInterface;
+    private SolverRenderer mRenderer;
+    private boolean mCreated;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    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);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    SolverRenderer getRenderer()
+      {
+      return mRenderer;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    OSInterface getInterface()
+      {
+      return mInterface;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    ObjectControl getObjectControl()
+      {
+      return mObjectController;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public SolverSurfaceView(Context context, AttributeSet attrs)
+      {
+      super(context,attrs);
+
+      mCreated = false;
+
+      if(!isInEditMode())
+        {
+        SolverActivity act = (SolverActivity)context;
+        SolverObjectLibInterface ref = new SolverObjectLibInterface(act);
+        mInterface = new OSInterface(act,ref);
+        mObjectController = new ObjectControl(mInterface);
+        mRenderer = new SolverRenderer(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/res/layout/main.xml b/src/main/res/layout/main.xml
index acc9e572..e0e8e65d 100644
--- a/src/main/res/layout/main.xml
+++ b/src/main/res/layout/main.xml
@@ -1,43 +1,160 @@
 <?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/relativeLayout"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <org.distorted.main_old.RubikSurfaceView
-        android:id="@+id/rubikSurfaceView"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_alignParentStart="true"
-        android:layout_alignParentTop="true"/>
+    android:layout_height="match_parent"
+    android:weightSum="1.0"
+    android:orientation="vertical">
 
     <LinearLayout
         android:id="@+id/hiddenBar"
-        android:layout_alignParentTop="true"
         android:layout_width="match_parent"
         android:layout_height="0dp"
+        android:layout_weight="0.00"
         android:gravity="center"
         android:orientation="horizontal"
-        android:background="@android:color/transparent">
+        android:background="@color/dark_grey">
     </LinearLayout>
 
     <LinearLayout
         android:id="@+id/upperBar"
-        android:layout_below="@id/hiddenBar"
         android:layout_width="match_parent"
         android:layout_height="0dp"
+        android:layout_weight="0.08"
         android:gravity="center"
+        android:weightSum="1.0"
         android:orientation="horizontal"
-        android:background="@android:color/transparent">
+        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="0dp"
+        android:layout_weight="0.84"
+        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_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="0dp"
+        android:layout_weight="0.08"
         android:orientation="horizontal"
-        android:background="@android:color/transparent">
+        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>
 
-</RelativeLayout>
+</LinearLayout>
diff --git a/src/main/res/layout/new_main.xml b/src/main/res/layout/new_main.xml
deleted file mode 100644
index e0e8e65d..00000000
--- a/src/main/res/layout/new_main.xml
+++ /dev/null
@@ -1,160 +0,0 @@
-<?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:weightSum="1.0"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:id="@+id/hiddenBar"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="0.00"
-        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="0dp"
-        android:layout_weight="0.08"
-        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="0dp"
-        android:layout_weight="0.84"
-        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="0dp"
-        android:layout_weight="0.08"
-        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/layout/old_main.xml b/src/main/res/layout/old_main.xml
new file mode 100644
index 00000000..acc9e572
--- /dev/null
+++ b/src/main/res/layout/old_main.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/relativeLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <org.distorted.main_old.RubikSurfaceView
+        android:id="@+id/rubikSurfaceView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"/>
+
+    <LinearLayout
+        android:id="@+id/hiddenBar"
+        android:layout_alignParentTop="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/upperBar"
+        android:layout_below="@id/hiddenBar"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/lowerBar"
+        android:layout_alignParentBottom="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/src/main/res/layout/solver.xml b/src/main/res/layout/solver.xml
new file mode 100644
index 00000000..efd6f70e
--- /dev/null
+++ b/src/main/res/layout/solver.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/relativeLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <org.distorted.solverui.SolverSurfaceView
+        android:id="@+id/solverSurfaceView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"/>
+
+    <LinearLayout
+        android:id="@+id/hiddenBar"
+        android:layout_alignParentTop="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/upperBar"
+        android:layout_below="@id/hiddenBar"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/lowerBar"
+        android:layout_alignParentBottom="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+</RelativeLayout>
