Browse Source

提交类型 编写功能
提交内容 编写日程提交代码

Bitliker 7 years ago
parent
commit
e13491b5d3
38 changed files with 3745 additions and 46 deletions
  1. 0 3
      WeiChat/src/main/java/com/xzjmyk/pm/activity/ui/erp/adapter/oa/OACalenderViewPagerAdapter.java
  2. 7 1
      WeiChat/src/main/java/com/xzjmyk/pm/activity/ui/me/MeFragment.java
  3. 10 0
      app_core/common/src/main/assets/work_menu.json
  4. 10 0
      app_core/common/src/main/res/drawable/bg_circular_bule.xml
  5. 1 0
      app_core/common/src/main/res/values/colors.xml
  6. 2 0
      app_core/common/src/main/res/values/strings.xml
  7. 210 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/AnimationHandler.java
  8. 22 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/AnimationListener.java
  9. 25 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/AnimatorListener.java
  10. 50 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/CollapsingAnimation.java
  11. 1002 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/CompactCalendarController.java
  12. 440 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/CompactCalendarView.java
  13. 52 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/Events.java
  14. 158 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/EventsContainer.java
  15. 175 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/Lunar.java
  16. 1 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/README.md
  17. 37 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/WeekUtils.java
  18. 14 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/comparators/EventComparator.java
  19. 65 0
      app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/domain/Event.java
  20. 31 0
      app_modular/apputils/src/main/res/values/attrs.xml
  21. 5 0
      app_modular/apputils/src/main/res/values/dimens.xml
  22. 12 0
      app_modular/apputils/src/main/res/values/strings.xml
  23. 39 41
      app_modular/appworks/src/main/AndroidManifest.xml
  24. 1 1
      app_modular/appworks/src/main/java/com/uas/appworks/CRM/erp/activity/DeviceDataFormAddActivity.java
  25. 169 0
      app_modular/appworks/src/main/java/com/uas/appworks/activity/ScheduleActivity.java
  26. 38 0
      app_modular/appworks/src/main/java/com/uas/appworks/activity/ScheduleSearchActivity.java
  27. 204 0
      app_modular/appworks/src/main/java/com/uas/appworks/activity/SchedulerCreateActivity.java
  28. 69 0
      app_modular/appworks/src/main/java/com/uas/appworks/adapter/SchedulerAdapter.java
  29. 135 0
      app_modular/appworks/src/main/java/com/uas/appworks/model/Schedule.java
  30. 224 0
      app_modular/appworks/src/main/java/com/uas/appworks/utils/ScheduleUtils.java
  31. 54 0
      app_modular/appworks/src/main/java/com/uas/appworks/widget/ScheduleDividerItemDecoration.java
  32. 79 0
      app_modular/appworks/src/main/res/layout/activity_schedule.xml
  33. 29 0
      app_modular/appworks/src/main/res/layout/activity_schedule_search.xml
  34. 250 0
      app_modular/appworks/src/main/res/layout/activity_scheduler_create.xml
  35. 24 0
      app_modular/appworks/src/main/res/layout/item_create_schedule.xml
  36. 46 0
      app_modular/appworks/src/main/res/layout/item_schedule_bottom.xml
  37. 46 0
      app_modular/appworks/src/main/res/layout/item_schedule_search.xml
  38. 9 0
      app_modular/appworks/src/main/res/menu/menu_input_ok.xml

+ 0 - 3
WeiChat/src/main/java/com/xzjmyk/pm/activity/ui/erp/adapter/oa/OACalenderViewPagerAdapter.java

@@ -6,7 +6,6 @@ import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.common.LogUtil;
 import com.core.widget.view.selectcalendar.OACalendarView;
 
 import java.util.Calendar;
@@ -45,7 +44,6 @@ public class OACalenderViewPagerAdapter extends PagerAdapter {
 
     @Override
     public Object instantiateItem(ViewGroup container, int position) {
-        LogUtil.i("instantiateItem="+position);
         if (mViews.get(position) == null) {
             OACalendarView calendarView = new OACalendarView(mContext);
             Date date = this.date[position];
@@ -83,7 +81,6 @@ public class OACalenderViewPagerAdapter extends PagerAdapter {
         for (int i = 0; i < MAX_NUM; i++) {
             c.setTime(date);
             c.add(Calendar.MONTH, i - (MAX_NUM / 2));
-//            c.set(Calendar.DAY_OF_MONTH, 1);
             this.date[i] = c.getTime();
         }
     }

+ 7 - 1
WeiChat/src/main/java/com/xzjmyk/pm/activity/ui/me/MeFragment.java

@@ -72,6 +72,7 @@ import com.uas.appme.settings.activity.CheckWagesActivity;
 import com.uas.appme.settings.activity.SettingActivity;
 import com.uas.appme.settings.activity.SystemAdminActivity;
 import com.uas.appme.widget.MasterDialog;
+import com.uas.appworks.activity.ScheduleActivity;
 import com.uuzuche.lib_zxing.activity.CaptureActivity;
 import com.xzjmyk.pm.activity.CaptureResultActivity;
 import com.xzjmyk.pm.activity.R;
@@ -500,7 +501,12 @@ public class MeFragment extends EasyFragment implements View.OnClickListener, On
                         , CommonUtil.getSharedPreferences(ct, "user_password"), ct);
                 break;
             case R.id.editInfoIv://个人资料
-                ct.startActivity(new Intent(getActivity(), BaseInfoActivity.class));
+                if (BaseConfig.isDebug()){
+                    startActivity(new Intent(ct, ScheduleActivity.class));
+                }else{
+                    ct.startActivity(new Intent(getActivity(), BaseInfoActivity.class));
+
+                }
                 break;
             case R.id.rl_master_change:
                 if (!platform) {

+ 10 - 0
app_core/common/src/main/assets/work_menu.json

@@ -65,6 +65,16 @@
         "menuUrl": "",
         "caller": "",
         "isHide": false
+      },
+      {
+        "isLocalMenu": true,
+        "menuName": "str_work_my_schedule",
+        "menuIcon": "ic_work_uu_education",
+        "menuActivity": "com.modular.work.ScheduleActivity",
+        "menuTag": "local_yingtang_song",
+        "menuUrl": "",
+        "caller": "",
+        "isHide": false
       }
     ]
   },

+ 10 - 0
app_core/common/src/main/res/drawable/bg_circular_bule.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <padding
+        android:bottom="4dp"
+        android:left="4dp"
+        android:right="4dp"
+        android:top="4dp" />
+    <corners android:radius="360dp" />
+    <solid android:color="@color/deep_blue"/>
+</shape>

+ 1 - 0
app_core/common/src/main/res/values/colors.xml

@@ -6,6 +6,7 @@
     <color name="calendar_bg_color">#EBE9E9</color>
     <color name="week_text_color">#FF000000</color>
     <color name="select_circle_color">#FF8594</color>
+    <color name="deep_blue">#206390</color>
     <!--umeng-->
     <color name="umeng_socialize_comments_bg">#F4F4F4</color>
     <color name="umeng_socialize_color_group">#2c3035</color>

+ 2 - 0
app_core/common/src/main/res/values/strings.xml

@@ -1885,6 +1885,8 @@
 
 
     <!--工作台菜单-->
+    <string name="str_work_my_schedule">我的日程</string>
+
     <string name="str_work_add_func">添加应用</string>
 
     <string name="str_uu_application">UU应用</string>

+ 210 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/AnimationHandler.java

@@ -0,0 +1,210 @@
+package com.modular.apputils.widget.compactcalender;
+
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.support.annotation.NonNull;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.OvershootInterpolator;
+
+class AnimationHandler {
+
+    private static final int HEIGHT_ANIM_DURATION_MILLIS = 650;
+    private static final int INDICATOR_ANIM_DURATION_MILLIS = 600;
+    private boolean isAnimating = false;
+    private CompactCalendarController compactCalendarController;
+    private CompactCalendarView compactCalendarView;
+    private CompactCalendarView.CompactCalendarAnimationListener compactCalendarAnimationListener;
+
+    AnimationHandler(CompactCalendarController compactCalendarController, CompactCalendarView compactCalendarView) {
+        this.compactCalendarController = compactCalendarController;
+        this.compactCalendarView = compactCalendarView;
+    }
+
+    void setCompactCalendarAnimationListener(CompactCalendarView.CompactCalendarAnimationListener compactCalendarAnimationListener){
+        this.compactCalendarAnimationListener = compactCalendarAnimationListener;
+    }
+
+    void openCalendar() {
+        if (isAnimating) {
+            return;
+        }
+        isAnimating = true;
+        Animation heightAnim = getCollapsingAnimation(true);
+        heightAnim.setDuration(HEIGHT_ANIM_DURATION_MILLIS);
+        heightAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        compactCalendarController.setAnimationStatus(CompactCalendarController.EXPAND_COLLAPSE_CALENDAR);
+        setUpAnimationLisForOpen(heightAnim);
+        compactCalendarView.getLayoutParams().height = 0;
+        compactCalendarView.requestLayout();
+        compactCalendarView.startAnimation(heightAnim);
+    }
+
+    void closeCalendar() {
+        if (isAnimating) {
+            return;
+        }
+        isAnimating = true;
+        Animation heightAnim = getCollapsingAnimation(false);
+        heightAnim.setDuration(HEIGHT_ANIM_DURATION_MILLIS);
+        heightAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        setUpAnimationLisForClose(heightAnim);
+        compactCalendarController.setAnimationStatus(CompactCalendarController.EXPAND_COLLAPSE_CALENDAR);
+        compactCalendarView.getLayoutParams().height = compactCalendarView.getHeight();
+        compactCalendarView.requestLayout();
+        compactCalendarView.startAnimation(heightAnim);
+    }
+
+    void openCalendarWithAnimation() {
+        if (isAnimating) {
+            return;
+        }
+        isAnimating = true;
+        final Animator indicatorAnim = getIndicatorAnimator(1f, compactCalendarController.getDayIndicatorRadius());
+        final Animation heightAnim = getExposeCollapsingAnimation(true);
+        compactCalendarView.getLayoutParams().height = 0;
+        compactCalendarView.requestLayout();
+        setUpAnimationLisForExposeOpen(indicatorAnim, heightAnim);
+        compactCalendarView.startAnimation(heightAnim);
+    }
+
+    void closeCalendarWithAnimation() {
+        if (isAnimating) {
+            return;
+        }
+        isAnimating = true;
+        final Animator indicatorAnim = getIndicatorAnimator(compactCalendarController.getDayIndicatorRadius(), 1f);
+        final Animation heightAnim = getExposeCollapsingAnimation(false);
+        compactCalendarView.getLayoutParams().height = compactCalendarView.getHeight();
+        compactCalendarView.requestLayout();
+        setUpAnimationLisForExposeClose(indicatorAnim, heightAnim);
+        compactCalendarView.startAnimation(heightAnim);
+    }
+
+    private void setUpAnimationLisForExposeOpen(final Animator indicatorAnim, Animation heightAnim) {
+        heightAnim.setAnimationListener(new AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+                compactCalendarController.setAnimationStatus(CompactCalendarController.EXPOSE_CALENDAR_ANIMATION);
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                indicatorAnim.start();
+            }
+        });
+        indicatorAnim.addListener(new AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                compactCalendarController.setAnimationStatus(CompactCalendarController.ANIMATE_INDICATORS);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                compactCalendarController.setAnimationStatus(CompactCalendarController.IDLE);
+                onOpen();
+                isAnimating = false;
+            }
+        });
+    }
+
+    private void setUpAnimationLisForExposeClose(final Animator indicatorAnim, Animation heightAnim) {
+        heightAnim.setAnimationListener(new AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+                compactCalendarController.setAnimationStatus(CompactCalendarController.EXPOSE_CALENDAR_ANIMATION);
+                indicatorAnim.start();
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                compactCalendarController.setAnimationStatus(CompactCalendarController.IDLE);
+                onClose();
+                isAnimating = false;
+            }
+        });
+        indicatorAnim.addListener(new AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                compactCalendarController.setAnimationStatus(CompactCalendarController.ANIMATE_INDICATORS);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+            }
+        });
+    }
+
+    @NonNull
+    private Animation getExposeCollapsingAnimation(final boolean isCollapsing) {
+        Animation heightAnim = getCollapsingAnimation(isCollapsing);
+        heightAnim.setDuration(HEIGHT_ANIM_DURATION_MILLIS);
+        heightAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        return heightAnim;
+    }
+
+    @NonNull
+    private Animation getCollapsingAnimation(boolean isCollapsing) {
+        return new CollapsingAnimation(compactCalendarView, compactCalendarController, compactCalendarController.getTargetHeight(), getTargetGrowRadius(), isCollapsing);
+    }
+
+    @NonNull
+    private Animator getIndicatorAnimator(float from, float to) {
+        ValueAnimator animIndicator = ValueAnimator.ofFloat(from, to);
+        animIndicator.setDuration(INDICATOR_ANIM_DURATION_MILLIS);
+        animIndicator.setInterpolator(new OvershootInterpolator());
+        animIndicator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                compactCalendarController.setGrowFactorIndicator((Float) animation.getAnimatedValue());
+                compactCalendarView.invalidate();
+            }
+        });
+        return animIndicator;
+    }
+
+    private int getTargetGrowRadius() {
+        int heightSq = compactCalendarController.getTargetHeight() * compactCalendarController.getTargetHeight();
+        int widthSq = compactCalendarController.getWidth() * compactCalendarController.getWidth();
+        return (int) (0.5 * Math.sqrt(heightSq + widthSq));
+    }
+
+    private void onOpen() {
+        if (compactCalendarAnimationListener != null) {
+            compactCalendarAnimationListener.onOpened();
+        }
+    }
+
+    private void onClose() {
+        if (compactCalendarAnimationListener != null) {
+            compactCalendarAnimationListener.onClosed();
+        }
+    }
+
+    private void setUpAnimationLisForOpen(Animation openAnimation) {
+        openAnimation.setAnimationListener(new AnimationListener() {
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                super.onAnimationEnd(animation);
+                onOpen();
+                isAnimating = false;
+            }
+        });
+    }
+
+    private void setUpAnimationLisForClose(Animation openAnimation) {
+        openAnimation.setAnimationListener(new AnimationListener() {
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                super.onAnimationEnd(animation);
+                onClose();
+                isAnimating = false;
+            }
+        });
+    }
+
+    public boolean isAnimating() {
+        return isAnimating;
+    }
+}

+ 22 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/AnimationListener.java

@@ -0,0 +1,22 @@
+package com.modular.apputils.widget.compactcalender;
+
+
+import android.view.animation.Animation;
+
+public abstract class AnimationListener implements Animation.AnimationListener{
+
+    @Override
+    public void onAnimationStart(Animation animation) {
+
+    }
+
+    @Override
+    public void onAnimationEnd(Animation animation) {
+
+    }
+
+    @Override
+    public void onAnimationRepeat(Animation animation) {
+
+    }
+}

+ 25 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/AnimatorListener.java

@@ -0,0 +1,25 @@
+package com.modular.apputils.widget.compactcalender;
+
+
+import android.animation.Animator;
+
+public abstract class AnimatorListener implements Animator.AnimatorListener{
+
+    @Override
+    public void onAnimationStart(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationEnd(Animator animation) {
+
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+    }
+
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+    }
+
+}

+ 50 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/CollapsingAnimation.java

@@ -0,0 +1,50 @@
+package com.modular.apputils.widget.compactcalender;
+
+
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+class CollapsingAnimation extends Animation {
+    private final int targetHeight;
+    private final CompactCalendarView view;
+    private int targetGrowRadius;
+    private final boolean down;
+    private CompactCalendarController compactCalendarController;
+
+    public CollapsingAnimation(CompactCalendarView view, CompactCalendarController compactCalendarController, int targetHeight, int targetGrowRadius, boolean down) {
+        this.view = view;
+        this.compactCalendarController = compactCalendarController;
+        this.targetHeight = targetHeight;
+        this.targetGrowRadius = targetGrowRadius;
+        this.down = down;
+    }
+
+    @Override
+    protected void applyTransformation(float interpolatedTime, Transformation t) {
+        float grow = 0;
+        int newHeight;
+        if (down) {
+            newHeight = (int) (targetHeight * interpolatedTime);
+            grow = (interpolatedTime * (targetGrowRadius * 2));
+        } else {
+            float progress = 1 - interpolatedTime;
+            newHeight = (int) (targetHeight * progress);
+            grow = (progress * (targetGrowRadius * 2));
+        }
+        compactCalendarController.setGrowProgress(grow);
+        view.getLayoutParams().height = newHeight;
+        view.requestLayout();
+
+    }
+
+    @Override
+    public void initialize(int width, int height, int parentWidth,
+                           int parentHeight) {
+        super.initialize(width, height, parentWidth, parentHeight);
+    }
+
+    @Override
+    public boolean willChangeBounds() {
+        return true;
+    }
+}

+ 1002 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/CompactCalendarController.java

@@ -0,0 +1,1002 @@
+package com.modular.apputils.widget.compactcalender;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.widget.OverScroller;
+
+import com.modular.apputils.R;
+import com.modular.apputils.widget.compactcalender.domain.Event;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import static com.modular.apputils.widget.compactcalender.CompactCalendarView.FILL_LARGE_INDICATOR;
+import static com.modular.apputils.widget.compactcalender.CompactCalendarView.NO_FILL_LARGE_INDICATOR;
+import static com.modular.apputils.widget.compactcalender.CompactCalendarView.SMALL_INDICATOR;
+
+
+class CompactCalendarController {
+
+    public static final int IDLE = 0;
+    public static final int EXPOSE_CALENDAR_ANIMATION = 1;
+    public static final int EXPAND_COLLAPSE_CALENDAR = 2;
+    public static final int ANIMATE_INDICATORS = 3;
+    private static final int VELOCITY_UNIT_PIXELS_PER_SECOND = 1000;
+    private static final int LAST_FLING_THRESHOLD_MILLIS = 300;
+    private static final int DAYS_IN_WEEK = 7;
+    private static final float SNAP_VELOCITY_DIP_PER_SECOND = 400;
+    private static final float ANIMATION_SCREEN_SET_DURATION_MILLIS = 700;
+
+    private int eventIndicatorStyle = SMALL_INDICATOR;
+    private int currentDayIndicatorStyle = FILL_LARGE_INDICATOR;
+    private int currentSelectedDayIndicatorStyle = FILL_LARGE_INDICATOR;
+    private int paddingWidth = 40;
+    private int paddingHeight = 40;
+    private int textHeight;
+    private int textWidth;
+    private int widthPerDay;
+    private int monthsScrolledSoFar;
+    private int heightPerDay;
+    private int textSize = 30;
+    private int width;
+    private int height;
+    private int paddingRight;
+    private int paddingLeft;
+    private int maximumVelocity;
+    private int densityAdjustedSnapVelocity;
+    private int distanceThresholdForAutoScroll;
+    private int targetHeight;
+    private int animationStatus = 0;
+    private int firstDayOfWeekToDraw = Calendar.MONDAY;
+    private float xIndicatorOffset;
+    private float multiDayIndicatorStrokeWidth;
+    private float bigCircleIndicatorRadius;
+    private float smallIndicatorRadius;
+    private float growFactor = 0f;
+    private float screenDensity = 1;
+    private float growfactorIndicator;
+    private float distanceX;
+    private long lastAutoScrollFromFling;
+
+    private boolean useThreeLetterAbbreviation = false;
+    private boolean isSmoothScrolling;
+    private boolean isScrolling;
+    private boolean shouldDrawDaysHeader = true;
+    private boolean shouldDrawIndicatorsBelowSelectedDays = false;
+    private boolean displayOtherMonthDays = false;
+    private boolean shouldSelectFirstDayOfMonthOnScroll = true;
+    private boolean isRtl = false;
+
+    private CompactCalendarView.CompactCalendarViewListener listener;
+    private VelocityTracker velocityTracker = null;
+    private Direction currentDirection = Direction.NONE;
+    private Date currentDate = new Date();
+    private Locale locale;
+    private Calendar currentCalender;
+    private Calendar todayCalender;
+    private Calendar calendarWithFirstDayOfMonth;
+    private Calendar eventsCalendar;
+    private EventsContainer eventsContainer;
+    private PointF accumulatedScrollOffset = new PointF();
+    private OverScroller scroller;
+    private Paint dayPaint = new Paint();
+    private Paint background = new Paint();
+    private Rect textSizeRect;
+    private String[] dayColumnNames;
+
+    // colors
+    private int multiEventIndicatorColor;
+    private int currentDayBackgroundColor;
+    private int currentDayTextColor;
+    private int calenderTextColor;
+    private int currentSelectedDayBackgroundColor;
+    private int currentSelectedDayTextColor;
+    private int calenderBackgroundColor = Color.WHITE;
+    private int otherMonthDaysTextColor;
+    private TimeZone timeZone;
+
+    /**
+     * Only used in onDrawCurrentMonth to temporarily calculate previous month days
+     */
+    private Calendar tempPreviousMonthCalendar;
+
+    private enum Direction {
+        NONE, HORIZONTAL, VERTICAL
+    }
+
+    CompactCalendarController(Paint dayPaint, OverScroller scroller, Rect textSizeRect, AttributeSet attrs,
+                              Context context, int currentDayBackgroundColor, int calenderTextColor,
+                              int currentSelectedDayBackgroundColor, VelocityTracker velocityTracker,
+                              int multiEventIndicatorColor, EventsContainer eventsContainer,
+                              Locale locale, TimeZone timeZone) {
+        this.dayPaint = dayPaint;
+        this.scroller = scroller;
+        this.textSizeRect = textSizeRect;
+        this.currentDayBackgroundColor = currentDayBackgroundColor;
+        this.calenderTextColor = calenderTextColor;
+        this.currentSelectedDayBackgroundColor = currentSelectedDayBackgroundColor;
+        this.otherMonthDaysTextColor = calenderTextColor;
+        this.velocityTracker = velocityTracker;
+        this.multiEventIndicatorColor = multiEventIndicatorColor;
+        this.eventsContainer = eventsContainer;
+        this.locale = locale;
+        this.timeZone = timeZone;
+        this.displayOtherMonthDays = false;
+        loadAttributes(attrs, context);
+        init(context);
+    }
+
+    private void loadAttributes(AttributeSet attrs, Context context) {
+        if (attrs != null && context != null) {
+            TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CompactCalendarView, 0, 0);
+            try {
+                currentDayBackgroundColor = typedArray.getColor(R.styleable.CompactCalendarView_compactCalendarCurrentDayBackgroundColor, currentDayBackgroundColor);
+                calenderTextColor = typedArray.getColor(R.styleable.CompactCalendarView_compactCalendarTextColor, calenderTextColor);
+                currentDayTextColor = typedArray.getColor(R.styleable.CompactCalendarView_compactCalendarCurrentDayTextColor, calenderTextColor);
+                otherMonthDaysTextColor = typedArray.getColor(R.styleable.CompactCalendarView_compactCalendarOtherMonthDaysTextColor, otherMonthDaysTextColor);
+                currentSelectedDayBackgroundColor = typedArray.getColor(R.styleable.CompactCalendarView_compactCalendarCurrentSelectedDayBackgroundColor, currentSelectedDayBackgroundColor);
+                currentSelectedDayTextColor = typedArray.getColor(R.styleable.CompactCalendarView_compactCalendarCurrentSelectedDayTextColor, calenderTextColor);
+                calenderBackgroundColor = typedArray.getColor(R.styleable.CompactCalendarView_compactCalendarBackgroundColor, calenderBackgroundColor);
+                multiEventIndicatorColor = typedArray.getColor(R.styleable.CompactCalendarView_compactCalendarMultiEventIndicatorColor, multiEventIndicatorColor);
+                textSize = typedArray.getDimensionPixelSize(R.styleable.CompactCalendarView_compactCalendarTextSize,
+                        (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSize, context.getResources().getDisplayMetrics()));
+                targetHeight = typedArray.getDimensionPixelSize(R.styleable.CompactCalendarView_compactCalendarTargetHeight,
+                        (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, targetHeight, context.getResources().getDisplayMetrics()));
+                eventIndicatorStyle = typedArray.getInt(R.styleable.CompactCalendarView_compactCalendarEventIndicatorStyle, SMALL_INDICATOR);
+                currentDayIndicatorStyle = typedArray.getInt(R.styleable.CompactCalendarView_compactCalendarCurrentDayIndicatorStyle, FILL_LARGE_INDICATOR);
+                currentSelectedDayIndicatorStyle = typedArray.getInt(R.styleable.CompactCalendarView_compactCalendarCurrentSelectedDayIndicatorStyle, FILL_LARGE_INDICATOR);
+                displayOtherMonthDays = typedArray.getBoolean(R.styleable.CompactCalendarView_compactCalendarDisplayOtherMonthDays, displayOtherMonthDays);
+                shouldSelectFirstDayOfMonthOnScroll = typedArray.getBoolean(R.styleable.CompactCalendarView_compactCalendarShouldSelectFirstDayOfMonthOnScroll, shouldSelectFirstDayOfMonthOnScroll);
+            } finally {
+                typedArray.recycle();
+            }
+        }
+    }
+
+    private void init(Context context) {
+        currentCalender = Calendar.getInstance(timeZone, locale);
+        todayCalender = Calendar.getInstance(timeZone, locale);
+        calendarWithFirstDayOfMonth = Calendar.getInstance(timeZone, locale);
+        eventsCalendar = Calendar.getInstance(timeZone, locale);
+        tempPreviousMonthCalendar = Calendar.getInstance(timeZone, locale);
+
+        // make setMinimalDaysInFirstWeek same across android versions
+        eventsCalendar.setMinimalDaysInFirstWeek(1);
+        calendarWithFirstDayOfMonth.setMinimalDaysInFirstWeek(1);
+        todayCalender.setMinimalDaysInFirstWeek(1);
+        currentCalender.setMinimalDaysInFirstWeek(1);
+        tempPreviousMonthCalendar.setMinimalDaysInFirstWeek(1);
+
+        setFirstDayOfWeek(firstDayOfWeekToDraw);
+
+        setUseWeekDayAbbreviation(false);
+        dayPaint.setTextAlign(Paint.Align.CENTER);
+        dayPaint.setStyle(Paint.Style.STROKE);
+        dayPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+        dayPaint.setTypeface(Typeface.SANS_SERIF);
+        dayPaint.setTextSize(textSize);
+        dayPaint.setColor(calenderTextColor);
+        dayPaint.getTextBounds("31", 0, "31".length(), textSizeRect);
+        textHeight = textSizeRect.height() * 3;
+        textWidth = textSizeRect.width() * 2;
+
+        todayCalender.setTime(new Date());
+        setToMidnight(todayCalender);
+
+        currentCalender.setTime(currentDate);
+        setCalenderToFirstDayOfMonth(calendarWithFirstDayOfMonth, currentDate, -monthsScrolledSoFar, 0);
+
+        initScreenDensityRelatedValues(context);
+
+        xIndicatorOffset = 3.5f * screenDensity;
+
+        //scale small indicator by screen density
+        smallIndicatorRadius = 2.5f * screenDensity;
+
+        //just set a default growFactor to draw full calendar when initialised
+        growFactor = Integer.MAX_VALUE;
+    }
+
+    private void initScreenDensityRelatedValues(Context context) {
+        if (context != null) {
+            screenDensity = context.getResources().getDisplayMetrics().density;
+            final ViewConfiguration configuration = ViewConfiguration
+                    .get(context);
+            densityAdjustedSnapVelocity = (int) (screenDensity * SNAP_VELOCITY_DIP_PER_SECOND);
+            maximumVelocity = configuration.getScaledMaximumFlingVelocity();
+
+            final DisplayMetrics dm = context.getResources().getDisplayMetrics() ;
+            multiDayIndicatorStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, dm);
+        }
+    }
+
+    private void setCalenderToFirstDayOfMonth(Calendar calendarWithFirstDayOfMonth, Date currentDate, int scrollOffset, int monthOffset) {
+        setMonthOffset(calendarWithFirstDayOfMonth, currentDate, scrollOffset, monthOffset);
+        calendarWithFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
+    }
+
+    private void setMonthOffset(Calendar calendarWithFirstDayOfMonth, Date currentDate, int scrollOffset, int monthOffset) {
+        calendarWithFirstDayOfMonth.setTime(currentDate);
+        calendarWithFirstDayOfMonth.add(Calendar.MONTH, scrollOffset + monthOffset);
+        calendarWithFirstDayOfMonth.set(Calendar.HOUR_OF_DAY, 0);
+        calendarWithFirstDayOfMonth.set(Calendar.MINUTE, 0);
+        calendarWithFirstDayOfMonth.set(Calendar.SECOND, 0);
+        calendarWithFirstDayOfMonth.set(Calendar.MILLISECOND, 0);
+    }
+
+    void setIsRtl(boolean isRtl){
+        this.isRtl = isRtl;
+    }
+
+    void setShouldSelectFirstDayOfMonthOnScroll(boolean shouldSelectFirstDayOfMonthOnScroll){
+        this.shouldSelectFirstDayOfMonthOnScroll = shouldSelectFirstDayOfMonthOnScroll;
+    }
+
+    void setDisplayOtherMonthDays(boolean displayOtherMonthDays) {
+        this.displayOtherMonthDays = displayOtherMonthDays;
+    }
+
+    void shouldDrawIndicatorsBelowSelectedDays(boolean shouldDrawIndicatorsBelowSelectedDays){
+        this.shouldDrawIndicatorsBelowSelectedDays = shouldDrawIndicatorsBelowSelectedDays;
+    }
+
+    void setCurrentDayIndicatorStyle(int currentDayIndicatorStyle) {
+        this.currentDayIndicatorStyle = currentDayIndicatorStyle;
+    }
+
+    void setEventIndicatorStyle(int eventIndicatorStyle) {
+        this.eventIndicatorStyle = eventIndicatorStyle;
+    }
+
+    void setCurrentSelectedDayIndicatorStyle(int currentSelectedDayIndicatorStyle){
+        this.currentSelectedDayIndicatorStyle = currentSelectedDayIndicatorStyle;
+    }
+
+    void setTargetHeight(int targetHeight) {
+        this.targetHeight = targetHeight;
+    }
+
+    float getScreenDensity(){
+        return screenDensity;
+    }
+
+    float getDayIndicatorRadius(){
+        return bigCircleIndicatorRadius;
+    }
+
+    void setGrowFactorIndicator(float growfactorIndicator) {
+        this.growfactorIndicator = growfactorIndicator;
+    }
+
+    float getGrowFactorIndicator() {
+        return growfactorIndicator;
+    }
+
+    void setAnimationStatus(int animationStatus) {
+        this.animationStatus = animationStatus;
+    }
+
+    int getTargetHeight() {
+        return targetHeight;
+    }
+
+    int getWidth(){
+        return width;
+    }
+
+    void setListener(CompactCalendarView.CompactCalendarViewListener listener) {
+        this.listener = listener;
+    }
+
+    void removeAllEvents() {
+        eventsContainer.removeAllEvents();
+    }
+
+    void setFirstDayOfWeek(int day){
+        if (day < 1 || day > 7) {
+            throw new IllegalArgumentException("Day must be an int between 1 and 7 or DAY_OF_WEEK from Java Calendar class. For more information please see Calendar.DAY_OF_WEEK.");
+        }
+        this.firstDayOfWeekToDraw = day;
+        setUseWeekDayAbbreviation(useThreeLetterAbbreviation);
+        eventsCalendar.setFirstDayOfWeek(day);
+        calendarWithFirstDayOfMonth.setFirstDayOfWeek(day);
+        todayCalender.setFirstDayOfWeek(day);
+        currentCalender.setFirstDayOfWeek(day);
+        tempPreviousMonthCalendar.setFirstDayOfWeek(day);
+    }
+
+    void setCurrentSelectedDayBackgroundColor(int currentSelectedDayBackgroundColor) {
+        this.currentSelectedDayBackgroundColor = currentSelectedDayBackgroundColor;
+    }
+
+    void setCurrentSelectedDayTextColor(int currentSelectedDayTextColor) {
+        this.currentSelectedDayTextColor = currentSelectedDayTextColor;
+    }
+
+    void setCalenderBackgroundColor(int calenderBackgroundColor) {
+        this.calenderBackgroundColor = calenderBackgroundColor;
+    }
+
+    void setCurrentDayBackgroundColor(int currentDayBackgroundColor) {
+        this.currentDayBackgroundColor = currentDayBackgroundColor;
+    }
+
+    void setCurrentDayTextColor(int currentDayTextColor) {
+        this.currentDayTextColor = currentDayTextColor;
+    }
+
+    void scrollRight() {
+        if (isRtl) {
+            scrollPrev();
+        } else {
+            scrollNext();
+        }
+    }
+
+    void scrollLeft() {
+        if (isRtl) {
+            scrollNext();
+        } else {
+            scrollPrev();
+        }
+    }
+
+    private void scrollNext() {
+        monthsScrolledSoFar = monthsScrolledSoFar - 1;
+        accumulatedScrollOffset.x = monthsScrolledSoFar * width;
+        if(shouldSelectFirstDayOfMonthOnScroll){
+            setCalenderToFirstDayOfMonth(calendarWithFirstDayOfMonth, currentCalender.getTime(), 0, 1);
+            setCurrentDate(calendarWithFirstDayOfMonth.getTime());
+        }
+        performMonthScrollCallback();
+    }
+
+    private void scrollPrev() {
+        monthsScrolledSoFar = monthsScrolledSoFar + 1;
+        accumulatedScrollOffset.x = monthsScrolledSoFar * width;
+        if(shouldSelectFirstDayOfMonthOnScroll){
+            setCalenderToFirstDayOfMonth(calendarWithFirstDayOfMonth, currentCalender.getTime(), 0, -1);
+            setCurrentDate(calendarWithFirstDayOfMonth.getTime());
+        }
+        performMonthScrollCallback();
+    }
+
+    void setLocale(TimeZone timeZone, Locale locale) {
+        if (locale == null) {
+            throw new IllegalArgumentException("Locale cannot be null.");
+        }
+        if (timeZone == null) {
+            throw new IllegalArgumentException("TimeZone cannot be null.");
+        }
+        this.locale = locale;
+        this.timeZone = timeZone;
+        this.eventsContainer = new EventsContainer(Calendar.getInstance(this.timeZone, this.locale));
+        // passing null will not re-init density related values - and that's ok
+        init(null);
+    }
+
+    void setUseWeekDayAbbreviation(boolean useThreeLetterAbbreviation) {
+        this.useThreeLetterAbbreviation = useThreeLetterAbbreviation;
+        this.dayColumnNames = WeekUtils.getWeekdayNames(locale, firstDayOfWeekToDraw, this.useThreeLetterAbbreviation);
+    }
+
+    void setDayColumnNames(String[] dayColumnNames) {
+        if (dayColumnNames == null || dayColumnNames.length != 7) {
+            throw new IllegalArgumentException("Column names cannot be null and must contain a value for each day of the week");
+        }
+        this.dayColumnNames = dayColumnNames;
+    }
+
+    void setShouldDrawDaysHeader(boolean shouldDrawDaysHeader) {
+        this.shouldDrawDaysHeader = shouldDrawDaysHeader;
+    }
+
+    void onMeasure(int width, int height, int paddingRight, int paddingLeft) {
+        widthPerDay = (width) / DAYS_IN_WEEK;
+        heightPerDay = targetHeight > 0 ? targetHeight / 7 : height / 7;
+        this.width = width;
+        this.distanceThresholdForAutoScroll = (int) (width * 0.50);
+        this.height = height;
+        this.paddingRight = paddingRight;
+        this.paddingLeft = paddingLeft;
+
+        //makes easier to find radius
+        bigCircleIndicatorRadius = getInterpolatedBigCircleIndicator();
+
+        // scale the selected day indicators slightly so that event indicators can be drawn below
+        bigCircleIndicatorRadius = shouldDrawIndicatorsBelowSelectedDays && eventIndicatorStyle == SMALL_INDICATOR ? bigCircleIndicatorRadius * 0.85f : bigCircleIndicatorRadius;
+    }
+
+    //assume square around each day of width and height = heightPerDay and get diagonal line length
+    //interpolate height and radius
+    //https://en.wikipedia.org/wiki/Linear_interpolation
+    private float getInterpolatedBigCircleIndicator() {
+        float x0 = textSizeRect.height();
+        float x1 = heightPerDay; // take into account indicator offset
+        float x =  (x1 + textSizeRect.height()) / 2f; // pick a point which is almost half way through heightPerDay and textSizeRect
+        double y1 = 0.5 * Math.sqrt((x1 * x1) + (x1 * x1));
+        double y0 = 0.5 * Math.sqrt((x0 * x0) + (x0 * x0));
+
+        return (float) (y0 + ((y1 - y0) * ((x - x0) / (x1 - x0))));
+    }
+
+    void onDraw(Canvas canvas) {
+        paddingWidth = widthPerDay / 2;
+        paddingHeight = heightPerDay / 2;
+        calculateXPositionOffset();
+
+        if (animationStatus == EXPOSE_CALENDAR_ANIMATION) {
+            drawCalendarWhileAnimating(canvas);
+        } else if (animationStatus == ANIMATE_INDICATORS) {
+            drawCalendarWhileAnimatingIndicators(canvas);
+        } else {
+            drawCalenderBackground(canvas);
+            drawScrollableCalender(canvas);
+        }
+    }
+
+    private void drawCalendarWhileAnimatingIndicators(Canvas canvas) {
+        dayPaint.setColor(calenderBackgroundColor);
+        dayPaint.setStyle(Paint.Style.FILL);
+        canvas.drawCircle(0, 0, growFactor, dayPaint);
+        dayPaint.setStyle(Paint.Style.STROKE);
+        dayPaint.setColor(Color.WHITE);
+        drawScrollableCalender(canvas);
+    }
+
+    private void drawCalendarWhileAnimating(Canvas canvas) {
+        background.setColor(calenderBackgroundColor);
+        background.setStyle(Paint.Style.FILL);
+        canvas.drawCircle(0, 0, growFactor, background);
+        dayPaint.setStyle(Paint.Style.STROKE);
+        dayPaint.setColor(Color.WHITE);
+        drawScrollableCalender(canvas);
+    }
+
+    void onSingleTapUp(MotionEvent e) {
+        // Don't handle single tap when calendar is scrolling and is not stationary
+        if (isScrolling()) {
+            return;
+        }
+
+        int dayColumn = Math.round((paddingLeft + e.getX() - paddingWidth - paddingRight) / widthPerDay);
+        int dayRow = Math.round((e.getY() - paddingHeight) / heightPerDay);
+
+        setCalenderToFirstDayOfMonth(calendarWithFirstDayOfMonth, currentDate, monthsScrolledSoFar(), 0);
+
+        int firstDayOfMonth = getDayOfWeek(calendarWithFirstDayOfMonth);
+
+        int dayOfMonth = ((dayRow - 1) * 7) - firstDayOfMonth;
+        if (isRtl) {
+            dayOfMonth +=  6 - dayColumn;
+        } else {
+            dayOfMonth += dayColumn;
+        }
+        if (dayOfMonth < calendarWithFirstDayOfMonth.getActualMaximum(Calendar.DAY_OF_MONTH)
+                && dayOfMonth >= 0) {
+            calendarWithFirstDayOfMonth.add(Calendar.DATE, dayOfMonth);
+
+            currentCalender.setTimeInMillis(calendarWithFirstDayOfMonth.getTimeInMillis());
+            performOnDayClickCallback(currentCalender.getTime());
+        }
+    }
+
+    // Add a little leeway buy checking if amount scrolled is almost same as expected scroll
+    // as it maybe off by a few pixels
+    private boolean isScrolling() {
+        float scrolledX = Math.abs(accumulatedScrollOffset.x);
+        int expectedScrollX = Math.abs(width * monthsScrolledSoFar);
+        return scrolledX < expectedScrollX - 5 || scrolledX > expectedScrollX + 5;
+    }
+
+    private void performOnDayClickCallback(Date date) {
+        if (listener != null) {
+            listener.onDayClick(date);
+        }
+    }
+
+    boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        //ignore scrolling callback if already smooth scrolling
+        if (isSmoothScrolling) {
+            return true;
+        }
+
+        if (currentDirection == Direction.NONE) {
+            if (Math.abs(distanceX) > Math.abs(distanceY)) {
+                currentDirection = Direction.HORIZONTAL;
+            } else {
+                currentDirection = Direction.VERTICAL;
+            }
+        }
+
+        isScrolling = true;
+        this.distanceX = distanceX;
+        return true;
+    }
+
+    boolean onTouch(MotionEvent event) {
+        if (velocityTracker == null) {
+            velocityTracker = VelocityTracker.obtain();
+        }
+
+        velocityTracker.addMovement(event);
+
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+
+            if (!scroller.isFinished()) {
+                scroller.abortAnimation();
+            }
+            isSmoothScrolling = false;
+
+        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
+            velocityTracker.addMovement(event);
+            velocityTracker.computeCurrentVelocity(500);
+
+        } else if (event.getAction() == MotionEvent.ACTION_UP) {
+            handleHorizontalScrolling();
+            velocityTracker.recycle();
+            velocityTracker.clear();
+            velocityTracker = null;
+            isScrolling = false;
+        }
+        return false;
+    }
+
+    private void snapBackScroller() {
+        float remainingScrollAfterFingerLifted1 = (accumulatedScrollOffset.x - (monthsScrolledSoFar * width));
+        scroller.startScroll((int) accumulatedScrollOffset.x, 0, (int) -remainingScrollAfterFingerLifted1, 0);
+    }
+
+    private void handleHorizontalScrolling() {
+        int velocityX = computeVelocity();
+        handleSmoothScrolling(velocityX);
+
+        currentDirection = Direction.NONE;
+        setCalenderToFirstDayOfMonth(calendarWithFirstDayOfMonth, currentDate, monthsScrolledSoFar(), 0);
+
+        if (calendarWithFirstDayOfMonth.get(Calendar.MONTH) != currentCalender.get(Calendar.MONTH) && shouldSelectFirstDayOfMonthOnScroll) {
+            setCalenderToFirstDayOfMonth(currentCalender, currentDate, monthsScrolledSoFar(), 0);
+        }
+    }
+
+    private int computeVelocity() {
+        velocityTracker.computeCurrentVelocity(VELOCITY_UNIT_PIXELS_PER_SECOND, maximumVelocity);
+        return (int) velocityTracker.getXVelocity();
+    }
+
+    private void handleSmoothScrolling(int velocityX) {
+        int distanceScrolled = (int) (accumulatedScrollOffset.x - (width * monthsScrolledSoFar));
+        boolean isEnoughTimeElapsedSinceLastSmoothScroll = System.currentTimeMillis() - lastAutoScrollFromFling > LAST_FLING_THRESHOLD_MILLIS;
+        if (velocityX > densityAdjustedSnapVelocity && isEnoughTimeElapsedSinceLastSmoothScroll) {
+            scrollPreviousMonth();
+        } else if (velocityX < -densityAdjustedSnapVelocity && isEnoughTimeElapsedSinceLastSmoothScroll) {
+            scrollNextMonth();
+        } else if (isScrolling && distanceScrolled > distanceThresholdForAutoScroll) {
+            scrollPreviousMonth();
+        } else if (isScrolling && distanceScrolled < -distanceThresholdForAutoScroll) {
+            scrollNextMonth();
+        } else {
+            isSmoothScrolling = false;
+            snapBackScroller();
+        }
+    }
+
+    private void scrollNextMonth() {
+        lastAutoScrollFromFling = System.currentTimeMillis();
+        monthsScrolledSoFar = monthsScrolledSoFar - 1;
+        performScroll();
+        isSmoothScrolling = true;
+        performMonthScrollCallback();
+    }
+
+    private void scrollPreviousMonth() {
+        lastAutoScrollFromFling = System.currentTimeMillis();
+        monthsScrolledSoFar = monthsScrolledSoFar + 1;
+        performScroll();
+        isSmoothScrolling = true;
+        performMonthScrollCallback();
+    }
+
+    private void performMonthScrollCallback() {
+        if (listener != null) {
+            listener.onMonthScroll(getFirstDayOfCurrentMonth());
+        }
+    }
+
+    private void performScroll() {
+        int targetScroll = (monthsScrolledSoFar ) * width;
+        float remainingScrollAfterFingerLifted = targetScroll - accumulatedScrollOffset.x;
+        scroller.startScroll((int) accumulatedScrollOffset.x, 0, (int) (remainingScrollAfterFingerLifted), 0,
+                (int) (Math.abs((int) (remainingScrollAfterFingerLifted)) / (float) width * ANIMATION_SCREEN_SET_DURATION_MILLIS));
+    }
+
+    int getHeightPerDay() {
+        return heightPerDay;
+    }
+
+    int getWeekNumberForCurrentMonth() {
+        Calendar calendar = Calendar.getInstance(timeZone, locale);
+        calendar.setTime(currentDate);
+        return calendar.get(Calendar.WEEK_OF_MONTH);
+    }
+
+    Date getFirstDayOfCurrentMonth() {
+        Calendar calendar = Calendar.getInstance(timeZone, locale);
+        calendar.setTime(currentDate);
+        calendar.add(Calendar.MONTH, monthsScrolledSoFar());
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        setToMidnight(calendar);
+        return calendar.getTime();
+    }
+
+    void setCurrentDate(Date dateTimeMonth) {
+        distanceX = 0;
+        monthsScrolledSoFar = 0;
+        accumulatedScrollOffset.x = 0;
+        scroller.startScroll(0, 0, 0, 0);
+        currentDate = new Date(dateTimeMonth.getTime());
+        currentCalender.setTime(currentDate);
+        todayCalender = Calendar.getInstance(timeZone, locale);
+        setToMidnight(currentCalender);
+    }
+
+    private void setToMidnight(Calendar calendar) {
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+    }
+
+    void addEvent(Event event) {
+        eventsContainer.addEvent(event);
+    }
+
+    void addEvents(List<Event> events) {
+       eventsContainer.addEvents(events);
+    }
+
+    List<Event> getCalendarEventsFor(long epochMillis) {
+        return eventsContainer.getEventsFor(epochMillis);
+    }
+
+    List<Event> getCalendarEventsForMonth(long epochMillis) {
+        return eventsContainer.getEventsForMonth(epochMillis);
+    }
+
+    void removeEventsFor(long epochMillis) {
+        eventsContainer.removeEventByEpochMillis(epochMillis);
+    }
+
+    void removeEvent(Event event) {
+       eventsContainer.removeEvent(event);
+    }
+
+    void removeEvents(List<Event> events) {
+       eventsContainer.removeEvents(events);
+    }
+
+    void setGrowProgress(float grow) {
+        growFactor = grow;
+    }
+
+    float getGrowFactor() {
+        return growFactor;
+    }
+
+    boolean onDown(MotionEvent e) {
+        scroller.forceFinished(true);
+        return true;
+    }
+
+    boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        scroller.forceFinished(true);
+        return true;
+    }
+
+    boolean computeScroll() {
+        if (scroller.computeScrollOffset()) {
+            accumulatedScrollOffset.x = scroller.getCurrX();
+            return true;
+        }
+        return false;
+    }
+
+    private void drawScrollableCalender(Canvas canvas) {
+        if (isRtl) {
+            drawNextMonth(canvas, -1);
+            drawCurrentMonth(canvas);
+            drawPreviousMonth(canvas,1);
+        } else {
+            drawPreviousMonth(canvas, -1);
+            drawCurrentMonth(canvas);
+            drawNextMonth(canvas, 1);
+        }
+    }
+
+    private void drawNextMonth(Canvas canvas, int offset) {
+        setCalenderToFirstDayOfMonth(calendarWithFirstDayOfMonth, currentDate, -monthsScrolledSoFar, offset);
+        drawMonth(canvas, calendarWithFirstDayOfMonth, (width * (-monthsScrolledSoFar + 1)));
+    }
+
+    private void drawCurrentMonth(Canvas canvas) {
+        setCalenderToFirstDayOfMonth(calendarWithFirstDayOfMonth, currentDate, monthsScrolledSoFar(), 0);
+        drawMonth(canvas, calendarWithFirstDayOfMonth, width * -monthsScrolledSoFar);
+    }
+
+    private int monthsScrolledSoFar() {
+        return isRtl? monthsScrolledSoFar : -monthsScrolledSoFar;
+    }
+
+    private void drawPreviousMonth(Canvas canvas, int offset) {
+        setCalenderToFirstDayOfMonth(calendarWithFirstDayOfMonth, currentDate, -monthsScrolledSoFar, offset);
+        drawMonth(canvas, calendarWithFirstDayOfMonth, (width * (-monthsScrolledSoFar - 1)));
+    }
+
+    private void calculateXPositionOffset() {
+        if (currentDirection == Direction.HORIZONTAL) {
+            accumulatedScrollOffset.x -= distanceX;
+        }
+    }
+
+    private void drawCalenderBackground(Canvas canvas) {
+        dayPaint.setColor(calenderBackgroundColor);
+        dayPaint.setStyle(Paint.Style.FILL);
+        canvas.drawRect(0, 0, width, height, dayPaint);
+        dayPaint.setStyle(Paint.Style.STROKE);
+        dayPaint.setColor(calenderTextColor);
+    }
+
+    void drawEvents(Canvas canvas, Calendar currentMonthToDrawCalender, int offset) {
+        int currentMonth = currentMonthToDrawCalender.get(Calendar.MONTH);
+        List<Events> uniqEvents = eventsContainer.getEventsForMonthAndYear(currentMonth, currentMonthToDrawCalender.get(Calendar.YEAR));
+
+        boolean shouldDrawCurrentDayCircle = currentMonth == todayCalender.get(Calendar.MONTH);
+        boolean shouldDrawSelectedDayCircle = currentMonth == currentCalender.get(Calendar.MONTH);
+
+        int todayDayOfMonth = todayCalender.get(Calendar.DAY_OF_MONTH);
+        int currentYear = todayCalender.get(Calendar.YEAR);
+        int selectedDayOfMonth = currentCalender.get(Calendar.DAY_OF_MONTH);
+        float indicatorOffset = bigCircleIndicatorRadius / 2;
+        if (uniqEvents != null) {
+            for (int i = 0; i < uniqEvents.size(); i++) {
+                Events events = uniqEvents.get(i);
+                long timeMillis = events.getTimeInMillis();
+                eventsCalendar.setTimeInMillis(timeMillis);
+
+                int dayOfWeek = getDayOfWeek(eventsCalendar);
+                if (isRtl) {
+                    dayOfWeek =  6 - dayOfWeek;
+                }
+
+                int weekNumberForMonth = eventsCalendar.get(Calendar.WEEK_OF_MONTH);
+                float xPosition = widthPerDay * dayOfWeek + paddingWidth + paddingLeft + accumulatedScrollOffset.x + offset - paddingRight;
+                float yPosition = weekNumberForMonth * heightPerDay + paddingHeight;
+
+                if (((animationStatus == EXPOSE_CALENDAR_ANIMATION || animationStatus == ANIMATE_INDICATORS) && xPosition >= growFactor ) || yPosition >= growFactor) {
+                    // only draw small event indicators if enough of the calendar is exposed
+                    continue;
+                } else if (animationStatus == EXPAND_COLLAPSE_CALENDAR && yPosition >= growFactor){
+                    // expanding animation, just draw event indicators if enough of the calendar is visible
+                    continue;
+                } else if (animationStatus == EXPOSE_CALENDAR_ANIMATION && (eventIndicatorStyle == FILL_LARGE_INDICATOR || eventIndicatorStyle == NO_FILL_LARGE_INDICATOR)) {
+                    // Don't draw large indicators during expose animation, until animation is done
+                    continue;
+                }
+
+                List<Event> eventsList = events.getEvents();
+                int dayOfMonth = eventsCalendar.get(Calendar.DAY_OF_MONTH);
+                int eventYear = eventsCalendar.get(Calendar.YEAR);
+                boolean isSameDayAsCurrentDay = shouldDrawCurrentDayCircle && (todayDayOfMonth == dayOfMonth) && (eventYear == currentYear);
+                boolean isCurrentSelectedDay = shouldDrawSelectedDayCircle && (selectedDayOfMonth == dayOfMonth);
+
+                if (shouldDrawIndicatorsBelowSelectedDays || (!shouldDrawIndicatorsBelowSelectedDays && !isSameDayAsCurrentDay && !isCurrentSelectedDay) || animationStatus == EXPOSE_CALENDAR_ANIMATION) {
+                    if (eventIndicatorStyle == FILL_LARGE_INDICATOR || eventIndicatorStyle == NO_FILL_LARGE_INDICATOR) {
+                        if (!eventsList.isEmpty()) {
+                            Event event = eventsList.get(0);
+                            drawEventIndicatorCircle(canvas, xPosition, yPosition, event.getColor());
+                        }
+                    } else {
+                        yPosition += indicatorOffset;
+                        // offset event indicators to draw below selected day indicators
+                        // this makes sure that they do no overlap
+                        if (shouldDrawIndicatorsBelowSelectedDays && (isSameDayAsCurrentDay || isCurrentSelectedDay)) {
+                            yPosition += indicatorOffset;
+                        }
+
+                        if (eventsList.size() >= 3) {
+                            drawEventsWithPlus(canvas, xPosition, yPosition, eventsList);
+                        } else if (eventsList.size() == 2) {
+                            drawTwoEvents(canvas, xPosition, yPosition, eventsList);
+                        } else if (eventsList.size() == 1) {
+                            drawSingleEvent(canvas, xPosition, yPosition, eventsList);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void drawSingleEvent(Canvas canvas, float xPosition, float yPosition, List<Event> eventsList) {
+        Event event = eventsList.get(0);
+        drawEventIndicatorCircle(canvas, xPosition, yPosition, event.getColor());
+    }
+
+    private void drawTwoEvents(Canvas canvas, float xPosition, float yPosition, List<Event> eventsList) {
+        //draw fist event just left of center
+        drawEventIndicatorCircle(canvas, xPosition + (xIndicatorOffset * -1), yPosition, eventsList.get(0).getColor());
+        //draw second event just right of center
+        drawEventIndicatorCircle(canvas, xPosition + (xIndicatorOffset * 1), yPosition, eventsList.get(1).getColor());
+    }
+
+    //draw 2 eventsByMonthAndYearMap followed by plus indicator to show there are more than 2 eventsByMonthAndYearMap
+    private void drawEventsWithPlus(Canvas canvas, float xPosition, float yPosition, List<Event> eventsList) {
+        // k = size() - 1, but since we don't want to draw more than 2 indicators, we just stop after 2 iterations so we can just hard k = -2 instead
+        // we can use the below loop to draw arbitrary eventsByMonthAndYearMap based on the current screen size, for example, larger screens should be able to
+        // display more than 2 evens before displaying plus indicator, but don't draw more than 3 indicators for now
+        for (int j = 0, k = -2; j < 3; j++, k += 2) {
+            Event event = eventsList.get(j);
+            float xStartPosition = xPosition + (xIndicatorOffset * k);
+            if (j == 2) {
+                dayPaint.setColor(multiEventIndicatorColor);
+                dayPaint.setStrokeWidth(multiDayIndicatorStrokeWidth);
+                canvas.drawLine(xStartPosition - smallIndicatorRadius, yPosition, xStartPosition + smallIndicatorRadius, yPosition, dayPaint);
+                canvas.drawLine(xStartPosition, yPosition - smallIndicatorRadius, xStartPosition, yPosition + smallIndicatorRadius, dayPaint);
+                dayPaint.setStrokeWidth(0);
+            } else {
+                drawEventIndicatorCircle(canvas, xStartPosition, yPosition, event.getColor());
+            }
+        }
+    }
+
+    // zero based indexes used internally so instead of returning range of 1-7 like calendar class
+    // it returns 0-6 where 0 is Sunday instead of 1
+    int getDayOfWeek(Calendar calendar) {
+        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - firstDayOfWeekToDraw;
+        dayOfWeek = dayOfWeek < 0 ? 7 + dayOfWeek: dayOfWeek;
+        return dayOfWeek;
+    }
+
+    void drawMonth(Canvas canvas, Calendar monthToDrawCalender, int offset) {
+        drawEvents(canvas, monthToDrawCalender, offset);
+
+        //offset by one because we want to start from Monday
+        int firstDayOfMonth = getDayOfWeek(monthToDrawCalender);
+
+        boolean isSameMonthAsToday = monthToDrawCalender.get(Calendar.MONTH) == todayCalender.get(Calendar.MONTH);
+        boolean isSameYearAsToday = monthToDrawCalender.get(Calendar.YEAR) == todayCalender.get(Calendar.YEAR);
+        boolean isSameMonthAsCurrentCalendar = monthToDrawCalender.get(Calendar.MONTH) == currentCalender.get(Calendar.MONTH) &&
+                                               monthToDrawCalender.get(Calendar.YEAR) == currentCalender.get(Calendar.YEAR);
+        int todayDayOfMonth = todayCalender.get(Calendar.DAY_OF_MONTH);
+        boolean isAnimatingWithExpose = animationStatus == EXPOSE_CALENDAR_ANIMATION;
+
+        int maximumMonthDay = monthToDrawCalender.getActualMaximum(Calendar.DAY_OF_MONTH);
+
+        tempPreviousMonthCalendar.setTimeInMillis(monthToDrawCalender.getTimeInMillis());
+        tempPreviousMonthCalendar.add(Calendar.MONTH, -1);
+        int maximumPreviousMonthDay = tempPreviousMonthCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);
+
+        for (int dayColumn = 0, colDirection = isRtl? 6 : 0, dayRow = 0; dayColumn <= 6; dayRow++) {
+            if (dayRow == 7) {
+                if (isRtl) {
+                    colDirection--;
+                } else {
+                    colDirection++;
+                }
+                dayRow = 0;
+                if (dayColumn <= 6) {
+                    dayColumn++;
+                }
+            }
+            if (dayColumn == dayColumnNames.length) {
+                break;
+            }
+            float xPosition = widthPerDay * dayColumn + paddingWidth + paddingLeft + accumulatedScrollOffset.x + offset - paddingRight;
+            float yPosition = dayRow * heightPerDay + paddingHeight;
+            if (xPosition >= growFactor && (isAnimatingWithExpose || animationStatus == ANIMATE_INDICATORS) || yPosition >= growFactor) {
+                // don't draw days if animating expose or indicators
+                continue;
+            }
+            if (dayRow == 0) {
+                // first row, so draw the first letter of the day
+                if (shouldDrawDaysHeader) {
+                    dayPaint.setColor(calenderTextColor);
+                    dayPaint.setTypeface(Typeface.DEFAULT_BOLD);
+                    dayPaint.setStyle(Paint.Style.FILL);
+                    dayPaint.setColor(calenderTextColor);
+                    canvas.drawText(dayColumnNames[colDirection], xPosition, paddingHeight, dayPaint);
+                    dayPaint.setTypeface(Typeface.DEFAULT);
+                }
+            } else {
+                int day = ((dayRow - 1) * 7 + colDirection + 1) - firstDayOfMonth;
+                int defaultCalenderTextColorToUse = calenderTextColor;
+                if (currentCalender.get(Calendar.DAY_OF_MONTH) == day && isSameMonthAsCurrentCalendar && !isAnimatingWithExpose) {
+                    drawDayCircleIndicator(currentSelectedDayIndicatorStyle, canvas, xPosition, yPosition, currentSelectedDayBackgroundColor);
+                    defaultCalenderTextColorToUse = currentSelectedDayTextColor;
+                } else if (isSameYearAsToday && isSameMonthAsToday && todayDayOfMonth == day && !isAnimatingWithExpose) {
+                    // TODO calculate position of circle in a more reliable way
+                    drawDayCircleIndicator(currentDayIndicatorStyle, canvas, xPosition, yPosition, currentDayBackgroundColor);
+                    defaultCalenderTextColorToUse = currentDayTextColor;
+                }
+                if (day <= 0) {
+                    if (displayOtherMonthDays) {
+                        // Display day month before
+                        dayPaint.setStyle(Paint.Style.FILL);
+                        dayPaint.setColor(otherMonthDaysTextColor);
+                        canvas.drawText(String.valueOf(maximumPreviousMonthDay + day), xPosition, yPosition, dayPaint);
+                    }
+                } else if (day > maximumMonthDay) {
+                    if (displayOtherMonthDays) {
+                        // Display day month after
+                        dayPaint.setStyle(Paint.Style.FILL);
+                        dayPaint.setColor(otherMonthDaysTextColor);
+                        canvas.drawText(String.valueOf(day - maximumMonthDay), xPosition, yPosition, dayPaint);
+                    }
+                } else {
+                    dayPaint.setStyle(Paint.Style.FILL);
+                    dayPaint.setColor(defaultCalenderTextColorToUse);
+                    canvas.drawText(String.valueOf(day), xPosition, yPosition, dayPaint);
+                }
+            }
+        }
+    }
+
+    private void drawDayCircleIndicator(int indicatorStyle, Canvas canvas, float x, float y, int color) {
+        drawDayCircleIndicator(indicatorStyle, canvas, x, y, color, 1);
+    }
+
+    private void drawDayCircleIndicator(int indicatorStyle, Canvas canvas, float x, float y, int color, float circleScale) {
+        float strokeWidth = dayPaint.getStrokeWidth();
+        if (indicatorStyle == NO_FILL_LARGE_INDICATOR) {
+            dayPaint.setStrokeWidth(2 * screenDensity);
+            dayPaint.setStyle(Paint.Style.STROKE);
+        } else {
+            dayPaint.setStyle(Paint.Style.FILL);
+        }
+        drawCircle(canvas, x, y, color, circleScale);
+        dayPaint.setStrokeWidth(strokeWidth);
+        dayPaint.setStyle(Paint.Style.FILL);
+    }
+
+    // Draw Circle on certain days to highlight them
+    private void drawCircle(Canvas canvas, float x, float y, int color, float circleScale) {
+        dayPaint.setColor(color);
+        if (animationStatus == ANIMATE_INDICATORS) {
+            float maxRadius = circleScale * bigCircleIndicatorRadius * 1.4f;
+            drawCircle(canvas, growfactorIndicator > maxRadius ? maxRadius: growfactorIndicator, x, y - (textHeight / 6));
+        } else {
+            drawCircle(canvas, circleScale * bigCircleIndicatorRadius, x, y - (textHeight / 6));
+        }
+    }
+
+    private void drawEventIndicatorCircle(Canvas canvas, float x, float y, int color) {
+        dayPaint.setColor(color);
+        if (eventIndicatorStyle == SMALL_INDICATOR) {
+            dayPaint.setStyle(Paint.Style.FILL);
+            drawCircle(canvas, smallIndicatorRadius, x, y);
+        } else if (eventIndicatorStyle == NO_FILL_LARGE_INDICATOR){
+            dayPaint.setStyle(Paint.Style.STROKE);
+            drawDayCircleIndicator(NO_FILL_LARGE_INDICATOR, canvas, x, y, color);
+        } else if (eventIndicatorStyle == FILL_LARGE_INDICATOR) {
+            drawDayCircleIndicator(FILL_LARGE_INDICATOR, canvas, x, y, color);
+        }
+    }
+
+    private void drawCircle(Canvas canvas, float radius, float x, float y) {
+        canvas.drawCircle(x, y, radius, dayPaint);
+    }
+}

+ 440 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/CompactCalendarView.java

@@ -0,0 +1,440 @@
+package com.modular.apputils.widget.compactcalender;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.v4.view.GestureDetectorCompat;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.widget.OverScroller;
+
+
+import com.modular.apputils.widget.compactcalender.domain.Event;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class CompactCalendarView extends View {
+
+    public static final int FILL_LARGE_INDICATOR = 1;
+    public static final int NO_FILL_LARGE_INDICATOR = 2;
+    public static final int SMALL_INDICATOR = 3;
+
+    private final AnimationHandler animationHandler;
+    private CompactCalendarController compactCalendarController;
+    private GestureDetectorCompat gestureDetector;
+    private boolean horizontalScrollEnabled = true;
+
+    public interface CompactCalendarViewListener {
+        public void onDayClick(Date dateClicked);
+        public void onMonthScroll(Date firstDayOfNewMonth);
+    }
+
+    public interface CompactCalendarAnimationListener {
+        public void onOpened();
+        public void onClosed();
+    }
+
+    private final GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
+        @Override
+        public void onLongPress(MotionEvent e) {
+        }
+
+        @Override
+        public boolean onSingleTapUp(MotionEvent e) {
+            compactCalendarController.onSingleTapUp(e);
+            invalidate();
+            return super.onSingleTapUp(e);
+        }
+
+        @Override
+        public boolean onDown(MotionEvent e) {
+            return true;
+        }
+
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+            return true;
+        }
+
+        @Override
+        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+            if(horizontalScrollEnabled) {
+                if (Math.abs(distanceX) > 0) {
+                    getParent().requestDisallowInterceptTouchEvent(true);
+
+                    compactCalendarController.onScroll(e1, e2, distanceX, distanceY);
+                    invalidate();
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    };
+
+    public CompactCalendarView(Context context) {
+        this(context, null);
+    }
+
+    public CompactCalendarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CompactCalendarView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        compactCalendarController = new CompactCalendarController(new Paint(), new OverScroller(getContext()),
+                new Rect(), attrs, getContext(),  Color.argb(255, 233, 84, 81),
+                Color.argb(255, 64, 64, 64), Color.argb(255, 219, 219, 219), VelocityTracker.obtain(),
+                Color.argb(255, 100, 68, 65), new EventsContainer(Calendar.getInstance()),
+                Locale.getDefault(), TimeZone.getDefault());
+        gestureDetector = new GestureDetectorCompat(getContext(), gestureListener);
+        animationHandler = new AnimationHandler(compactCalendarController, this);
+    }
+
+    public void setAnimationListener(CompactCalendarAnimationListener compactCalendarAnimationListener){
+        animationHandler.setCompactCalendarAnimationListener(compactCalendarAnimationListener);
+    }
+
+    /*
+    Use a custom locale for compact calendar and reinitialise the view.
+     */
+    public void setLocale(TimeZone timeZone, Locale locale){
+        compactCalendarController.setLocale(timeZone, locale);
+        invalidate();
+    }
+
+    /*
+    Compact calendar will use the locale to determine the abbreviation to use as the day column names.
+    The default is to use the default locale and to abbreviate the day names to one character.
+    Setting this to true will displace the short weekday string provided by java.
+     */
+    public void setUseThreeLetterAbbreviation(boolean useThreeLetterAbbreviation){
+        compactCalendarController.setUseWeekDayAbbreviation(useThreeLetterAbbreviation);
+        invalidate();
+    }
+
+    public void setCalendarBackgroundColor(final int calenderBackgroundColor) {
+        compactCalendarController.setCalenderBackgroundColor(calenderBackgroundColor);
+        invalidate();
+    }
+
+    /*
+    Sets the name for each day of the week. No attempt is made to adjust width or text size based on the length of each day name.
+    Works best with 3-4 characters for each day.
+     */
+    public void setDayColumnNames(String[] dayColumnNames){
+        compactCalendarController.setDayColumnNames(dayColumnNames);
+    }
+
+    public void setFirstDayOfWeek(int dayOfWeek){
+        compactCalendarController.setFirstDayOfWeek(dayOfWeek);
+        invalidate();
+    }
+
+    public void setCurrentSelectedDayBackgroundColor(int currentSelectedDayBackgroundColor) {
+        compactCalendarController.setCurrentSelectedDayBackgroundColor(currentSelectedDayBackgroundColor);
+        invalidate();
+    }
+
+    public void setCurrentDayBackgroundColor(int currentDayBackgroundColor) {
+        compactCalendarController.setCurrentDayBackgroundColor(currentDayBackgroundColor);
+        invalidate();
+    }
+
+    public int getHeightPerDay(){
+        return compactCalendarController.getHeightPerDay();
+    }
+
+    public void setListener(CompactCalendarViewListener listener){
+        compactCalendarController.setListener(listener);
+    }
+
+    public Date getFirstDayOfCurrentMonth(){
+        return compactCalendarController.getFirstDayOfCurrentMonth();
+    }
+
+    public void shouldDrawIndicatorsBelowSelectedDays(boolean shouldDrawIndicatorsBelowSelectedDays){
+        compactCalendarController.shouldDrawIndicatorsBelowSelectedDays(shouldDrawIndicatorsBelowSelectedDays);
+    }
+
+    public void setCurrentDate(Date dateTimeMonth){
+        compactCalendarController.setCurrentDate(dateTimeMonth);
+        invalidate();
+    }
+
+    public int getWeekNumberForCurrentMonth(){
+        return compactCalendarController.getWeekNumberForCurrentMonth();
+    }
+
+    public void setShouldDrawDaysHeader(boolean shouldDrawDaysHeader){
+        compactCalendarController.setShouldDrawDaysHeader(shouldDrawDaysHeader);
+    }
+
+    public void setCurrentSelectedDayTextColor(int currentSelectedDayTextColor) {
+        compactCalendarController.setCurrentSelectedDayTextColor(currentSelectedDayTextColor);
+    }
+
+    public void setCurrentDayTextColor(int currentDayTextColor) {
+        compactCalendarController.setCurrentDayTextColor(currentDayTextColor);
+    }
+
+    /**
+     * see {@link #addEvent(Event, boolean)} when adding single events to control if calendar should redraw
+     * or {@link #addEvents(java.util.List)}  when adding multiple events
+     * @param event
+     */
+    public void addEvent(Event event){
+        addEvent(event, true);
+    }
+
+    /**
+     *  Adds an event to be drawn as an indicator in the calendar.
+     *  If adding multiple events see {@link #addEvents(List)}} method.
+     * @param event to be added to the calendar
+     * @param shouldInvalidate true if the view should invalidate
+     */
+    public void addEvent(Event event, boolean shouldInvalidate){
+        compactCalendarController.addEvent(event);
+        if(shouldInvalidate){
+            invalidate();
+        }
+    }
+
+    /**
+     * Adds multiple events to the calendar and invalidates the view once all events are added.
+     */
+    public void addEvents(List<Event> events){
+        compactCalendarController.addEvents(events);
+        invalidate();
+    }
+
+    /**
+     * Fetches the events for the date passed in
+     * @param date
+     * @return
+     */
+    public List<Event> getEvents(Date date){
+        return compactCalendarController.getCalendarEventsFor(date.getTime());
+    }
+
+    /**
+     * Fetches the events for the epochMillis passed in
+     * @param epochMillis
+     * @return
+     */
+    public List<Event> getEvents(long epochMillis){
+        return compactCalendarController.getCalendarEventsFor(epochMillis);
+    }
+
+    /**
+     * Fetches the events for the month of the epochMillis passed in and returns a sorted list of events
+     * @param epochMillis
+     * @return
+     */
+    public List<Event> getEventsForMonth(long epochMillis){
+        return compactCalendarController.getCalendarEventsForMonth(epochMillis);
+    }
+
+    /**
+     * Fetches the events for the month of the date passed in and returns a sorted list of events
+     * @param date
+     * @return
+     */
+    public List<Event> getEventsForMonth(Date date){
+        return compactCalendarController.getCalendarEventsForMonth(date.getTime());
+    }
+
+    /**
+     * Remove the event associated with the Date passed in
+     * @param date
+     */
+    public void removeEvents(Date date){
+        compactCalendarController.removeEventsFor(date.getTime());
+    }
+
+    public void removeEvents(long epochMillis){
+        compactCalendarController.removeEventsFor(epochMillis);
+    }
+
+    /**
+     * see {@link #removeEvent(Event, boolean)} when removing single events to control if calendar should redraw
+     * or {@link #removeEvents(java.util.List)} (java.util.List)}  when removing multiple events
+     * @param event
+     */
+    public void removeEvent(Event event){
+        removeEvent(event, true);
+    }
+
+    /**
+     * Removes an event from the calendar.
+     * If removing multiple events see {@link #removeEvents(List)}
+     *
+     * @param event event to remove from the calendar
+     * @param shouldInvalidate true if the view should invalidate
+     */
+    public void removeEvent(Event event, boolean shouldInvalidate){
+        compactCalendarController.removeEvent(event);
+        if(shouldInvalidate){
+            invalidate();
+        }
+    }
+
+    /**
+     * Removes multiple events from the calendar and invalidates the view once all events are added.
+     */
+    public void removeEvents(List<Event> events){
+        compactCalendarController.removeEvents(events);
+        invalidate();
+    }
+
+    /**
+     * Clears all Events from the calendar.
+     */
+    public void removeAllEvents() {
+        compactCalendarController.removeAllEvents();
+        invalidate();
+    }
+
+    public void setIsRtl(boolean isRtl) {
+        compactCalendarController.setIsRtl(isRtl);
+    }
+
+    public void shouldSelectFirstDayOfMonthOnScroll(boolean shouldSelectFirstDayOfMonthOnScroll){
+        compactCalendarController.setShouldSelectFirstDayOfMonthOnScroll(shouldSelectFirstDayOfMonthOnScroll);
+    }
+
+    public void setCurrentSelectedDayIndicatorStyle(final int currentSelectedDayIndicatorStyle){
+        compactCalendarController.setCurrentSelectedDayIndicatorStyle(currentSelectedDayIndicatorStyle);
+        invalidate();
+    }
+
+    public void setCurrentDayIndicatorStyle(final int currentDayIndicatorStyle){
+        compactCalendarController.setCurrentDayIndicatorStyle(currentDayIndicatorStyle);
+        invalidate();
+    }
+
+    public void setEventIndicatorStyle(final int eventIndicatorStyle){
+        compactCalendarController.setEventIndicatorStyle(eventIndicatorStyle);
+        invalidate();
+    }
+
+    private void checkTargetHeight() {
+        if (compactCalendarController.getTargetHeight() <= 0) {
+            throw new IllegalStateException("Target height must be set in xml properties in order to expand/collapse CompactCalendar.");
+        }
+    }
+
+    public void displayOtherMonthDays(boolean displayOtherMonthDays){
+        compactCalendarController.setDisplayOtherMonthDays(displayOtherMonthDays);
+        invalidate();
+    }
+
+    public void setTargetHeight(int targetHeight){
+        compactCalendarController.setTargetHeight(targetHeight);
+        checkTargetHeight();
+    }
+
+    public void showCalendar(){
+        checkTargetHeight();
+        animationHandler.openCalendar();
+    }
+
+    public void hideCalendar(){
+        checkTargetHeight();
+        animationHandler.closeCalendar();
+    }
+
+    public void showCalendarWithAnimation(){
+        checkTargetHeight();
+        animationHandler.openCalendarWithAnimation();
+    }
+
+    public void hideCalendarWithAnimation(){
+        checkTargetHeight();
+        animationHandler.closeCalendarWithAnimation();
+    }
+
+    /**
+     * Moves the calendar to the right. This will show the next month when {@link #setIsRtl(boolean)}
+     * is set to false. If in rtl mode, it will show the previous month.
+     */
+    public void scrollRight(){
+        compactCalendarController.scrollRight();
+        invalidate();
+    }
+
+    /**
+     * Moves the calendar to the left. This will show the previous month when {@link #setIsRtl(boolean)}
+     * is set to false. If in rtl mode, it will show the next month.
+     */
+    public void scrollLeft(){
+        compactCalendarController.scrollLeft();
+        invalidate();
+    }
+
+    public boolean isAnimating(){
+        return animationHandler.isAnimating();
+    }
+
+    @Override
+    protected void onMeasure(int parentWidth, int parentHeight) {
+        super.onMeasure(parentWidth, parentHeight);
+        int width = MeasureSpec.getSize(parentWidth);
+        int height = MeasureSpec.getSize(parentHeight);
+        if(width > 0 && height > 0) {
+            compactCalendarController.onMeasure(width, height, getPaddingRight(), getPaddingLeft());
+        }
+        setMeasuredDimension(width, height);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        compactCalendarController.onDraw(canvas);
+    }
+
+    @Override
+    public void computeScroll() {
+        super.computeScroll();
+        if(compactCalendarController.computeScroll()){
+            invalidate();
+        }
+    }
+
+    public void shouldScrollMonth(boolean enableHorizontalScroll){
+        this.horizontalScrollEnabled = enableHorizontalScroll;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        if (horizontalScrollEnabled) {
+            compactCalendarController.onTouch(event);
+            invalidate();
+        }
+
+        // on touch action finished (CANCEL or UP), we re-allow the parent container to intercept touch events (scroll inside ViewPager + RecyclerView issue #82)
+        if((event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) && horizontalScrollEnabled) {
+            getParent().requestDisallowInterceptTouchEvent(false);
+        }
+
+        // always allow gestureDetector to detect onSingleTap and scroll events
+        return gestureDetector.onTouchEvent(event);
+    }
+
+    @Override
+    public boolean canScrollHorizontally(int direction) {
+        if (this.getVisibility() == View.GONE) {
+            return false;
+        }
+        // Prevents ViewPager from scrolling horizontally by announcing that (issue #82)
+        return this.horizontalScrollEnabled;
+    }
+
+}

+ 52 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/Events.java

@@ -0,0 +1,52 @@
+package com.modular.apputils.widget.compactcalender;
+
+import com.modular.apputils.widget.compactcalender.domain.Event;
+
+import java.util.List;
+
+class Events {
+
+    private final List<Event> events;
+    private final long timeInMillis;
+
+    Events(long timeInMillis, List<Event> events) {
+        this.timeInMillis = timeInMillis;
+        this.events = events;
+    }
+
+    long getTimeInMillis() {
+        return timeInMillis;
+    }
+
+    List<Event> getEvents() {
+        return events;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Events event = (Events) o;
+
+        if (timeInMillis != event.timeInMillis) return false;
+        if (events != null ? !events.equals(event.events) : event.events != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = events != null ? events.hashCode() : 0;
+        result = 31 * result + (int) (timeInMillis ^ (timeInMillis >>> 32));
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Events{" +
+                "events=" + events +
+                ", timeInMillis=" + timeInMillis +
+                '}';
+    }
+}

+ 158 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/EventsContainer.java

@@ -0,0 +1,158 @@
+package com.modular.apputils.widget.compactcalender;
+
+import com.modular.apputils.widget.compactcalender.comparators.EventComparator;
+import com.modular.apputils.widget.compactcalender.domain.Event;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class EventsContainer {
+
+    private Map<String, List<Events>> eventsByMonthAndYearMap = new HashMap<>();
+    private Comparator<Event> eventsComparator = new EventComparator();
+    private Calendar eventsCalendar;
+
+    public EventsContainer(Calendar eventsCalendar) {
+        this.eventsCalendar = eventsCalendar;
+    }
+
+    void addEvent(Event event) {
+        eventsCalendar.setTimeInMillis(event.getTimeInMillis());
+        String key = getKeyForCalendarEvent(eventsCalendar);
+        List<Events> eventsForMonth = eventsByMonthAndYearMap.get(key);
+        if (eventsForMonth == null) {
+            eventsForMonth = new ArrayList<>();
+        }
+        Events eventsForTargetDay = getEventDayEvent(event.getTimeInMillis());
+        if (eventsForTargetDay == null) {
+            List<Event> events = new ArrayList<>();
+            events.add(event);
+            eventsForMonth.add(new Events(event.getTimeInMillis(), events));
+        } else {
+            eventsForTargetDay.getEvents().add(event);
+        }
+        eventsByMonthAndYearMap.put(key, eventsForMonth);
+    }
+
+    void removeAllEvents() {
+        eventsByMonthAndYearMap.clear();
+    }
+
+    void addEvents(List<Event> events) {
+        int count = events.size();
+        for (int i = 0; i < count; i++) {
+            addEvent(events.get(i));
+        }
+    }
+
+    List<Event> getEventsFor(long epochMillis) {
+        Events events = getEventDayEvent(epochMillis);
+        if (events == null) {
+            return new ArrayList<>();
+        } else {
+            return events.getEvents();
+        }
+    }
+
+    List<Events> getEventsForMonthAndYear(int month, int year){
+        return eventsByMonthAndYearMap.get(year + "_" + month);
+    }
+
+    List<Event> getEventsForMonth(long eventTimeInMillis){
+        eventsCalendar.setTimeInMillis(eventTimeInMillis);
+        String keyForCalendarEvent = getKeyForCalendarEvent(eventsCalendar);
+        List<Events> events = eventsByMonthAndYearMap.get(keyForCalendarEvent);
+        List<Event> allEventsForMonth = new ArrayList<>();
+        if (events != null) {
+            for(Events eve : events){
+                if (eve != null) {
+                    allEventsForMonth.addAll(eve.getEvents());
+                }
+            }
+        }
+        Collections.sort(allEventsForMonth, eventsComparator);
+        return allEventsForMonth;
+    }
+
+    private Events getEventDayEvent(long eventTimeInMillis){
+        eventsCalendar.setTimeInMillis(eventTimeInMillis);
+        int dayInMonth = eventsCalendar.get(Calendar.DAY_OF_MONTH);
+        String keyForCalendarEvent = getKeyForCalendarEvent(eventsCalendar);
+        List<Events> eventsForMonthsAndYear = eventsByMonthAndYearMap.get(keyForCalendarEvent);
+        if (eventsForMonthsAndYear != null) {
+            for (Events events : eventsForMonthsAndYear) {
+                eventsCalendar.setTimeInMillis(events.getTimeInMillis());
+                int dayInMonthFromCache = eventsCalendar.get(Calendar.DAY_OF_MONTH);
+                if (dayInMonthFromCache == dayInMonth) {
+                    return events;
+                }
+            }
+        }
+        return null;
+    }
+
+    void removeEventByEpochMillis(long epochMillis) {
+        eventsCalendar.setTimeInMillis(epochMillis);
+        int dayInMonth = eventsCalendar.get(Calendar.DAY_OF_MONTH);
+        String key = getKeyForCalendarEvent(eventsCalendar);
+        List<Events> eventsForMonthAndYear = eventsByMonthAndYearMap.get(key);
+        if (eventsForMonthAndYear != null) {
+            Iterator<Events> calendarDayEventIterator = eventsForMonthAndYear.iterator();
+            while (calendarDayEventIterator.hasNext()) {
+                Events next = calendarDayEventIterator.next();
+                eventsCalendar.setTimeInMillis(next.getTimeInMillis());
+                int dayInMonthFromCache = eventsCalendar.get(Calendar.DAY_OF_MONTH);
+                if (dayInMonthFromCache == dayInMonth) {
+                    calendarDayEventIterator.remove();
+                    break;
+                }
+            }
+            if (eventsForMonthAndYear.isEmpty()) {
+                eventsByMonthAndYearMap.remove(key);
+            }
+        }
+    }
+
+    void removeEvent(Event event) {
+        eventsCalendar.setTimeInMillis(event.getTimeInMillis());
+        String key = getKeyForCalendarEvent(eventsCalendar);
+        List<Events> eventsForMonthAndYear = eventsByMonthAndYearMap.get(key);
+        if (eventsForMonthAndYear != null) {
+            Iterator<Events> eventsForMonthYrItr = eventsForMonthAndYear.iterator();
+            while(eventsForMonthYrItr.hasNext()) {
+                Events events = eventsForMonthYrItr.next();
+                int indexOfEvent = events.getEvents().indexOf(event);
+                if (indexOfEvent >= 0) {
+                    if (events.getEvents().size() == 1) {
+                        eventsForMonthYrItr.remove();
+                    } else {
+                        events.getEvents().remove(indexOfEvent);
+                    }
+                    break;
+                }
+            }
+            if (eventsForMonthAndYear.isEmpty()) {
+                eventsByMonthAndYearMap.remove(key);
+            }
+        }
+    }
+
+    void removeEvents(List<Event> events) {
+        int count = events.size();
+        for (int i = 0; i < count; i++) {
+            removeEvent(events.get(i));
+        }
+    }
+
+    //E.g. 4 2016 becomes 2016_4
+    private String getKeyForCalendarEvent(Calendar cal) {
+        return cal.get(Calendar.YEAR) + "_" + cal.get(Calendar.MONTH);
+    }
+
+}

+ 175 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/Lunar.java

@@ -0,0 +1,175 @@
+package com.modular.apputils.widget.compactcalender;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class Lunar {
+    private int year;
+    private int month;
+    private int day;
+    private boolean leap;
+    final static String chineseNumber[] = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"};
+    static SimpleDateFormat chineseDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
+    final static long[] lunarInfo = new long[]
+            {0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
+                    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
+                    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
+                    0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
+                    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
+                    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
+                    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
+                    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
+                    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
+                    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
+                    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
+                    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
+                    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
+                    0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
+                    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0};
+
+    //====== 传回农历 y年的总天数
+    final private static int yearDays(int y) {
+        int i, sum = 348;
+        for (i = 0x8000; i > 0x8; i >>= 1) {
+            if ((lunarInfo[y - 1900] & i) != 0) sum += 1;
+        }
+        return (sum + leapDays(y));
+    }
+
+    //====== 传回农历 y年闰月的天数
+    final private static int leapDays(int y) {
+        if (leapMonth(y) != 0) {
+            if ((lunarInfo[y - 1900] & 0x10000) != 0)
+                return 30;
+            else
+                return 29;
+        } else
+            return 0;
+    }
+
+    //====== 传回农历 y年闰哪个月 1-12 , 没闰传回 0
+    final private static int leapMonth(int y) {
+        return (int) (lunarInfo[y - 1900] & 0xf);
+    }
+
+    //====== 传回农历 y年m月的总天数
+    final private static int monthDays(int y, int m) {
+        if ((lunarInfo[y - 1900] & (0x10000 >> m)) == 0)
+            return 29;
+        else
+            return 30;
+    }
+
+    //====== 传回农历 y年的生肖
+    final public String animalsYear() {
+        final String[] Animals = new String[]{"鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"};
+        return Animals[(year - 4) % 12];
+    }
+
+    //====== 传入 月日的offset 传回干支, 0=甲子
+    final private static String cyclicalm(int num) {
+        final String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};
+        final String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};
+        return (Gan[num % 10] + Zhi[num % 12]);
+    }
+
+    //====== 传入 offset 传回干支, 0=甲子
+    final public String cyclical() {
+        int num = year - 1900 + 36;
+        return (cyclicalm(num));
+    }
+
+    public String getLunarMonthString() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public Lunar(Date date) {
+        @SuppressWarnings("unused") int yearCyl, monCyl, dayCyl;
+        int leapMonth = 0;
+        Date baseDate = null;
+        try {
+            baseDate = chineseDateFormat.parse("1900年1月31日");
+        } catch (ParseException e) {
+            e.printStackTrace(); //To change body of catch statement use Options | File Templates.
+        }
+
+        //求出和1900年1月31日相差的天数
+        int offset = (int) ((date.getTime() - baseDate.getTime()) / 86400000L);
+        dayCyl = offset + 40;
+        monCyl = 14;
+
+        //用offset减去每农历年的天数
+        // 计算当天是农历第几天
+        //i最终结果是农历的年份
+        //offset是当年的第几天
+        int iYear, daysOfYear = 0;
+        for (iYear = 1900; iYear < 2050 && offset > 0; iYear++) {
+            daysOfYear = yearDays(iYear);
+            offset -= daysOfYear;
+            monCyl += 12;
+        }
+        if (offset < 0) {
+            offset += daysOfYear;
+            iYear--;
+            monCyl -= 12;
+        }
+        //农历年份
+        year = iYear;
+
+        yearCyl = iYear - 1864;
+        leapMonth = leapMonth(iYear); //闰哪个月,1-12
+        leap = false;
+
+        //用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
+        int iMonth, daysOfMonth = 0;
+        for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) {
+            //闰月
+            if (leapMonth > 0 && iMonth == (leapMonth + 1) && !leap) {
+                --iMonth;
+                leap = true;
+                daysOfMonth = leapDays(year);
+            } else
+                daysOfMonth = monthDays(year, iMonth);
+
+            offset -= daysOfMonth;
+            //解除闰月
+            if (leap && iMonth == (leapMonth + 1)) leap = false;
+            if (!leap) monCyl++;
+        }
+        //offset为0时,并且刚才计算的月份是闰月,要校正
+        if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) {
+            if (leap) {
+                leap = false;
+            } else {
+                leap = true;
+                --iMonth;
+                --monCyl;
+            }
+        }
+        //offset小于0时,也要校正
+        if (offset < 0) {
+            offset += daysOfMonth;
+            --iMonth;
+            --monCyl;
+        }
+        month = iMonth;
+        day = offset + 1;
+    }
+
+    public static String getChinaDayString(int day) {
+        String chineseTen[] = {"初", "十", "廿", "三"};
+        int n = day % 10 == 0 ? 9 : day % 10 - 1;
+        if (day > 30)
+            return "";
+        if (day == 10)
+            return "初十";
+        else
+            return chineseTen[day / 10] + chineseNumber[n];
+    }
+
+    public String toString() {
+        return (leap ? "闰" : "") + chineseNumber[month - 1] + "月" + getChinaDayString(day);
+    }
+}

+ 1 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/README.md

@@ -0,0 +1 @@
+[来源](https://github.com/SundeepK/CompactCalendarView)

+ 37 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/WeekUtils.java

@@ -0,0 +1,37 @@
+package com.modular.apputils.widget.compactcalender;
+
+import java.text.DateFormatSymbols;
+import java.util.Arrays;
+import java.util.Locale;
+
+public class WeekUtils {
+
+    static String[] getWeekdayNames(Locale locale, int day, boolean useThreeLetterAbbreviation){
+        DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(locale);
+        String[] dayNames = dateFormatSymbols.getShortWeekdays();
+        if (dayNames == null) {
+            throw new IllegalStateException("Unable to determine weekday names from default locale");
+        }
+        if (dayNames.length != 8) {
+            throw new IllegalStateException("Expected weekday names from default locale to be of size 7 but: "
+                    + Arrays.toString(dayNames) + " with size " + dayNames.length + " was returned.");
+        }
+
+        String[] weekDayNames = new String[7];
+        String[] weekDaysFromSunday = {dayNames[1], dayNames[2], dayNames[3], dayNames[4], dayNames[5], dayNames[6], dayNames[7]};
+        for (int currentDay = day - 1, i = 0; i <= 6; i++, currentDay++) {
+            currentDay = currentDay >= 7 ? 0 : currentDay;
+            weekDayNames[i] = weekDaysFromSunday[currentDay];
+        }
+
+        if (!useThreeLetterAbbreviation) {
+            for (int i = 0; i < weekDayNames.length; i++) {
+                weekDayNames[i] = weekDayNames[i].substring(0, 1);
+            }
+        }
+
+        return weekDayNames;
+    }
+
+
+}

+ 14 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/comparators/EventComparator.java

@@ -0,0 +1,14 @@
+package com.modular.apputils.widget.compactcalender.comparators;
+
+
+import com.modular.apputils.widget.compactcalender.domain.Event;
+
+import java.util.Comparator;
+
+public class EventComparator implements Comparator<Event> {
+
+    @Override
+    public int compare(Event lhs, Event rhs) {
+        return lhs.getTimeInMillis() < rhs.getTimeInMillis() ? -1 : lhs.getTimeInMillis() == rhs.getTimeInMillis() ? 0 : 1;
+    }
+}

+ 65 - 0
app_modular/apputils/src/main/java/com/modular/apputils/widget/compactcalender/domain/Event.java

@@ -0,0 +1,65 @@
+package com.modular.apputils.widget.compactcalender.domain;
+
+import android.support.annotation.Nullable;
+
+public class Event {
+
+    private int color;
+    private long timeInMillis;
+    private Object data;
+
+    public Event(int color, long timeInMillis) {
+        this.color = color;
+        this.timeInMillis = timeInMillis;
+    }
+
+    public Event(int color, long timeInMillis, Object data) {
+        this.color = color;
+        this.timeInMillis = timeInMillis;
+        this.data = data;
+    }
+
+    public int getColor() {
+        return color;
+    }
+
+    public long getTimeInMillis() {
+        return timeInMillis;
+    }
+
+    @Nullable
+    public Object getData() {
+        return data;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Event event = (Event) o;
+
+        if (color != event.color) return false;
+        if (timeInMillis != event.timeInMillis) return false;
+        if (data != null ? !data.equals(event.data) : event.data != null) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = color;
+        result = 31 * result + (int) (timeInMillis ^ (timeInMillis >>> 32));
+        result = 31 * result + (data != null ? data.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Event{" +
+                "color=" + color +
+                ", timeInMillis=" + timeInMillis +
+                ", data=" + data +
+                '}';
+    }
+}

+ 31 - 0
app_modular/apputils/src/main/res/values/attrs.xml

@@ -9,4 +9,35 @@
         <attr name="indicator_selected_color" format="color" />
         <attr name="indicator_bottommargin" format="dimension" />
     </declare-styleable>
+
+    <attr name="compactCalendarEventIndicatorStyle" format="enum">
+        <enum name="fill_large_indicator" value="1" />
+        <enum name="no_fill_large_indicator" value="2" />
+        <enum name="small_indicator" value="3" />
+    </attr>
+    <attr name="compactCalendarCurrentDayIndicatorStyle" format="enum">
+        <enum name="fill_large_indicator" value="1" />
+        <enum name="no_fill_large_indicator" value="2" />
+    </attr>
+    <attr name="compactCalendarCurrentSelectedDayIndicatorStyle" format="enum">
+        <enum name="fill_large_indicator" value="1" />
+        <enum name="no_fill_large_indicator" value="2" />
+    </attr>
+    <declare-styleable name="CompactCalendarView">
+        <attr name="compactCalendarTextSize" format="dimension"/>
+        <attr name="compactCalendarBackgroundColor" format="color"/>
+        <attr name="compactCalendarCurrentDayBackgroundColor" format="color"/>
+        <attr name="compactCalendarCurrentDayTextColor" format="color"/>
+        <attr name="compactCalendarCurrentSelectedDayBackgroundColor" format="color"/>
+        <attr name="compactCalendarCurrentSelectedDayTextColor" format="color"/>
+        <attr name="compactCalendarTextColor" format="color"/>
+        <attr name="compactCalendarMultiEventIndicatorColor" format="color"/>
+        <attr name="compactCalendarTargetHeight" format="dimension"/>
+        <attr name="compactCalendarEventIndicatorStyle"/>
+        <attr name="compactCalendarCurrentDayIndicatorStyle"/>
+        <attr name="compactCalendarCurrentSelectedDayIndicatorStyle"/>
+        <attr name="compactCalendarDisplayOtherMonthDays" format="boolean"/>
+        <attr name="compactCalendarShouldSelectFirstDayOfMonthOnScroll" format="boolean"/>
+        <attr name="compactCalendarOtherMonthDaysTextColor" format="color"/>
+    </declare-styleable>
 </resources>

+ 5 - 0
app_modular/apputils/src/main/res/values/dimens.xml

@@ -2,4 +2,9 @@
 <resources>
     <dimen name="textXXL">20sp</dimen>
     <dimen name="textXL">18sp</dimen>
+
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="common_vertical_margin">10dp</dimen>
+    <dimen name="common_left_margin">16dp</dimen>
 </resources>

+ 12 - 0
app_modular/apputils/src/main/res/values/strings.xml

@@ -5,4 +5,16 @@
     <string name="str_error_wechat_pay_cancel">支付已取消</string>
     <string name="click_to"><u>点击前往</u></string>
     <string name="administrators_phone"><u>13430818775</u></string>
+
+
+    <string name="monday">一</string>
+    <string name="tuseday">二</string>
+    <string name="wednesday">三</string>
+    <string name="thursday">四</string>
+    <string name="friday">五</string>
+    <string name="saturday">六</string>
+    <string name="sunday">日</string>
+    <string name="hot_news">热点事件</string>
+    <string name="kou_hao">标新 ,立异</string>
+    <string name="advertisement">新一代梅赛德斯</string>
 </resources>

+ 39 - 41
app_modular/appworks/src/main/AndroidManifest.xml

@@ -1,23 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest package="com.uas.appworks"
-          xmlns:android="http://schemas.android.com/apk/res/android">
-
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.uas.appworks">
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
     <application
         android:allowBackup="true"
         android:label="@string/app_name"
         android:supportsRtl="true">
-        <activity
-            android:name=".OA.erp.activity.DailydetailsActivity" />
-        <activity
-            android:name=".OA.erp.activity.WorkDailyAddActivity">
+        <activity android:name=".OA.erp.activity.DailydetailsActivity" />
+        <activity android:name=".OA.erp.activity.WorkDailyAddActivity">
             <intent-filter>
                 <action android:name="com.modular.plat.WorkDailyAddActivity" />
 
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-        <activity
-            android:name=".OA.erp.activity.WorkDailyShowActivity" />
+        <activity android:name=".OA.erp.activity.WorkDailyShowActivity" />
         <activity android:name=".OA.platform.activity.PlatDailyShowActivity" />
         <activity android:name=".OA.platform.activity.PlatWDdetailyActivity" />
         <activity
@@ -64,7 +62,6 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-
         <activity
             android:name=".OA.erp.activity.form.WorkLogsActivity"
             android:label="打卡记录"
@@ -101,22 +98,17 @@
             android:label="@string/meet_task" />
         <activity android:name=".CRM.erp.activity.DbfindListActivity" />
         <activity android:name=".CRM.erp.activity.DbfindList2Activity" />
-        <activity
-            android:name=".CRM.erp.activity.BusinessActivity">
+        <activity android:name=".CRM.erp.activity.BusinessActivity">
             <intent-filter>
                 <action android:name="com.modular.crm.BusinessActivity" />
 
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-        <activity
-            android:name=".CRM.erp.activity.BusinessDetailActivty" />
-        <activity
-            android:name=".CRM.erp.activity.BusinessDetailInfoActivity" />
-        <activity
-            android:name=".CRM.erp.activity.BusinessLessActivity" />
-        <activity
-            android:name=".CRM.erp.activity.BusinessTransferActivity" />
+        <activity android:name=".CRM.erp.activity.BusinessDetailActivty" />
+        <activity android:name=".CRM.erp.activity.BusinessDetailInfoActivity" />
+        <activity android:name=".CRM.erp.activity.BusinessLessActivity" />
+        <activity android:name=".CRM.erp.activity.BusinessTransferActivity" />
         <activity
             android:name=".CRM.erp.activity.BusinessSelectCustomerActivity"
             android:launchMode="singleTask"
@@ -136,18 +128,15 @@
             android:name=".CRM.erp.activity.AddBusinessActivity"
             android:label="@string/crm_creat_business" />
         <activity android:name=".CRM.erp.activity.SearchSelectActivity" />
-        <activity
-            android:name=".CRM.erp.activity.CustomerDetailActivity" />
-        <activity
-            android:name=".CRM.erp.activity.CustomerListActivity">
+        <activity android:name=".CRM.erp.activity.CustomerDetailActivity" />
+        <activity android:name=".CRM.erp.activity.CustomerListActivity">
             <intent-filter>
                 <action android:name="com.modular.crm.CustomerListActivity" />
 
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-        <activity
-            android:name=".CRM.erp.activity.CustomerListSelectActivity" />
+        <activity android:name=".CRM.erp.activity.CustomerListSelectActivity" />
         <activity
             android:name=".CRM.erp.activity.UserSelectActivity"
             android:label="@string/select_user" />
@@ -234,16 +223,18 @@
         <activity android:name=".OA.platform.activity.CharitSearchActivity" />
         <activity android:name=".OA.platform.activity.JoinCharitActivity" />
 
-        <!--数据统计,报表查询-->
+        <!-- 数据统计,报表查询 -->
         <activity android:name=".datainquiry.activity.ReportStatisticsActivity">
             <intent-filter>
                 <action android:name="com.modular.company.ReportStatisticsActivity" />
+
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
         <activity android:name=".datainquiry.activity.DataInquiryActivity">
             <intent-filter>
                 <action android:name="com.modular.company.DataInquiryActivity" />
+
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -261,7 +252,6 @@
         <activity android:name=".datainquiry.activity.DataInquirySearchActivity" />
         <activity android:name=".datainquiry.activity.ReportQuerySearchActivity" />
 
-
         <!-- 工作台模块设置页面 -->
         <activity android:name=".activity.WorkModuleSortActivity">
             <intent-filter>
@@ -280,8 +270,7 @@
         </activity>
 
         <!-- 产城服务主页面 -->
-        <activity
-            android:name=".activity.CityIndustryServiceMainActivity">
+        <activity android:name=".activity.CityIndustryServiceMainActivity">
             <intent-filter>
                 <action android:name="com.modular.cityIndustry.CityIndustryServiceMainActivity" />
 
@@ -328,7 +317,7 @@
             </intent-filter>
         </activity>
 
-        <!--设备管理-->
+        <!-- 设备管理 -->
         <activity
             android:name=".CRM.erp.activity.DeviceManageActivity"
             android:label="@string/title_device_manage"
@@ -372,8 +361,7 @@
         <activity
             android:name=".CRM.erp.activity.DeviceCycleCountAddActivity"
             android:label="@string/text_cycle_count_add"
-            android:theme="@style/MainBaseTheme"
-            />
+            android:theme="@style/MainBaseTheme" />
         <activity
             android:name=".OA.platform.activity.BusinessTravelActivity"
             android:label="@string/business_services"
@@ -395,7 +383,7 @@
             </intent-filter>
         </activity>
 
-        <!--B2B商务-->
+        <!-- B2B商务 -->
         <activity
             android:name=".activity.B2BBusinessMainActivity"
             android:hardwareAccelerated="true">
@@ -428,12 +416,12 @@
             android:theme="@style/StyledBlueIndicators"
             android:windowSoftInputMode="adjustPan|stateHidden" />
         <activity android:name=".activity.PublicInquiryDetailActivity" />
-
         <activity
             android:name=".activity.CustomerInquiryDetailActivity"
             android:windowSoftInputMode="adjustResize|stateHidden">
             <intent-filter>
                 <action android:name="com.modular.work.platform.activity.CustomerInquiryDetailActivity" />
+
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -442,46 +430,56 @@
             android:theme="@style/MainBaseTheme">
             <intent-filter>
                 <action android:name="com.modular.work.platform.activity.PurchaseDetailsActivity" />
+
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-
         <activity
             android:name=".activity.CompanyBusinessListActivity"
             android:hardwareAccelerated="true"
             android:theme="@style/StyledBlueIndicators"
             android:windowSoftInputMode="adjustPan|stateHidden" />
-
         <activity
             android:name=".activity.PublicInquiryQuoteActivity"
             android:windowSoftInputMode="adjustPan|stateHidden">
             <intent-filter>
                 <action android:name="com.modular.work.platform.activity.PublicInquiryQuoteActivity" />
+
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-        <!--B2B商务end-->
+        <!-- B2B商务end -->
 
-        <!--邀请注册-->
+
+        <!-- 邀请注册 -->
         <activity android:name=".activity.InviteRegisterActivity">
             <intent-filter>
                 <action android:name="com.modular.work.InviteRegisterActivity" />
+
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-
         <activity
             android:name=".activity.InviteRegisterMainActivity"
             android:hardwareAccelerated="true">
             <intent-filter>
                 <action android:name="com.modular.work.InviteRegisterMainActivity" />
+
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
-
         <activity android:name=".activity.InviteRegisterListActivity" />
         <activity android:name=".activity.RegisterDetailActivity" />
         <activity android:name=".activity.EnterpriseInviteStatisticsActivity" />
+        <activity android:name=".activity.ScheduleSearchActivity" />
+        <activity android:name=".activity.SchedulerCreateActivity" />
+        <activity android:name=".activity.ScheduleActivity">
+            <intent-filter>
+                <action android:name="com.modular.work.ScheduleActivity" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
     </application>
 
 </manifest>

+ 1 - 1
app_modular/appworks/src/main/java/com/uas/appworks/CRM/erp/activity/DeviceDataFormAddActivity.java

@@ -694,7 +694,7 @@ public class DeviceDataFormAddActivity extends SupportToolBarActivity implements
         if (intent != null) {
             formid = intent.getIntExtra("id", 0);
             status = intent.getStringExtra("status");
-            mDevice = intent.getParcelableExtra(Constants.FLAG.MODEL);
+             mDevice = intent.getParcelableExtra(Constants.FLAG.MODEL);
             caller = intent.getStringExtra("caller");
             if (!StringUtil.isEmpty(intent.getStringExtra("title"))) {
                 setTitle(intent.getStringExtra("title"));

+ 169 - 0
app_modular/appworks/src/main/java/com/uas/appworks/activity/ScheduleActivity.java

@@ -0,0 +1,169 @@
+package com.uas.appworks.activity;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.common.data.DateFormatUtil;
+import com.common.data.ListUtils;
+import com.core.base.BaseActivity;
+import com.modular.apputils.widget.compactcalender.CompactCalendarView;
+import com.modular.apputils.widget.compactcalender.Lunar;
+import com.modular.apputils.widget.compactcalender.domain.Event;
+import com.uas.appworks.R;
+import com.uas.appworks.model.Schedule;
+import com.uas.appworks.widget.ScheduleDividerItemDecoration;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class ScheduleActivity extends BaseActivity {
+
+    private CompactCalendarView compactCalendarView;
+    private RecyclerView mRecyclerView;
+    private TextView monthTv, lunarCalendarTv, newDayTv;
+
+    private Date mCurrentDate;
+    private String mCurrentDateStr;
+    private ScheduleAdapter mScheduleAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_schedule);
+
+        initView();
+    }
+
+    private void initView() {
+        mCurrentDate = new Date();
+        mCurrentDateStr = DateFormatUtil.date2Str(mCurrentDate, "yyyy-MM-dd");
+        compactCalendarView = findViewById(R.id.compactcalendar_view);
+        mRecyclerView = findViewById(R.id.mRecyclerView);
+        monthTv = findViewById(R.id.monthTv);
+        newDayTv = findViewById(R.id.newDayTv);
+        lunarCalendarTv = findViewById(R.id.lunarCalendarTv);
+        compactCalendarView.setUseThreeLetterAbbreviation(false);
+        compactCalendarView.setFirstDayOfWeek(Calendar.MONDAY);
+        compactCalendarView.setIsRtl(false);
+        compactCalendarView.displayOtherMonthDays(false);
+        compactCalendarView.setLocale(TimeZone.getDefault(), Locale.CHINESE);
+        compactCalendarView.setUseThreeLetterAbbreviation(true);
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(ct));
+        mRecyclerView.addItemDecoration(new ScheduleDividerItemDecoration(ct));
+
+        changeDate(mCurrentDate);
+        newDayTv.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                newDayTv.setVisibility(View.GONE);
+                compactCalendarView.setCurrentDate(mCurrentDate);
+                changeDate(mCurrentDate);
+            }
+        });
+        compactCalendarView.setListener(new CompactCalendarView.CompactCalendarViewListener() {
+            @Override
+            public void onDayClick(Date dateClicked) {
+                changeDate(dateClicked);
+            }
+
+            @Override
+            public void onMonthScroll(Date firstDayOfNewMonth) {
+                changeDate(firstDayOfNewMonth);
+            }
+        });
+        addTestData();
+    }
+
+    private void addTestData() {
+        List<Schedule> mSchedules = new ArrayList<>();
+        mSchedules.add(new Schedule());
+        mSchedules.add(new Schedule());
+        mSchedules.add(new Schedule());
+        mSchedules.add(new Schedule());
+        mScheduleAdapter = new ScheduleAdapter(mSchedules);
+        mRecyclerView.setAdapter(mScheduleAdapter);
+        //TODO 获取到接口以后,添加事件
+        compactCalendarView.addEvent(new Event(Color.argb(255, 169, 68, 65), mCurrentDate.getTime() + 48 * 60 * 60 * 1000));
+
+    }
+
+    private void changeDate(Date date) {
+        String dateStr = DateFormatUtil.date2Str(date, "yyyy-MM-dd");
+        lunarCalendarTv.setText(new Lunar(date).toString());
+        monthTv.setText(dateStr);
+        newDayTv.setVisibility(dateStr.equals(mCurrentDateStr) ? View.GONE : View.VISIBLE);
+    }
+
+    private class ScheduleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+        private List<Schedule> mSchedules = null;
+
+        public ScheduleAdapter(List<Schedule> mSchedules) {
+            this.mSchedules = mSchedules;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return position > ListUtils.getSize(mSchedules) - 1 ? -1 : 1;
+        }
+
+        @Override
+        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            if (viewType == 1) {
+                return new ViewHoder(LayoutInflater.from(ct).inflate(R.layout.item_schedule_bottom, parent, false));
+
+            } else if (viewType == -1) {
+                return new CreateViewHoder(LayoutInflater.from(ct).inflate(R.layout.item_create_schedule, parent, false));
+
+            }
+            return null;
+        }
+
+        @Override
+        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+            if (holder instanceof ViewHoder) {
+
+
+            } else if (holder instanceof CreateViewHoder) {
+                ((CreateViewHoder) holder).createBtn.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        startActivity(new Intent(ct, SchedulerCreateActivity.class));
+                    }
+                });
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return ListUtils.getSize(mSchedules) + 1;
+        }
+
+        class ViewHoder extends RecyclerView.ViewHolder {
+
+            public ViewHoder(View itemView) {
+                super(itemView);
+            }
+        }
+
+        class CreateViewHoder extends RecyclerView.ViewHolder {
+            Button createBtn;
+
+            public CreateViewHoder(View itemView) {
+                super(itemView);
+                createBtn = itemView.findViewById(R.id.createBtn);
+            }
+        }
+    }
+}

+ 38 - 0
app_modular/appworks/src/main/java/com/uas/appworks/activity/ScheduleSearchActivity.java

@@ -0,0 +1,38 @@
+package com.uas.appworks.activity;
+
+import android.os.Bundle;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.core.base.BaseActivity;
+import com.uas.appworks.R;
+
+public class ScheduleSearchActivity extends BaseActivity {
+    private RecyclerView mRecyclerView;
+    private View emptyView;
+    private String lastKey;//最后一次输入
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_schedule_search);
+        initView();
+    }
+
+    private void initView() {
+        mRecyclerView = findViewById(R.id.mRecyclerView);
+        emptyView = findViewById(R.id.emptyView);
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(ct));
+        mRecyclerView.addItemDecoration(new DividerItemDecoration(ct, LinearLayout.VERTICAL));
+    }
+
+    private void searchChange(String keyWord) {
+        this.lastKey = keyWord;
+        //TODO 网络请求
+    }
+
+
+}

+ 204 - 0
app_modular/appworks/src/main/java/com/uas/appworks/activity/SchedulerCreateActivity.java

@@ -0,0 +1,204 @@
+package com.uas.appworks.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.core.base.BaseActivity;
+import com.core.model.SelectBean;
+import com.core.utils.time.wheel.DateTimePicker;
+import com.core.widget.view.Activity.SelectActivity;
+import com.core.widget.view.SwitchView;
+import com.uas.appworks.R;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+
+public class SchedulerCreateActivity extends BaseActivity {
+    private final String[] warns = {"不提醒", "开始时(默认)", "提醒前5分钟", "提醒前15分钟", "提醒前30分钟", "提醒前1小时", "提醒前一天"};
+    private final String[] warns2 = {"不提醒", "当天8点(默认)", "当天9点", "提前一天8点", "提前一天9点"};
+    private final String[] types = {"工作(默认)", "学习", "娱乐", "运动", "约会", "纪念日"};
+    private final String[] repeats = {"不重复(默认)", "每天重复", "每周重复", "每月重复"};
+
+    private EditText contentEd;
+    private SwitchView allDaySv;
+    private TextView startTimeTv, endTimeTv, warnTimeTv, repeatTv, typeTv;
+    private boolean submiting;
+
+
+    private boolean isEnable;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_scheduler_create);
+        isEnable = getIntent().getBooleanExtra("isEnable", true);
+        findById();
+        updateEnable(isEnable);
+    }
+
+    private void findById() {
+        contentEd = findViewById(R.id.contentEd);
+        allDaySv = findViewById(R.id.allDaySv);
+        startTimeTv = findViewById(R.id.startTimeTv);
+        endTimeTv = findViewById(R.id.endTimeTv);
+        warnTimeTv = findViewById(R.id.warnTimeTv);
+        repeatTv = findViewById(R.id.repeatTv);
+        typeTv = findViewById(R.id.typeTv);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.menu_input_ok, menu);
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.complete) {
+            save();
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+
+    private void updateEnable(boolean isEnable) {
+        updateEnable(isEnable, startTimeTv, endTimeTv, warnTimeTv, repeatTv, typeTv);
+    }
+
+    private void updateEnable(boolean isEnable, View... views) {
+        if (views != null && views.length > 0) {
+            for (View v : views) {
+                v.setEnabled(isEnable);
+                v.setClickable(isEnable);
+                v.setFocusable(isEnable);
+                v.setOnClickListener(isEnable ? mOnClickListener : null);
+            }
+        }
+        allDaySv.setEnabled(isEnable);
+        allDaySv.setClickable(isEnable);
+        allDaySv.setFocusable(isEnable);
+        allDaySv.setOnCheckedChangeListener(isEnable ? mOnCheckedChangeListener : null);
+    }
+
+    private SwitchView.OnCheckedChangeListener mOnCheckedChangeListener = new SwitchView.OnCheckedChangeListener() {
+        @Override
+        public void onCheckedChanged(View view, boolean isChecked) {
+
+        }
+    };
+    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            if (view == startTimeTv) {
+                showTimeSelect(true);
+            } else if (view == endTimeTv) {
+                showTimeSelect(false);
+            } else if (warnTimeTv == view) {
+                ArrayList<SelectBean> beans = new ArrayList<>();
+                String[] datas = allDaySv.isChecked() ? warns2 : warns;
+                SelectBean bean = null;
+                for (String e : datas) {
+                    bean = new SelectBean();
+                    bean.setName(e);
+                    bean.setClick(e.contains("默认") ? true : false);
+                    beans.add(bean);
+                }
+                Intent intent = new Intent(ct, SelectActivity.class);
+                intent.putExtra("type", 2);
+                intent.putParcelableArrayListExtra("data", beans);
+                intent.putExtra("title", getString(R.string.select_approvel_people));
+                startActivityForResult(intent, 0x11);
+            } else if (repeatTv == view) {
+                ArrayList<SelectBean> beans = new ArrayList<>();
+                SelectBean bean = null;
+                for (String e : repeats) {
+                    bean = new SelectBean();
+                    bean.setName(e);
+                    bean.setClick(e.contains("默认") ? true : false);
+                    beans.add(bean);
+                }
+                Intent intent = new Intent(ct, SelectActivity.class);
+                intent.putExtra("type", 2);
+                intent.putParcelableArrayListExtra("data", beans);
+                intent.putExtra("title", getString(R.string.select_approvel_people));
+                startActivityForResult(intent, 0x12);
+            } else if (typeTv == view) {
+                ArrayList<SelectBean> beans = new ArrayList<>();
+                SelectBean bean = null;
+                for (String e : types) {
+                    bean = new SelectBean();
+                    bean.setName(e);
+                    bean.setClick(e.contains("默认") ? true : false);
+                    beans.add(bean);
+                }
+                Intent intent = new Intent(ct, SelectActivity.class);
+                intent.putExtra("type", 2);
+                intent.putParcelableArrayListExtra("data", beans);
+                intent.putExtra("title", getString(R.string.select_approvel_people));
+                startActivityForResult(intent, 0x13);
+            }
+        }
+    };
+
+    private void save() {
+        if (submiting) {
+            showToast("当前正在处理,请稍等!!");
+            return;
+        }
+        submiting = true;
+        submit();
+    }
+
+    private void showTimeSelect(final boolean isStart) {
+        DateTimePicker picker = new DateTimePicker(this, allDaySv.isChecked() ? DateTimePicker.YEAR_MONTH_DAY : DateTimePicker.HOUR_OF_DAY);
+        picker.setRange(2000, 2030);
+        picker.setSelectedItem(Calendar.getInstance().get(Calendar.YEAR),
+                Calendar.getInstance().get(Calendar.MONTH) + 1,
+                Calendar.getInstance().get(Calendar.DAY_OF_MONTH),
+                Calendar.getInstance().get(Calendar.HOUR_OF_DAY),
+                Calendar.getInstance().get(Calendar.MINUTE));
+        picker.setOnDateTimePickListener(new DateTimePicker.OnYearMonthDayTimePickListener() {
+            @Override
+            public void onDateTimePicked(String year, String month, String day, String hour, String minute) {
+                TextView tv = isStart ? startTimeTv : endTimeTv;
+                tv.setText(year + "-" + month + "-" + day + " " + hour + ":" + minute + ":00");
+
+            }
+        });
+        picker.show();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (data != null) {
+            SelectBean b = data.getParcelableExtra("data");
+            if (b != null) {
+                switch (requestCode) {
+                    case 0x11:
+                        warnTimeTv.setText(b.getName());
+                        break;
+                    case 0x12:
+                        repeatTv.setText(b.getName());
+                        break;
+                    case 0x13:
+                        typeTv.setText(b.getName());
+
+                        break;
+
+                }
+            }
+        }
+    }
+
+
+    private void submit() {
+
+
+    }
+}

+ 69 - 0
app_modular/appworks/src/main/java/com/uas/appworks/adapter/SchedulerAdapter.java

@@ -0,0 +1,69 @@
+package com.uas.appworks.adapter;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.common.data.ListUtils;
+import com.uas.appworks.R;
+import com.uas.appworks.model.Schedule;
+
+import java.util.List;
+
+public class SchedulerAdapter extends RecyclerView.Adapter<SchedulerAdapter.SchedulerViewHolder> {
+
+    private Context ct;
+    private List<Schedule> mSchedules;
+    private LayoutInflater mLayoutInflater;
+
+    public SchedulerAdapter(Context ct, List<Schedule> mSchedules) {
+        this.ct = ct;
+        this.mSchedules = mSchedules;
+    }
+
+    public void setSchedules(List<Schedule> mSchedules) {
+        this.mSchedules = mSchedules;
+        notifyDataSetChanged();
+    }
+
+    public List<Schedule> getSchedules() {
+        return mSchedules;
+    }
+
+    public LayoutInflater getLayoutInflater() {
+        if (mLayoutInflater == null) {
+            mLayoutInflater = LayoutInflater.from(ct);
+        }
+        return mLayoutInflater;
+    }
+
+    @Override
+    public SchedulerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new SchedulerViewHolder(getLayoutInflater().inflate(R.layout.item_schedule_search, parent, false));
+    }
+
+
+    @Override
+    public int getItemCount() {
+        return ListUtils.getSize(mSchedules);
+    }
+
+    class SchedulerViewHolder extends RecyclerView.ViewHolder {
+        private TextView titleTv, timeTv;
+
+        public SchedulerViewHolder(View itemView) {
+            super(itemView);
+            titleTv = itemView.findViewById(R.id.titleTv);
+            timeTv = itemView.findViewById(R.id.timeTv);
+        }
+    }
+
+
+    @Override
+    public void onBindViewHolder(SchedulerViewHolder holder, int position) {
+
+    }
+}

+ 135 - 0
app_modular/appworks/src/main/java/com/uas/appworks/model/Schedule.java

@@ -0,0 +1,135 @@
+package com.uas.appworks.model;
+
+public class Schedule {
+
+    private int id;
+    private String type;//类型 (UU互联||手机日程)
+    private int allDay;//是否全天 ( 1.全天  0.非全天 )
+    private int repeat;//是否重复 ( 1.重复  0.非重复 )
+    private int repeatTag;//重复类型 ( 1.每天  2.每周 3.每月 4.每年 )
+    private String title;//标题
+    private String tag;//标签(工作|会议|学习)
+    private String remarks;//备注
+    private long startTime;//开始时间戳
+    private long endTime;//结束时间戳
+    private int warnTime;  //提醒时间(单位 分钟:  如:12==12分钟   负数为不提醒)
+    private String warnRealTime;//提醒精确时间(预留)
+    private String address;//地点(预留)
+    private String status;//状态(预留)
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public int getAllDay() {
+        return allDay;
+    }
+
+    public void setAllDay(int allDay) {
+        this.allDay = allDay;
+    }
+
+    public int getRepeat() {
+        return repeat;
+    }
+
+    public void setRepeat(int repeat) {
+        this.repeat = repeat;
+    }
+
+    public int getRepeatTag() {
+        return repeatTag;
+    }
+
+    public void setRepeatTag(int repeatTag) {
+        this.repeatTag = repeatTag;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getTag() {
+        return tag;
+    }
+
+    public void setTag(String tag) {
+        this.tag = tag;
+    }
+
+    public String getRemarks() {
+        return remarks;
+    }
+
+    public void setRemarks(String remarks) {
+        this.remarks = remarks;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(long endTime) {
+        this.endTime = endTime;
+    }
+
+    public int getWarnTime() {
+        return warnTime;
+    }
+
+    public void setWarnTime(int warnTime) {
+        this.warnTime = warnTime;
+    }
+
+    public String getWarnRealTime() {
+        return warnRealTime;
+    }
+
+    public void setWarnRealTime(String warnRealTime) {
+        this.warnRealTime = warnRealTime;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public int hasAlarm() {
+        return warnTime > 0 ? 1 : 0;
+    }
+}

+ 224 - 0
app_modular/appworks/src/main/java/com/uas/appworks/utils/ScheduleUtils.java

@@ -0,0 +1,224 @@
+package com.uas.appworks.utils;
+
+import android.Manifest;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.net.Uri;
+import android.provider.CalendarContract;
+import android.support.v4.app.ActivityCompat;
+
+import com.uas.appworks.model.Schedule;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TimeZone;
+
+public class ScheduleUtils {
+    private static final String DEF_EMAIL = "usoftchina@163.com";
+    private static final String NAME = "UU互联";
+
+    /**
+     * 检查是否存在指定账号
+     * 存在则返回账户id,否则返回-1
+     * 没有权限返回-2
+     */
+    public static int checkCalendarAccount(Context context) {
+        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
+            //TODO 没有权限
+            return -2;
+        }
+        String selection = CalendarContract.Calendars.ACCOUNT_NAME + " =? ";
+        String[] selectionArgs = new String[]{DEF_EMAIL};
+        Cursor userCursor = context.getContentResolver().query(CalendarContract.Calendars.CONTENT_URI,
+                null, selection, selectionArgs, null);
+        try {
+            if (userCursor == null)//查询返回空值
+                return -1;
+            int count = userCursor.getCount();
+            if (count > 0) {//存在现有账户,取第一个账户的id返回
+                userCursor.moveToFirst();
+                return userCursor.getInt(userCursor.getColumnIndex(CalendarContract.Calendars._ID));
+            } else {
+                return -1;
+            }
+        } finally {
+            if (userCursor != null) {
+                userCursor.close();
+            }
+        }
+    }
+
+    /**
+     * 向系统日历添加一个账号
+     *
+     * @param context
+     * @return
+     */
+    public static long addCalendarAccount(Context context) {
+        String accountType = "com.android.exchange";
+        TimeZone timeZone = TimeZone.getDefault();
+        ContentValues value = new ContentValues();
+        value.put(CalendarContract.Calendars.NAME, NAME);
+        value.put(CalendarContract.Calendars.ACCOUNT_NAME, DEF_EMAIL);
+        value.put(CalendarContract.Calendars.OWNER_ACCOUNT, DEF_EMAIL);
+        value.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, NAME);
+        value.put(CalendarContract.Calendars.ACCOUNT_TYPE, accountType);
+        value.put(CalendarContract.Calendars.VISIBLE, 1);
+        value.put(CalendarContract.Calendars.CALENDAR_COLOR, Color.BLUE);
+        value.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_OWNER);
+        value.put(CalendarContract.Calendars.SYNC_EVENTS, 1);
+        value.put(CalendarContract.Calendars.CALENDAR_TIME_ZONE, timeZone.getID());
+        value.put(CalendarContract.Calendars.CAN_ORGANIZER_RESPOND, 0);
+        Uri calendarUri = CalendarContract.Calendars.CONTENT_URI;
+        calendarUri = calendarUri.buildUpon()
+                .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
+                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, DEF_EMAIL)
+                .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, accountType)
+                .build();
+
+        Uri result = context.getContentResolver().insert(calendarUri, value);
+        long id = result == null ? -1 : ContentUris.parseId(result);
+        return id;
+    }
+
+
+    /**
+     * 添加日程事件和提醒
+     *
+     * @param context
+     * @param mSchedule
+     * @return :
+     * -1: 获取账户id失败直接返回,添加日历事件失败
+     * -2: 没有获取到权限
+     * -3: 添加日历事件失败直接返回
+     * -4: 添加闹钟提醒失败直接返回
+     */
+    public static long addCalendarEvent(Context context, Schedule mSchedule) {
+        // 获取日历账户的id
+        int calId = checkCalendarAccount(context);
+        if (calId < 0) {
+            // 获取账户id失败直接返回,添加日历事件失败
+            return -1;
+        }
+        //开始添加日程事件
+        ContentValues event = new ContentValues();
+        event.put(CalendarContract.Events.CALENDAR_ID, calId);//选择插入账户的id
+        event.put(CalendarContract.Events.SELF_ATTENDEE_STATUS, mSchedule.getId());//选择插入账户的id
+        event.put(CalendarContract.Events.TITLE, mSchedule.getTitle());//添加标题
+        event.put(CalendarContract.Events.DESCRIPTION, mSchedule.getRemarks());//添加描述|备注
+        event.put(CalendarContract.Events.EVENT_LOCATION, mSchedule.getAddress());//事件地点
+        event.put(CalendarContract.Events.EVENT_COLOR, Color.GREEN);//颜色
+//        event.put(CalendarContract.Events.DISPLAY_COLOR, Color.GREEN);//显示颜色
+//        event.put(CalendarContract.Events.RDATE,"TODO");//事件重复日期
+        event.put(CalendarContract.Events.ORGANIZER, DEF_EMAIL);//事件所有者的名称
+        event.put(CalendarContract.Events.LAST_DATE, System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000);//事件重复日期
+        event.put(CalendarContract.Events.DTSTART, mSchedule.getStartTime());//日程开始时间
+        event.put(CalendarContract.Events.DTEND, mSchedule.getEndTime());//日程结束时间时间
+        event.put(CalendarContract.Events.HAS_ALARM, mSchedule.hasAlarm());//设置是否有闹钟提醒
+        event.put(CalendarContract.Events.ORIGINAL_ALL_DAY, mSchedule.getAllDay());//是否全天
+        event.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID());  //这个是时区,必须有,
+        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
+            // 没有 权限
+            return -2;
+        }
+        //添加事件
+        Uri newEvent = context.getContentResolver().insert(CalendarContract.Events.CONTENT_URI, event);
+        if (newEvent == null) {
+            // 添加日历事件失败直接返回
+            return -3;
+        }
+        if (mSchedule.hasAlarm() > 0) {
+            // 如果需要提醒的情况下
+            //事件提醒的设定
+            ContentValues values = new ContentValues();
+            values.put(CalendarContract.Reminders.EVENT_ID, ContentUris.parseId(newEvent));
+            // 提前10分钟有提醒
+            values.put(CalendarContract.Reminders.MINUTES, mSchedule.getWarnTime());//提前时间提醒
+            values.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT);
+            Uri uri = context.getContentResolver().insert(CalendarContract.Reminders.CONTENT_URI, values);
+            if (uri == null) {
+                // 添加闹钟提醒失败直接返回
+                return -4;
+            }
+        }
+        return 1;
+    }
+
+    /**
+     * 获取到系统的对应账户的日程
+     *
+     * @param context
+     * @return
+     */
+    public static List<Schedule> getSystemCalendar(Context context) {
+        List<Schedule> mSchedules = null;
+        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
+            return mSchedules;
+        }
+        String selection = CalendarContract.Events.ORGANIZER + " =? ";
+        String[] selectionArgs = new String[]{DEF_EMAIL};
+        Cursor userCursor = context.getContentResolver().query(CalendarContract.Events.CONTENT_URI,
+                null, selection, selectionArgs, null);
+        try {
+            if (userCursor == null)//查询返回空值
+                return null;
+            mSchedules = new ArrayList<>();
+            Schedule mSchedule = null;
+            while (userCursor.moveToNext()) {
+                mSchedule = new Schedule();
+                mSchedule.setId(userCursor.getInt(userCursor.getColumnIndex(CalendarContract.Events.SELF_ATTENDEE_STATUS)));
+                mSchedule.setTitle(userCursor.getString(userCursor.getColumnIndex(CalendarContract.Events.TITLE)));
+                mSchedule.setRemarks(userCursor.getString(userCursor.getColumnIndex(CalendarContract.Events.DESCRIPTION)));
+                mSchedule.setAddress(userCursor.getString(userCursor.getColumnIndex(CalendarContract.Events.EVENT_LOCATION)));
+                mSchedule.setStartTime(userCursor.getLong(userCursor.getColumnIndex(CalendarContract.Events.DTSTART)));
+                mSchedule.setEndTime(userCursor.getLong(userCursor.getColumnIndex(CalendarContract.Events.DTEND)));
+                mSchedule.setAllDay(userCursor.getInt(userCursor.getColumnIndex(CalendarContract.Events.ORIGINAL_ALL_DAY)));
+                mSchedules.add(mSchedule);
+            }
+
+        } finally {
+            if (userCursor != null) {
+                userCursor.close();
+            }
+            return mSchedules;
+        }
+    }
+
+
+    /**
+     * 删除对应的日程
+     *
+     * @param context
+     * @param id      id
+     * @return
+     */
+    public static long deleteSystemCalendar(Context context, int id) {
+        int deleteOk = -1;
+        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
+            return deleteOk;
+        }
+        try {
+            String where = CalendarContract.Events.ORGANIZER + " =? and " + CalendarContract.Events.SELF_ATTENDEE_STATUS + " =? ";
+            String[] whereArgs = new String[]{DEF_EMAIL, String.valueOf(id)};
+            deleteOk = context.getContentResolver().delete(CalendarContract.Events.CONTENT_URI, where, whereArgs);
+        } finally {
+            return deleteOk;
+        }
+    }
+
+    /**
+     * 更新系统日程
+     *
+     * @param context
+     * @param mSchedule 必须有id
+     * @return
+     */
+    public static long updateSystemCalendar(Context context, Schedule mSchedule) {
+        deleteSystemCalendar(context, mSchedule.getId());
+        return addCalendarEvent(context, mSchedule);
+    }
+}

+ 54 - 0
app_modular/appworks/src/main/java/com/uas/appworks/widget/ScheduleDividerItemDecoration.java

@@ -0,0 +1,54 @@
+package com.uas.appworks.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+import com.common.system.DisplayUtil;
+
+public class ScheduleDividerItemDecoration extends RecyclerView.ItemDecoration {
+
+    private Paint mPaint;
+    private Context context;
+    public ScheduleDividerItemDecoration(Context context) {
+        super();
+        this.context=context;
+        mPaint=new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setColor(Color.BLACK);
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        super.onDraw(c, parent, state);
+        int childCount = parent.getChildCount();
+        for ( int i = 0; i < childCount; i++ ) {
+            View view = parent.getChildAt(i);
+            int index = parent.getChildAdapterPosition(view);
+            //第一个ItemView不需要绘制
+            if ( index == 0 ) {
+                continue;
+            }
+//            float dividerTop = view.getTop() - mDividerHeight;
+//            float dividerLeft = parent.getPaddingLeft();
+//            float dividerBottom = view.getTop();
+//            float dividerRight = parent.getWidth() - parent.getPaddingRight();
+//            c.drawRect(dividerLeft,dividerTop,dividerRight,dividerBottom,mPaint);
+            c.drawLine(view.getX(),view.getTop(),view.getWidth(),view.getTop(),mPaint);
+        }
+    }
+
+    @Override
+    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        super.onDrawOver(c, parent, state);
+    }
+
+    @Override
+    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+        outRect.set(DisplayUtil.dip2px(context,8), 0, 0, 2);
+    }
+}

+ 79 - 0
app_modular/appworks/src/main/res/layout/activity_schedule.xml

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".activity.ScheduleActivity">
+
+
+    <TextView
+        android:id="@+id/monthTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/padding"
+        android:text="2018-08"
+        android:textColor="#FF000000"
+        android:textSize="14sp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent" />
+
+    <TextView
+        android:id="@+id/lunarCalendarTv"
+        android:layout_width="wrap_content"
+        app:layout_constraintRight_toRightOf="parent"
+        android:layout_marginRight="30dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="@id/monthTv"
+        app:layout_constraintBottom_toBottomOf="@id/monthTv"
+        android:text="农历 十八"
+        android:textColor="#FF696969"
+        android:textSize="11sp"
+        />
+    <TextView
+        android:id="@+id/newDayTv"
+        android:layout_width="wrap_content"
+        app:layout_constraintLeft_toLeftOf="parent"
+        android:layout_marginLeft="30dp"
+        android:background="@drawable/bg_circular_bule"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="@id/monthTv"
+        app:layout_constraintBottom_toBottomOf="@id/monthTv"
+        android:text="今"
+        android:textColor="@color/white"
+        android:textSize="12sp"
+        />
+    <com.modular.apputils.widget.compactcalender.CompactCalendarView xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:id="@+id/compactcalendar_view"
+        android:layout_width="fill_parent"
+        android:layout_height="230dp"
+        app:compactCalendarBackgroundColor="@color/white"
+        app:compactCalendarCurrentDayBackgroundColor="#206390"
+        app:compactCalendarCurrentDayIndicatorStyle="fill_large_indicator"
+        app:compactCalendarCurrentDayTextColor="@color/white"
+        app:compactCalendarCurrentSelectedDayBackgroundColor="#ffe95451"
+        app:compactCalendarEventIndicatorStyle="small_indicator"
+        app:compactCalendarOtherMonthDaysTextColor="#534c4c"
+        app:compactCalendarShouldSelectFirstDayOfMonthOnScroll="true"
+        app:compactCalendarTargetHeight="250dp"
+        app:compactCalendarTextColor="#181818"
+        app:compactCalendarTextSize="12sp"
+        app:layout_constraintTop_toBottomOf="@id/monthTv" />
+
+    <View
+        android:id="@+id/gapView"
+        android:layout_width="match_parent"
+        android:layout_height="10dp"
+        android:background="#F3F3F3"
+        app:layout_constraintTop_toBottomOf="@id/compactcalendar_view" />
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/mRecyclerView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@color/white"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/gapView" />
+</android.support.constraint.ConstraintLayout>

+ 29 - 0
app_modular/appworks/src/main/res/layout/activity_schedule_search.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/emptyView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:text="无日程"
+        android:textColor="#FF737373"
+        android:textSize="18sp"
+        android:gravity="center"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <android.support.v7.widget.RecyclerView
+        android:layout_width="0dp"
+        android:id="@+id/mRecyclerView"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:layout_height="0dp"/>
+</android.support.constraint.ConstraintLayout>

+ 250 - 0
app_modular/appworks/src/main/res/layout/activity_scheduler_create.xml

@@ -0,0 +1,250 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/white">
+
+    <View
+        android:id="@+id/tag1"
+        android:layout_width="match_parent"
+        android:layout_height="10dp"
+        android:background="#F3F3F3" />
+
+    <EditText
+        android:id="@+id/contentEd"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="@dimen/padding"
+        android:layout_marginRight="@dimen/padding"
+        android:background="@null"
+        android:padding="@dimen/padding"
+        android:hint="请输入日程内容…"
+        android:textColor="#5D5D5D"
+        android:textSize="13sp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tag1" />
+
+    <View
+        android:id="@+id/tag2"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/line"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:background="#F3F3F3"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/contentEd" />
+
+    <TextView
+        android:id="@+id/allDayTag"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/padding"
+        android:text="全天"
+        android:textColor="#FF000000"
+        android:textSize="12sp"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintTop_toBottomOf="@id/tag2" />
+
+    <com.core.widget.view.SwitchView
+        android:id="@+id/allDaySv"
+        android:layout_width="40dp"
+        android:layout_height="25dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        app:layout_constraintBottom_toBottomOf="@id/allDayTag"
+        app:layout_constraintRight_toRightOf="@id/contentEd"
+        app:layout_constraintTop_toTopOf="@id/allDayTag" />
+
+    <View
+        android:id="@+id/tag3"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/line"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:background="#F3F3F3"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/allDayTag" />
+
+
+    <TextView
+        android:id="@+id/startTimeTag"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/padding"
+        android:text="开始时间"
+        android:textColor="#FF000000"
+        android:textSize="12sp"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintTop_toBottomOf="@id/tag3" />
+
+
+    <TextView
+        android:id="@+id/startTimeTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:drawablePadding="@dimen/paddingMin"
+        android:drawableRight="@drawable/oa_next"
+        android:text="2018年8月09日  14:00"
+        android:textColor="#909090"
+        android:textSize="11sp"
+        app:layout_constraintBottom_toBottomOf="@id/startTimeTag"
+        app:layout_constraintRight_toRightOf="@id/contentEd"
+        app:layout_constraintTop_toTopOf="@id/startTimeTag" />
+
+    <View
+        android:id="@+id/tag4"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/line"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:background="#F3F3F3"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/startTimeTag" />
+
+
+    <TextView
+        android:id="@+id/endTimeTag"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/padding"
+        android:text="结束时间"
+        android:textColor="#FF000000"
+        android:textSize="12sp"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintTop_toBottomOf="@id/tag4" />
+
+
+    <TextView
+        android:id="@+id/endTimeTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:drawablePadding="@dimen/paddingMin"
+        android:drawableRight="@drawable/oa_next"
+        android:text="2018年8月09日  14:00"
+        android:textColor="#FF909090"
+        android:textSize="11sp"
+        app:layout_constraintBottom_toBottomOf="@id/endTimeTag"
+        app:layout_constraintRight_toRightOf="@id/contentEd"
+        app:layout_constraintTop_toTopOf="@id/endTimeTag" />
+
+    <View
+        android:id="@+id/tag5"
+        android:layout_width="0dp"
+        android:layout_height="10dp"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:background="#F3F3F3"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/endTimeTag" />
+
+
+    <TextView
+        android:id="@+id/warnTag"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/padding"
+        android:text="提醒时间"
+        android:textColor="#FF000000"
+        android:textSize="12sp"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintTop_toBottomOf="@id/tag5" />
+
+
+    <TextView
+        android:id="@+id/warnTimeTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:drawablePadding="@dimen/paddingMin"
+        android:drawableRight="@drawable/oa_next"
+        android:text="2018年8月09日  14:00"
+        android:textColor="#FF909090"
+        android:textSize="11sp"
+        app:layout_constraintBottom_toBottomOf="@id/warnTag"
+        app:layout_constraintRight_toRightOf="@id/contentEd"
+        app:layout_constraintTop_toTopOf="@id/warnTag" />
+
+    <View
+        android:id="@+id/tag6"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/line"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:background="#F3F3F3"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/warnTag" />
+
+
+    <TextView
+        android:id="@+id/repeatTag"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/padding"
+        android:text="是否重复"
+        android:textColor="#FF000000"
+        android:textSize="12sp"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintTop_toBottomOf="@id/tag6" />
+
+
+    <TextView
+        android:id="@+id/repeatTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:drawablePadding="@dimen/paddingMin"
+        android:drawableRight="@drawable/oa_next"
+        android:text="2018年8月09日  14:00"
+        android:textColor="#FF909090"
+        android:textSize="11sp"
+        app:layout_constraintBottom_toBottomOf="@id/repeatTag"
+        app:layout_constraintRight_toRightOf="@id/contentEd"
+        app:layout_constraintTop_toTopOf="@id/repeatTag" />
+
+    <View
+        android:id="@+id/tag7"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/line"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:background="#F3F3F3"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/repeatTag" />
+
+
+    <TextView
+        android:id="@+id/typeTag"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/padding"
+        android:text="日程类型"
+        android:textColor="#FF000000"
+        android:textSize="12sp"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintTop_toBottomOf="@id/tag7" />
+
+
+    <TextView
+        android:id="@+id/typeTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:drawablePadding="@dimen/paddingMin"
+        android:drawableRight="@drawable/oa_next"
+        android:text="2018年8月09日  14:00"
+        android:textColor="#FF909090"
+        android:textSize="11sp"
+        app:layout_constraintBottom_toBottomOf="@id/typeTag"
+        app:layout_constraintRight_toRightOf="@id/contentEd"
+        app:layout_constraintTop_toTopOf="@id/typeTag" />
+
+    <View
+        android:id="@+id/tag8"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/line"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:background="#F3F3F3"
+        app:layout_constraintLeft_toLeftOf="@id/contentEd"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/typeTag" />
+
+</android.support.constraint.ConstraintLayout>

+ 24 - 0
app_modular/appworks/src/main/res/layout/item_create_schedule.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:id="@+id/createBtn"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/info"
+        android:layout_marginBottom="150dp"
+        android:layout_marginLeft="10dp"
+        android:layout_marginRight="10dp"
+        android:layout_marginTop="20dp"
+        android:background="@drawable/bg_bule_btn"
+        android:padding="10dp"
+        android:text="@string/oacreat_calender"
+        android:textColor="@color/white"
+        android:textSize="@dimen/text_main"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+</android.support.constraint.ConstraintLayout>

+ 46 - 0
app_modular/appworks/src/main/res/layout/item_schedule_bottom.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/white"
+    android:padding="@dimen/padding">
+
+    <TextView
+        android:id="@+id/timeTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="全天"
+        android:textColor="#FF000000"
+        android:textSize="14sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/contentTv"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="20dp"
+        android:text="需求设计需求设计需求设计需求设计需…"
+        android:textColor="#FF282828"
+        android:textSize="12sp"
+        app:layout_constraintLeft_toRightOf="@id/timeTv" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:text="(来自手机日历)"
+        android:textColor="#FF909090"
+        android:textSize="8sp"
+        app:layout_constraintLeft_toLeftOf="@id/contentTv"
+        app:layout_constraintTop_toBottomOf="@id/contentTv" />
+
+    <ImageView
+        android:layout_width="@dimen/next_width"
+        android:layout_height="@dimen/next_height"
+        android:src="@drawable/oa_next"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+</android.support.constraint.ConstraintLayout>

+ 46 - 0
app_modular/appworks/src/main/res/layout/item_schedule_search.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/white"
+    android:paddingBottom="@dimen/paddingMin"
+    android:paddingLeft="15dp"
+    android:paddingTop="@dimen/padding">
+
+
+    <View
+        android:id="@+id/nextView"
+        android:layout_width="@dimen/next_width"
+        android:layout_height="@dimen/next_height"
+        android:layout_marginRight="10dp"
+        android:background="@drawable/oa_next"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/titleTv"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:inputType="text"
+        android:lines="1"
+        android:text="需求评审"
+        android:textColor="#FF000000"
+        android:textSize="14sp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toLeftOf="@id/nextView" />
+
+    <TextView
+        android:id="@+id/timeTv"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/paddingMin"
+        android:text="2018-08-05 17:00"
+        android:textColor="#FF737373"
+        android:textSize="12sp"
+        app:layout_constraintTop_toBottomOf="@id/titleTv" />
+
+
+</android.support.constraint.ConstraintLayout>

+ 9 - 0
app_modular/appworks/src/main/res/menu/menu_input_ok.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/complete"
+        android:title="@string/complete"
+        app:showAsAction="always" />
+</menu>