commit 18b0ae9c5e3df3917c59fb942415e7066d057887
Author: leszek <leszek@koltunski.pl>
Date:   Tue Nov 14 02:14:14 2023 +0100

    Progress with the generic PlayActivity.

diff --git a/src/main/java/org/distorted/main/MainScrollGrid.java b/src/main/java/org/distorted/main/MainScrollGrid.java
index 05c363ba..0b6337b8 100644
--- a/src/main/java/org/distorted/main/MainScrollGrid.java
+++ b/src/main/java/org/distorted/main/MainScrollGrid.java
@@ -58,8 +58,6 @@ public class MainScrollGrid
         @Override
         public void onClick(View v)
           {
-          android.util.Log.e("D", "clicked on child "+ordinal);
-
           int w = displaymetrics.widthPixels;
           int h = displaymetrics.heightPixels;
           MainObjectPopup popup = new MainObjectPopup(act,ordinal,w,h);
diff --git a/src/main/java/org/distorted/playui/PlayActivity.java b/src/main/java/org/distorted/playui/PlayActivity.java
index 73d63adc..67d78430 100644
--- a/src/main/java/org/distorted/playui/PlayActivity.java
+++ b/src/main/java/org/distorted/playui/PlayActivity.java
@@ -24,6 +24,8 @@ import android.widget.LinearLayout;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.preference.PreferenceManager;
 
+import com.google.firebase.analytics.FirebaseAnalytics;
+
 import org.distorted.library.main.DistortedLibrary;
 import org.distorted.objectlib.main.InitAssets;
 import org.distorted.objectlib.main.ObjectControl;
@@ -57,6 +59,7 @@ public class PlayActivity extends AppCompatActivity
     private boolean mObjectLocal;
     private int mObjectOrdinal;
     private boolean mModeFree;
+    private FirebaseAnalytics mFirebaseAnalytics;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -68,6 +71,8 @@ public class PlayActivity extends AppCompatActivity
       setTheme(R.style.MaterialThemeNoActionBar);
       setContentView(R.layout.play);
 
+      mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
+
       Bundle b = getIntent().getExtras();
 
       if( b!=null )
@@ -208,7 +213,7 @@ public class PlayActivity extends AppCompatActivity
       PlayView view = findViewById(R.id.playView);
       view.onResume();
 
-      ScreenList.switchScreen(this,ScreenList.FREE);
+      ScreenList.switchScreen(this, mModeFree ? ScreenList.FREE : ScreenList.SCRA );
 
       SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
       restorePreferences(preferences);
@@ -218,6 +223,13 @@ public class PlayActivity extends AppCompatActivity
         {
         changeIfDifferent(mObjectName,mObjectLocal,mObjectOrdinal,view.getObjectControl());
         }
+
+      if( !mModeFree )
+        {
+        android.util.Log.e("D", "scrambles: "+mNumScrambles);
+        ObjectControl control = getControl();
+        control.scrambleObject(mNumScrambles);
+        }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -325,6 +337,13 @@ public class PlayActivity extends AppCompatActivity
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public FirebaseAnalytics getAnalytics()
+      {
+      return mFirebaseAnalytics;
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public int getScreenWidthInPixels()
diff --git a/src/main/java/org/distorted/playui/PlayLibInterface.java b/src/main/java/org/distorted/playui/PlayLibInterface.java
index 2576349a..5964fe18 100644
--- a/src/main/java/org/distorted/playui/PlayLibInterface.java
+++ b/src/main/java/org/distorted/playui/PlayLibInterface.java
@@ -9,13 +9,34 @@
 
 package org.distorted.playui;
 
+import static org.distorted.external.RubikScores.RECORD_FIRST;
+import static org.distorted.external.RubikScores.RECORD_NEW;
+import static org.distorted.external.RubikScores.RECORD_NOT_NEW;
+
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+
 import java.lang.ref.WeakReference;
 
+import com.google.android.play.core.review.ReviewInfo;
+import com.google.android.play.core.review.ReviewManager;
+import com.google.android.play.core.review.ReviewManagerFactory;
+import com.google.android.play.core.tasks.OnCompleteListener;
+import com.google.android.play.core.tasks.OnFailureListener;
+import com.google.android.play.core.tasks.Task;
+import com.google.firebase.analytics.FirebaseAnalytics;
 import com.google.firebase.crashlytics.FirebaseCrashlytics;
 
+import org.distorted.dialogs.RubikDialogNewRecord;
+import org.distorted.dialogs.RubikDialogScoresView;
+import org.distorted.dialogs.RubikDialogSolved;
+import org.distorted.external.RubikNetwork;
+import org.distorted.external.RubikScores;
 import org.distorted.library.message.EffectMessageSender;
 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.main.BuildConfig;
@@ -25,36 +46,218 @@ import org.distorted.main.BuildConfig;
 public class PlayLibInterface implements ObjectLibInterface
 {
   private final WeakReference<PlayActivity> mAct;
+  private int mIsNewRecord;
+  private int mNewRecord;
+  private boolean mReviewAsked;
+  private int mNumRotations, mNumScrambles;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   PlayLibInterface(PlayActivity act)
     {
     mAct = new WeakReference<>(act);
+    mReviewAsked = false;
+    mNumRotations = 0;
+    mNumScrambles = 0;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void onWinEffectFinished(long startTime, long endTime, String debug, int scrambleNum) { }
-  public void onScrambleEffectFinished() { }
-  public void onBeginRotation() { }
-  public void onSolved() { }
   public void onObjectCreated(long time) { }
   public void onReplaceModeDown(int cubit, int face) { }
   public void onReplaceModeUp() { }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void analyticsReport(PlayActivity act, String message, String name, long timeBegin)
+    {
+    long elapsed = System.currentTimeMillis() - timeBegin;
+    String msg = message+" startTime: "+timeBegin+" elapsed: "+elapsed+" name: "+name;
+
+    if( BuildConfig.DEBUG )
+      {
+      android.util.Log.d("libInterface", msg);
+      }
+    else
+      {
+      FirebaseAnalytics analytics = act.getAnalytics();
+
+      if( analytics!=null )
+        {
+        Bundle bundle = new Bundle();
+        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, msg);
+        analytics.logEvent(FirebaseAnalytics.Event.SHARE, bundle);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportRecord(PlayActivity act, long startTime, long endTime, String debug, int scrambleNum)
+    {
+    RubikScores scores  = RubikScores.getInstance();
+    int object  = RubikObjectList.getCurrObject();
+    String name = scores.getName();
+    RubikObject obj = RubikObjectList.getObject(object);
+    String objName = obj==null ? "NULL" : obj.getUpperName();
+
+    String record = objName+" time "+mNewRecord+" isNew: "+mIsNewRecord+" scrambleNum: "+scrambleNum;
+
+    if( BuildConfig.DEBUG )
+      {
+      android.util.Log.e("libInterface", debug);
+      android.util.Log.e("libInterface", name);
+      android.util.Log.e("libInterface", record);
+      }
+    else
+      {
+      if( scrambleNum>=9 && mNewRecord<300*scrambleNum )
+        {
+        long timeNow = System.currentTimeMillis();
+        long elapsed = timeNow - startTime;
+        String suspicious ="start"+startTime+"end"+endTime+"elapsed"+elapsed+"obj"+objName+"record"+mNewRecord+"scrambles"+scrambleNum+debug;
+        RubikNetwork network = RubikNetwork.getInstance();
+        network.suspicious(suspicious,act);
+        }
+
+      FirebaseAnalytics analytics = act.getAnalytics();
+
+      if( analytics!=null )
+        {
+        Bundle bundle = new Bundle();
+        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, debug);
+        bundle.putString(FirebaseAnalytics.Param.CHARACTER, name);
+        bundle.putString(FirebaseAnalytics.Param.LEVEL, record);
+        analytics.logEvent(FirebaseAnalytics.Event.LEVEL_UP, bundle);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private Bundle createDialogBundle()
+    {
+    Bundle bundle = new Bundle();
+    String arg = RubikDialogScoresView.formatRecord(mNewRecord);
+    bundle.putString("argument", arg );
+    return bundle;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void requestReview(PlayActivity act)
+    {
+    android.util.Log.e("D", "ASKING FOR REVIEW");
+
+    mReviewAsked = true;
+    final String name = RubikScores.getInstance().getName();
+    final long timeBegin = System.currentTimeMillis();
+    final ReviewManager manager = ReviewManagerFactory.create(act);
+    Task<ReviewInfo> request = manager.requestReviewFlow();
+
+    request.addOnCompleteListener(new OnCompleteListener<ReviewInfo>()
+      {
+      @Override
+      public void onComplete (@NonNull Task<ReviewInfo> task)
+        {
+        if (task.isSuccessful())
+          {
+          ReviewInfo reviewInfo = task.getResult();
+          Task<Void> flow = manager.launchReviewFlow(act, reviewInfo);
+
+          flow.addOnFailureListener(new OnFailureListener()
+            {
+            @Override
+            public void onFailure(Exception e)
+            {
+            analyticsReport(act,"Failed", name, timeBegin);
+            }
+            });
+
+          flow.addOnCompleteListener(new OnCompleteListener<Void>()
+            {
+            @Override
+            public void onComplete(@NonNull Task<Void> task)
+              {
+              analyticsReport(act,"Complete", name, timeBegin);
+              }
+            });
+          }
+        else analyticsReport(act,"Not Successful", name, timeBegin);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onScrambleEffectFinished()
+    {
+    if( ScreenList.getCurrentScreen()==ScreenList.SCRA )
+      {
+      PlayActivity act = mAct.get();
+      RubikScores.getInstance().incrementNumPlays();
+
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenScrambling screen = (ScreenScrambling)ScreenList.SCRA.getScreenClass();
+          screen.setReady();
+          ObjectControl control = act.getControl();
+          control.unblockEverything();
+          }
+        });
+      }
+
+    mNumScrambles++;
+
+    if( mNumScrambles==10 && !mReviewAsked )
+      {
+      PlayActivity act = mAct.get();
+      requestReview(act);
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void onFinishRotation(int axis, int row, int angle)
     {
+    mNumRotations++;
     PlayActivity act = mAct.get();
 
     if( act!=null )
       {
       ScreenList screen = ScreenList.getCurrentScreen();
 
-      if( screen==ScreenList.FREE ) ((ScreenFree   )screen.getScreenClass()).addMove(act,axis,row,angle);
-      if( screen==ScreenList.SOLV ) ((ScreenSolving)screen.getScreenClass()).addMove(act,axis,row,angle);
+      if( screen==ScreenList.FREE ||
+          screen==ScreenList.SOLV  ) ((ScreenBase)screen.getScreenClass()).addMove(act,axis,row,angle);
+      }
+
+    if( mNumRotations==40 && !mReviewAsked )
+      {
+      requestReview(act);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onBeginRotation()
+    {
+    if( ScreenList.getCurrentScreen()==ScreenList.SCRA )
+      {
+      ScreenSolving solving = (ScreenSolving) ScreenList.SOLV.getScreenClass();
+      solving.resetElapsed();
+      PlayActivity act = mAct.get();
+
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchScreen( act,ScreenList.SOLV );
+          }
+        });
       }
     }
 
@@ -68,8 +271,65 @@ public class PlayLibInterface implements ObjectLibInterface
       {
       ScreenList screen = ScreenList.getCurrentScreen();
 
-      if( screen==ScreenList.FREE ) ((ScreenFree   )screen.getScreenClass()).reddenLock(act);
-      if( screen==ScreenList.SOLV ) ((ScreenSolving)screen.getScreenClass()).reddenLock(act);
+      if( screen==ScreenList.FREE ||
+          screen==ScreenList.SOLV ||
+          screen==ScreenList.SCRA  ) ((ScreenBase)screen.getScreenClass()).reddenLock(act);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onSolved()
+    {
+    if( ScreenList.getCurrentScreen()==ScreenList.SOLV )
+      {
+      ScreenSolving solving = (ScreenSolving)ScreenList.SOLV.getScreenClass();
+      mNewRecord = solving.stopTimerAndGetRecord();
+      mIsNewRecord = solving.setRecord();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onWinEffectFinished(long startTime, long endTime, String debug, int scrambleNum)
+    {
+    if( ScreenList.getCurrentScreen()==ScreenList.SOLV )
+      {
+      PlayActivity act = mAct.get();
+      reportRecord(act,startTime,endTime,debug,scrambleNum);
+
+      RubikScores scores = RubikScores.getInstance();
+      int numWins = scores.incrementNumWins();
+      int numRuns = scores.getNumRuns();
+
+      if( numRuns==3 || numRuns==6 || numWins==4 || numWins==20 || numWins==50 || numWins==80 || numWins==100)
+        {
+        requestReview(act);
+        }
+
+      switch(mIsNewRecord)
+        {
+        case RECORD_FIRST  :
+        case RECORD_NEW    : Bundle byes = createDialogBundle();
+                             RubikDialogNewRecord dyes = new RubikDialogNewRecord();
+                             dyes.setArguments(byes);
+                             dyes.show( act.getSupportFragmentManager(), RubikDialogNewRecord.getDialogTag() );
+                             break;
+        case RECORD_NOT_NEW: Bundle bno = createDialogBundle();
+                             RubikDialogSolved dno = new RubikDialogSolved();
+                             dno.setArguments(bno);
+                             dno.show( act.getSupportFragmentManager(), RubikDialogSolved.getDialogTag() );
+        break;
+        }
+
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          ScreenList.switchScreen( act,ScreenList.DONE );
+          }
+        });
       }
     }
 
diff --git a/src/main/java/org/distorted/playui/ScreenScrambling.java b/src/main/java/org/distorted/playui/ScreenScrambling.java
index eb4d4868..b631db98 100644
--- a/src/main/java/org/distorted/playui/ScreenScrambling.java
+++ b/src/main/java/org/distorted/playui/ScreenScrambling.java
@@ -1,5 +1,5 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 Leszek Koltunski                                                               //
+// Copyright 2023 Leszek Koltunski                                                               //
 //                                                                                               //
 // This file is part of Magic Cube.                                                              //
 //                                                                                               //
@@ -21,9 +21,10 @@ import org.distorted.main.R;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class ScreenScrambling extends ScreenBase
+public class ScreenScrambling extends ScreenAbstract
   {
   private TransparentImageButton mBackButton;
+  private TextView mLabel;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -43,13 +44,34 @@ public class ScreenScrambling extends ScreenBase
     // TOP ////////////////////////////
     LinearLayout layoutTop = act.findViewById(R.id.upperBar);
     layoutTop.removeAllViews();
-    TextView label = (TextView)inflater.inflate(R.layout.upper_text, null);
-    label.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
-    label.setText(R.string.ready);
-    layoutTop.addView(label);
+    mLabel = (TextView)inflater.inflate(R.layout.upper_text, null);
+    mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    layoutTop.addView(mLabel);
+
+    // BOT ////////////////////////////
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,2);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutRight = new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
 
     setupBackButton(act);
-    createBottomPane(act,mBackButton);
+
+    layoutRight.addView(mBackButton);
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void setReady()
+    {
+    mLabel.setText(R.string.ready);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
