commit e019c70bf37953b61bc8a44034cf2f8625f1e7b6
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Tue Oct 5 15:12:28 2021 +0200

    Remove the concept of a 'TwistyActivity' altogether.

diff --git a/src/main/java/org/distorted/helpers/LockController.java b/src/main/java/org/distorted/helpers/LockController.java
index 0a05db2d..3112ea3f 100644
--- a/src/main/java/org/distorted/helpers/LockController.java
+++ b/src/main/java/org/distorted/helpers/LockController.java
@@ -29,7 +29,6 @@ import android.widget.LinearLayout;
 
 import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
-import org.distorted.objectlib.helpers.TwistyActivity;
 import org.distorted.objectlib.main.ObjectControl;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -152,7 +151,7 @@ public class LockController
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void setupButton(final TwistyActivity act, ObjectControl control, final float width)
+  public void setupButton(final Activity act, ObjectControl control, final float width)
     {
     boolean locked = control.retLocked();
     final int icon = getLockIcon(locked,false);
diff --git a/src/main/java/org/distorted/helpers/MovesController.java b/src/main/java/org/distorted/helpers/MovesController.java
index 4fe3daa0..d4b3acf2 100644
--- a/src/main/java/org/distorted/helpers/MovesController.java
+++ b/src/main/java/org/distorted/helpers/MovesController.java
@@ -21,13 +21,13 @@ package org.distorted.helpers;
 
 import java.util.ArrayList;
 
+import android.app.Activity;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 
 import org.distorted.objectlib.helpers.BlockController;
 import org.distorted.objectlib.helpers.MovesFinished;
-import org.distorted.objectlib.helpers.TwistyActivity;
 import org.distorted.objectlib.main.ObjectControl;
 
 import org.distorted.main.R;
@@ -87,7 +87,7 @@ public class MovesController implements MovesFinished
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void backMove(TwistyActivity act)
+  public void backMove(Activity act, ObjectControl control)
     {
     if( mCanPrevMove )
       {
@@ -102,7 +102,7 @@ public class MovesController implements MovesFinished
         if( angle!=0 )
           {
           mCanPrevMove = false;
-          mControl = act.getControl();
+          mControl = control;
           mControl.blockTouch(BlockController.MOVES_PLACE_0);
           mControl.addRotation(this, axis, (1<<move.mRow), -angle, MILLIS_PER_DEGREE);
           }
@@ -118,7 +118,7 @@ public class MovesController implements MovesFinished
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void changeBackMove(TwistyActivity act, final boolean on)
+  private void changeBackMove(Activity act, final boolean on)
     {
     act.runOnUiThread(new Runnable()
       {
@@ -133,7 +133,7 @@ public class MovesController implements MovesFinished
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void addMove(TwistyActivity act, int axis, int row, int angle)
+  public void addMove(Activity act, int axis, int row, int angle)
     {
     if( mMoves.isEmpty() ) changeBackMove(act,true);
     mMoves.add(new Move(axis,row,angle));
@@ -149,7 +149,7 @@ public class MovesController implements MovesFinished
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void clearMoves(final TwistyActivity act)
+  public void clearMoves(final Activity act)
     {
     mMoves.clear();
     changeBackMove(act,false);
@@ -164,7 +164,7 @@ public class MovesController implements MovesFinished
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void setupButton(final TwistyActivity act, final float width)
+  public void setupButton(final Activity act, ObjectControl control, final float width)
     {
     final int icon = getPrevIcon( !mMoves.isEmpty() );
     mPrevButton = new TransparentImageButton(act, icon, width, LinearLayout.LayoutParams.MATCH_PARENT);
@@ -174,7 +174,7 @@ public class MovesController implements MovesFinished
       @Override
       public void onClick(View v)
         {
-        backMove(act);
+        backMove(act,control);
         }
       });
     }
diff --git a/src/main/java/org/distorted/main/RubikActivity.java b/src/main/java/org/distorted/main/RubikActivity.java
index 00dbdde8..0c7f455b 100644
--- a/src/main/java/org/distorted/main/RubikActivity.java
+++ b/src/main/java/org/distorted/main/RubikActivity.java
@@ -35,6 +35,8 @@ import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.LinearLayout;
 
+import androidx.appcompat.app.AppCompatActivity;
+
 import com.google.firebase.analytics.FirebaseAnalytics;
 
 import org.distorted.library.main.DistortedLibrary;
@@ -45,7 +47,6 @@ import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.objectlib.main.ObjectType;
 import org.distorted.objectlib.helpers.BlockController;
 import org.distorted.objectlib.effects.BaseEffect;
-import org.distorted.objectlib.helpers.TwistyActivity;
 
 import org.distorted.dialogs.RubikDialogError;
 import org.distorted.dialogs.RubikDialogPrivacy;
@@ -57,7 +58,7 @@ import org.distorted.tutorials.TutorialActivity;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikActivity extends TwistyActivity
+public class RubikActivity extends AppCompatActivity
 {
     public static final float PADDING             = 0.01f;
     public static final float MARGIN              = 0.004f;
diff --git a/src/main/java/org/distorted/main/RubikObjectLibInterface.java b/src/main/java/org/distorted/main/RubikObjectLibInterface.java
new file mode 100644
index 00000000..516a89bc
--- /dev/null
+++ b/src/main/java/org/distorted/main/RubikObjectLibInterface.java
@@ -0,0 +1,340 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+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 org.distorted.library.main.DistortedScreen;
+import org.distorted.objectlib.helpers.ObjectLibInterface;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.ObjectType;
+
+import org.distorted.dialogs.RubikDialogNewRecord;
+import org.distorted.dialogs.RubikDialogSolved;
+import org.distorted.network.RubikScores;
+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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikObjectLibInterface implements ObjectLibInterface
+{
+  WeakReference<RubikActivity> mAct;
+  private boolean mIsNewRecord;
+  private long mNewRecord;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikObjectLibInterface(RubikActivity act)
+    {
+    mAct = new WeakReference<>(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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("pre", 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, String debug, int scrambleNum)
+    {
+    RubikScreenPlay play= (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
+    RubikScores scores  = RubikScores.getInstance();
+    ObjectType object   = play.getObject();
+    int level           = play.getLevel();
+    String name         = scores.getName();
+
+    String record = object.name()+" level "+level+" time "+mNewRecord+" isNew: "+mIsNewRecord+" scrambleNum: "+scrambleNum;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("pre", debug);
+       android.util.Log.e("pre", name);
+       android.util.Log.e("pre", record);
+       }
+    else
+      {
+      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 void requestReview(RubikActivity act)
+    {
+    final RubikScores scores = RubikScores.getInstance();
+    int numWins = scores.incrementNumWins();
+
+    if( numWins==7 || numWins==30 || numWins==100 || numWins==200)
+      {
+      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())
+            {
+            final String name = scores.getName();
+            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
+            {
+            String name = scores.getName();
+            analyticsReport(act,"Not Successful", name, timeBegin);
+            }
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onWinEffectFinished(String debug, int scrambleNum)
+    {
+    if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
+      {
+      RubikActivity act = mAct.get();
+      Bundle bundle = new Bundle();
+      bundle.putLong("time", mNewRecord );
+
+      reportRecord(act,debug,scrambleNum);
+      requestReview(act);
+
+      if( mIsNewRecord )
+        {
+        RubikDialogNewRecord dialog = new RubikDialogNewRecord();
+        dialog.setArguments(bundle);
+        dialog.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
+        }
+      else
+        {
+        RubikDialogSolved dialog = new RubikDialogSolved();
+        dialog.setArguments(bundle);
+        dialog.show( act.getSupportFragmentManager(), RubikDialogSolved.getDialogTag() );
+        }
+
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchScreen( act, ScreenList.DONE);
+          }
+        });
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onScrambleEffectFinished()
+    {
+    RubikActivity act = mAct.get();
+    RubikScores.getInstance().incrementNumPlays();
+
+    act.runOnUiThread(new Runnable()
+      {
+      @Override
+      public void run()
+        {
+        ScreenList.switchScreen( act, ScreenList.READ);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onFinishRotation(int axis, int row, int angle)
+    {
+    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);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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.getRecord();
+
+      if( mNewRecord< 0 )
+        {
+        mNewRecord = -mNewRecord;
+        mIsNewRecord = false;
+        }
+      else
+        {
+        mIsNewRecord = true;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCurrentColor()
+    {
+    RubikScreenSolver solver = (RubikScreenSolver) ScreenList.SVER.getScreenClass();
+    return solver.getCurrentColor();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int cubitIsLocked(ObjectType type, int cubit)
+    {
+    return SolverMain.cubitIsLocked(type,cubit);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectControl getControl()
+    {
+    RubikActivity act = mAct.get();
+    return act.getControl();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public DistortedScreen getScreen()
+    {
+    RubikActivity act = mAct.get();
+    return act.getScreen();
+    }
+}
diff --git a/src/main/java/org/distorted/main/RubikObjectStateActioner.java b/src/main/java/org/distorted/main/RubikObjectStateActioner.java
deleted file mode 100644
index 91693e8b..00000000
--- a/src/main/java/org/distorted/main/RubikObjectStateActioner.java
+++ /dev/null
@@ -1,316 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is free software: you can redistribute it and/or modify                            //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Magic Cube is distributed in the hope that it will be useful,                                 //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-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 org.distorted.objectlib.helpers.ObjectStateActioner;
-import org.distorted.objectlib.helpers.TwistyActivity;
-import org.distorted.objectlib.main.ObjectType;
-
-import org.distorted.dialogs.RubikDialogNewRecord;
-import org.distorted.dialogs.RubikDialogSolved;
-import org.distorted.network.RubikScores;
-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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikObjectStateActioner implements ObjectStateActioner
-{
-  private boolean mIsNewRecord;
-  private long mNewRecord;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void analyticsReport(TwistyActivity 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("pre", msg);
-       }
-    else
-      {
-      RubikActivity ract = (RubikActivity)act;
-      FirebaseAnalytics analytics = ract.getAnalytics();
-
-      if( analytics!=null )
-        {
-        Bundle bundle = new Bundle();
-        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, msg);
-        analytics.logEvent(FirebaseAnalytics.Event.SHARE, bundle);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void reportRecord(TwistyActivity act, String debug, int scrambleNum)
-    {
-    RubikActivity ract  = (RubikActivity)act;
-    RubikScreenPlay play= (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-    RubikScores scores  = RubikScores.getInstance();
-    ObjectType object   = play.getObject();
-    int level           = play.getLevel();
-    String name         = scores.getName();
-
-    String record = object.name()+" level "+level+" time "+mNewRecord+" isNew: "+mIsNewRecord+" scrambleNum: "+scrambleNum;
-
-    if( BuildConfig.DEBUG )
-       {
-       android.util.Log.e("pre", debug);
-       android.util.Log.e("pre", name);
-       android.util.Log.e("pre", record);
-       }
-    else
-      {
-      FirebaseAnalytics analytics = ract.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 void requestReview(TwistyActivity act)
-    {
-    final RubikScores scores = RubikScores.getInstance();
-    int numWins = scores.incrementNumWins();
-
-    if( numWins==7 || numWins==30 || numWins==100 || numWins==200)
-      {
-      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())
-            {
-            final String name = scores.getName();
-            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
-            {
-            String name = scores.getName();
-            analyticsReport(act,"Not Successful", name, timeBegin);
-            }
-          }
-        });
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void onWinEffectFinished(TwistyActivity act, String debug, int scrambleNum)
-     {
-     if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
-       {
-       RubikActivity ract = (RubikActivity)act;
-       Bundle bundle = new Bundle();
-       bundle.putLong("time", mNewRecord );
-
-       reportRecord(act,debug,scrambleNum);
-       requestReview(act);
-
-       if( mIsNewRecord )
-         {
-         RubikDialogNewRecord dialog = new RubikDialogNewRecord();
-         dialog.setArguments(bundle);
-         dialog.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
-         }
-       else
-         {
-         RubikDialogSolved dialog = new RubikDialogSolved();
-         dialog.setArguments(bundle);
-         dialog.show( act.getSupportFragmentManager(), RubikDialogSolved.getDialogTag() );
-         }
-
-       act.runOnUiThread(new Runnable()
-         {
-         @Override
-         public void run()
-           {
-           ScreenList.switchScreen( ract, ScreenList.DONE);
-           }
-         });
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void onScrambleEffectFinished(TwistyActivity act)
-     {
-     RubikActivity ract = (RubikActivity)act;
-
-     RubikScores.getInstance().incrementNumPlays();
-
-     act.runOnUiThread(new Runnable()
-       {
-       @Override
-       public void run()
-         {
-         ScreenList.switchScreen( ract, ScreenList.READ);
-         }
-       });
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void onFinishRotation(TwistyActivity act, int axis, int row, int angle)
-     {
-     if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
-       {
-       RubikScreenSolving solv = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
-       solv.addMove(act, axis, row, angle);
-       }
-     if( ScreenList.getCurrentScreen()== ScreenList.PLAY )
-       {
-       RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-       play.addMove(act, axis, row, angle);
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void onBeginRotation(TwistyActivity act)
-     {
-     if( ScreenList.getCurrentScreen()== ScreenList.READ )
-       {
-       RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
-       solving.resetElapsed();
-       RubikActivity ract = (RubikActivity)act;
-
-       ract.runOnUiThread(new Runnable()
-         {
-         @Override
-         public void run()
-           {
-           ScreenList.switchScreen( ract, ScreenList.SOLV);
-           }
-         });
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void failedToDrag(TwistyActivity act)
-      {
-      ScreenList curr = ScreenList.getCurrentScreen();
-
-      if( curr==ScreenList.PLAY )
-        {
-        RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
-        play.reddenLock(act);
-        }
-      else if( curr==ScreenList.READ )
-        {
-        RubikScreenReady read = (RubikScreenReady) ScreenList.READ.getScreenClass();
-        read.reddenLock(act);
-        }
-      else if( curr==ScreenList.SOLV )
-        {
-        RubikScreenSolving solv = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
-        solv.reddenLock(act);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void onSolved()
-     {
-     if( ScreenList.getCurrentScreen()== ScreenList.SOLV )
-        {
-        RubikScreenSolving solving = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
-        mNewRecord = solving.getRecord();
-
-        if( mNewRecord< 0 )
-          {
-          mNewRecord = -mNewRecord;
-          mIsNewRecord = false;
-          }
-        else
-          {
-          mIsNewRecord = true;
-          }
-        }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public int getCurrentColor()
-     {
-     RubikScreenSolver solver = (RubikScreenSolver) ScreenList.SVER.getScreenClass();
-     return solver.getCurrentColor();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public int cubitIsLocked(ObjectType type, int cubit)
-     {
-     return SolverMain.cubitIsLocked(type,cubit);
-     }
-}
diff --git a/src/main/java/org/distorted/main/RubikSurfaceView.java b/src/main/java/org/distorted/main/RubikSurfaceView.java
index 454b446a..bb2a3304 100644
--- a/src/main/java/org/distorted/main/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/main/RubikSurfaceView.java
@@ -84,7 +84,8 @@ public class RubikSurfaceView extends GLSurfaceView
       if(!isInEditMode())
         {
         RubikActivity act = (RubikActivity)context;
-        mObjectController = new ObjectControl(act,new RubikObjectStateActioner());
+        RubikObjectLibInterface ref = new RubikObjectLibInterface(act);
+        mObjectController = new ObjectControl(act,ref);
         mRenderer = new RubikRenderer(this);
 
         final ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
diff --git a/src/main/java/org/distorted/screens/RubikScreenBase.java b/src/main/java/org/distorted/screens/RubikScreenBase.java
index f06c8d91..301518b4 100644
--- a/src/main/java/org/distorted/screens/RubikScreenBase.java
+++ b/src/main/java/org/distorted/screens/RubikScreenBase.java
@@ -19,10 +19,11 @@
 
 package org.distorted.screens;
 
+import android.app.Activity;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 
-import org.distorted.objectlib.helpers.TwistyActivity;
+import org.distorted.main.RubikActivity;
 import org.distorted.objectlib.main.ObjectControl;
 
 import org.distorted.helpers.LockController;
@@ -38,7 +39,7 @@ abstract class RubikScreenBase extends RubikScreenAbstract
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void createBottomPane(final TwistyActivity act, float width, ImageButton button)
+  void createBottomPane(final RubikActivity act, float width, ImageButton button)
     {
     mMovesController.clearMoves(act);
 
@@ -54,9 +55,10 @@ abstract class RubikScreenBase extends RubikScreenAbstract
     LinearLayout layoutRight = new LinearLayout(act);
     layoutRight.setLayoutParams(params);
 
-    mMovesController.setupButton(act,width);
+    ObjectControl control = act.getControl();
+    mMovesController.setupButton(act,control,width);
     layoutLeft.addView(mMovesController.getButton());
-    mLockController.setupButton(act,act.getControl(),width);
+    mLockController.setupButton(act,control,width);
     layoutMid.addView(mLockController.getButton());
     layoutRight.addView(button);
 
@@ -67,7 +69,7 @@ abstract class RubikScreenBase extends RubikScreenAbstract
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void setLockState(final TwistyActivity act)
+  public void setLockState(final RubikActivity act)
     {
     boolean locked = act.getControl().retLocked();
     mLockController.setState(act,locked);
@@ -84,14 +86,14 @@ abstract class RubikScreenBase extends RubikScreenAbstract
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void addMove(TwistyActivity act, int axis, int row, int angle)
+  public void addMove(Activity act, int axis, int row, int angle)
     {
     mMovesController.addMove(act,axis,row,angle);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void reddenLock(final TwistyActivity act)
+  public void reddenLock(final RubikActivity act)
     {
     ObjectControl control = act.getControl();
     mLockController.reddenLock(act,control);
diff --git a/src/main/java/org/distorted/tutorials/TutorialActivity.java b/src/main/java/org/distorted/tutorials/TutorialActivity.java
index cfd7bcb4..bc715585 100644
--- a/src/main/java/org/distorted/tutorials/TutorialActivity.java
+++ b/src/main/java/org/distorted/tutorials/TutorialActivity.java
@@ -28,6 +28,8 @@ import android.view.WindowManager;
 import android.webkit.WebView;
 import android.widget.LinearLayout;
 
+import androidx.appcompat.app.AppCompatActivity;
+
 import com.google.firebase.analytics.FirebaseAnalytics;
 
 import org.distorted.library.main.DistortedLibrary;
@@ -36,7 +38,6 @@ import org.distorted.library.main.DistortedScreen;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objectlib.main.ObjectType;
 import org.distorted.objectlib.helpers.BlockController;
-import org.distorted.objectlib.helpers.TwistyActivity;
 
 import org.distorted.main.R;
 import org.distorted.dialogs.RubikDialogError;
@@ -45,7 +46,7 @@ import static org.distorted.main.RubikRenderer.BRIGHTNESS;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class TutorialActivity extends TwistyActivity
+public class TutorialActivity extends AppCompatActivity
 {
     private static final String URL = "https://www.youtube.com/embed/";
 
diff --git a/src/main/java/org/distorted/tutorials/TutorialObjectLibInterface.java b/src/main/java/org/distorted/tutorials/TutorialObjectLibInterface.java
new file mode 100644
index 00000000..227a25a0
--- /dev/null
+++ b/src/main/java/org/distorted/tutorials/TutorialObjectLibInterface.java
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2021 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is free software: you can redistribute it and/or modify                            //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Magic Cube is distributed in the hope that it will be useful,                                 //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.tutorials;
+
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.objectlib.helpers.ObjectLibInterface;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.ObjectType;
+
+import java.lang.ref.WeakReference;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class TutorialObjectLibInterface implements ObjectLibInterface
+{
+   WeakReference<TutorialActivity> mAct;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  TutorialObjectLibInterface(TutorialActivity act)
+    {
+    mAct = new WeakReference<>(act);
+    }
+
+   public void onWinEffectFinished(String debug, int scrambleNum) { }
+   public void onScrambleEffectFinished() { }
+   public void onBeginRotation() { }
+   public void onSolved() { }
+   public int getCurrentColor() { return 0; }
+   public int cubitIsLocked(ObjectType type, int cubit) { return 0; }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void onFinishRotation(int axis, int row, int angle)
+     {
+     TutorialActivity act = mAct.get();
+     TutorialScreen state = act.getState();
+     state.addMove(act, axis, row, angle);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void failedToDrag()
+     {
+     TutorialActivity act = mAct.get();
+     TutorialScreen state = act.getState();
+     state.reddenLock(act);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public ObjectControl getControl()
+    {
+    TutorialActivity act = mAct.get();
+    return act.getControl();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public DistortedScreen getScreen()
+    {
+    TutorialActivity act = mAct.get();
+    return act.getScreen();
+    }
+}
diff --git a/src/main/java/org/distorted/tutorials/TutorialObjectStateActioner.java b/src/main/java/org/distorted/tutorials/TutorialObjectStateActioner.java
deleted file mode 100644
index 9700d628..00000000
--- a/src/main/java/org/distorted/tutorials/TutorialObjectStateActioner.java
+++ /dev/null
@@ -1,54 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2021 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Magic Cube.                                                              //
-//                                                                                               //
-// Magic Cube is free software: you can redistribute it and/or modify                            //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Magic Cube is distributed in the hope that it will be useful,                                 //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.tutorials;
-
-import org.distorted.objectlib.helpers.ObjectStateActioner;
-import org.distorted.objectlib.helpers.TwistyActivity;
-import org.distorted.objectlib.main.ObjectType;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class TutorialObjectStateActioner implements ObjectStateActioner
-{
-   public void onWinEffectFinished(TwistyActivity act, String debug, int scrambleNum) { }
-   public void onScrambleEffectFinished(TwistyActivity act) { }
-   public void onBeginRotation(TwistyActivity act) { }
-   public void onSolved() { }
-   public int getCurrentColor() { return 0; }
-   public int cubitIsLocked(ObjectType type, int cubit) { return 0; }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void onFinishRotation(TwistyActivity act, int axis, int row, int angle)
-     {
-     TutorialActivity tact = (TutorialActivity)act;
-     TutorialScreen state = tact.getState();
-     state.addMove(act,axis, row, angle);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void failedToDrag(TwistyActivity act)
-     {
-     final TutorialActivity tact = (TutorialActivity)act;
-     TutorialScreen state = tact.getState();
-     state.reddenLock(act);
-     }
-}
diff --git a/src/main/java/org/distorted/tutorials/TutorialScreen.java b/src/main/java/org/distorted/tutorials/TutorialScreen.java
index 95986e2d..37ecfda7 100644
--- a/src/main/java/org/distorted/tutorials/TutorialScreen.java
+++ b/src/main/java/org/distorted/tutorials/TutorialScreen.java
@@ -19,6 +19,7 @@
 
 package org.distorted.tutorials;
 
+import android.app.Activity;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
@@ -26,7 +27,6 @@ import android.widget.LinearLayout;
 import org.distorted.helpers.MovesController;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objectlib.main.ObjectType;
-import org.distorted.objectlib.helpers.TwistyActivity;
 
 import org.distorted.helpers.LockController;
 import org.distorted.main.R;
@@ -107,8 +107,9 @@ public class TutorialScreen
     LinearLayout layout = act.findViewById(R.id.tutorialRightBar);
     layout.removeAllViews();
 
-    mMovesController.setupButton(act,width);
-    mLockController.setupButton(act,act.getControl(),width);
+    ObjectControl control = act.getControl();
+    mMovesController.setupButton(act,control, width);
+    mLockController.setupButton(act,control,width);
     setupSolveButton(act,width);
     setupScrambleButton(act,width);
     setupBackButton(act,width);
@@ -122,7 +123,7 @@ public class TutorialScreen
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void addMove(TwistyActivity act, int axis, int row, int angle)
+  void addMove(Activity act, int axis, int row, int angle)
     {
     mMovesController.addMove(act, axis,row,angle);
     }
@@ -138,8 +139,9 @@ public class TutorialScreen
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void reddenLock(final TwistyActivity act)
+  public void reddenLock(final TutorialActivity act)
     {
-    mLockController.reddenLock(act,act.getControl());
+    ObjectControl control = act.getControl();
+    mLockController.reddenLock(act,control);
     }
 }
diff --git a/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java b/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
index f0bd49e6..8a3b06e6 100644
--- a/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
+++ b/src/main/java/org/distorted/tutorials/TutorialSurfaceView.java
@@ -82,7 +82,8 @@ public class TutorialSurfaceView extends GLSurfaceView
       if(!isInEditMode())
         {
         TutorialActivity act = (TutorialActivity)context;
-        mObjectController = new ObjectControl(act,new TutorialObjectStateActioner());
+        TutorialObjectLibInterface ref = new TutorialObjectLibInterface(act);
+        mObjectController = new ObjectControl(act,ref);
         mRenderer = new TutorialRenderer(this);
 
         final ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
