commit 8f139bfbb3c29e1fd803054139c36f8180d311d5
Author: leszek <leszek@koltunski.pl>
Date:   Fri Sep 22 12:17:37 2023 +0200

    - some progress with Mosaic Cube.
    - very important memory optimizations for the main dialogs (mainly Scores - which no longer leaks memory!)
    - increase the size of object icons from 144x144 to 256x256

diff --git a/src/main/java/org/distorted/dialogs/RubikDialogPatternPagerAdapter.java b/src/main/java/org/distorted/dialogs/RubikDialogPatternPagerAdapter.java
index 3ba39268..5abab890 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogPatternPagerAdapter.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogPatternPagerAdapter.java
@@ -74,6 +74,7 @@ class RubikDialogPatternPagerAdapter extends PagerAdapter
   public void destroyItem(ViewGroup collection, int position, @NonNull Object view)
     {
     collection.removeView((View) view);
+    mViews[position] = null;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogScores.java b/src/main/java/org/distorted/dialogs/RubikDialogScores.java
index 35ae5357..4ad34c1e 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogScores.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogScores.java
@@ -61,7 +61,8 @@ public class RubikDialogScores extends RubikDialogAbstract
 
   public void positiveAction()
     {
-
+    RubikDialogScoresThread thr = RubikDialogScoresThread.getInstance();
+    thr.exit();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogScoresPagerAdapter.java b/src/main/java/org/distorted/dialogs/RubikDialogScoresPagerAdapter.java
index 534a3206..172a5e0d 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogScoresPagerAdapter.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogScoresPagerAdapter.java
@@ -31,116 +31,46 @@ import org.distorted.screens.RubikScreenPlay;
 
 class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikNetwork.ScoresReceiver
   {
+  private static final int NETWORK_NOT_READY = 0;
+  private static final int NETWORK_SUCCESS   = 1;
+  private static final int NETWORK_FAILURE   = 2;
+
   private final FragmentActivity mAct;
   private final RubikDialogScores mDialog;
   private final RubikDialogScoresView[] mViews;
   private final ViewPager mViewPager;
   private final int mNumTabs;
   private final boolean mIsSubmitting;
-  private int mToDoTab, mToDoLvl;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void prepareView()
-    {
-    mAct.runOnUiThread(new Runnable()
-      {
-      @Override
-      public void run()
-        {
-        for(int i=0; i<mNumTabs; i++)
-          {
-          mViews[i].prepareView(mAct);
-          }
-        }
-      });
-    try
-      {
-      Thread.sleep(150);
-      }
-    catch( InterruptedException ignored)
-      {
-
-      }
-    }
+  private final int[] mNumLevels;
+  private String[][][] mCountry, mName;
+  private int[][][] mTime;
+  private String mMessage;
+  private int mNetworkState;
+  private final Object mObj;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void addSection(int tab, int level, int numLevels, final RubikDialogScoresView view, final String[] country, final String[] name, final int[] time)
+  public void receive(final String[][][] country, final String[][][] name, final int[][][] time)
     {
-    String title = level==numLevels ?
-                   mAct.getString(R.string.levelM) :
-                   mAct.getString(R.string.lv_placeholder,level+1);
-
-    final LinearLayout section = view.createSection(mAct, tab, title, level, country, name, time);
-
-    mAct.runOnUiThread(new Runnable()
-      {
-      @Override
-      public void run()
-        {
-        view.addSection(section);
-        }
-      });
-
-    try
+    synchronized(mObj)
       {
-      Thread.sleep(50);
-      }
-    catch( InterruptedException ignored)
-      {
-
-      }
-    }
+      RubikDialogScoresThread thr = RubikDialogScoresThread.getInstance();
+      thr.equip(mAct,mViewPager);
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
+      mNetworkState = NETWORK_SUCCESS;
+      mCountry = country;
+      mName = name;
+      mTime = time;
 
-  private void getNext(int currentTab, int[] toDoTab, int[] numLevels)
-    {
-    if( toDoTab[currentTab]<=numLevels[currentTab] )
-      {
-      mToDoTab = currentTab;
-      mToDoLvl = toDoTab[currentTab];
-      toDoTab[currentTab]++;
-      }
-    else
-      {
-      for(int tab=0; tab<mNumTabs; tab++)
-        {
-        if( toDoTab[tab]<=numLevels[tab] )
+      for(int t=0; t<mNumTabs; t++)
+        if( mViews[t]!=null )
           {
-          mToDoTab = tab;
-          mToDoLvl = toDoTab[tab];
-          toDoTab[tab]++;
-          break;
+          int num = mNumLevels[t];
+          String[][] c = country[t];
+          String[][] n = name[t];
+          int[][] tm = time[t];
+          for(int l=0; l<=num; l++)  thr.newWork(t, l, num, mViews[t], c[l], n[l], tm[l]);
           }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void receive(final String[][][] country, final String[][][] name, final int[][][] time)
-    {
-    prepareView();
-    int toDo=0;
-    int[] toDoTab   = new int[mNumTabs];
-    int[] numLevels = new int[mNumTabs];
-
-    for(int i=0; i<mNumTabs; i++)
-      {
-      toDoTab[i]= 0;
-      RubikObject object = RubikObjectList.getObject(i);
-      int numScramble = object==null ? 1 : object.getNumScramble();
-      numLevels[i] = Math.min(numScramble-1,RubikScreenPlay.LEVELS_SHOWN);
-      toDo += (numLevels[i]+1);
-      }
-
-    while( toDo>0 )
-      {
-      toDo--;
-      getNext(mViewPager.getCurrentItem(), toDoTab, numLevels);
-      addSection( mToDoTab, mToDoLvl, numLevels[mToDoTab], mViews[mToDoTab], country[mToDoTab][mToDoLvl], name[mToDoTab][mToDoLvl], time[mToDoTab][mToDoLvl]);
       }
     }
 
@@ -148,12 +78,16 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikNetwork
 
   public void message(final String mess)
     {
+    mNetworkState = NETWORK_FAILURE;
+    mMessage = mess;
+
     mAct.runOnUiThread(new Runnable()
       {
       @Override
       public void run()
         {
-        for(int i=0; i<mNumTabs; i++) mViews[i].message(mess);
+        for(int i=0; i<mNumTabs; i++)
+          if( mViews[i]!=null ) mViews[i].message(mess);
         }
       });
     }
@@ -189,15 +123,31 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikNetwork
 
   RubikDialogScoresPagerAdapter(FragmentActivity act, ViewPager viewPager, boolean isSubmitting, RubikDialogScores diag)
     {
+    mObj = new Object();
     mAct = act;
     mDialog = diag;
     mNumTabs = RubikObjectList.getNumObjects();
     mViews = new RubikDialogScoresView[mNumTabs];
     mViewPager = viewPager;
     mIsSubmitting = isSubmitting;
+    mNetworkState = NETWORK_NOT_READY;
+
+    mNumLevels = new int[mNumTabs];
+
+    for(int i=0; i<mNumTabs; i++)
+      {
+      RubikObject object = RubikObjectList.getObject(i);
+      int numScramble = object==null ? 1 : object.getNumScramble();
+      mNumLevels[i] = Math.min(numScramble-1, RubikScreenPlay.LEVELS_SHOWN);
+      }
 
     viewPager.setAdapter(this);
-    viewPager.setOffscreenPageLimit(mNumTabs-1);
+    viewPager.setOffscreenPageLimit(1);
+
+    RubikNetwork network = RubikNetwork.getInstance();
+
+    if( mIsSubmitting )  network.submit  ( this, mAct );
+    else                 network.download( this, mAct );
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -206,31 +156,37 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikNetwork
   @NonNull
   public Object instantiateItem(@NonNull ViewGroup collection, int position)
     {
+    RubikDialogScoresView view;
     DisplayMetrics metrics = mAct.getResources().getDisplayMetrics();
 
-    mViews[position] = new RubikDialogScoresView(mAct, metrics.heightPixels, mIsSubmitting);
-    collection.addView(mViews[position]);
+    synchronized(mObj)
+      {
+      view = new RubikDialogScoresView(mAct, metrics.heightPixels, mIsSubmitting);
+      collection.addView(view);
 
-    boolean allCreated = true;
+      if( mNetworkState==NETWORK_SUCCESS )
+        {
+        int num = mNumLevels[position];
+        String[][] c = mCountry[position];
+        String[][] n = mName[position];
+        int[][] tm = mTime[position];
 
-    for(int i=0; i<mNumTabs; i++)
-      {
-      if( mViews[i]==null )
+        for(int l=0; l<=num; l++)
+          {
+          String title = (l==num ? mAct.getString(R.string.levelM) : mAct.getString(R.string.lv_placeholder, l+1));
+          LinearLayout section = view.createSection(mAct, position, title, l, c[l], n[l], tm[l]);
+          view.addSection(mAct,section);
+          }
+        }
+      else if( mNetworkState==NETWORK_FAILURE )
         {
-        allCreated = false;
-        break;
+        view.message(mMessage);
         }
       }
 
-    if( allCreated )
-      {
-      RubikNetwork network = RubikNetwork.getInstance();
-
-      if( mIsSubmitting )  network.submit  ( this, mAct );
-      else                 network.download( this, mAct );
-      }
+    mViews[position] = view;
 
-    return mViews[position];
+    return view;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -239,6 +195,7 @@ class RubikDialogScoresPagerAdapter extends PagerAdapter implements RubikNetwork
   public void destroyItem(ViewGroup collection, int position, @NonNull Object view)
     {
     collection.removeView((View) view);
+    mViews[position] = null;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogScoresThread.java b/src/main/java/org/distorted/dialogs/RubikDialogScoresThread.java
new file mode 100644
index 00000000..b88a1862
--- /dev/null
+++ b/src/main/java/org/distorted/dialogs/RubikDialogScoresThread.java
@@ -0,0 +1,173 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.dialogs;
+
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager.widget.ViewPager;
+
+import org.distorted.main.R;
+
+import java.lang.ref.WeakReference;
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class RubikDialogScoresThread extends Thread
+  {
+  private final Object mObject;
+  private final Vector<WorkLoad> mBuffers;
+  private WeakReference<FragmentActivity> mWeakAct;
+  private ViewPager mViewPager;
+  private boolean mRunning;
+
+  private static RubikDialogScoresThread mThis;
+
+  private static class WorkLoad
+    {
+    int tab, level, numLevels;
+    RubikDialogScoresView view;
+    String[] country, name;
+    int[] time;
+
+    WorkLoad(int t, int l, int nl, RubikDialogScoresView v, String[] c, String[] n, int[] tm)
+      {
+      tab = t;
+      level = l;
+      numLevels = nl;
+      view = v;
+      country = c;
+      name = n;
+      time = tm;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private RubikDialogScoresThread()
+    {
+    mObject  = new Object();
+    mRunning = true;
+    mBuffers = new Vector<>();
+    this.start();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static RubikDialogScoresThread getInstance()
+    {
+    if( mThis==null ) mThis = new RubikDialogScoresThread();
+    return mThis;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void equip(FragmentActivity act, ViewPager pager)
+    {
+    mWeakAct = new WeakReference<>(act);
+    mViewPager = pager;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void newWork(int t, int l, int nl, RubikDialogScoresView v, String[] c, String[] n, int[] tm)
+    {
+    synchronized(mObject)
+      {
+      WorkLoad load = new WorkLoad(t,l,nl,v,c,n,tm);
+      mBuffers.add(load);
+      mObject.notify();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void exit()
+    {
+    synchronized(mObject)
+      {
+      mRunning = false;
+      mThis = null;
+      mObject.notify();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void run()
+    {
+    WorkLoad load;
+
+    while(true)
+      {
+      synchronized(mObject)
+        {
+        do
+          {
+          load = getNextLoad();
+          if( load!=null ) process(load);
+          }
+        while(load!=null);
+
+        if( mRunning )
+          {
+          try { mObject.wait(); }
+          catch(InterruptedException ignored) { }
+          }
+        else break;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private WorkLoad getNextLoad()
+    {
+    if( mViewPager==null ) return null;
+
+    int currentTab = mViewPager.getCurrentItem();
+    int size = mBuffers.size();
+
+    for(int i=0; i<size; i++)
+      {
+      WorkLoad load = mBuffers.get(i);
+      if( load.tab==currentTab ) return mBuffers.remove(i);
+      }
+
+    return size>0 ? mBuffers.remove(0) : null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void process(WorkLoad load)
+    {
+    final FragmentActivity act = mWeakAct.get();
+    int tab = load.tab;
+    int level = load.level;
+    int numLevels = load.numLevels;
+    RubikDialogScoresView view = load.view;
+    String[] country = load.country;
+    String[] name = load.name;
+    int[] time = load.time;
+    String title = level==numLevels ? act.getString(R.string.levelM) : act.getString(R.string.lv_placeholder, level+1);
+
+    final LinearLayout section = view.createSection(act, tab, title, level, country, name, time);
+
+    act.runOnUiThread(new Runnable()
+      {
+      @Override
+      public void run()
+        {
+        view.addSection(act,section);
+        }
+      });
+    }
+  }
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogScoresView.java b/src/main/java/org/distorted/dialogs/RubikDialogScoresView.java
index c381b88d..8d430884 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogScoresView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogScoresView.java
@@ -30,7 +30,7 @@ import static org.distorted.external.RubikNetwork.MAX_PLACES;
 
 public class RubikDialogScoresView extends FrameLayout
   {
-  private LinearLayout mLayout;
+  private LinearLayout mLayout=null;
   private int mHeight;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -163,20 +163,16 @@ public class RubikDialogScoresView extends FrameLayout
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // needs to run on UI thread
 
-  void prepareView(FragmentActivity act)
+  void addSection(FragmentActivity act, LinearLayout section)
     {
-    removeAllViews();
-
-    View tab = inflate(act, R.layout.dialog_scores_tab, null);
-    mLayout = tab.findViewById(R.id.tabLayout);
-    addView(tab);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// needs to run on UI thread
+    if( mLayout==null )
+      {
+      removeAllViews();
+      View tab = inflate(act, R.layout.dialog_scores_tab, null);
+      mLayout = tab.findViewById(R.id.tabLayout);
+      addView(tab);
+      }
 
-  void addSection(LinearLayout section)
-    {
     mLayout.addView(section);
     }
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogTutorialPagerAdapter.java b/src/main/java/org/distorted/dialogs/RubikDialogTutorialPagerAdapter.java
index 5202e254..42ba6189 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogTutorialPagerAdapter.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogTutorialPagerAdapter.java
@@ -73,6 +73,7 @@ class RubikDialogTutorialPagerAdapter extends PagerAdapter
   public void destroyItem(ViewGroup collection, int position, @NonNull Object view)
     {
     collection.removeView((View) view);
+    mViews[position] = null;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
