commit b8b385486028fcfd57cf45be1652ce487a6a60b9
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Feb 10 22:20:31 2020 +0000

    Downloading High Scores

diff --git a/src/main/java/org/distorted/magic/RubikScores.java b/src/main/java/org/distorted/magic/RubikScores.java
index 5b8ae8c7..8d695e6f 100644
--- a/src/main/java/org/distorted/magic/RubikScores.java
+++ b/src/main/java/org/distorted/magic/RubikScores.java
@@ -37,11 +37,6 @@ import android.widget.TextView;
 
 public class RubikScores extends AppCompatDialogFragment
   {
-  interface ScoresReceiver
-    {
-    void receive(String scores);
-    }
-
   RubikScoresViewPager mViewPager;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -85,8 +80,6 @@ public class RubikScores extends AppCompatDialogFragment
       if(tab!=null) tab.setCustomView(imageView);
       }
 
-    RubikScoresDownloader.download(mViewPager);
-
     return builder.create();
     }
   }
diff --git a/src/main/java/org/distorted/magic/RubikScoresDownloader.java b/src/main/java/org/distorted/magic/RubikScoresDownloader.java
index 5cb7ab9b..bb59e256 100644
--- a/src/main/java/org/distorted/magic/RubikScoresDownloader.java
+++ b/src/main/java/org/distorted/magic/RubikScoresDownloader.java
@@ -21,40 +21,70 @@ package org.distorted.magic;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-class RubikScoresDownloader extends Thread
+class RubikScoresDownloader implements Runnable
   {
+  interface Receiver
+    {
+    void receive(String scores);
+    }
+
+  private static final int DOWNLOAD   = 0;
+  private static final int SUBMIT     = 1;
+  private static final int IDLE       = 2;
+
+  private static final String URL  ="http://koltunski.pl/rubik/cgi-bin";
+
+  private static boolean mRunning = false;
+  private static Thread mNetworkThrd = null;
+  private static int mMode = IDLE;
+  private static Receiver mReceiver;
+
   private static String mScores = "";
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   static void onPause()
     {
+    mRunning = false;
     mScores = "";
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static boolean gottaDownload()
+  private boolean gottaDownload()
     {
-    return mScores.length() == 0;
+    return ((mScores.length()==0) && !mRunning);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  static void download(RubikScores.ScoresReceiver receiver)
+  void abortNetworkTransaction()
     {
-    if( gottaDownload() )
-      {
-      doDownload();
-      }
+    mRunning = false;
+    }
 
-    receiver.receive(mScores);
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void download(Receiver receiver)
+    {
+    mReceiver = receiver;
+    mMode = DOWNLOAD;
+    mNetworkThrd = new Thread(this);
+    mNetworkThrd.start();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private static void doDownload()
+  public void run()
     {
+    try
+      {
+      if( gottaDownload() )
+        {
+        mRunning = true;
+
+        Thread.sleep(2000);
+
 mScores =
 
 "0 0 0 INEED7X7X7 1 rus" + "\n" +
@@ -77,31 +107,6 @@ mScores =
 "0 3 2 MDR04 9 ita" + "\n" +
 "0 3 3 ROBERT04 9 unk" + "\n" +
 "0 3 4 DULCE 10 mex" + "\n" +
-"0 4 0 THISNAVIS 11 usa" + "\n" +
-"0 4 1 Tilly 12 uk" + "\n" +
-"0 4 2 OHIOCUBER 14 usa" + "\n" +
-"0 4 3 GRV 15 mex" + "\n" +
-"0 4 4 INEED7X7X7 16 rus" + "\n" +
-"0 5 0 KADE 6 unk" + "\n" +
-"0 5 1 FREDRIK 11 den" + "\n" +
-"0 5 2 THISNAVIS 11 usa" + "\n" +
-"0 5 3 Tilly 14 uk" + "\n" +
-"0 5 4 A9QSKPJJDM 16 bra" + "\n" +
-"0 6 0 LoiraH 1 bra" + "\n" +
-"0 6 1 KAN 5 unk" + "\n" +
-"0 6 2 Tilly 7 uk" + "\n" +
-"0 6 3 FRITZ 17 ita" + "\n" +
-"0 6 4 JONIWWE 17 ina" + "\n" +
-"0 7 0 SULTANOUU 30 rus" + "\n" +
-"0 7 1 LOLO766 33 fra" + "\n" +
-"0 7 2 ElyKho 37 mly" + "\n" +
-"0 7 3 FREDRIK 37 den" + "\n" +
-"0 7 4 ILXOM707 37 uzb" + "\n" +
-"0 8 0 pardal 16 bra" + "\n" +
-"0 8 1 ILXOM707 32 uzb" + "\n" +
-"0 8 2 LOLO766 43 fra" + "\n" +
-"0 8 3 NIKODZ 44 geo" + "\n" +
-"0 8 4 SHAFFAF 48 ind" + "\n" +
 "1 0 0 INEED7X7X7 1 rus" + "\n" +
 "1 0 1 Tilly 1 uk" + "\n" +
 "1 0 2 REBECCA 1 fra" + "\n" +
@@ -122,31 +127,6 @@ mScores =
 "1 3 2 NIKITOS 21 rus" + "\n" +
 "1 3 3 PYCUK1707 21 rus" + "\n" +
 "1 3 4 KEDAR 22 ind" + "\n" +
-"1 4 0 Kinkz 7 ned" + "\n" +
-"1 4 1 UTEK 14 ina" + "\n" +
-"1 4 2 SJNT 23 ind" + "\n" +
-"1 4 3 ABCDEF89 24 vie" + "\n" +
-"1 4 4 SIJOYSIJ 27 ind" + "\n" +
-"1 5 0 SIJOYSIJ 40 ind" + "\n" +
-"1 5 1 UTEK 46 ina" + "\n" +
-"1 5 2 SJNT 54 ind" + "\n" +
-"1 5 3 FREDRIK 59 den" + "\n" +
-"1 5 4 YANY 64 arg" + "\n" +
-"1 6 0 UTEK 26 ina" + "\n" +
-"1 6 1 FLAVINHA 57 bra" + "\n" +
-"1 6 2 MILKMAN 57 fin" + "\n" +
-"1 6 3 MARIOEAGLE 59 ita" + "\n" +
-"1 6 4 LOLO766 85 fra" + "\n" +
-"1 7 0 UTEK 45 ina" + "\n" +
-"1 7 1 SEUA 157 vie" + "\n" +
-"1 7 2 FREDRIK 201 den" + "\n" +
-"1 7 3 ARMIN 205 rus" + "\n" +
-"1 7 4 ILXOM7 215 uzb" + "\n" +
-"1 8 0 SJNT 73 ind" + "\n" +
-"1 8 1 MILKOMANN 219 fin" + "\n" +
-"1 8 2 DEMONAIRE 288 uk" + "\n" +
-"1 8 3 SIJ 311 ind" + "\n" +
-"1 8 4 SIJOYSIJ 316 ind" + "\n" +
 "2 0 0 INEED7X7X7 1 rus" + "\n" +
 "2 0 1 HOMER0815 1 ger" + "\n" +
 "2 0 2 EIP 1 usa" + "\n" +
@@ -167,31 +147,6 @@ mScores =
 "2 3 2 NONAME 24 fin" + "\n" +
 "2 3 3 ABB0 26 ind" + "\n" +
 "2 3 4 NOYS 27 mex" + "\n" +
-"2 4 0 NIKITOS 37 rus" + "\n" +
-"2 4 1 INEED7X7X7 45 rus" + "\n" +
-"2 4 2 ILXOM7 50 uzb" + "\n" +
-"2 4 3 NOYS 52 mex" + "\n" +
-"2 4 4 MikeK 57 ger" + "\n" +
-"2 5 0 NIKITOS 62 rus" + "\n" +
-"2 5 1 LOLO766 70 fra" + "\n" +
-"2 5 2 HUBBUB 87 swe" + "\n" +
-"2 5 3 TETRAULT 90 usa" + "\n" +
-"2 5 4 NOYS 115 mex" + "\n" +
-"2 6 0 LOLO766 128 fra" + "\n" +
-"2 6 1 KAN 193 unk" + "\n" +
-"2 6 2 MEECMEEC 195 uk" + "\n" +
-"2 6 3 HUBBUB 197 swe" + "\n" +
-"2 6 4 PTOR 238 kyr" + "\n" +
-"2 7 0 CHRISTA 268 za" + "\n" +
-"2 7 1 MUZAFFER 288 tur" + "\n" +
-"2 7 2 INEED7X7X7 323 rus" + "\n" +
-"2 7 3 HECTOR58 360 mex" + "\n" +
-"2 7 4 CABC 391 rus" + "\n" +
-"2 8 0 CABC 245 rus" + "\n" +
-"2 8 1 SIRWALTR 289 arg" + "\n" +
-"2 8 2 NAMIT 558 ind" + "\n" +
-"2 8 3 HDH580702 602 mex" + "\n" +
-"2 8 4 HECTOR58 959 mex" + "\n" +
 "3 0 0 JARO 1 ger" + "\n" +
 "3 0 1 INEED7X7X7 1 rus" + "\n" +
 "3 0 2 MDR04 1 ita" + "\n" +
@@ -211,31 +166,15 @@ mScores =
 "3 3 1 THISNAVIS 23 usa" + "\n" +
 "3 3 2 INEED7X7X7 24 rus" + "\n" +
 "3 3 3 RUBIK123 30 lit" + "\n" +
-"3 3 4 SKY16 31 usa" + "\n" +
-"3 4 0 NIKITOS 37 rus" + "\n" +
-"3 4 1 6969696 43 usa" + "\n" +
-"3 4 2 ALFREDOPVV 45 bra" + "\n" +
-"3 4 3 HIEU 48 vie" + "\n" +
-"3 4 4 INEED7X7X7 52 rus" + "\n" +
-"3 5 0 ILXOM707 63 uzb" + "\n" +
-"3 5 1 PHOENIX 77 fin" + "\n" +
-"3 5 2 Nargiza 82 unk" + "\n" +
-"3 5 3 NIKITOS 83 rus" + "\n" +
-"3 5 4 ILXOM7 83 uzb" + "\n" +
-"3 6 0 ILXOM7 81 uzb" + "\n" +
-"3 6 1 ILXOM707 140 uzb" + "\n" +
-"3 6 2 INEED7X7X7 143 rus" + "\n" +
-"3 6 3 KAN 143 unk" + "\n" +
-"3 6 4 RUBIK123 177 lit" + "\n" +
-"3 7 0 ILXOM707 238 uzb" + "\n" +
-"3 7 1 LOLO766 283 fra" + "\n" +
-"3 7 2 INEED7X7X7 293 rus" + "\n" +
-"3 7 3 RUBIK123 313 lit" + "\n" +
-"3 7 4 SIRWALTR 343 arg" + "\n" +
-"3 8 0 CABC 312 rus" + "\n" +
-"3 8 1 HDH580702 411 mex" + "\n" +
-"3 8 2 RUBIK123 558 lit" + "\n" +
-"3 8 3 MUZAFFER 568 tur" + "\n" +
-"3 8 4 NOYS 649 mex";
+"3 3 4 SKY16 31 usa";
+      }
+    }
+  catch( Exception e )
+    {
+    android.util.Log.e("downloader", "Exception downloading records: "+e.getMessage() );
     }
+
+  mRunning = false;
+  mReceiver.receive(mScores);
   }
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/magic/RubikScoresView.java b/src/main/java/org/distorted/magic/RubikScoresView.java
index d7a9c38b..1419bab9 100644
--- a/src/main/java/org/distorted/magic/RubikScoresView.java
+++ b/src/main/java/org/distorted/magic/RubikScoresView.java
@@ -20,9 +20,12 @@
 package org.distorted.magic;
 
 import android.content.Context;
+import android.support.v4.app.FragmentActivity;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -58,17 +61,62 @@ public class RubikScoresView extends FrameLayout
     super(context);
 
     mPosition = position;
+    View view = inflate(context, R.layout.scores_downloading, null);
+    addView(view);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  @Override
-  public void onAttachedToWindow()
+  private int getLevelLayoutID(int level)
     {
-    super.onAttachedToWindow();
+    return 100+level;
+    }
 
-    Context context = getContext();
-    View view = inflate(context, R.layout.scores_downloading, null);
-    addView(view);
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void prepareView(FragmentActivity act)
+    {
+    removeAllViews();
+
+    View tab = inflate(act, R.layout.scores_tab, null);
+    LinearLayout layout = tab.findViewById(R.id.tabLayout);
+    addView(tab);
+
+    for(int i=1; i<=RubikActivity.MAX_SCRAMBLE; i++)
+      {
+      View level = inflate(act, R.layout.level_records, null);
+      level.setId(getLevelLayoutID(i));
+      TextView text = level.findViewById(R.id.levelTitle);
+      text.setText(act.getString(R.string.sc_placeholder,i));
+
+      layout.addView(level);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void fillRow(FragmentActivity act, int level, String place, int time, String name, String country)
+    {
+    if( level>=0 && level<RubikActivity.MAX_SCRAMBLE )
+      {
+      LinearLayout layout = findViewById(getLevelLayoutID(level+1));
+      View rowLay = inflate(act, R.layout.level_row, null);
+
+      TextView textName    = rowLay.findViewById(R.id.level_row_name);
+      TextView textTime    = rowLay.findViewById(R.id.level_row_time);
+      TextView textCountry = rowLay.findViewById(R.id.level_row_country);
+
+      float real_time = time/10.0f;
+
+      textName.setText(name);
+      textTime.setText( String.valueOf(real_time) );
+      textCountry.setText(country);
+
+      layout.addView(rowLay);
+      }
+    else
+      {
+      android.util.Log.e("receive", "level invalid: "+level);
+      }
     }
   }
diff --git a/src/main/java/org/distorted/magic/RubikScoresViewPager.java b/src/main/java/org/distorted/magic/RubikScoresViewPager.java
index 5d879afa..6dd78c4d 100644
--- a/src/main/java/org/distorted/magic/RubikScoresViewPager.java
+++ b/src/main/java/org/distorted/magic/RubikScoresViewPager.java
@@ -28,25 +28,81 @@ import android.view.ViewGroup;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-class RubikScoresViewPager extends PagerAdapter implements RubikScores.ScoresReceiver
+class RubikScoresViewPager extends PagerAdapter implements RubikScoresDownloader.Receiver
   {
   private FragmentActivity mAct;
+  private RubikScoresView[] mViews;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   RubikScoresViewPager(FragmentActivity act, ViewPager viewPager)
     {
     mAct = act;
+    mViews = new RubikScoresView[getCount()];
     viewPager.setAdapter(this);
     viewPager.setOffscreenPageLimit( RubikSize.LENGTH-1 );
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-// TODO
 
-  public void receive(String scores)
+  public void receive(final String scores)
     {
+    mAct.runOnUiThread(new Runnable()
+      {
+      @Override
+      public void run()
+        {
+        for(int i=0; i<RubikSize.LENGTH; i++)
+          {
+          mViews[i].prepareView(mAct);
+          }
+
+        int begin=-1 ,end, len = scores.length();
+
+        while( begin<len )
+          {
+          end = scores.indexOf('\n', begin+1);
+          if( end<0 ) end = len;
+          sendRow(scores.substring(begin+1,end));
+          begin = end;
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
+  private void sendRow(String row)
+    {
+    int s1 = row.indexOf(' ');
+    int s2 = row.indexOf(' ',s1+1);
+    int s3 = row.indexOf(' ',s2+1);
+    int s4 = row.indexOf(' ',s3+1);
+    int s5 = row.indexOf(' ',s4+1);
+    int s6 = row.length();
+
+    if( s5>s4 && s4>s3 && s3>s2 && s2>s1 && s1>0 )
+      {
+      int size       = Integer.valueOf( row.substring(   0,s1) );
+      int levl       = Integer.valueOf( row.substring(s1+1,s2) );
+      String place   = row.substring(s2+1, s3);
+      String name    = row.substring(s3+1, s4);
+      int time       = Integer.valueOf( row.substring(s4+1,s5) );
+      String country = row.substring(s5+1, s6);
+
+      if( size>=0 && size< RubikSize.LENGTH )
+        {
+        mViews[size].fillRow(mAct, levl, place, time, name, country);
+        }
+      else
+        {
+        android.util.Log.e("receive", "row invalid: size invalid: "+row);
+        }
+      }
+    else
+      {
+      android.util.Log.e("receive", "row invalid: "+row);
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -55,10 +111,27 @@ class RubikScoresViewPager extends PagerAdapter implements RubikScores.ScoresRec
   @NonNull
   public Object instantiateItem(@NonNull ViewGroup collection, int position)
     {
-    RubikScoresView view = new RubikScoresView(mAct,position);
-    collection.addView(view);
-
-    return view;
+    mViews[position] = new RubikScoresView(mAct,position);
+    collection.addView(mViews[position]);
+
+    boolean allCreated = true;
+
+    for(int i=0; i<RubikSize.LENGTH; i++)
+      {
+      if( mViews[i]==null )
+        {
+        allCreated = false;
+        break;
+        }
+      }
+
+    if( allCreated )
+      {
+      RubikScoresDownloader downloader = new RubikScoresDownloader();
+      downloader.download(this);
+      }
+
+    return mViews[position];
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/magic/RubikSettings.java b/src/main/java/org/distorted/magic/RubikSettings.java
index db9fce59..de61cc45 100644
--- a/src/main/java/org/distorted/magic/RubikSettings.java
+++ b/src/main/java/org/distorted/magic/RubikSettings.java
@@ -23,6 +23,7 @@ import android.app.Dialog;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.v4.app.FragmentActivity;
+import android.support.v4.content.ContextCompat;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatDialogFragment;
 import android.util.DisplayMetrics;
@@ -69,7 +70,7 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
     ///// OUTER LAYOUT ///////////////////////////////////////////////////////////////////
 
     int margin = (int)(scale*10 + 0.5f);
-    int color  = act.getResources().getColor(R.color.grey);
+    int color  = ContextCompat.getColor(act, R.color.grey);
     LinearLayout outerLayout = new LinearLayout(act);
     LinearLayout.LayoutParams outerLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT, 0.5f);
     outerLayoutParams.topMargin    = margin;
diff --git a/src/main/res/layout/level_records.xml b/src/main/res/layout/level_records.xml
new file mode 100644
index 00000000..91579cbe
--- /dev/null
+++ b/src/main/res/layout/level_records.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/levelLayout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/grey"
+
+    android:paddingBottom="0dp"
+    android:paddingTop="0dp"
+    android:paddingLeft="5dp"
+    android:paddingRight="5dp"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/levelTitle"
+        android:layout_width="match_parent"
+        android:layout_height="40dp"
+        android:textSize="26sp"
+        android:gravity="center"
+        android:background="@color/grey"
+        android:paddingBottom="5dp"
+        android:paddingTop="0dp"
+        android:paddingLeft="5dp"
+        android:paddingRight="5dp"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/src/main/res/layout/level_row.xml b/src/main/res/layout/level_row.xml
new file mode 100644
index 00000000..7c2e1349
--- /dev/null
+++ b/src/main/res/layout/level_row.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="32dp"
+    android:orientation="horizontal" >
+
+    <TextView
+        android:id="@+id/level_row_country"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1.5"
+        android:textSize="20sp"
+        android:gravity="left"
+        android:background="@color/black"
+        android:paddingLeft="5dp"/>
+
+    <TextView
+        android:id="@+id/level_row_name"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:textSize="20sp"
+        android:gravity="left"
+        android:background="@color/black"
+        />
+
+    <TextView
+        android:id="@+id/level_row_time"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_weight="1.5"
+        android:textSize="20sp"
+        android:gravity="right"
+        android:background="@color/black"
+        android:paddingRight="5dp"
+        />
+</LinearLayout>
diff --git a/src/main/res/layout/scores_tab.xml b/src/main/res/layout/scores_tab.xml
index c01e863d..58a5ccc8 100644
--- a/src/main/res/layout/scores_tab.xml
+++ b/src/main/res/layout/scores_tab.xml
@@ -14,6 +14,7 @@
              android:id="@+id/tabLayout"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
+             android:background="@color/grey"
              android:orientation="vertical" >
          </LinearLayout>
 
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
index 1993d3a3..d264d8a0 100644
--- a/src/main/res/values/colors.xml
+++ b/src/main/res/values/colors.xml
@@ -5,4 +5,5 @@
     <color name="colorAccent">#D81B60</color>
     <color name="red">#ffff0000</color>
     <color name="grey">#ff333333</color>
+    <color name="black">#ff010101</color>
 </resources>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 169ba333..c149499d 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -21,4 +21,5 @@
     <string name="credits2">Code, tutorials, learn how to write your own graphics effect: <a href="http://www.distorted.org/cube">Distorted.org</a></string>
 
     <string name="ms_placeholder">%1$d ms</string>
+    <string name="sc_placeholder">Scramble %1$d</string>
 </resources>
