commit 6fd8538de17e419369c58c3cd559e93fbc6f977c Author: mirk0dex Date: Sat Jan 27 18:53:36 2024 +0100 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..057376d --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Swatch .beat clock \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ae388c2 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..0114728 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..ccfe9b5 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,39 @@ +plugins { + id("com.android.application") +} + +android { + namespace = "eu.mirkodi.swatchbeatclock" + compileSdk = 34 + + defaultConfig { + applicationId = "eu.mirkodi.swatchbeatclock" + minSdk = 24 + targetSdk = 34 + versionCode = 6 + versionName = "v1" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/release/app-release.apk b/app/release/app-release.apk new file mode 100644 index 0000000..0c20362 Binary files /dev/null and b/app/release/app-release.apk differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..2e0be95 --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "eu.mirkodi.swatchbeatclock", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 5, + "versionName": "1.1.3", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/app/src/androidTest/java/eu/mirkodi/swatchbeatclock/ExampleInstrumentedTest.java b/app/src/androidTest/java/eu/mirkodi/swatchbeatclock/ExampleInstrumentedTest.java new file mode 100644 index 0000000..d28607d --- /dev/null +++ b/app/src/androidTest/java/eu/mirkodi/swatchbeatclock/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package eu.mirkodi.swatchbeatclock; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("eu.mirkodi.swatchbeatclock", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..015e072 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..5c2deda Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/eu/mirkodi/swatchbeatclock/ClockFragment.java b/app/src/main/java/eu/mirkodi/swatchbeatclock/ClockFragment.java new file mode 100644 index 0000000..52b835f --- /dev/null +++ b/app/src/main/java/eu/mirkodi/swatchbeatclock/ClockFragment.java @@ -0,0 +1,81 @@ +package eu.mirkodi.swatchbeatclock; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import android.os.Handler; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import java.text.DecimalFormat; + +public class ClockFragment extends Fragment { + // settings + private static final int UPDATEINTERVAL = 854 /* ms */; // one .beat is 85400 ms + + + public static String ARG_OBJECT = "Clock"; // will be replaced by the non-hardcoded translation + + static DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(); + + TextView clock; + ProgressBar pBar; + + final int PBARMAX = 100; + + private final Handler handler = new Handler(); // "thread" + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.clock, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + View gottenView = getView(); + + if (gottenView != null) { + ARG_OBJECT = gottenView.getContext().getString(R.string.clock); + + clock = gottenView.findViewById(R.id.clock); + pBar = gottenView.findViewById(R.id.pBar); + + pBar.setMax(PBARMAX); + + // define the code block to be executed + Runnable[] runnables = new Runnable[1]; + runnables[0] = () -> { + double curTime = InternetTime.getCurrentTimeBeats(); + clock.setText(gottenView.getContext().getString( + R.string.beatsDisplay, + df.format(curTime) + )); + + setProgressBar(curTime); + + // repeat every UPDATEINTERVAL ms + handler.postDelayed(runnables[0], UPDATEINTERVAL); + }; + + // start the Runnable immediately + handler.post(runnables[0]); + } + } + + protected void setProgressBar(double value) { + // isolate the number after floating point + double afterFP = value - ((int) value); + pBar.setProgress((int) (afterFP * PBARMAX) /* 1:value=PBARMAX:x*/); + } + + @NonNull + public String toString() { + return ARG_OBJECT; + } +} diff --git a/app/src/main/java/eu/mirkodi/swatchbeatclock/ConverterFragment.java b/app/src/main/java/eu/mirkodi/swatchbeatclock/ConverterFragment.java new file mode 100644 index 0000000..7965b13 --- /dev/null +++ b/app/src/main/java/eu/mirkodi/swatchbeatclock/ConverterFragment.java @@ -0,0 +1,132 @@ +package eu.mirkodi.swatchbeatclock; + +import android.os.Bundle; +import android.text.Editable; +import android.text.InputFilter; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.RadioGroup; +import android.widget.TimePicker; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import java.text.DecimalFormat; +import java.text.ParseException; + +public class ConverterFragment extends Fragment { + public static String ARG_OBJECT = "Converter"; // will be replaced by the non-hardcoded translation + private static final String LOGTITLE = "Converter"; + + static DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(); + + TimePicker timePicker; + EditText converted; + RadioGroup convertSideChooser; + Button clear; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.converter, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + View gottenView = getView(); + + if (gottenView != null) { + ARG_OBJECT = gottenView.getContext().getString(R.string.converter); + + timePicker = gottenView.findViewById(R.id.normal_date); + + converted = gottenView.findViewById(R.id.beats); + + convertSideChooser = gottenView.findViewById(R.id.convert_side_picker); + + clear = gottenView.findViewById(R.id.clear); + + clear.setOnClickListener(view1 -> { + timePicker.setHour(0); // run these first + timePicker.setMinute(0); + converted.setText(""); // and then this one + }); + + convertSideChooser.setOnCheckedChangeListener((radioGroup, i) -> { + if (convertSideChooser.getCheckedRadioButtonId() == R.id.normal_chosen) { + converted.setEnabled(false); + timePicker.setEnabled(true); + converted.setFilters(new InputFilter[]{}); + } else { + converted.setEnabled(true); + timePicker.setEnabled(false); + converted.setFilters(new InputFilter[]{new InputFilterMinMax(0, 1000)}); + } + }); + + timePicker.setOnTimeChangedListener((timePicker, i, i1) -> { + if (convertSideChooser.getCheckedRadioButtonId() == R.id.normal_chosen) { + converted.setText( + df.format( + InternetTime.timeToBeat( + timePicker.getHour(), + timePicker.getMinute(), + 0 /* seconds */, 0 /* millis */ + ) + ) + ); + } + }); + + converted.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + if (convertSideChooser.getCheckedRadioButtonId() == R.id.beats_chosen) { + int[] normalTime; + String text = converted.getText().toString(); + + try { + normalTime = InternetTime.beatToTime(text); + } catch (ParseException e) { + Log.e(LOGTITLE, "Got ParseException on "+text); + throw new RuntimeException(e); + } + + if (normalTime != null) { + timePicker.setHour(normalTime[0]); + Log.d(LOGTITLE, "Converted hour is "+normalTime[0]); + if (normalTime.length > 1) { + timePicker.setMinute(normalTime[1]); + Log.d(LOGTITLE, "Converted minute is "+normalTime[1]); + } + else + Log.d(LOGTITLE, "Minute not available"); + } else { + Log.w(LOGTITLE, "normalTime is null"); + } + } + } + }); + } + } + + @NonNull + public String toString() { + return ARG_OBJECT; + } +} diff --git a/app/src/main/java/eu/mirkodi/swatchbeatclock/InputFilterMinMax.java b/app/src/main/java/eu/mirkodi/swatchbeatclock/InputFilterMinMax.java new file mode 100644 index 0000000..a9bdb83 --- /dev/null +++ b/app/src/main/java/eu/mirkodi/swatchbeatclock/InputFilterMinMax.java @@ -0,0 +1,36 @@ +package eu.mirkodi.swatchbeatclock; + +import android.text.InputFilter; +import android.text.Spanned; + +import java.text.DecimalFormat; +import java.text.ParseException; +import java.util.Objects; + +// by [Pratik Sharma](https://stackoverflow.com/users/556975/pratik-sharma) + +public class InputFilterMinMax implements InputFilter { + private final double min, max; + static DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(); + + public InputFilterMinMax(double min, double max) { + this.min = min; + this.max = max; + } + + @Override + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, + int dstart, int dend) { + try { + double input = Objects.requireNonNull(df.parse(dest.toString() + source.toString())).doubleValue(); + if (isInRange(min, max, input)) + return null; + } catch (NumberFormatException | ParseException ignored) { + } + return ""; + } + + private boolean isInRange(double a, double b, double c) { + return b > a ? c >= a && c <= b : c >= b && c <= a; + } +} diff --git a/app/src/main/java/eu/mirkodi/swatchbeatclock/InternetTime.java b/app/src/main/java/eu/mirkodi/swatchbeatclock/InternetTime.java new file mode 100644 index 0000000..c00c709 --- /dev/null +++ b/app/src/main/java/eu/mirkodi/swatchbeatclock/InternetTime.java @@ -0,0 +1,101 @@ +package eu.mirkodi.swatchbeatclock; + +import android.util.Log; + +import java.text.DecimalFormat; +import java.text.ParseException; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Objects; +import java.util.TimeZone; + +public class InternetTime { + // settings + static final int MAXTIMEARRELEMS = 6; // hour, min, sec, millis, .... MAXTIMEARRELEMS + + static final String LOGTITLE = "InternetTime"; + + double initial; // set when obj is created + + static DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(); + + // initialise with current time + public InternetTime() { + initial = hoursToBeat(getCurrentTimeHours()); + } + + // @return decimal hour value + public static double getCurrentTimeHours() { + TimeZone tz = TimeZone.getTimeZone("CET"); + Calendar c = Calendar.getInstance(tz); + return timeToDoubleHours(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), + c.get(Calendar.SECOND), c.get(Calendar.MILLISECOND)); + } + + public static double hoursToBeat(double hours) { + // 24:hours=1000:x + return (hours * 1000) / 24; + } + + private static double timeToDoubleHours(int hour, int minute, int second, int millis) { + return hour + ((minute + ((second + (millis / 1000.0)) / 60.0)) / 60.0); + } + + // @return: [0]: hour; [1]: minute; [2]: seconds; [3]: millis...... + private static int[] doubleHoursToTime(double hours) { + Log.d(LOGTITLE, "Converting " + hours + " h to .beats"); + int[] ret = new int[MAXTIMEARRELEMS]; + + double n = hours; + int i = 0; + do { + int intN = (int) n; + Log.d(LOGTITLE, "i = " + i + "; n = " + n + "; intN = " + intN); + ret[i] = intN; + + double m = n - intN; + if (i < 2) { + n = m * 60; + } else { + n = m * 1000; + } + + i++; + } + while (n - ((int) n) != 0 && i < MAXTIMEARRELEMS); + + Log.d(LOGTITLE, "ret: " + Arrays.toString(ret)); + + return ret; + } + + public static double timeToBeat(int hour, int minute, int seconds, int millis) { + return hoursToBeat(timeToDoubleHours(hour, minute, seconds, millis)); + } + + public static double beatToDoubleHours(double beats) { + return (beats * 24) / 1000 /* 1000:beats=24:hours */; + } + + // @return: [0]: hour; [1]: minute; [2]: seconds; [3]: millis + public static int[] beatToTime(String beats) throws ParseException { + if (beats.isEmpty()) { + return null; + } + return beatToTime(Objects.requireNonNull(df.parse(beats)).doubleValue()); + } + + // @return: [0]: hour; [1]: minute; [2]: seconds; [3]: millis + public static int[] beatToTime(double beats) { + return doubleHoursToTime(beatToDoubleHours(beats)); + } + + public static double getCurrentTimeBeats() { + return hoursToBeat(getCurrentTimeHours()); + } + + // returns time saved when creating the class instance + public double getInitial() { + return initial; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/mirkodi/swatchbeatclock/MainActivity.java b/app/src/main/java/eu/mirkodi/swatchbeatclock/MainActivity.java new file mode 100644 index 0000000..d44e161 --- /dev/null +++ b/app/src/main/java/eu/mirkodi/swatchbeatclock/MainActivity.java @@ -0,0 +1,31 @@ +package eu.mirkodi.swatchbeatclock; + +import android.os.Bundle; +import android.view.WindowManager; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.tabs.TabLayout; + +public class MainActivity extends AppCompatActivity { + TabsPagerAdapter tabsPagerAdapter; + ViewPager viewPager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // "wakelock" + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + tabsPagerAdapter = new TabsPagerAdapter(getSupportFragmentManager()); + + viewPager = findViewById(R.id.pager); + viewPager.setAdapter(tabsPagerAdapter); + + TabLayout tabLayout = findViewById(R.id.tab_layout); + tabLayout.setupWithViewPager(viewPager); + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/mirkodi/swatchbeatclock/TabsPagerAdapter.java b/app/src/main/java/eu/mirkodi/swatchbeatclock/TabsPagerAdapter.java new file mode 100644 index 0000000..8fb0440 --- /dev/null +++ b/app/src/main/java/eu/mirkodi/swatchbeatclock/TabsPagerAdapter.java @@ -0,0 +1,34 @@ +package eu.mirkodi.swatchbeatclock; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; + +public class TabsPagerAdapter extends FragmentStatePagerAdapter { + ClockFragment clockFragment = new ClockFragment(); // 0 + ConverterFragment converterFragment = new ConverterFragment(); // 1; + + public TabsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @NonNull + @Override + public Fragment getItem(int i) { + if (i == 1) { + return converterFragment; + } + return clockFragment; + } + + @Override + public int getCount() { + return 2 /* hardcoded */; + } + + @Override + public CharSequence getPageTitle(int position) { + return getItem(position).toString(); + } +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..52db81d --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..8e0c170 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/layout/clock.xml b/app/src/main/res/layout/clock.xml new file mode 100644 index 0000000..6605fcf --- /dev/null +++ b/app/src/main/res/layout/clock.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/app/src/main/res/layout/converter.xml b/app/src/main/res/layout/converter.xml new file mode 100644 index 0000000..8f1c699 --- /dev/null +++ b/app/src/main/res/layout/converter.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + +