diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..95ec4fe
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,140 @@
+# Created by https://www.gitignore.io
+
+### Gradle ###
+.gradle
+build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+
+### Linux ###
+*~
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+
+### Windows ###
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+
+### Android ###
+# Built application files
+*.apk
+*.ap_
+
+# Files for the Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+
+# Gradle files
+.gradle/
+build/
+/*/build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+### Android Patch ###
+gen-external-apklibs
+
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
+
+*.iml
+
+## Directory-based project format:
+.idea/
+# if you remove the above rule, at least ignore the following:
+
+# User-specific stuff:
+# .idea/workspace.xml
+# .idea/tasks.xml
+# .idea/dictionaries
+
+# Sensitive or high-churn files:
+# .idea/dataSources.ids
+# .idea/dataSources.xml
+# .idea/sqlDataSources.xml
+# .idea/dynamic.xml
+# .idea/uiDesigner.xml
+
+# Gradle:
+# .idea/gradle.xml
+# .idea/libraries
+
+# Mongo Explorer plugin:
+# .idea/mongoSettings.xml
+
+## File-based project format:
+*.ipr
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+
+
+### Java ###
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..538a8a1
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,45 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ jcenter()
+ }
+
+ dependencies {
+ classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
+ classpath 'com.github.ben-manes:gradle-versions-plugin:0.11.3'
+ }
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'com.jakewharton.hugo'
+apply plugin: 'com.github.ben-manes.versions'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "com.idlegandalf.ledd"
+ minSdkVersion 16
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:22.2.1'
+ compile 'com.android.support:support-v4:22.2.1'
+ compile 'com.squareup.okhttp:okhttp:2.4.0'
+ compile 'com.jakewharton:butterknife:7.0.1'
+ compile 'com.koushikdutta.async:androidasync:2.1.6'
+ compile 'com.android.support:design:22.2.1'
+ provided "org.projectlombok:lombok:1.16.4"
+}
diff --git a/app/proguard-rules.txt b/app/proguard-rules.txt
new file mode 100644
index 0000000..e9ef993
--- /dev/null
+++ b/app/proguard-rules.txt
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/giovanni/android-studio/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# 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 *;
+#}
\ 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..0fcf6db
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/idlegandalf/ledd/AssistantActivity.java b/app/src/main/java/com/idlegandalf/ledd/AssistantActivity.java
new file mode 100644
index 0000000..9a18c62
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/AssistantActivity.java
@@ -0,0 +1,73 @@
+package com.idlegandalf.ledd;
+
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Menu;
+import android.widget.FrameLayout;
+
+import com.idlegandalf.ledd.fragments.WelcomeFragment;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+public class AssistantActivity extends AppCompatActivity {
+
+ @Bind(R.id.fragmentContainer)
+ FrameLayout container;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_asistent);
+ ButterKnife.bind(this);
+
+ // Check that the activity is using the layout version with
+ // the fragment_container FrameLayout
+ if (container != null) {
+
+ // However, if we're being restored from a previous state,
+ // then we don't need to do anything and should return or else
+ // we could end up with overlapping fragments.
+ if (savedInstanceState != null) {
+ return;
+ }
+
+ // Create a new Fragment to be placed in the activity layout
+ WelcomeFragment welcomeFragment = new WelcomeFragment();
+
+ // In case this activity was started with special instructions from an
+ // Intent, pass the Intent's extras to the fragment as arguments
+ welcomeFragment.setArguments(getIntent().getExtras());
+
+ // Add the fragment to the 'fragment_container' FrameLayout
+ getFragmentManager().beginTransaction()
+ .add(R.id.fragmentContainer, welcomeFragment).commit();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.welcome, menu);
+ return true;
+ }
+
+ public void switchFragment(Fragment fragment) {
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+
+ fragmentTransaction.replace(R.id.fragmentContainer, fragment);
+ fragmentTransaction.addToBackStack(null);
+
+ fragmentTransaction.commit();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (getFragmentManager().getBackStackEntryCount() > 0) {
+ getFragmentManager().popBackStack();
+ } else {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/ColorActivity.java b/app/src/main/java/com/idlegandalf/ledd/ColorActivity.java
new file mode 100644
index 0000000..2020457
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/ColorActivity.java
@@ -0,0 +1,147 @@
+package com.idlegandalf.ledd;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.idlegandalf.ledd.components.LEDDHelper;
+import com.idlegandalf.ledd.components.RGBStripe;
+import com.idlegandalf.ledd.components.StripeGroup;
+import com.idlegandalf.ledd.utils.ColorPicker;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+public class ColorActivity extends AppCompatActivity {
+
+ @Bind(R.id.colorPicker)
+ ColorPicker mWheel;
+ @Bind(R.id.drawer_layout)
+ DrawerLayout mDrawerLayout;
+ private ActionBarDrawerToggle mDrawerToggle;
+ private LEDDHelper mAPI;
+ private RGBStripe mActiveStripe;
+ private StripeGroup mActiveGroup;
+ private boolean firstRun = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_color);
+ ButterKnife.bind(this);
+
+ // check for connectivity
+ ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
+ if (networkInfo == null || !networkInfo.isConnected()) {
+ // TODO: Display error
+ }
+
+ mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.app_name, R.string.app_name) {
+
+ /** Called when a drawer has settled in a completely closed state. */
+ public void onDrawerClosed(View view) {
+ // TODO: do things that have to be done here
+ }
+
+ /** Called when a drawer has settled in a completely open state. */
+ public void onDrawerOpened(View drawerView) {
+ // TODO: do things that have to be done here
+ }
+ };
+
+ // set the drawer toggle as the DrawerListener
+ mDrawerLayout.setDrawerListener(mDrawerToggle);
+
+ // enable Homebutton navigation to drawer
+ //getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ //getSupportActionBar().setHomeButtonEnabled(true);
+ // getSupportActionBar().setIcon(R.drawable.ic_bar);
+
+ if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
+ "firstRun", true)) {
+ System.out.println("first run!");
+ firstRun = true;
+ Intent intent = new Intent(this, AssistantActivity.class);
+ startActivity(intent);
+ }
+
+ if (ColorApplication.getInstance().isAPIActive()) {
+ this.mAPI = ColorApplication.getInstance().getAPI();
+ System.out.println("API active!");
+ } else {
+ System.out.println("API not active :(");
+ this.finish();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.color, menu);
+ return true;
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ mDrawerToggle.syncState();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Pass the event to ActionBarDrawerToggle, if it returns
+ // true, then it has handled the app icon touch event
+ if (mDrawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+ // Handle your other action bar items...
+
+ switch (item.getItemId()) {
+ case R.id.action_settings:
+ Intent intent = new Intent(this, SettingsActivity.class);
+ startActivity(intent);
+ return super.onOptionsItemSelected(item);
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_MENU) {
+ if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
+ mDrawerLayout.closeDrawer(Gravity.LEFT);
+ } else {
+ mDrawerLayout.openDrawer(Gravity.LEFT);
+ }
+
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/ColorApplication.java b/app/src/main/java/com/idlegandalf/ledd/ColorApplication.java
new file mode 100644
index 0000000..2ce0b90
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/ColorApplication.java
@@ -0,0 +1,55 @@
+package com.idlegandalf.ledd;
+
+import android.app.Application;
+import android.preference.PreferenceManager;
+
+import com.idlegandalf.ledd.components.LEDDHelper;
+
+import java.io.IOException;
+
+public class ColorApplication extends Application {
+ private static ColorApplication singleton;
+ private LEDDHelper mAPI = null;
+ private boolean mAPIActive = false;
+
+ public static ColorApplication getInstance() {
+ return singleton;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ singleton = this;
+ try {
+ initAPI();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public LEDDHelper getAPI() {
+ return this.mAPI;
+ }
+
+ public void initAPI() throws IOException {
+ if (PreferenceManager.getDefaultSharedPreferences(this)
+ .contains("pref_key_host")) {
+
+ mAPI = new LEDDHelper(PreferenceManager
+ .getDefaultSharedPreferences(this).getString(
+ "pref_key_host", ""),
+ PreferenceManager
+ .getDefaultSharedPreferences(this).getInt(
+ "pref_key_port", 8825),
+ getApplicationContext()
+ );
+
+ this.mAPIActive = true;
+ System.out.println("api is declared as active now");
+ }
+ }
+
+ public boolean isAPIActive() {
+ return mAPIActive;
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/ColorService.java b/app/src/main/java/com/idlegandalf/ledd/ColorService.java
new file mode 100644
index 0000000..c5c137f
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/ColorService.java
@@ -0,0 +1,99 @@
+package com.idlegandalf.ledd;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.widget.Toast;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+
+public class ColorService extends Service {
+ private Looper mServiceLooper;
+ private ServiceHandler mServiceHandler;
+ private DatagramSocket socket;
+
+ // Handler that receives messages from the thread
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ Bundle msgdata = msg.getData();
+
+ if (msgdata != null) {
+ try {
+ byte[] data = msgdata.getString("json").getBytes();
+ DatagramPacket packet = new DatagramPacket(data, data.length,
+ InetAddress.getByName(msgdata.getString("ip")), msgdata.getInt("port"));
+ socket.send(packet);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Stop the service using the startId, so that we don't stop
+ // the service in the middle of handling another job
+ //stopSelf(msg.arg1);
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ Toast.makeText(this, "ColorService started", Toast.LENGTH_SHORT).show();
+
+ try {
+ socket = new DatagramSocket();
+ } catch (SocketException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ // Start up the thread running the service. Note that we create a
+ // separate thread because the service normally runs in the process's
+ // main thread, which we don't want to block. We also make it
+ // background priority so CPU-intensive work will not disrupt our UI.
+ HandlerThread thread = new HandlerThread("ServiceStartArguments",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ // Get the HandlerThread's Looper and use it for our Handler
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // For each start request, send a message to start a job and deliver the
+ // start ID so we know which request we're stopping when we finish the
+ // job
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.setData(intent.getExtras());
+ mServiceHandler.sendMessage(msg);
+
+ // If we get killed, after returning from here, restart
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // We don't provide binding, so return null
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ socket.close();
+ Toast.makeText(this, "ColorService stopped", Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/DrawerFragment.java b/app/src/main/java/com/idlegandalf/ledd/DrawerFragment.java
new file mode 100644
index 0000000..5187e2a
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/DrawerFragment.java
@@ -0,0 +1,16 @@
+package com.idlegandalf.ledd;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class DrawerFragment extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.drawer_fragment, container, false);
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/ProgressBarActionBarActivity.java b/app/src/main/java/com/idlegandalf/ledd/ProgressBarActionBarActivity.java
new file mode 100644
index 0000000..cfa606e
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/ProgressBarActionBarActivity.java
@@ -0,0 +1,32 @@
+package com.idlegandalf.ledd;
+
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ProgressBar;
+
+public abstract class ProgressBarActionBarActivity extends AppCompatActivity {
+ private ProgressBar mProgressBar;
+
+ public void setContentView(View view) {
+ init().addView(view);
+ }
+
+ public void setContentView(int layoutResID) {
+ getLayoutInflater().inflate(layoutResID, init(), true);
+ }
+
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ init().addView(view, params);
+ }
+
+ private ViewGroup init() {
+ super.setContentView(R.layout.progress);
+ mProgressBar = (ProgressBar) findViewById(R.id.activity_bar);
+ return (ViewGroup) findViewById(R.id.activity_frame);
+ }
+
+ protected ProgressBar getProgressBar() {
+ return mProgressBar;
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/SettingsActivity.java b/app/src/main/java/com/idlegandalf/ledd/SettingsActivity.java
new file mode 100644
index 0000000..3f06731
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/SettingsActivity.java
@@ -0,0 +1,17 @@
+package com.idlegandalf.ledd;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+
+public class SettingsActivity extends ActionBarActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ // Display the fragment as the main content.
+ getFragmentManager().beginTransaction()
+ .replace(android.R.id.content, new SettingsFragment())
+ .commit();
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/SettingsFragment.java b/app/src/main/java/com/idlegandalf/ledd/SettingsFragment.java
new file mode 100644
index 0000000..0fe6fe8
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/SettingsFragment.java
@@ -0,0 +1,14 @@
+package com.idlegandalf.ledd;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+public class SettingsFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.settings);
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/components/Controller.java b/app/src/main/java/com/idlegandalf/ledd/components/Controller.java
new file mode 100644
index 0000000..3192bf4
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/components/Controller.java
@@ -0,0 +1,13 @@
+package com.idlegandalf.ledd.components;
+
+import java.util.ArrayList;
+
+import lombok.Getter;
+import lombok.Setter;
+
+
+@Getter
+@Setter
+public class Controller {
+ private ArrayList stripes;
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/components/HSV.java b/app/src/main/java/com/idlegandalf/ledd/components/HSV.java
new file mode 100644
index 0000000..8cf72f6
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/components/HSV.java
@@ -0,0 +1,12 @@
+package com.idlegandalf.ledd.components;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class HSV {
+ private float hue;
+ private float saturation;
+ private float value;
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/components/Host.java b/app/src/main/java/com/idlegandalf/ledd/components/Host.java
new file mode 100644
index 0000000..6f98b79
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/components/Host.java
@@ -0,0 +1,27 @@
+package com.idlegandalf.ledd.components;
+
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class Host {
+ private String address;
+ private int port;
+
+ public Host(String address, int port) {
+ this.address = address;
+ this.port = port;
+ }
+
+ public Host() {
+ this.address = null;
+ this.port = 0;
+ }
+
+ @Override
+ public String toString() {
+ return address + ":" + port;
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/components/LEDDHelper.java b/app/src/main/java/com/idlegandalf/ledd/components/LEDDHelper.java
new file mode 100644
index 0000000..85f06b1
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/components/LEDDHelper.java
@@ -0,0 +1,68 @@
+package com.idlegandalf.ledd.components;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.idlegandalf.ledd.ColorService;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+public class LEDDHelper {
+ private String mAddr;
+ private int mPort;
+ private Context context;
+
+ final String ACTION_SETCOLOR = "set_color";
+ final String ACTION_GETCOLOR = "get_color";
+
+ public LEDDHelper(String ip, int port, Context appl) throws UnsupportedEncodingException,
+ IOException {
+ this.mAddr = ip;
+ this.mPort = port;
+ this.context = appl;
+ }
+
+ /**
+ * Send RGB values to dameon using the stripeid
+ * @param sid Stripeid
+ * @param color Color in HSV format
+ * @throws JSONException not valid json
+ * @throws IOException socket error
+ */
+ public void sendColor(int sid, HSV color)
+ throws JSONException, IOException {
+ JSONObject jnson = new JSONObject();
+ JSONArray hsv = new JSONArray();
+
+ hsv.put(color.getHue());
+ hsv.put(color.getSaturation());
+ hsv.put(color.getValue());
+
+ jnson.put("action", ACTION_SETCOLOR);
+ jnson.put("sid", sid);
+ jnson.put("color", hsv);
+
+ sendJSON(jnson.toString());
+ }
+
+ private void sendJSON(String json) throws IOException {
+ Intent intent = new Intent(context, ColorService.class);
+ Bundle bndl = new Bundle();
+ bndl.putString("json", json);
+ bndl.putString("host", this.mAddr);
+ bndl.putInt("port", mPort);
+ intent.putExtras(bndl);
+ context.startService(intent);
+ }
+
+ public void onDestroy() {
+ Intent intent = new Intent(context, ColorService.class);
+ context.stopService(intent);
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/components/RGBStripe.java b/app/src/main/java/com/idlegandalf/ledd/components/RGBStripe.java
new file mode 100644
index 0000000..d964868
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/components/RGBStripe.java
@@ -0,0 +1,17 @@
+package com.idlegandalf.ledd.components;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class RGBStripe {
+ private int id;
+ private int channelRed;
+ private int channelGreen;
+ private int channelBlue;
+ private String nice_name;
+ private HSV color;
+ private double gammaCorrection;
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/idlegandalf/ledd/components/StripeGroup.java b/app/src/main/java/com/idlegandalf/ledd/components/StripeGroup.java
new file mode 100644
index 0000000..d625d53
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/components/StripeGroup.java
@@ -0,0 +1,62 @@
+package com.idlegandalf.ledd.components;
+
+
+import org.json.JSONException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class StripeGroup {
+ private String mNiceName;
+ private String mDescription;
+ private ArrayList mStripes;
+
+ public String getNiceName() {
+ return mNiceName;
+ }
+
+ public void setNiceName(String mNiceName) {
+ this.mNiceName = mNiceName;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public void setDescription(String mDescription) {
+ this.mDescription = mDescription;
+ }
+
+ public RGBStripe getStripeByPos(int pos) {
+ return mStripes.get(pos);
+ }
+
+ public RGBStripe getStripeById(int sid) {
+ for (int i = 0; i < mStripes.size(); i++) {
+ if (mStripes.get(i).getId() == sid) {
+ return mStripes.get(i);
+ }
+ }
+ return null;
+ }
+
+ public void setRGB(int r, int g, int b, LEDDHelper api) throws JSONException, IOException {
+ for (int i = 0; i < mStripes.size(); i++) {
+ //mStripes.get(i).setRGB(r, g, b, api);
+ }
+ }
+
+ public int addStripe(RGBStripe stripe) {
+ mStripes.add(stripe);
+ return mStripes.size()-1;
+ }
+
+ public void removeStripeByObject(RGBStripe stripe) {
+ for (int i = 0; i < mStripes.size(); i++) {
+ if (mStripes.get(i).getId() == stripe.getId()) {
+ mStripes.remove(i);
+ return;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/fragments/StepOneFragment.java b/app/src/main/java/com/idlegandalf/ledd/fragments/StepOneFragment.java
new file mode 100644
index 0000000..91491af
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/fragments/StepOneFragment.java
@@ -0,0 +1,233 @@
+package com.idlegandalf.ledd.fragments;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.idlegandalf.ledd.R;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+public class StepOneFragment extends Fragment {
+ @Bind(R.id.step2_back)
+ Button mBack;
+ @Bind(R.id.ipSelect)
+ EditText mAutoIP;
+ @Bind(R.id.step2_next)
+ Button mTest;
+ @Bind(R.id.portSelect)
+ EditText mAutoPort;
+ ArrayList mIPList = new ArrayList<>();
+ boolean isAutoDetected = false;
+
+
+ public static StepOneFragment newInstance(String addrs) {
+ Bundle args = new Bundle();
+
+ args.putString("ips", addrs);
+
+ StepOneFragment fragment = new StepOneFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.fragment_step1, container, false);
+ ButterKnife.bind(this, v);
+
+ return v;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ String ips = getArguments().getString("ips");
+
+ if (ips != null) {
+ if (!ips.isEmpty()) {
+ for (String string : ips.split(";")) {
+ if (string.isEmpty()) {
+ System.out.println("added addr " + string);
+ mIPList.add(string);
+ }
+ }
+ if (mIPList.size() > 0) isAutoDetected = true;
+ }
+ }
+
+ mBack.setOnClickListener(nextClickListener);
+ mTest.setOnClickListener(nextClickListener);
+ }
+
+ private OnClickListener nextClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.step2_back:
+ getActivity().onBackPressed();
+ break;
+ case R.id.step2_next:
+ System.out.println("forward calling!");
+ testController();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ private void testController() {
+ ConnectivityManager connManager = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+
+ if (mWifi.isConnected()) {
+ setInputState(false);
+ if (!isValidAddrInput(mAutoIP, mAutoPort)) {
+ setInputState(true);
+ return;
+ }
+ getActivity().setProgressBarIndeterminateVisibility(true);
+ getActivity().setProgressBarVisibility(true);
+ new pokeController().execute(mAutoIP.toString(), mAutoPort.toString());
+ } else {
+ Toast.makeText(getActivity().getApplicationContext(), "please connect WiFi", Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private class pokeController extends AsyncTask {
+ protected Boolean doInBackground(String... ippo) {
+ try {
+ InetAddress controller = InetAddress.getByName(ippo[0]);
+ int port = Integer.parseInt(ippo[1]);
+
+ DatagramSocket socket = new DatagramSocket();
+ String hstr = new JSONObject().put("action", 5).toString();
+
+ DatagramPacket hello = new DatagramPacket(hstr.getBytes(), hstr.getBytes().length);
+
+ hello.setPort(port);
+ socket.setSoTimeout(100);
+
+ if (!controller.isReachable(50)) {
+ socket.close();
+ return false;
+ }
+
+ hello.setAddress(controller);
+ socket.send(hello);
+
+ DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
+ try {
+ socket.receive(packet);
+ } catch (SocketTimeoutException e) {
+ socket.close();
+ return false;
+ }
+
+ Toast.makeText(getActivity().getApplicationContext(), new String(packet.getData(), 0, packet.getLength()), Toast.LENGTH_LONG).show();
+
+ JSONObject resjson = new JSONObject(new String(packet.getData(), 0, packet.getLength()));
+
+ if (resjson.getInt("action") != 5) {
+ socket.close();
+ return false;
+ }
+
+ socket.close();
+ } catch (SocketException e1) {
+ return false;
+ } catch (IOException e1) {
+ return false;
+ } catch (JSONException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected void onProgressUpdate(Void... progress) {
+
+ }
+
+ protected void onPostExecute(Boolean result) {
+ getActivity().setProgressBarIndeterminateVisibility(false);
+ getActivity().setProgressBarVisibility(false);
+ setInputState(true);
+ if (result) {
+ Snackbar.make(getActivity().findViewById(R.id.linlayp), "found daemon running on " +
+ "address " + mAutoIP.toString() + ":" + mAutoPort.toString(), Snackbar.LENGTH_LONG).show();
+ } else {
+ Snackbar.make(getActivity().findViewById(R.id.linlayp), "No running daemon " + "found", Snackbar.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ private boolean isValidIPFromEditText(EditText atv) {
+ String exip[] = atv.toString().trim().split("\\.");
+ if (exip.length != 4) return false;
+
+ for (String string : exip) {
+ if (!string.isEmpty()) {
+ try {
+ if (Integer.parseInt(string) < 0 || Integer.parseInt(string) > 255) return false;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private boolean isValidPortFromEditText(EditText atv) {
+ return !atv.toString().isEmpty() && Integer.parseInt(atv.toString()) > 1023 && Integer.parseInt(atv.toString()) <= 65535;
+ }
+
+ private boolean isValidAddrInput(EditText ipatv, EditText portatv) {
+ if (!isValidIPFromEditText(ipatv)) {
+ Snackbar.make(getActivity().findViewById(R.id.linlayp), "Please enter valid IP " + "address", Snackbar.LENGTH_LONG).show();
+
+ return false;
+ }
+ if (!isValidPortFromEditText(portatv)) {
+ Snackbar.make(getActivity().findViewById(R.id.linlayp), "Please enter valid port " + "(1024-65535)", Snackbar.LENGTH_LONG).show();
+
+ return false;
+ }
+ return true;
+ }
+
+ private void setInputState(boolean state) {
+ mAutoIP.setEnabled(state);
+ mAutoPort.setEnabled(state);
+ mBack.setEnabled(state);
+ mTest.setEnabled(state);
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/fragments/StepStripesFragment.java b/app/src/main/java/com/idlegandalf/ledd/fragments/StepStripesFragment.java
new file mode 100644
index 0000000..1e8ecc5
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/fragments/StepStripesFragment.java
@@ -0,0 +1,7 @@
+package com.idlegandalf.ledd.fragments;
+
+import android.app.Fragment;
+
+public class StepStripesFragment extends Fragment {
+
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/fragments/WelcomeFragment.java b/app/src/main/java/com/idlegandalf/ledd/fragments/WelcomeFragment.java
new file mode 100644
index 0000000..b63ac5c
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/fragments/WelcomeFragment.java
@@ -0,0 +1,218 @@
+package com.idlegandalf.ledd.fragments;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.idlegandalf.ledd.AssistantActivity;
+import com.idlegandalf.ledd.R;
+import com.idlegandalf.ledd.components.Host;
+
+import java.net.Inet4Address;
+import java.util.ArrayList;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+public class WelcomeFragment extends Fragment {
+
+ @Bind(R.id.step1_auto)
+ Button mAuto;
+ @Bind(R.id.step1_manual)
+ Button mManual;
+ @Bind(R.id.host_container)
+ LinearLayout hostContainer;
+ @Bind(R.id.toolbar)
+ Toolbar toolbar;
+
+ NsdManager mNsdManager;
+ final String TAG = "ColorD";
+ NsdManager.DiscoveryListener mDiscoveryListener;
+ NsdManager.ResolveListener mResolveListener;
+ ArrayList hosts;
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.fragment_welcome, container, false);
+ ButterKnife.bind(this, v);
+ return v;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mAuto.setOnClickListener(nextClickListener);
+ mManual.setOnClickListener(nextClickListener);
+
+ ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
+
+ hosts = new ArrayList<>();
+
+ mResolveListener = new NsdManager.ResolveListener() {
+
+ @Override
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ // Called when the resolve fails. Use the error code to debug.
+ Log.e(TAG, "Resolve failed" + errorCode);
+ }
+
+ @Override
+ public void onServiceResolved(final NsdServiceInfo serviceInfo) {
+ Log.e(TAG, "Resolve Succeeded. " + serviceInfo);
+
+ if (serviceInfo.getHost() instanceof Inet4Address) {
+ Log.d(TAG, "Found IPv4! Yay!");
+
+ final Host fHost = new Host(serviceInfo.getHost().getHostAddress(), serviceInfo.getPort());
+
+ if (!hosts.contains(fHost)) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ View v = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout
+ .host_row, hostContainer, false);
+
+ TextView host = (TextView) v.findViewById(R.id.welcome_host);
+
+ host.setText(serviceInfo.getHost().getHostAddress() + ":" + serviceInfo.getPort());
+ host.setTag(serviceInfo);
+
+ View.OnClickListener listener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ StepOneFragment stepOneFragment = StepOneFragment.newInstance(hosts.get((int) v.getTag()).toString());
+ ((AssistantActivity) getActivity()).switchFragment(stepOneFragment);
+ }
+ };
+
+ v.setOnClickListener(listener);
+ host.setOnClickListener(listener);
+
+ v.setTag(hosts.size());
+ host.setTag(hosts.size());
+
+ hostContainer.addView(v);
+ hosts.add(fHost);
+ }
+ });
+ }
+ }
+ }
+ };
+
+ mDiscoveryListener = new NsdManager.DiscoveryListener() {
+
+ // Called as soon as service discovery begins.
+ @Override
+ public void onDiscoveryStarted(String regType) {
+ Log.d(TAG, "Service discovery started");
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo service) {
+ // A service was found! Do something with it.
+ Log.d(TAG, "Service discovery success " + service);
+ if (!service.getServiceType().equals("_ledd._tcp.")) {
+ // Service type is the string containing the protocol and
+ // transport layer for this service.
+ Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
+ } else {
+ mNsdManager.resolveService(service, mResolveListener);
+ }
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo service) {
+ // When the network service is no longer available.
+ // Internal bookkeeping code goes here.
+ Log.e(TAG, "service lost" + service);
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ Log.i(TAG, "Discovery stopped: " + serviceType);
+ }
+
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ Log.e(TAG, "Discovery failed: Error code:" + errorCode);
+ mNsdManager.stopServiceDiscovery(this);
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ Log.e(TAG, "Discovery failed: Error code:" + errorCode);
+ mNsdManager.stopServiceDiscovery(this);
+ }
+ };
+ }
+
+ private View.OnClickListener nextClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.step1_auto:
+ System.out.println("auto pressed");
+ //getProgressBar().setIndeterminate(true);
+ //setProgressBarIndeterminateVisibility(true);
+ //setProgressBarVisibility(true);
+ //setProgressBarIndeterminate(true);
+ setButtonState(false);
+
+ mNsdManager = (NsdManager) getActivity().getSystemService(Context.NSD_SERVICE);
+ mNsdManager.discoverServices("_ledd._tcp", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
+
+ break;
+ case R.id.step1_manual:
+ System.out.println("manual pressed");
+ setButtonState(false);
+ StepOneFragment stepOneFragment = StepOneFragment.newInstance(null);
+ ((AssistantActivity) getActivity()).switchFragment(stepOneFragment);
+ setButtonState(true);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ void setButtonState(boolean state) {
+ mAuto.setEnabled(state);
+ mManual.setEnabled(state);
+ }
+
+ @Override
+ public void onPause() {
+ if (mNsdManager != null) {
+ tearDown();
+ setButtonState(true);
+ }
+
+ super.onPause();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mNsdManager != null) tearDown();
+ super.onDestroy();
+ }
+
+ // NsdHelper's tearDown method
+ public void tearDown() {
+ mNsdManager.stopServiceDiscovery(mDiscoveryListener);
+ }
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/utils/ColorPicker.java b/app/src/main/java/com/idlegandalf/ledd/utils/ColorPicker.java
new file mode 100644
index 0000000..52ebfd6
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/utils/ColorPicker.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2013 Piotr Adamus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.idlegandalf.ledd.utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Join;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.RadialGradient;
+import android.graphics.RectF;
+import android.graphics.Shader.TileMode;
+import android.graphics.SweepGradient;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class ColorPicker extends View {
+
+ /**
+ * Customizable display parameters (in percents)
+ */
+ private final int paramOuterPadding = 2; // outer padding of the whole color picker view
+ private final int paramInnerPadding = 5; // distance between value slider wheel and inner color wheel
+ private final int paramValueSliderWidth = 10; // width of the value slider
+ private final int paramArrowPointerSize = 4; // size of the arrow pointer; set to 0 to hide the pointer
+
+ private Paint colorWheelPaint;
+ private Paint valueSliderPaint;
+
+ private Paint colorViewPaint;
+
+ private Paint colorPointerPaint;
+ private RectF colorPointerCoords;
+
+ private Paint valuePointerPaint;
+ private Paint valuePointerArrowPaint;
+
+ private RectF outerWheelRect;
+ private RectF innerWheelRect;
+
+ private Path colorViewPath;
+ private Path valueSliderPath;
+ private Path arrowPointerPath;
+
+ private Bitmap colorWheelBitmap;
+
+ private int valueSliderWidth;
+ private int innerPadding;
+ private int outerPadding;
+
+ private int arrowPointerSize;
+ private int outerWheelRadius;
+ private int innerWheelRadius;
+ private int colorWheelRadius;
+
+ private Matrix gradientRotationMatrix;
+
+ /** Currently selected color */
+ private float[] colorHSV = new float[] { 0f, 0f, 1f };
+
+ public ColorPicker(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public ColorPicker(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ColorPicker(Context context) {
+ super(context);
+ init();
+ }
+
+ private void init() {
+
+ colorPointerPaint = new Paint();
+ colorPointerPaint.setStyle(Style.STROKE);
+ colorPointerPaint.setStrokeWidth(2f);
+ colorPointerPaint.setARGB(128, 0, 0, 0);
+
+ valuePointerPaint = new Paint();
+ valuePointerPaint.setStyle(Style.STROKE);
+ valuePointerPaint.setStrokeWidth(2f);
+
+ valuePointerArrowPaint = new Paint();
+
+ colorWheelPaint = new Paint();
+ colorWheelPaint.setAntiAlias(true);
+ colorWheelPaint.setDither(true);
+
+ valueSliderPaint = new Paint();
+ valueSliderPaint.setAntiAlias(true);
+ valueSliderPaint.setDither(true);
+
+ colorViewPaint = new Paint();
+ colorViewPaint.setAntiAlias(true);
+
+ colorViewPath = new Path();
+ valueSliderPath = new Path();
+ arrowPointerPath = new Path();
+
+ outerWheelRect = new RectF();
+ innerWheelRect = new RectF();
+
+ colorPointerCoords = new RectF();
+
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ int size = Math.min(widthSize, heightSize);
+ setMeasuredDimension(size, size);
+ }
+
+ @SuppressLint("DrawAllocation")
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ int centerX = getWidth() / 2;
+ int centerY = getHeight() / 2;
+
+ // drawing color wheel
+
+ canvas.drawBitmap(colorWheelBitmap, centerX - colorWheelRadius, centerY - colorWheelRadius, null);
+
+ // drawing color view
+
+ colorViewPaint.setColor(Color.HSVToColor(colorHSV));
+ canvas.drawPath(colorViewPath, colorViewPaint);
+
+ // drawing value slider
+
+ float[] hsv = new float[] { colorHSV[0], colorHSV[1], 1f };
+
+ SweepGradient sweepGradient = new SweepGradient(centerX, centerY, new int[] { Color.BLACK, Color.HSVToColor(hsv), Color.WHITE }, null);
+ sweepGradient.setLocalMatrix(gradientRotationMatrix);
+ valueSliderPaint.setShader(sweepGradient);
+
+ canvas.drawPath(valueSliderPath, valueSliderPaint);
+
+ // drawing color wheel pointer
+
+ float hueAngle = (float) Math.toRadians(colorHSV[0]);
+ int colorPointX = (int) (-Math.cos(hueAngle) * colorHSV[1] * colorWheelRadius) + centerX;
+ int colorPointY = (int) (-Math.sin(hueAngle) * colorHSV[1] * colorWheelRadius) + centerY;
+
+ float pointerRadius = 0.075f * colorWheelRadius;
+ int pointerX = (int) (colorPointX - pointerRadius / 2);
+ int pointerY = (int) (colorPointY - pointerRadius / 2);
+
+ colorPointerCoords.set(pointerX, pointerY, pointerX + pointerRadius, pointerY + pointerRadius);
+ canvas.drawOval(colorPointerCoords, colorPointerPaint);
+
+ // drawing value pointer
+
+ valuePointerPaint.setColor(Color.HSVToColor(new float[] { 0f, 0f, 1f - colorHSV[2] }));
+
+ double valueAngle = (colorHSV[2] - 0.5f) * Math.PI;
+ float valueAngleX = (float) Math.cos(valueAngle);
+ float valueAngleY = (float) Math.sin(valueAngle);
+
+ canvas.drawLine(valueAngleX * innerWheelRadius + centerX, valueAngleY * innerWheelRadius + centerY, valueAngleX * outerWheelRadius + centerX,
+ valueAngleY * outerWheelRadius + centerY, valuePointerPaint);
+
+ // drawing pointer arrow
+
+ if (arrowPointerSize > 0) {
+ drawPointerArrow(canvas);
+ }
+
+ }
+
+ private void drawPointerArrow(Canvas canvas) {
+
+ int centerX = getWidth() / 2;
+ int centerY = getHeight() / 2;
+
+ double tipAngle = (colorHSV[2] - 0.5f) * Math.PI;
+ double leftAngle = tipAngle + Math.PI / 96;
+ double rightAngle = tipAngle - Math.PI / 96;
+
+ double tipAngleX = Math.cos(tipAngle) * outerWheelRadius;
+ double tipAngleY = Math.sin(tipAngle) * outerWheelRadius;
+ double leftAngleX = Math.cos(leftAngle) * (outerWheelRadius + arrowPointerSize);
+ double leftAngleY = Math.sin(leftAngle) * (outerWheelRadius + arrowPointerSize);
+ double rightAngleX = Math.cos(rightAngle) * (outerWheelRadius + arrowPointerSize);
+ double rightAngleY = Math.sin(rightAngle) * (outerWheelRadius + arrowPointerSize);
+
+ arrowPointerPath.reset();
+ arrowPointerPath.moveTo((float) tipAngleX + centerX, (float) tipAngleY + centerY);
+ arrowPointerPath.lineTo((float) leftAngleX + centerX, (float) leftAngleY + centerY);
+ arrowPointerPath.lineTo((float) rightAngleX + centerX, (float) rightAngleY + centerY);
+ arrowPointerPath.lineTo((float) tipAngleX + centerX, (float) tipAngleY + centerY);
+
+ valuePointerArrowPaint.setColor(Color.HSVToColor(colorHSV));
+ valuePointerArrowPaint.setStyle(Style.FILL);
+ canvas.drawPath(arrowPointerPath, valuePointerArrowPaint);
+
+ valuePointerArrowPaint.setStyle(Style.STROKE);
+ valuePointerArrowPaint.setStrokeJoin(Join.ROUND);
+ valuePointerArrowPaint.setColor(Color.BLACK);
+ canvas.drawPath(arrowPointerPath, valuePointerArrowPaint);
+
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldw, int oldh) {
+
+ int centerX = width / 2;
+ int centerY = height / 2;
+
+ innerPadding = (int) (paramInnerPadding * width / 100);
+ outerPadding = (int) (paramOuterPadding * width / 100);
+ arrowPointerSize = (int) (paramArrowPointerSize * width / 100);
+ valueSliderWidth = (int) (paramValueSliderWidth * width / 100);
+
+ outerWheelRadius = width / 2 - outerPadding - arrowPointerSize;
+ innerWheelRadius = outerWheelRadius - valueSliderWidth;
+ colorWheelRadius = innerWheelRadius - innerPadding;
+
+ outerWheelRect.set(centerX - outerWheelRadius, centerY - outerWheelRadius, centerX + outerWheelRadius, centerY + outerWheelRadius);
+ innerWheelRect.set(centerX - innerWheelRadius, centerY - innerWheelRadius, centerX + innerWheelRadius, centerY + innerWheelRadius);
+
+ colorWheelBitmap = createColorWheelBitmap(colorWheelRadius * 2, colorWheelRadius * 2);
+
+ gradientRotationMatrix = new Matrix();
+ gradientRotationMatrix.preRotate(270, width / 2, height / 2);
+
+ colorViewPath.arcTo(outerWheelRect, 270, -180);
+ colorViewPath.arcTo(innerWheelRect, 90, 180);
+
+ valueSliderPath.arcTo(outerWheelRect, 270, 180);
+ valueSliderPath.arcTo(innerWheelRect, 90, -180);
+
+ }
+
+ private Bitmap createColorWheelBitmap(int width, int height) {
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+
+ int colorCount = 12;
+ int colorAngleStep = 360 / 12;
+ int colors[] = new int[colorCount + 1];
+ float hsv[] = new float[] { 0f, 1f, 1f };
+ for (int i = 0; i < colors.length; i++) {
+ hsv[0] = (i * colorAngleStep + 180) % 360;
+ colors[i] = Color.HSVToColor(hsv);
+ }
+ colors[colorCount] = colors[0];
+
+ SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
+ RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorWheelRadius, 0xFFFFFFFF, 0x00FFFFFF, TileMode.CLAMP);
+ ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
+
+ colorWheelPaint.setShader(composeShader);
+
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawCircle(width / 2, height / 2, colorWheelRadius, colorWheelPaint);
+
+ return bitmap;
+
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int cx = x - getWidth() / 2;
+ int cy = y - getHeight() / 2;
+ double d = Math.sqrt(cx * cx + cy * cy);
+
+ if (d <= colorWheelRadius) {
+
+ colorHSV[0] = (float) (Math.toDegrees(Math.atan2(cy, cx)) + 180f);
+ colorHSV[1] = Math.max(0f, Math.min(1f, (float) (d / colorWheelRadius)));
+
+ invalidate();
+
+ } else if (x >= getWidth() / 2 && d >= innerWheelRadius) {
+
+ colorHSV[2] = (float) Math.max(0, Math.min(1, Math.atan2(cy, cx) / Math.PI + 0.5f));
+
+ invalidate();
+ }
+
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ public void setColor(int color) {
+ Color.colorToHSV(color, colorHSV);
+ }
+
+ public int getColor() {
+ return Color.HSVToColor(colorHSV);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Bundle state = new Bundle();
+ state.putFloatArray("color", colorHSV);
+ state.putParcelable("super", super.onSaveInstanceState());
+ return state;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof Bundle) {
+ Bundle bundle = (Bundle) state;
+ colorHSV = bundle.getFloatArray("color");
+ super.onRestoreInstanceState(bundle.getParcelable("super"));
+ } else {
+ super.onRestoreInstanceState(state);
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/utils/ColorPickerDialog.java b/app/src/main/java/com/idlegandalf/ledd/utils/ColorPickerDialog.java
new file mode 100644
index 0000000..097d2f1
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/utils/ColorPickerDialog.java
@@ -0,0 +1,54 @@
+package com.idlegandalf.ledd.utils;
+
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.widget.RelativeLayout;
+import android.widget.RelativeLayout.LayoutParams;
+
+public class ColorPickerDialog extends AlertDialog {
+
+ private ColorPicker colorPickerView;
+ private final OnColorSelectedListener onColorSelectedListener;
+
+ public ColorPickerDialog(Context context, int initialColor, OnColorSelectedListener onColorSelectedListener) {
+ super(context);
+
+ this.onColorSelectedListener = onColorSelectedListener;
+
+ RelativeLayout relativeLayout = new RelativeLayout(context);
+ LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+
+ colorPickerView = new ColorPicker(context);
+ colorPickerView.setColor(initialColor);
+
+ relativeLayout.addView(colorPickerView, layoutParams);
+
+ setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok), onClickListener);
+ setButton(BUTTON_NEGATIVE, context.getString(android.R.string.cancel), onClickListener);
+
+ setView(relativeLayout);
+
+ }
+
+ private OnClickListener onClickListener = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case BUTTON_POSITIVE:
+ int selectedColor = colorPickerView.getColor();
+ onColorSelectedListener.onColorSelected(selectedColor);
+ break;
+ case BUTTON_NEGATIVE:
+ dialog.dismiss();
+ break;
+ }
+ }
+ };
+
+ public interface OnColorSelectedListener {
+ public void onColorSelected(int color);
+ }
+
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/utils/ColorPickerPreference.java b/app/src/main/java/com/idlegandalf/ledd/utils/ColorPickerPreference.java
new file mode 100644
index 0000000..576b0c0
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/utils/ColorPickerPreference.java
@@ -0,0 +1,73 @@
+package com.idlegandalf.ledd.utils;
+
+
+import android.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.RelativeLayout.LayoutParams;
+
+public class ColorPickerPreference extends DialogPreference {
+
+ public static final int DEFAULT_COLOR = Color.WHITE;
+
+ private int selectedColor;
+ private ColorPicker colorPickerView;
+
+ public ColorPickerPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected View onCreateDialogView() {
+
+ RelativeLayout relativeLayout = new RelativeLayout(getContext());
+ LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+
+ colorPickerView = new ColorPicker(getContext());
+ colorPickerView.setId(1);
+
+ relativeLayout.addView(colorPickerView, layoutParams);
+
+ return relativeLayout;
+
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+ colorPickerView.setColor(selectedColor);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ builder.setTitle(null); // remove dialog title to get more space for color picker
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ if (positiveResult && shouldPersist()) {
+ if (callChangeListener(colorPickerView.getColor())) {
+ selectedColor = colorPickerView.getColor();
+ persistInt(selectedColor);
+ }
+ }
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ selectedColor = restoreValue ? getPersistedInt(DEFAULT_COLOR) : (Integer) defaultValue;
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getInt(index, DEFAULT_COLOR);
+ }
+
+}
diff --git a/app/src/main/java/com/idlegandalf/ledd/utils/MultiColorPicker.java b/app/src/main/java/com/idlegandalf/ledd/utils/MultiColorPicker.java
new file mode 100644
index 0000000..b6a20fe
--- /dev/null
+++ b/app/src/main/java/com/idlegandalf/ledd/utils/MultiColorPicker.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2013 Piotr Adamus
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.idlegandalf.ledd.utils;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Join;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.RadialGradient;
+import android.graphics.RectF;
+import android.graphics.Shader.TileMode;
+import android.graphics.SweepGradient;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class MultiColorPicker extends View {
+
+ /**
+ * Customizable display parameters (in percents)
+ */
+ private final int paramOuterPadding = 2; // outer padding of the whole color picker view
+ private final int paramInnerPadding = 5; // distance between value slider wheel and inner color wheel
+ private final int paramValueSliderWidth = 10; // width of the value slider
+ private final int paramArrowPointerSize = 4; // size of the arrow pointer; set to 0 to hide the pointer
+
+ private final int paramColorCount = 5;
+ private final float paramHueSpreadAngle = 30f; // in degrees
+
+ private Paint colorWheelPaint;
+ private Paint valueSliderPaint;
+
+ private Paint colorViewPaint;
+
+ private Paint colorPointerPaint;
+ private RectF colorPointerCoords;
+
+ private Paint valuePointerPaint;
+ private Paint valuePointerArrowPaint;
+
+ private RectF outerWheelRect;
+ private RectF innerWheelRect;
+
+ private Path colorViewPath;
+ private Path valueSliderPath;
+ private Path arrowPointerPath;
+
+ private Bitmap colorWheelBitmap;
+
+ private int valueSliderWidth;
+ private int innerPadding;
+ private int outerPadding;
+
+ private int arrowPointerSize;
+ private int outerWheelRadius;
+ private int innerWheelRadius;
+ private int colorWheelRadius;
+
+ private Matrix gradientRotationMatrix;
+
+ /** Currently selected color */
+ private float[] colorHSV = new float[] { 0f, 0f, 1f };
+ private float[] adjacentHue = new float[paramColorCount];
+
+ public MultiColorPicker(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public MultiColorPicker(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public MultiColorPicker(Context context) {
+ super(context);
+ init();
+ }
+
+ private void init() {
+
+ colorPointerPaint = new Paint();
+ colorPointerPaint.setStyle(Style.STROKE);
+ colorPointerPaint.setStrokeWidth(2f);
+ colorPointerPaint.setARGB(128, 0, 0, 0);
+
+ valuePointerPaint = new Paint();
+ valuePointerPaint.setStyle(Style.STROKE);
+ valuePointerPaint.setStrokeWidth(2f);
+
+ valuePointerArrowPaint = new Paint();
+
+ colorWheelPaint = new Paint();
+ colorWheelPaint.setAntiAlias(true);
+ colorWheelPaint.setDither(true);
+
+ valueSliderPaint = new Paint();
+ valueSliderPaint.setAntiAlias(true);
+ valueSliderPaint.setDither(true);
+
+ colorViewPaint = new Paint();
+ colorViewPaint.setAntiAlias(true);
+
+ colorViewPath = new Path();
+ valueSliderPath = new Path();
+ arrowPointerPath = new Path();
+
+ outerWheelRect = new RectF();
+ innerWheelRect = new RectF();
+
+ colorPointerCoords = new RectF();
+
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ int size = Math.min(widthSize, heightSize);
+ setMeasuredDimension(size, size);
+ }
+
+ @SuppressLint("DrawAllocation")
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ int centerX = getWidth() / 2;
+ int centerY = getHeight() / 2;
+
+ // drawing color wheel
+
+ canvas.drawBitmap(colorWheelBitmap, centerX - colorWheelRadius, centerY - colorWheelRadius, null);
+
+ // drawing color view
+
+ int[] segmentColors = getColors();
+ float sweepAngleStep = 180f / paramColorCount;
+ for (int i = 0; i < paramColorCount; i++) {
+
+ colorViewPath.reset();
+ colorViewPath.arcTo(outerWheelRect, 270 - i * sweepAngleStep, -sweepAngleStep);
+ colorViewPath.arcTo(innerWheelRect, 90 + (paramColorCount - i - 1) * sweepAngleStep, sweepAngleStep);
+
+ colorViewPaint.setColor(segmentColors[i]);
+
+ canvas.drawPath(colorViewPath, colorViewPaint);
+
+ }
+
+ // drawing value slider
+
+ float[] hsv = new float[] { colorHSV[0], colorHSV[1], 1f };
+
+ SweepGradient sweepGradient = new SweepGradient(centerX, centerY, new int[] { Color.BLACK, Color.HSVToColor(hsv), Color.WHITE }, null);
+ sweepGradient.setLocalMatrix(gradientRotationMatrix);
+ valueSliderPaint.setShader(sweepGradient);
+
+ canvas.drawPath(valueSliderPath, valueSliderPaint);
+
+ // drawing color wheel pointer
+
+ for (int i = 0; i < paramColorCount; i++) {
+ drawColorWheelPointer(canvas, (float) Math.toRadians(adjacentHue[i]));
+ }
+
+ // drawing value pointer
+
+ valuePointerPaint.setColor(Color.HSVToColor(new float[] { 0f, 0f, 1f - colorHSV[2] }));
+
+ double valueAngle = (colorHSV[2] - 0.5f) * Math.PI;
+ float valueAngleX = (float) Math.cos(valueAngle);
+ float valueAngleY = (float) Math.sin(valueAngle);
+
+ canvas.drawLine(valueAngleX * innerWheelRadius + centerX, valueAngleY * innerWheelRadius + centerY, valueAngleX * outerWheelRadius + centerX,
+ valueAngleY * outerWheelRadius + centerY, valuePointerPaint);
+
+ // drawing pointer arrow
+
+ if (arrowPointerSize > 0) {
+ drawPointerArrow(canvas);
+ }
+
+ }
+
+ private void drawColorWheelPointer(Canvas canvas, float hueAngle) {
+
+ int centerX = getWidth() / 2;
+ int centerY = getHeight() / 2;
+
+ int colorPointX = (int) (-Math.cos(hueAngle) * colorHSV[1] * colorWheelRadius) + centerX;
+ int colorPointY = (int) (-Math.sin(hueAngle) * colorHSV[1] * colorWheelRadius) + centerY;
+
+ float pointerRadius = 0.075f * colorWheelRadius;
+ int pointerX = (int) (colorPointX - pointerRadius / 2);
+ int pointerY = (int) (colorPointY - pointerRadius / 2);
+
+ colorPointerCoords.set(pointerX, pointerY, pointerX + pointerRadius, pointerY + pointerRadius);
+ canvas.drawOval(colorPointerCoords, colorPointerPaint);
+
+ }
+
+ private void drawPointerArrow(Canvas canvas) {
+
+ int centerX = getWidth() / 2;
+ int centerY = getHeight() / 2;
+
+ double tipAngle = (colorHSV[2] - 0.5f) * Math.PI;
+ double leftAngle = tipAngle + Math.PI / 96;
+ double rightAngle = tipAngle - Math.PI / 96;
+
+ double tipAngleX = Math.cos(tipAngle) * outerWheelRadius;
+ double tipAngleY = Math.sin(tipAngle) * outerWheelRadius;
+ double leftAngleX = Math.cos(leftAngle) * (outerWheelRadius + arrowPointerSize);
+ double leftAngleY = Math.sin(leftAngle) * (outerWheelRadius + arrowPointerSize);
+ double rightAngleX = Math.cos(rightAngle) * (outerWheelRadius + arrowPointerSize);
+ double rightAngleY = Math.sin(rightAngle) * (outerWheelRadius + arrowPointerSize);
+
+ arrowPointerPath.reset();
+ arrowPointerPath.moveTo((float) tipAngleX + centerX, (float) tipAngleY + centerY);
+ arrowPointerPath.lineTo((float) leftAngleX + centerX, (float) leftAngleY + centerY);
+ arrowPointerPath.lineTo((float) rightAngleX + centerX, (float) rightAngleY + centerY);
+ arrowPointerPath.lineTo((float) tipAngleX + centerX, (float) tipAngleY + centerY);
+
+ valuePointerArrowPaint.setColor(Color.HSVToColor(colorHSV));
+ valuePointerArrowPaint.setStyle(Style.FILL);
+ canvas.drawPath(arrowPointerPath, valuePointerArrowPaint);
+
+ valuePointerArrowPaint.setStyle(Style.STROKE);
+ valuePointerArrowPaint.setStrokeJoin(Join.ROUND);
+ valuePointerArrowPaint.setColor(Color.BLACK);
+ canvas.drawPath(arrowPointerPath, valuePointerArrowPaint);
+
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldw, int oldh) {
+
+ int centerX = width / 2;
+ int centerY = height / 2;
+
+ innerPadding = (int) (paramInnerPadding * width / 100);
+ outerPadding = (int) (paramOuterPadding * width / 100);
+ arrowPointerSize = (int) (paramArrowPointerSize * width / 100);
+ valueSliderWidth = (int) (paramValueSliderWidth * width / 100);
+
+ outerWheelRadius = width / 2 - outerPadding - arrowPointerSize;
+ innerWheelRadius = outerWheelRadius - valueSliderWidth;
+ colorWheelRadius = innerWheelRadius - innerPadding;
+
+ outerWheelRect.set(centerX - outerWheelRadius, centerY - outerWheelRadius, centerX + outerWheelRadius, centerY + outerWheelRadius);
+ innerWheelRect.set(centerX - innerWheelRadius, centerY - innerWheelRadius, centerX + innerWheelRadius, centerY + innerWheelRadius);
+
+ colorWheelBitmap = createColorWheelBitmap(colorWheelRadius * 2, colorWheelRadius * 2);
+
+ gradientRotationMatrix = new Matrix();
+ gradientRotationMatrix.preRotate(270, width / 2, height / 2);
+
+ valueSliderPath.arcTo(outerWheelRect, 270, 180);
+ valueSliderPath.arcTo(innerWheelRect, 90, -180);
+
+ }
+
+ private Bitmap createColorWheelBitmap(int width, int height) {
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+
+ int colorCount = 12;
+ int colorAngleStep = 360 / 12;
+ int colors[] = new int[colorCount + 1];
+ float hsv[] = new float[] { 0f, 1f, 1f };
+ for (int i = 0; i < colors.length; i++) {
+ hsv[0] = (i * colorAngleStep + 180) % 360;
+ colors[i] = Color.HSVToColor(hsv);
+ }
+ colors[colorCount] = colors[0];
+
+ SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
+ RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorWheelRadius, 0xFFFFFFFF, 0x00FFFFFF, TileMode.CLAMP);
+ ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
+
+ colorWheelPaint.setShader(composeShader);
+
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawCircle(width / 2, height / 2, colorWheelRadius, colorWheelPaint);
+
+ return bitmap;
+
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int cx = x - getWidth() / 2;
+ int cy = y - getHeight() / 2;
+ double d = Math.sqrt(cx * cx + cy * cy);
+
+ if (d <= colorWheelRadius) {
+
+ colorHSV[0] = (float) (Math.toDegrees(Math.atan2(cy, cx)) + 180f);
+ colorHSV[1] = Math.max(0f, Math.min(1f, (float) (d / colorWheelRadius)));
+
+ updateAdjacentHue();
+ invalidate();
+
+ } else if (x >= getWidth() / 2 && d >= innerWheelRadius) {
+
+ colorHSV[2] = (float) Math.max(0, Math.min(1, Math.atan2(cy, cx) / Math.PI + 0.5f));
+
+ updateAdjacentHue();
+ invalidate();
+
+ }
+
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+
+ private void updateAdjacentHue() {
+
+ for (int i = 0; i < paramColorCount; i++) {
+ adjacentHue[i] = (colorHSV[0] - paramHueSpreadAngle * (paramColorCount / 2 - i)) % 360.0f;
+ adjacentHue[i] = (adjacentHue[i] < 0) ? adjacentHue[i] + 360f : adjacentHue[i];
+ }
+ adjacentHue[paramColorCount / 2] = colorHSV[0];
+
+ }
+
+ public void setColor(int color) {
+ Color.colorToHSV(color, colorHSV);
+ updateAdjacentHue();
+ }
+
+ public int getColor() {
+ return Color.HSVToColor(colorHSV);
+ }
+
+ public int[] getColors() {
+ int[] colors = new int[paramColorCount];
+ float[] hsv = new float[3];
+ for (int i = 0; i < paramColorCount; i++) {
+ hsv[0] = adjacentHue[i];
+ hsv[1] = colorHSV[1];
+ hsv[2] = colorHSV[2];
+ colors[i] = Color.HSVToColor(hsv);
+ }
+ return colors;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Bundle state = new Bundle();
+ state.putFloatArray("color", colorHSV);
+ state.putParcelable("super", super.onSaveInstanceState());
+ return state;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof Bundle) {
+ Bundle bundle = (Bundle) state;
+ colorHSV = bundle.getFloatArray("color");
+ updateAdjacentHue();
+ super.onRestoreInstanceState(bundle.getParcelable("super"));
+ } else {
+ super.onRestoreInstanceState(state);
+ }
+ }
+
+}
diff --git a/app/src/main/res/drawable-hdpi/drawer_shadow.9.png b/app/src/main/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..236bff5
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/drawer_shadow.9.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_settings.png b/app/src/main/res/drawable-hdpi/ic_action_settings.png
new file mode 100644
index 0000000..0eb78f7
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_settings.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_drawer.png b/app/src/main/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 0000000..c59f601
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..36b4a5d
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-mdpi/drawer_shadow.9.png b/app/src/main/res/drawable-mdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..ffe3a28
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/drawer_shadow.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_settings.png b/app/src/main/res/drawable-mdpi/ic_action_settings.png
new file mode 100644
index 0000000..c290e59
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_settings.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_drawer.png b/app/src/main/res/drawable-mdpi/ic_drawer.png
new file mode 100644
index 0000000..1ed2c56
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..9ce85cf
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_launcher.png b/app/src/main/res/drawable-nodpi/ic_launcher.png
new file mode 100644
index 0000000..90c0f0b
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png b/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..fabe9d9
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_settings.png b/app/src/main/res/drawable-xhdpi/ic_action_settings.png
new file mode 100644
index 0000000..999d0f0
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_settings.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer.png b/app/src/main/res/drawable-xhdpi/ic_drawer.png
new file mode 100644
index 0000000..a5fa74d
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..fca8186
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png b/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..b91e9d7
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_settings.png b/app/src/main/res/drawable-xxhdpi/ic_action_settings.png
new file mode 100644
index 0000000..530227e
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_settings.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer.png b/app/src/main/res/drawable-xxhdpi/ic_drawer.png
new file mode 100644
index 0000000..9c4685d
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_drawer.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..0ae19d6
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable/dimme.xml b/app/src/main/res/drawable/dimme.xml
new file mode 100644
index 0000000..dbc433c
--- /dev/null
+++ b/app/src/main/res/drawable/dimme.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/layout/act_demo.xml b/app/src/main/res/layout/act_demo.xml
new file mode 100644
index 0000000..31ed633
--- /dev/null
+++ b/app/src/main/res/layout/act_demo.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/act_multi_picker.xml b/app/src/main/res/layout/act_multi_picker.xml
new file mode 100644
index 0000000..d064d56
--- /dev/null
+++ b/app/src/main/res/layout/act_multi_picker.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/act_picker.xml b/app/src/main/res/layout/act_picker.xml
new file mode 100644
index 0000000..c7dc3d1
--- /dev/null
+++ b/app/src/main/res/layout/act_picker.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/act_pref.xml b/app/src/main/res/layout/act_pref.xml
new file mode 100644
index 0000000..856cb65
--- /dev/null
+++ b/app/src/main/res/layout/act_pref.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_asistent.xml b/app/src/main/res/layout/activity_asistent.xml
new file mode 100644
index 0000000..5790325
--- /dev/null
+++ b/app/src/main/res/layout/activity_asistent.xml
@@ -0,0 +1,4 @@
+
diff --git a/app/src/main/res/layout/activity_color.xml b/app/src/main/res/layout/activity_color.xml
new file mode 100644
index 0000000..eb62b8e
--- /dev/null
+++ b/app/src/main/res/layout/activity_color.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/drawer_fragment.xml b/app/src/main/res/layout/drawer_fragment.xml
new file mode 100644
index 0000000..7cecb70
--- /dev/null
+++ b/app/src/main/res/layout/drawer_fragment.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_step1.xml b/app/src/main/res/layout/fragment_step1.xml
new file mode 100644
index 0000000..544b2a6
--- /dev/null
+++ b/app/src/main/res/layout/fragment_step1.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_welcome.xml b/app/src/main/res/layout/fragment_welcome.xml
new file mode 100644
index 0000000..0760cfc
--- /dev/null
+++ b/app/src/main/res/layout/fragment_welcome.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/host_row.xml b/app/src/main/res/layout/host_row.xml
new file mode 100644
index 0000000..0cd6514
--- /dev/null
+++ b/app/src/main/res/layout/host_row.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/progress.xml b/app/src/main/res/layout/progress.xml
new file mode 100644
index 0000000..b3b82b5
--- /dev/null
+++ b/app/src/main/res/layout/progress.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/color.xml b/app/src/main/res/menu/color.xml
new file mode 100644
index 0000000..3ea7faf
--- /dev/null
+++ b/app/src/main/res/menu/color.xml
@@ -0,0 +1,10 @@
+
diff --git a/app/src/main/res/menu/step1.xml b/app/src/main/res/menu/step1.xml
new file mode 100644
index 0000000..d56f59d
--- /dev/null
+++ b/app/src/main/res/menu/step1.xml
@@ -0,0 +1,4 @@
+
diff --git a/app/src/main/res/menu/welcome.xml b/app/src/main/res/menu/welcome.xml
new file mode 100644
index 0000000..4e7b289
--- /dev/null
+++ b/app/src/main/res/menu/welcome.xml
@@ -0,0 +1,3 @@
+
diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..44f01db
--- /dev/null
+++ b/app/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/values-sw720dp-land/dimens.xml b/app/src/main/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..61e3fa8
--- /dev/null
+++ b/app/src/main/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,9 @@
+
+
+
+ 128dp
+
+
diff --git a/app/src/main/res/values-v11/styles.xml b/app/src/main/res/values-v11/styles.xml
new file mode 100644
index 0000000..4943f36
--- /dev/null
+++ b/app/src/main/res/values-v11/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values-v14/styles.xml b/app/src/main/res/values-v14/styles.xml
new file mode 100644
index 0000000..0fe4dbc
--- /dev/null
+++ b/app/src/main/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..cbfe349
--- /dev/null
+++ b/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..55c1e59
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,7 @@
+
+
+
+ 16dp
+ 16dp
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..6c6d634
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,9 @@
+
+
+
+ LedD
+ OK
+ Pick a color
+ DialogPreference with Color Picker.
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..57b9e98
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml
new file mode 100644
index 0000000..90b8390
--- /dev/null
+++ b/app/src/main/res/xml/settings.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..1b7886d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,19 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..5d08ba7
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Settings specified in this file will override any Gradle settings
+# configured through the IDE.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b0dff85
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Nov 30 16:28:30 CET 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000..cac7bd5
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1 @@
+lombok.addGeneratedAnnotation = false
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'