فهرست منبع

slowly migrate to a redux style

David 9 سال پیش
والد
کامیت
839b1bf2b3

+ 14 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,14 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
+      <option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
+    </inspection_tool>
+    <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
+      <option name="processCode" value="true" />
+      <option name="processLiterals" value="true" />
+      <option name="processComments" value="true" />
+    </inspection_tool>
+  </profile>
+</component>

+ 7 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,7 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="PROJECT_PROFILE" value="Project Default" />
+    <option name="USE_PROJECT_PROFILE" value="true" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 2 - 0
README.md

@@ -40,6 +40,8 @@ Supported Actions (and their parameters):
 	- <Int, Int> Stream (Ring, Alarm, Music, Notification, System), Volume in %
 - State:
 	- <String, Object>, set a state key to a value. Will trigger the 'State' special event if the value changed.
+- Media key:
+	- String (key) { KEYCODE\_MEDIA\_PREVIOUS, KEYCODE\_MEDIA\_NEXT, KEYCODE\_MEDIA\_PAUSE, KEYCODE\_MEDIA\_PLAY, KEYCODE\_MEDIA\_PLAY\_PAUSE, KEYCODE\_MEDIA\_STOP }
 
 
 For example you could do something like:

+ 1 - 1
app/src/main/AndroidManifest.xml

@@ -10,7 +10,7 @@
     <uses-permission android:name="android.permission.READ_SMS" />
     <uses-permission android:name="android.permission.SEND_SMS" />
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
-    <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
 
     <application

+ 32 - 6
app/src/main/java/com/example/david/libretasker/Actions/Actions.java

@@ -8,29 +8,45 @@ import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.support.v4.app.NotificationCompat;
+import android.view.KeyEvent;
 import android.widget.Toast;
 
 import com.example.david.libretasker.Events.Event;
 import com.example.david.libretasker.R;
-import com.example.david.libretasker.State.State;
+import com.example.david.libretasker.Store.Store;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 
 import static android.content.Context.NOTIFICATION_SERVICE;
+import static android.view.KeyEvent.KEYCODE_MEDIA_NEXT;
+import static android.view.KeyEvent.KEYCODE_MEDIA_PAUSE;
+import static android.view.KeyEvent.KEYCODE_MEDIA_PLAY;
+import static android.view.KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
+import static android.view.KeyEvent.KEYCODE_MEDIA_PREVIOUS;
+import static android.view.KeyEvent.KEYCODE_MEDIA_STOP;
 
 /**
  * Created by david on 01/01/17.
  */
 
 public class Actions {
+    static final List<Integer> MediaKeys = Arrays.asList(
+            KEYCODE_MEDIA_PREVIOUS,
+            KEYCODE_MEDIA_NEXT,
+            KEYCODE_MEDIA_PLAY,
+            KEYCODE_MEDIA_PAUSE,
+            KEYCODE_MEDIA_STOP,
+            KEYCODE_MEDIA_PLAY_PAUSE);
+
     public static ArrayList<String> list() {
         ArrayList<String> ret = new ArrayList<>();
         ret.add("Toast");
         ret.add("Notification");
         ret.add("Beep");
-        ret.add("State");
+        ret.add("Store");
         /*
          * Display: Lock, Auto brightness
          * Media: Music Play | skip | stop, Record audio, Record audio stop, Take Photo
@@ -67,13 +83,14 @@ public class Actions {
                 Ringtone r = RingtoneManager.getRingtone(context.getApplicationContext(), notification);
                 r.play();
                 return;
-            case "State":
+            case "Store":
                 HashMap<String, Object> s = (HashMap<String, Object>)payload;
-                if (State.set((String) s.get("Key"), s.get("Value"))) { //Did the state change? Trigger events
+                /*if (Store.dispatch ((String) s.get("Key"), s.get("Value"))) { //Did the state change? Trigger events
                     Intent i = new Intent(Event.STATE_CHANGED);
-                    i.setData(Uri.parse("State"));
+                    i.setData(Uri.parse("Store"));
                     context.sendBroadcast(i);
-                }
+                }*/
+                //FIXME
                 return;
             case "Volume":
                 HashMap<String, Integer> v = (HashMap<String, Integer>)payload;
@@ -98,6 +115,15 @@ public class Actions {
                 audio.setStreamVolume(streamType, finalVolume, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                 //audio.setStreamVolume(streamType, finalVolume, AudioManager.FLAG_SHOW_UI);
                 return;
+            case "MediaKey":
+                if ( !MediaKeys.contains((int)payload) )
+                    throw new AssertionError("Payload is not a valid media keycode");
+
+                KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, (int)payload);
+                Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+                intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+                context.sendBroadcast(intent);
+                return;
             default:
                 Toast.makeText(context, name + " " + (String)payload, Toast.LENGTH_SHORT).show();
                 return;

+ 0 - 91
app/src/main/java/com/example/david/libretasker/EventReceiver.java

@@ -1,91 +0,0 @@
-package com.example.david.libretasker;
-
-import android.telephony.TelephonyManager;
-import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.provider.Telephony;
-import android.telephony.SmsMessage;
-import android.util.Log;
-
-import com.example.david.libretasker.Actions.Actions;
-import com.example.david.libretasker.Events.Event;
-import com.example.david.libretasker.Events.NetworkEvent;
-import com.example.david.libretasker.Events.PhoneEvent;
-import com.example.david.libretasker.Events.SMSEvent;
-import com.example.david.libretasker.Events.ScreenEvent;
-import com.example.david.libretasker.Events.StateEvent;
-import com.example.david.libretasker.Events.TimeEvent;
-import com.example.david.libretasker.UserData.UserData;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * Created by david on 01/01/17.
- */
-
-public class EventReceiver extends BroadcastReceiver {
-    String TAG = " EventReceiver";
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Event e = null;
-        HashMap<String, Object> data = new HashMap<>();
-        HashMap<String, Object> eventData;
-        ArrayList<HashMap<String, Object>> actions;
-
-        switch (intent.getAction()) {
-            case ConnectivityManager.CONNECTIVITY_ACTION:
-                e = new NetworkEvent();
-                data = UserData.getData("Network");
-                break;
-            case Intent.ACTION_TIME_TICK:
-            case Intent.ACTION_TIME_CHANGED:
-            case Intent.ACTION_TIMEZONE_CHANGED:
-                e = new TimeEvent();
-                data = UserData.getData("DateTime");
-                break;
-
-            case Intent.ACTION_SCREEN_ON:
-            case Intent.ACTION_SCREEN_OFF:
-                e = new ScreenEvent();
-                data = UserData.getData("Screen");
-                break;
-            case Telephony.Sms.Intents.SMS_RECEIVED_ACTION:
-                e = new SMSEvent();
-                data = UserData.getData("SMS");
-                break;
-            case Event.STATE_CHANGED:
-                e = new StateEvent();
-                String s = intent.getDataString();
-
-                break;
-            case TelephonyManager.ACTION_PHONE_STATE_CHANGED:
-                e = new PhoneEvent();
-                data = UserData.getData("PhoneCall");
-                break;
-            default:
-                Log.d(TAG, intent.getAction());
-                return;
-        }
-
-        if ( e == null )
-            return;
-
-        if ( data == null )
-            throw new AssertionError("Data is null! WTF!");
-
-        actions = (ArrayList<HashMap<String, Object>>)data.get("Actions");
-        eventData = (HashMap<String, Object>)data.get("Event");
-        eventData.put("Intent", intent);
-        if (e.trigger(context, eventData)) {
-            for (HashMap<String, Object> a : actions) {
-                Actions.Launch(context, (String) a.get("Type"), a.get("Value"));
-            }
-        }
-    }
-}

+ 49 - 0
app/src/main/java/com/example/david/libretasker/EventReducer.java

@@ -0,0 +1,49 @@
+package com.example.david.libretasker;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.provider.Telephony;
+import android.telephony.TelephonyManager;
+
+import com.example.david.libretasker.State.Network;
+import com.example.david.libretasker.State.Time;
+import com.example.david.libretasker.Store.Store;
+
+import java.util.HashMap;
+
+/**
+ * Created by david on 04/01/17.
+ */
+
+public class EventReducer {
+    public static HashMap<String, Object> reduce(HashMap<String, Object> oldState, HashMap<String, Object> action, Context context) {
+        String type = (String)action.get("type");
+        //HashMap<String, Object> payload = (HashMap<String, Object>)action.get("payload");
+
+        HashMap<String, Object> newState = new HashMap<>(oldState);
+        //FIXME: Reducer might be part of the store
+        switch (type) {
+            case ConnectivityManager.CONNECTIVITY_ACTION:
+                Network n = new Network(context);
+                newState.put("Network", n);
+                break;
+            case Intent.ACTION_TIME_TICK:
+            case Intent.ACTION_TIME_CHANGED:
+            case Intent.ACTION_TIMEZONE_CHANGED:
+                Time t = new Time();
+                newState.put("Time", t);
+                break;
+
+            case Intent.ACTION_SCREEN_ON:
+            case Intent.ACTION_SCREEN_OFF:
+                break;
+            case Telephony.Sms.Intents.SMS_RECEIVED_ACTION:
+                break;
+            case TelephonyManager.ACTION_PHONE_STATE_CHANGED:
+                break;
+        }
+
+        return newState;
+    }
+}

+ 5 - 48
app/src/main/java/com/example/david/libretasker/Events/NetworkEvent.java

@@ -7,6 +7,8 @@ import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.widget.Toast;
 
+import com.example.david.libretasker.State.Network;
+
 import java.util.HashMap;
 import java.util.List;
 
@@ -22,54 +24,9 @@ public class NetworkEvent implements Event {
     @Override
     public boolean trigger(Context context, HashMap<String, Object> data) {
 
-        assert (data.get("type") != null);
-        failureReason = "";
-        Connection c = new Connection();
-        c.state = (NetworkInfo.State)data.get("state");
-        c.type = (int) data.get("type");
-        c.ValidSSIDs =(List<String>)data.get("SSIDs");
-
-
-        ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        NetworkInfo mActive = conn.getActiveNetworkInfo();
-
-        if (mActive == null) {
-            failureReason = "No active connection";
-            return false;
-        }
-
-        if (c.state != null && c.state != mActive.getState()) {
-            failureReason = "Active conn state != requested state";
-            return false;
-        }
-
-        if (mActive.getType() != c.type) {
-            failureReason = "Active conn type != requested";
-            return false;
-        }
-
-        if (c.ValidSSIDs != null && c.ValidSSIDs.size() > 0 && c.type == ConnectivityManager.TYPE_WIFI){
-            WifiManager wifiManager = (WifiManager) context.getSystemService (Context.WIFI_SERVICE);
-            WifiInfo info = wifiManager.getConnectionInfo ();
-            String ssid  = info.getSSID(); //Returns "ssid"
-            ssid = ssid.substring(1, ssid.length()-1);
-            if (!c.ValidSSIDs.contains(ssid)) {
-                failureReason = "Active ssid != requested";
-                return false;
-            }
-        }
-
-        return true;
-
-    }
-
-    /**
-     * Created by david on 01/01/17.
-     */
+        Network target = new Network(data);
+        Network state = new Network(context);
 
-    static class Connection {
-        public int type;
-        public List<String> ValidSSIDs;
-        public NetworkInfo.State state;
+        return (target.equals(state));
     }
 }

+ 1 - 1
app/src/main/java/com/example/david/libretasker/Events/PhoneEvent.java

@@ -27,7 +27,7 @@ public class PhoneEvent implements Event {
         }
 
         String state = bundle.getString(TelephonyManager.EXTRA_STATE);
-        if (hm.get("State") != null && !state.equals(hm.get("State"))) {
+        if (hm.get("Store") != null && !state.equals(hm.get("Store"))) {
             failureReason = "States don't match";
             return false;
         }

+ 0 - 22
app/src/main/java/com/example/david/libretasker/Events/StateEvent.java

@@ -1,22 +0,0 @@
-package com.example.david.libretasker.Events;
-
-import android.content.Context;
-
-import java.util.HashMap;
-
-/**
- * Created by david on 03/01/17.
- */
-
-public class StateEvent implements Event {
-    @Override
-    public boolean trigger(Context ctx, HashMap<String, Object> hm) {
-
-        return true;
-    }
-
-    @Override
-    public String getFailureReason() {
-        return null;
-    }
-}

+ 0 - 61
app/src/main/java/com/example/david/libretasker/Events/TimeEvent.java

@@ -1,61 +0,0 @@
-package com.example.david.libretasker.Events;
-
-import android.content.Context;
-
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Created by david on 02/01/17.
- */
-
-public class TimeEvent implements Event {
-    private String failureReason = "";
-    @Override
-    public String getFailureReason() {
-        return failureReason;
-    }
-    @Override
-    public boolean trigger(Context ctx, HashMap<String, Object> hm) {
-        Calendar c = Calendar.getInstance();
-        List<Integer> months = (List<Integer>) hm.get("Month");
-        List<Integer> DOW = (List<Integer>) hm.get("DOW");
-        List<Integer> DOM = (List<Integer>) hm.get("DOM");
-        List<Integer> hours= (List<Integer>) hm.get("Hour");
-        List<Integer> minutes= (List<Integer>) hm.get("Minute");
-
-        boolean invertHour = (boolean) ( hm.get("InvertHour") == null ? false: hm.get("InvertHour") );
-        boolean invertMinute = (boolean) ( hm.get("InvertMinute") == null ? false: hm.get("InvertMinute") );
-
-        if (months != null && !months.contains(c.get(Calendar.MONTH))) {
-            failureReason = "Month != requested";
-            return false;
-        }
-
-        if (DOW != null && !DOW.contains(c.get(Calendar.DAY_OF_WEEK))) {
-            failureReason = "DOW != requested";
-            return false;
-        }
-
-        if (DOM != null && !DOM.contains(c.get(Calendar.DAY_OF_MONTH))) {
-            failureReason = "DOM!= requested";
-            return false;
-        }
-
-        if (hours != null && !hours.contains(c.get(c.HOUR_OF_DAY))) { //HOUR == 12, HOUR_OF_DAY == 24
-            if (!invertHour) {
-                failureReason = "Hour != requested, invertHour==false";
-                return false;
-            }
-        }
-
-        if (minutes != null && !minutes.contains(c.get(Calendar.MINUTE)))
-            if ( !invertMinute ) {
-                failureReason = "Minute != requested, invertMinute == false ";
-                return false;
-            }
-
-        return true;
-    }
-}

+ 3 - 3
app/src/main/java/com/example/david/libretasker/MainActivity.java

@@ -9,8 +9,6 @@ import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.ListView;
 
-import com.example.david.libretasker.Actions.Actions;
-
 import java.util.ArrayList;
 
 
@@ -26,7 +24,6 @@ public class MainActivity extends AppCompatActivity {
         fab.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
-                //Actions.Launch(getApplicationContext(), "Toast", "Clicked");
                 Intent i = new Intent(getApplicationContext(), NewEventActivity.class);
                 startActivity(i);
             }
@@ -57,6 +54,9 @@ public class MainActivity extends AppCompatActivity {
             }
 
         });
+
+
+
         startService(new Intent(this, MainService.class));
     }
 

+ 35 - 0
app/src/main/java/com/example/david/libretasker/MainService.java

@@ -1,6 +1,7 @@
 package com.example.david.libretasker;
 
 import android.app.Service;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -10,7 +11,12 @@ import android.support.annotation.Nullable;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import com.example.david.libretasker.Actions.Actions;
 import com.example.david.libretasker.Events.Event;
+import com.example.david.libretasker.Store.Store;
+
+import java.util.ArrayList;
+import java.util.HashMap;
 
 /**
  * Created by david on 01/01/17.
@@ -19,10 +25,28 @@ import com.example.david.libretasker.Events.Event;
 public class MainService extends Service {
     private static final String TAG = "LibreTasker";
 
+    private String test;
     private static final String BCAST_CONFIGCHANGED = "android.intent.action.CONFIGURATION_CHANGED";
     private static Context mContext;
     private EventReceiver receiver = new EventReceiver();
 
+    private Runnable r = new Runnable() {
+        @Override
+        public void run() {
+            HashMap<String, Object> data = new HashMap<>();
+            HashMap<String, Object> eventData = Store.getState();
+            ArrayList<HashMap<String, Object>> actions;
+            test = "asd";
+            /*actions = (ArrayList<HashMap<String, Object>>)data.get("Actions");
+            eventData = (HashMap<String, Object>)data.get("Event");
+            eventData.put("Intent", intent);
+            if (e.trigger(context, eventData)) {
+                for (HashMap<String, Object> a : actions) {
+                    Actions.Launch(context, (String) a.get("Type"), a.get("Value"));
+                }
+            }*/
+        }
+    };
 
     @Override
     public void onCreate() {
@@ -31,6 +55,8 @@ public class MainService extends Service {
         mContext = this;
         receiver = new EventReceiver();
 
+        Store.subscribe(r);
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(Intent.ACTION_TIME_TICK);
@@ -62,4 +88,13 @@ public class MainService extends Service {
         return null;
     }
 
+    class EventReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            HashMap<String, Object> action = new HashMap<>();
+            action.put("type", intent.getAction());
+            Store.dispatch(action, context);
+        }
+    }
+
 }

+ 78 - 0
app/src/main/java/com/example/david/libretasker/State/Network.java

@@ -0,0 +1,78 @@
+package com.example.david.libretasker.State;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Created by david on 04/01/17.
+ */
+
+public class Network {
+    private Boolean active;
+    private Integer type = null;
+    private List<String> ValidSSIDs = null;
+    private NetworkInfo.State state = null;
+    private String SSID = null;
+
+    public Network(Context context) {
+        ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo mActive = conn.getActiveNetworkInfo();
+
+        active = (mActive != null);
+        if (mActive == null)
+            return;
+
+        state =  mActive.getState();
+        type = mActive.getType();
+        if (type != ConnectivityManager.TYPE_WIFI) {
+            ValidSSIDs = null;
+            SSID = null;
+        } else {
+            WifiManager wifiManager = (WifiManager) context.getSystemService (Context.WIFI_SERVICE);
+            WifiInfo info = wifiManager.getConnectionInfo ();
+            String ssid  = info.getSSID(); //Returns "ssid"
+            SSID = ssid.substring(1, ssid.length()-1);
+        }
+    }
+
+    public Network(HashMap<String, Object> data) {
+        if (data.get("type") == null)
+            throw new AssertionError("Type can't be null");
+
+        active = (data.get("active") != null && (boolean)data.get("active"));
+        state = (NetworkInfo.State)data.get("state");
+        type = (int) data.get("type");
+        ValidSSIDs =(List<String>)data.get("SSIDs");
+    }
+
+    @Override
+    public boolean equals(Object obj) { // 'this' => Target state, with ssid list, null fields, etc.
+        if (obj.getClass() != Network.class)
+            return false;
+        Network currentState = (Network)obj;
+
+        if (active != null && (active != currentState.active))
+            return false;
+
+        if ( state != null && (state != currentState.state))
+            return false;
+
+        if ( type == null )
+            return true;
+
+        if (!type.equals(currentState.type))
+            return false;
+
+        if ( type == ConnectivityManager.TYPE_WIFI )
+            if (ValidSSIDs != null && (ValidSSIDs.contains(currentState.SSID)))
+                return false;
+
+        return true;
+    }
+}

+ 0 - 31
app/src/main/java/com/example/david/libretasker/State/State.java

@@ -1,31 +0,0 @@
-package com.example.david.libretasker.State;
-
-import android.support.v4.util.Pair;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Set;
-
-/**
- * Created by david on 03/01/17.
- */
-
-public class State {
-    private static HashMap<String, Object> state = new HashMap<>();
-    public static Object get(String key){
-        return state.get(key);
-    }
-
-    public static boolean set(String key, Object val) {
-        if( state.get(key) == val ) //FIXME: Equals?
-            return false;
-
-        state.put(key, val);
-        return true;
-
-    }
-
-    public static HashMap<String, Object> getState() {
-        return state;
-    }
-}

+ 62 - 0
app/src/main/java/com/example/david/libretasker/State/Time.java

@@ -0,0 +1,62 @@
+package com.example.david.libretasker.State;
+
+import android.content.Context;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Created by david on 04/01/17.
+ */
+
+public class Time {
+    private List<Integer> months, DOW, DOM, hours, minutes = null;
+    private Boolean invertHour, invertMinute = null;
+    private int dow, dom, hour, minute, month = -1;
+
+    public Time(){
+        Calendar c = Calendar.getInstance();
+        month   = c.get(Calendar.MONTH);
+        dow     = c.get(Calendar.DAY_OF_WEEK);
+        dom     = c.get(Calendar.DAY_OF_MONTH);
+        hour    = c.get(Calendar.HOUR_OF_DAY);
+        minute  = c.get(Calendar.MINUTE);
+    }
+
+    public Time(HashMap<String, Object> hm) {
+
+        months  = (List<Integer>) hm.get("Month");
+        DOW     = (List<Integer>) hm.get("DOW");
+        DOM     = (List<Integer>) hm.get("DOM");
+        hours   = (List<Integer>) hm.get("Hour");
+        minutes = (List<Integer>) hm.get("Minute");
+
+        invertHour = (boolean) (hm.get("InvertHour") == null ? false : hm.get("InvertHour"));
+        invertMinute = (boolean) (hm.get("InvertMinute") == null ? false : hm.get("InvertMinute"));
+    }
+
+    public boolean equals(Object o) {
+        if (o.getClass() != Time.class)
+            return false;
+        Time other = (Time)o;
+
+        if (months != null && !months.contains(month))
+            return false;
+
+        if (DOW != null && !DOW.contains(dow))
+            return false;
+
+        if (DOM != null && !DOM.contains(dom))
+            return false;
+
+        if (hours != null && !hours.contains(hour))
+            if (!invertHour)
+                return false;
+
+        if (minutes != null && !minutes.contains(minute))
+            if (!invertMinute)
+                return false;
+        return true;
+    }
+}

+ 48 - 0
app/src/main/java/com/example/david/libretasker/Store/Store.java

@@ -0,0 +1,48 @@
+package com.example.david.libretasker.Store;
+
+import android.content.Context;
+
+import com.example.david.libretasker.EventReducer;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Created by david on 03/01/17.
+ */
+
+public class Store {
+    private static HashMap<String, Object> state = new HashMap<>();
+    private static ArrayList<Runnable> listeners = new ArrayList<>();
+
+    public static Object get(String key){
+        return state.get(key);
+    }
+
+    public static void dispatch(HashMap<String, Object> val, Context context) {
+        String key = (String)val.get("type");
+        if( key == null )
+            return;
+
+        if ( state.get(key) != null && state.get(key).equals(val) )
+            return;
+
+        state = EventReducer.reduce(state, val, context);
+
+        for ( Runnable r : listeners )
+            r.run();
+
+    }
+
+    public static HashMap<String, Object> getState() {
+        return state;
+    }
+
+    public static void subscribe(Runnable r){
+        listeners.add(r);
+    }
+
+    public static void unsuscribe(Runnable r){
+        listeners.remove(r);
+    }
+}