diff --git a/Android/Module/.classpath b/Android/Module/.classpath new file mode 100644 index 0000000..84a0c49 --- /dev/null +++ b/Android/Module/.classpath @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/Android/Module/.gitignore b/Android/Module/.gitignore new file mode 100644 index 0000000..f97964b --- /dev/null +++ b/Android/Module/.gitignore @@ -0,0 +1,5 @@ +tmp +bin +build +*.zip +.apt_generated diff --git a/Android/Module/.project b/Android/Module/.project new file mode 100644 index 0000000..d49be36 --- /dev/null +++ b/Android/Module/.project @@ -0,0 +1,29 @@ + + + calendarview + + + + + + com.appcelerator.titanium.core.builder + + + + + com.aptana.ide.core.unifiedBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + com.appcelerator.titanium.mobile.module.nature + com.aptana.projects.webnature + + diff --git a/Android/Module/.settings/org.eclipse.jdt.apt.core.prefs b/Android/Module/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..80036a7 --- /dev/null +++ b/Android/Module/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,7 @@ +#Thu Sep 02 15:18:34 CDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=.apt_generated +org.eclipse.jdt.apt.reconcileEnabled=true + +org.eclipse.jdt.apt.processorOptions/kroll.jsonFile=calendarview.json diff --git a/Android/Module/.settings/org.eclipse.jdt.core.prefs b/Android/Module/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..2595d34 --- /dev/null +++ b/Android/Module/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,3 @@ +#Thu Sep 02 15:18:34 CDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.processAnnotations=enabled diff --git a/Android/Module/CHANGELOG.txt b/Android/Module/CHANGELOG.txt new file mode 100644 index 0000000..de1e091 --- /dev/null +++ b/Android/Module/CHANGELOG.txt @@ -0,0 +1 @@ +Place your change log text here. This file will be incorporated with your app at package time. \ No newline at end of file diff --git a/Android/Module/LICENSE b/Android/Module/LICENSE new file mode 100644 index 0000000..6ae867d --- /dev/null +++ b/Android/Module/LICENSE @@ -0,0 +1 @@ +TODO: place your license here and we'll include it in the module distribution diff --git a/Android/Module/LICENSE.txt b/Android/Module/LICENSE.txt new file mode 100644 index 0000000..4124b1d --- /dev/null +++ b/Android/Module/LICENSE.txt @@ -0,0 +1 @@ +Place your license text here. This file will be incorporated with your app at package time. \ No newline at end of file diff --git a/Android/Module/assets/README b/Android/Module/assets/README new file mode 100644 index 0000000..2c9f6a4 --- /dev/null +++ b/Android/Module/assets/README @@ -0,0 +1,6 @@ +Place your assets like PNG files in this directory and they will be packaged with your module. + +If you create a file named ti.squaredcalendar.js in this directory, it will be +compiled and used as your module. This allows you to run pure Javascript +modules that are pre-compiled. + diff --git a/Android/Module/build.properties b/Android/Module/build.properties new file mode 100644 index 0000000..c6ad27f --- /dev/null +++ b/Android/Module/build.properties @@ -0,0 +1,3 @@ +titanium.platform=/Users/benjamin/Library/Application Support/Titanium/mobilesdk/osx/3.1.0.GA/android +android.platform=/Users/benjamin/Android/sdk/platforms/android-8 +google.apis=/Users/benjamin/Android/sdk/add-ons/addon-google_apis-google-8 \ No newline at end of file diff --git a/Android/Module/build.xml b/Android/Module/build.xml new file mode 100644 index 0000000..f7ba5ac --- /dev/null +++ b/Android/Module/build.xml @@ -0,0 +1,10 @@ + + + Ant build script for Titanium Android module calendarview + + + + + + + diff --git a/Android/Module/documentation/index.md b/Android/Module/documentation/index.md new file mode 100644 index 0000000..51bba54 --- /dev/null +++ b/Android/Module/documentation/index.md @@ -0,0 +1,39 @@ +# calendarview Module + +## Description + +TODO: Enter your module description here + +## Accessing the calendarview Module + +To access this module from JavaScript, you would do the following: + + var calendarview = require("ti.squaredcalendar"); + +The calendarview variable is a reference to the Module object. + +## Reference + +TODO: If your module has an API, you should document +the reference here. + +### ___PROJECTNAMEASIDENTIFIER__.function + +TODO: This is an example of a module function. + +### ___PROJECTNAMEASIDENTIFIER__.property + +TODO: This is an example of a module property. + +## Usage + +TODO: Enter your usage example here + +## Author + +TODO: Enter your author name, email and other contact +details you want to share here. + +## License + +TODO: Enter your license/legal information here. diff --git a/Android/Module/example/app.js b/Android/Module/example/app.js new file mode 100644 index 0000000..94908c7 --- /dev/null +++ b/Android/Module/example/app.js @@ -0,0 +1,39 @@ +// This is a test harness for your module +// You should do something interesting in this harness +// to test out the module and to provide instructions +// to users on how to use it by example. + + +// open a single window +var win = Ti.UI.createWindow({ + backgroundColor:'white' +}); +var label = Ti.UI.createLabel(); +win.add(label); +win.open(); + +// TODO: write your module tests here +var calendarview = require('ti.squaredcalendar'); +Ti.API.info("module is => " + calendarview); + +label.text = calendarview.example(); + +Ti.API.info("module exampleProp is => " + calendarview.exampleProp); +calendarview.exampleProp = "This is a test value"; + +if (Ti.Platform.name == "android") { + var proxy = calendarview.createExample({ + message: "Creating an example Proxy", + backgroundColor: "red", + width: 100, + height: 100, + top: 100, + left: 150 + }); + + proxy.printMessage("Hello world!"); + proxy.message = "Hi world!. It's me again."; + proxy.printMessage("Hello world!"); + win.add(proxy); +} + diff --git a/Android/Module/hooks/README b/Android/Module/hooks/README new file mode 100644 index 0000000..66b10a8 --- /dev/null +++ b/Android/Module/hooks/README @@ -0,0 +1 @@ +These files are not yet supported as of 1.4.0 but will be in a near future release. diff --git a/Android/Module/hooks/add.py b/Android/Module/hooks/add.py new file mode 100644 index 0000000..04e1c1d --- /dev/null +++ b/Android/Module/hooks/add.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# +# This is the module project add hook that will be +# called when your module is added to a project +# +import os, sys + +def dequote(s): + if s[0:1] == '"': + return s[1:-1] + return s + +def main(args,argc): + # You will get the following command line arguments + # in the following order: + # + # project_dir = the full path to the project root directory + # project_type = the type of project (desktop, mobile, ipad) + # project_name = the name of the project + # + project_dir = dequote(os.path.expanduser(args[1])) + project_type = dequote(args[2]) + project_name = dequote(args[3]) + + # TODO: write your add hook here (optional) + + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/Android/Module/hooks/install.py b/Android/Module/hooks/install.py new file mode 100644 index 0000000..b423fe9 --- /dev/null +++ b/Android/Module/hooks/install.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# +# This is the module install hook that will be +# called when your module is first installed +# +import os, sys + +def main(args,argc): + + # TODO: write your install hook here (optional) + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/Android/Module/hooks/remove.py b/Android/Module/hooks/remove.py new file mode 100644 index 0000000..f92a234 --- /dev/null +++ b/Android/Module/hooks/remove.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# +# This is the module project remove hook that will be +# called when your module is remove from a project +# +import os, sys + +def dequote(s): + if s[0:1] == '"': + return s[1:-1] + return s + +def main(args,argc): + # You will get the following command line arguments + # in the following order: + # + # project_dir = the full path to the project root directory + # project_type = the type of project (desktop, mobile, ipad) + # project_name = the name of the project + # + project_dir = dequote(os.path.expanduser(args[1])) + project_type = dequote(args[2]) + project_name = dequote(args[3]) + + # TODO: write your remove hook here (optional) + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/Android/Module/hooks/uninstall.py b/Android/Module/hooks/uninstall.py new file mode 100644 index 0000000..a7ffd91 --- /dev/null +++ b/Android/Module/hooks/uninstall.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# +# This is the module uninstall hook that will be +# called when your module is uninstalled +# +import os, sys + +def main(args,argc): + + # TODO: write your uninstall hook here (optional) + + # exit + sys.exit(0) + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/Android/Module/manifest b/Android/Module/manifest new file mode 100644 index 0000000..df3e883 --- /dev/null +++ b/Android/Module/manifest @@ -0,0 +1,18 @@ +# +# this is your module manifest and used by Titanium +# during compilation, packaging, distribution, etc. +# +version: 0.1 +apiversion: 2 +description: My module +author: Benjamin Bahrenburg +license: Specify your license +copyright: Copyright (c) 2013 by Your Company + + +# these should not be edited +name: calendarview +moduleid: ti.squaredcalendar +guid: a1dc9ab6-7c5a-41b9-a18a-70273d5815d0 +platform: android +minsdk: 3.1.0.GA diff --git a/Android/Module/platform/README b/Android/Module/platform/README new file mode 100644 index 0000000..7ac991c --- /dev/null +++ b/Android/Module/platform/README @@ -0,0 +1,3 @@ +You can place platform-specific files here in sub-folders named "android" and/or "iphone", just as you can with normal Titanium Mobile SDK projects. Any folders and files you place here will be merged with the platform-specific files in a Titanium Mobile project that uses this module. + +When a Titanium Mobile project that uses this module is built, the files from this platform/ folder will be treated the same as files (if any) from the Titanium Mobile project's platform/ folder. diff --git a/Android/Module/src/ti/squaredcalendar/CalendarView.java b/Android/Module/src/ti/squaredcalendar/CalendarView.java new file mode 100644 index 0000000..77050d4 --- /dev/null +++ b/Android/Module/src/ti/squaredcalendar/CalendarView.java @@ -0,0 +1,26 @@ +package ti.squaredcalendar; + +import java.util.Date; + +import com.squareup.timessquare.CalendarPickerView; +import com.squareup.timessquare.CalendarPickerView.SelectionMode; + +import org.appcelerator.titanium.view.TiUIView; +import org.appcelerator.titanium.proxy.TiViewProxy; + +public class CalendarView extends TiUIView { + public CalendarView(TiViewProxy proxy) + { + super(proxy); + + final CalendarPickerView calendar = + new CalendarPickerView(proxy.getActivity(),null); + calendar.init(new Date(), new Date()) // + .inMode(SelectionMode.SINGLE) // + .withSelectedDate(new Date()); + + // Set the view as the native view. You must set the native view + // for your view to be rendered correctly. + setNativeView(calendar); + } +} diff --git a/Android/Module/src/ti/squaredcalendar/CalendarViewProxy.java b/Android/Module/src/ti/squaredcalendar/CalendarViewProxy.java new file mode 100644 index 0000000..7c21857 --- /dev/null +++ b/Android/Module/src/ti/squaredcalendar/CalendarViewProxy.java @@ -0,0 +1,28 @@ +package ti.squaredcalendar; +import org.appcelerator.kroll.annotations.Kroll; +import org.appcelerator.titanium.view.TiUIView; +import org.appcelerator.titanium.proxy.TiViewProxy; + +import android.app.Activity; +@Kroll.proxy(creatableInModule = CalendarviewModule.class) +public class CalendarViewProxy extends TiViewProxy { + + + public CalendarViewProxy() { + super(); + } + + public TiUIView createView(Activity activity) + { + // This method is called when the view needs to be created. This is + // a required method for a TiViewProxy subclass. + + CalendarView view = new CalendarView(this); + view.getLayoutParams().autoFillsHeight = true; + view.getLayoutParams().autoFillsWidth = true; + + return view; + + } + +} diff --git a/Android/Module/src/ti/squaredcalendar/CalendarviewModule.java b/Android/Module/src/ti/squaredcalendar/CalendarviewModule.java new file mode 100644 index 0000000..b967916 --- /dev/null +++ b/Android/Module/src/ti/squaredcalendar/CalendarviewModule.java @@ -0,0 +1,62 @@ +/** + * This file was auto-generated by the Titanium Module SDK helper for Android + * Appcelerator Titanium Mobile + * Copyright (c) 2009-2013 by Appcelerator, Inc. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + * + */ +package ti.squaredcalendar; + +import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.annotations.Kroll; + +import org.appcelerator.titanium.TiApplication; +import org.appcelerator.kroll.common.Log; + +@Kroll.module(name="Calendarview", id="ti.squaredcalendar") +public class CalendarviewModule extends KrollModule +{ + + // Standard Debugging variables + private static final String TAG = "CalendarviewModule"; + + // You can define constants with @Kroll.constant, for example: + // @Kroll.constant public static final String EXTERNAL_NAME = value; + + public CalendarviewModule() + { + super(); + } + + @Kroll.onAppCreate + public static void onAppCreate(TiApplication app) + { + Log.d(TAG, "inside onAppCreate"); + // put module init code that needs to run when the application is created + } + + // Methods + @Kroll.method + public String example() + { + Log.d(TAG, "example called"); + return "hello world"; + } + + // Properties + @Kroll.getProperty + public String getExampleProp() + { + Log.d(TAG, "get example property"); + return "hello world"; + } + + + @Kroll.setProperty + public void setExampleProp(String value) { + Log.d(TAG, "set example property: " + value); + } + +} + diff --git a/Android/Module/timodule.xml b/Android/Module/timodule.xml new file mode 100644 index 0000000..6affb2f --- /dev/null +++ b/Android/Module/timodule.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/Android/SquareLibrary/.classpath b/Android/SquareLibrary/.classpath new file mode 100644 index 0000000..a4763d1 --- /dev/null +++ b/Android/SquareLibrary/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Android/SquareLibrary/.project b/Android/SquareLibrary/.project new file mode 100644 index 0000000..28900ec --- /dev/null +++ b/Android/SquareLibrary/.project @@ -0,0 +1,33 @@ + + + SquareLibrary + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/Android/SquareLibrary/AndroidManifest.xml b/Android/SquareLibrary/AndroidManifest.xml new file mode 100755 index 0000000..f5b602f --- /dev/null +++ b/Android/SquareLibrary/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/Android/SquareLibrary/bin/AndroidManifest.xml b/Android/SquareLibrary/bin/AndroidManifest.xml new file mode 100644 index 0000000..f5b602f --- /dev/null +++ b/Android/SquareLibrary/bin/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/Android/SquareLibrary/bin/R.txt b/Android/SquareLibrary/bin/R.txt new file mode 100644 index 0000000..9c264d8 --- /dev/null +++ b/Android/SquareLibrary/bin/R.txt @@ -0,0 +1,41 @@ +int attr state_current_month 0x7f010001 +int attr state_range_first 0x7f010003 +int attr state_range_last 0x7f010005 +int attr state_range_middle 0x7f010004 +int attr state_selectable 0x7f010000 +int attr state_today 0x7f010002 +int color calendar_active_month_bg 0x7f040000 +int color calendar_bg 0x7f040001 +int color calendar_divider 0x7f040002 +int color calendar_inactive_month_bg 0x7f040003 +int color calendar_selected_day_bg 0x7f040004 +int color calendar_selected_range_bg 0x7f040005 +int color calendar_text_active 0x7f040007 +int color calendar_text_inactive 0x7f040006 +int color calendar_text_selected 0x7f040008 +int color calendar_text_selector 0x7f04000a +int color calendar_text_unselectable 0x7f040009 +int dimen calendar_day_headers_paddingbottom 0x7f050000 +int dimen calendar_month_title_bottommargin 0x7f050002 +int dimen calendar_month_topmargin 0x7f050001 +int dimen calendar_text_medium 0x7f050003 +int dimen calendar_text_small 0x7f050004 +int drawable calendar_bg_selector 0x7f020000 +int id calendar_grid 0x7f080001 +int id title 0x7f080000 +int layout month 0x7f030000 +int layout week 0x7f030001 +int string day_name_format 0x7f060000 +int string invalid_date 0x7f060001 +int string month_name_format 0x7f060002 +int style CalendarCell 0x7f070001 +int style CalendarCell_CalendarDate 0x7f070003 +int style CalendarCell_DayHeader 0x7f070002 +int style CalendarTitle 0x7f070000 +int[] styleable calendar_cell { 0x7f010000, 0x7f010001, 0x7f010002, 0x7f010003, 0x7f010004, 0x7f010005 } +int styleable calendar_cell_state_current_month 1 +int styleable calendar_cell_state_range_first 3 +int styleable calendar_cell_state_range_last 5 +int styleable calendar_cell_state_range_middle 4 +int styleable calendar_cell_state_selectable 0 +int styleable calendar_cell_state_today 2 diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/BuildConfig.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/BuildConfig.class new file mode 100644 index 0000000..0eb39ed Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/BuildConfig.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarCellView.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarCellView.class new file mode 100644 index 0000000..354b7e0 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarCellView.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarGridView.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarGridView.class new file mode 100644 index 0000000..12987c1 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarGridView.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$1.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$1.class new file mode 100644 index 0000000..7adaf8a Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$1.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$CellClickedListener.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$CellClickedListener.class new file mode 100644 index 0000000..defbd38 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$CellClickedListener.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$DateSelectableFilter.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$DateSelectableFilter.class new file mode 100644 index 0000000..0342f2e Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$DateSelectableFilter.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$DefaultOnInvalidDateSelectedListener.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$DefaultOnInvalidDateSelectedListener.class new file mode 100644 index 0000000..b44aabd Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$DefaultOnInvalidDateSelectedListener.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$FluentInitializer.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$FluentInitializer.class new file mode 100644 index 0000000..a0e67ef Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$FluentInitializer.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$MonthAdapter.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$MonthAdapter.class new file mode 100644 index 0000000..d9a9685 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$MonthAdapter.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$MonthCellWithMonthIndex.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$MonthCellWithMonthIndex.class new file mode 100644 index 0000000..5d09982 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$MonthCellWithMonthIndex.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$OnDateSelectedListener.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$OnDateSelectedListener.class new file mode 100644 index 0000000..55e31ef Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$OnDateSelectedListener.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$OnInvalidDateSelectedListener.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$OnInvalidDateSelectedListener.class new file mode 100644 index 0000000..40e7722 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$OnInvalidDateSelectedListener.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$SelectionMode.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$SelectionMode.class new file mode 100644 index 0000000..0970e93 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView$SelectionMode.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView.class new file mode 100644 index 0000000..e7323c4 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarPickerView.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarRowView.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarRowView.class new file mode 100644 index 0000000..2322ef5 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/CalendarRowView.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/Logr.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/Logr.class new file mode 100644 index 0000000..7f4a1e6 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/Logr.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthCellDescriptor$RangeState.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthCellDescriptor$RangeState.class new file mode 100644 index 0000000..d9f32ee Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthCellDescriptor$RangeState.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthCellDescriptor.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthCellDescriptor.class new file mode 100644 index 0000000..a063d8e Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthCellDescriptor.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthDescriptor.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthDescriptor.class new file mode 100644 index 0000000..cc6bbc2 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthDescriptor.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthView$Listener.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthView$Listener.class new file mode 100644 index 0000000..3cf739d Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthView$Listener.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthView.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthView.class new file mode 100644 index 0000000..b1121ee Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/MonthView.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$attr.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$attr.class new file mode 100644 index 0000000..d052c53 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$attr.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$color.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$color.class new file mode 100644 index 0000000..0eab267 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$color.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$dimen.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$dimen.class new file mode 100644 index 0000000..bfe47f6 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$dimen.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$drawable.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$drawable.class new file mode 100644 index 0000000..819968c Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$drawable.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$id.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$id.class new file mode 100644 index 0000000..6fce9bb Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$id.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$layout.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$layout.class new file mode 100644 index 0000000..1f67c41 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$layout.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$string.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$string.class new file mode 100644 index 0000000..2c68a89 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$string.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$style.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$style.class new file mode 100644 index 0000000..f4cf752 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$style.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$styleable.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$styleable.class new file mode 100644 index 0000000..57624f5 Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R$styleable.class differ diff --git a/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R.class b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R.class new file mode 100644 index 0000000..b420f6e Binary files /dev/null and b/Android/SquareLibrary/bin/classes/com/squareup/timessquare/R.class differ diff --git a/Android/SquareLibrary/bin/squarelibrary.jar b/Android/SquareLibrary/bin/squarelibrary.jar new file mode 100644 index 0000000..9cb85b1 Binary files /dev/null and b/Android/SquareLibrary/bin/squarelibrary.jar differ diff --git a/Android/SquareLibrary/gen/com/squareup/timessquare/BuildConfig.java b/Android/SquareLibrary/gen/com/squareup/timessquare/BuildConfig.java new file mode 100644 index 0000000..584e3ab --- /dev/null +++ b/Android/SquareLibrary/gen/com/squareup/timessquare/BuildConfig.java @@ -0,0 +1,6 @@ +/** Automatically generated file. DO NOT MODIFY */ +package com.squareup.timessquare; + +public final class BuildConfig { + public final static boolean DEBUG = true; +} \ No newline at end of file diff --git a/Android/SquareLibrary/gen/com/squareup/timessquare/R.java b/Android/SquareLibrary/gen/com/squareup/timessquare/R.java new file mode 100644 index 0000000..aa95247 --- /dev/null +++ b/Android/SquareLibrary/gen/com/squareup/timessquare/R.java @@ -0,0 +1,213 @@ +/* AUTO-GENERATED FILE. DO NOT MODIFY. + * + * This class was automatically generated by the + * aapt tool from the resource data it found. It + * should not be modified by hand. + */ + +package com.squareup.timessquare; + +public final class R { + public static final class attr { + /**

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + */ + public static int state_current_month=0x7f010001; + /**

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + */ + public static int state_range_first=0x7f010003; + /**

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + */ + public static int state_range_last=0x7f010005; + /**

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + */ + public static int state_range_middle=0x7f010004; + /**

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + */ + public static int state_selectable=0x7f010000; + /**

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + */ + public static int state_today=0x7f010002; + } + public static final class color { + public static int calendar_active_month_bg=0x7f040000; + public static int calendar_bg=0x7f040001; + public static int calendar_divider=0x7f040002; + public static int calendar_inactive_month_bg=0x7f040003; + public static int calendar_selected_day_bg=0x7f040004; + public static int calendar_selected_range_bg=0x7f040005; + public static int calendar_text_active=0x7f040007; + public static int calendar_text_inactive=0x7f040006; + public static int calendar_text_selected=0x7f040008; + public static int calendar_text_selector=0x7f04000a; + public static int calendar_text_unselectable=0x7f040009; + } + public static final class dimen { + public static int calendar_day_headers_paddingbottom=0x7f050000; + public static int calendar_month_title_bottommargin=0x7f050002; + public static int calendar_month_topmargin=0x7f050001; + public static int calendar_text_medium=0x7f050003; + public static int calendar_text_small=0x7f050004; + } + public static final class drawable { + public static int calendar_bg_selector=0x7f020000; + } + public static final class id { + public static int calendar_grid=0x7f080001; + public static int title=0x7f080000; + } + public static final class layout { + public static int month=0x7f030000; + public static int week=0x7f030001; + } + public static final class string { + public static int day_name_format=0x7f060000; + public static int invalid_date=0x7f060001; + public static int month_name_format=0x7f060002; + } + public static final class style { + public static int CalendarCell=0x7f070001; + public static int CalendarCell_CalendarDate=0x7f070003; + public static int CalendarCell_DayHeader=0x7f070002; + public static int CalendarTitle=0x7f070000; + } + public static final class styleable { + /** Attributes that can be used with a calendar_cell. +

Includes the following attributes:

+ + + + + + + + + + +
AttributeDescription
{@link #calendar_cell_state_current_month com.squareup.timessquare:state_current_month}
{@link #calendar_cell_state_range_first com.squareup.timessquare:state_range_first}
{@link #calendar_cell_state_range_last com.squareup.timessquare:state_range_last}
{@link #calendar_cell_state_range_middle com.squareup.timessquare:state_range_middle}
{@link #calendar_cell_state_selectable com.squareup.timessquare:state_selectable}
{@link #calendar_cell_state_today com.squareup.timessquare:state_today}
+ @see #calendar_cell_state_current_month + @see #calendar_cell_state_range_first + @see #calendar_cell_state_range_last + @see #calendar_cell_state_range_middle + @see #calendar_cell_state_selectable + @see #calendar_cell_state_today + */ + public static final int[] calendar_cell = { + 0x7f010000, 0x7f010001, 0x7f010002, 0x7f010003, + 0x7f010004, 0x7f010005 + }; + /** +

This symbol is the offset where the {@link com.squareup.timessquare.R.attr#state_current_month} + attribute's value can be found in the {@link #calendar_cell} array. + + +

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + @attr name android:state_current_month + */ + public static final int calendar_cell_state_current_month = 1; + /** +

This symbol is the offset where the {@link com.squareup.timessquare.R.attr#state_range_first} + attribute's value can be found in the {@link #calendar_cell} array. + + +

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + @attr name android:state_range_first + */ + public static final int calendar_cell_state_range_first = 3; + /** +

This symbol is the offset where the {@link com.squareup.timessquare.R.attr#state_range_last} + attribute's value can be found in the {@link #calendar_cell} array. + + +

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + @attr name android:state_range_last + */ + public static final int calendar_cell_state_range_last = 5; + /** +

This symbol is the offset where the {@link com.squareup.timessquare.R.attr#state_range_middle} + attribute's value can be found in the {@link #calendar_cell} array. + + +

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + @attr name android:state_range_middle + */ + public static final int calendar_cell_state_range_middle = 4; + /** +

This symbol is the offset where the {@link com.squareup.timessquare.R.attr#state_selectable} + attribute's value can be found in the {@link #calendar_cell} array. + + +

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + @attr name android:state_selectable + */ + public static final int calendar_cell_state_selectable = 0; + /** +

This symbol is the offset where the {@link com.squareup.timessquare.R.attr#state_today} + attribute's value can be found in the {@link #calendar_cell} array. + + +

Must be a boolean value, either "true" or "false". +

This may also be a reference to a resource (in the form +"@[package:]type:name") or +theme attribute (in the form +"?[package:][type:]name") +containing a value of this type. + @attr name android:state_today + */ + public static final int calendar_cell_state_today = 2; + }; +} diff --git a/Android/SquareLibrary/pom.xml b/Android/SquareLibrary/pom.xml new file mode 100755 index 0000000..00b291e --- /dev/null +++ b/Android/SquareLibrary/pom.xml @@ -0,0 +1,58 @@ + + + + 4.0.0 + + + com.squareup + android-times-square-parent + 1.0.4-SNAPSHOT + ../pom.xml + + + android-times-square + Android-TimesSquare + apklib + + + + com.google.android + android + provided + + + + junit + junit + test + + + com.squareup + fest-android + test + + + org.robolectric + robolectric + test + + + com.intellij + annotations + test + + + + + src + test + + + + com.jayway.maven.plugins.android.generation2 + android-maven-plugin + true + + + + diff --git a/Android/SquareLibrary/project.properties b/Android/SquareLibrary/project.properties new file mode 100755 index 0000000..0e58ae1 --- /dev/null +++ b/Android/SquareLibrary/project.properties @@ -0,0 +1,12 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +android.library=true +# Project target. +target=android-17 diff --git a/Android/SquareLibrary/res/color/calendar_text_selector.xml b/Android/SquareLibrary/res/color/calendar_text_selector.xml new file mode 100755 index 0000000..9abdfea --- /dev/null +++ b/Android/SquareLibrary/res/color/calendar_text_selector.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/Android/SquareLibrary/res/drawable/calendar_bg_selector.xml b/Android/SquareLibrary/res/drawable/calendar_bg_selector.xml new file mode 100755 index 0000000..ffce8c4 --- /dev/null +++ b/Android/SquareLibrary/res/drawable/calendar_bg_selector.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Android/SquareLibrary/res/layout/month.xml b/Android/SquareLibrary/res/layout/month.xml new file mode 100755 index 0000000..078cd38 --- /dev/null +++ b/Android/SquareLibrary/res/layout/month.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Android/SquareLibrary/res/layout/week.xml b/Android/SquareLibrary/res/layout/week.xml new file mode 100755 index 0000000..c917377 --- /dev/null +++ b/Android/SquareLibrary/res/layout/week.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Android/SquareLibrary/res/values/attrs.xml b/Android/SquareLibrary/res/values/attrs.xml new file mode 100755 index 0000000..7122458 --- /dev/null +++ b/Android/SquareLibrary/res/values/attrs.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Android/SquareLibrary/res/values/colors.xml b/Android/SquareLibrary/res/values/colors.xml new file mode 100755 index 0000000..7349fc8 --- /dev/null +++ b/Android/SquareLibrary/res/values/colors.xml @@ -0,0 +1,13 @@ + + + #fff5f7f9 + #ffffffff + #ffbababa + #ffd7d9db + #ff379bff + #ff96caff + #40778088 + #ff778088 + #ffffffff + #7f778088 + diff --git a/Android/SquareLibrary/res/values/dimens.xml b/Android/SquareLibrary/res/values/dimens.xml new file mode 100755 index 0000000..29d834b --- /dev/null +++ b/Android/SquareLibrary/res/values/dimens.xml @@ -0,0 +1,8 @@ + + + 4dp + 16dp + 4dp + 18sp + 14sp + diff --git a/Android/SquareLibrary/res/values/strings.xml b/Android/SquareLibrary/res/values/strings.xml new file mode 100755 index 0000000..960473b --- /dev/null +++ b/Android/SquareLibrary/res/values/strings.xml @@ -0,0 +1,8 @@ + + + + + EEE + Date must be between %1$s and %2$s. + MMMM yyyy + diff --git a/Android/SquareLibrary/res/values/styles.xml b/Android/SquareLibrary/res/values/styles.xml new file mode 100755 index 0000000..d8a0ea5 --- /dev/null +++ b/Android/SquareLibrary/res/values/styles.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/Android/SquareLibrary/src/com/squareup/timessquare/CalendarCellView.java b/Android/SquareLibrary/src/com/squareup/timessquare/CalendarCellView.java new file mode 100755 index 0000000..7802aa0 --- /dev/null +++ b/Android/SquareLibrary/src/com/squareup/timessquare/CalendarCellView.java @@ -0,0 +1,93 @@ +// Copyright 2013 Square, Inc. + +package com.squareup.timessquare; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; +import com.squareup.timessquare.MonthCellDescriptor.RangeState; + +public class CalendarCellView extends TextView { + + private static final int[] STATE_SELECTABLE = { + R.attr.state_selectable + }; + private static final int[] STATE_CURRENT_MONTH = { + R.attr.state_current_month + }; + private static final int[] STATE_TODAY = { + R.attr.state_today + }; + private static final int[] STATE_RANGE_FIRST = { + R.attr.state_range_first + }; + private static final int[] STATE_RANGE_MIDDLE = { + R.attr.state_range_middle + }; + private static final int[] STATE_RANGE_LAST = { + R.attr.state_range_last + }; + + private boolean isSelectable = false; + private boolean isCurrentMonth = false; + private boolean isToday = false; + private RangeState rangeState = RangeState.NONE; + + public CalendarCellView(Context context) { + super(context); + } + + public CalendarCellView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CalendarCellView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setSelectable(boolean isSelectable) { + this.isSelectable = isSelectable; + refreshDrawableState(); + } + + public void setCurrentMonth(boolean isCurrentMonth) { + this.isCurrentMonth = isCurrentMonth; + refreshDrawableState(); + } + + public void setToday(boolean isToday) { + this.isToday = isToday; + refreshDrawableState(); + } + + public void setRangeState(MonthCellDescriptor.RangeState rangeState) { + this.rangeState = rangeState; + refreshDrawableState(); + } + + @Override protected int[] onCreateDrawableState(int extraSpace) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 4); + + if (isSelectable) { + mergeDrawableStates(drawableState, STATE_SELECTABLE); + } + + if (isCurrentMonth) { + mergeDrawableStates(drawableState, STATE_CURRENT_MONTH); + } + + if (isToday) { + mergeDrawableStates(drawableState, STATE_TODAY); + } + + if (rangeState == MonthCellDescriptor.RangeState.FIRST) { + mergeDrawableStates(drawableState, STATE_RANGE_FIRST); + } else if (rangeState == MonthCellDescriptor.RangeState.MIDDLE) { + mergeDrawableStates(drawableState, STATE_RANGE_MIDDLE); + } else if (rangeState == RangeState.LAST) { + mergeDrawableStates(drawableState, STATE_RANGE_LAST); + } + + return drawableState; + } +} diff --git a/Android/SquareLibrary/src/com/squareup/timessquare/CalendarGridView.java b/Android/SquareLibrary/src/com/squareup/timessquare/CalendarGridView.java new file mode 100755 index 0000000..ac8421b --- /dev/null +++ b/Android/SquareLibrary/src/com/squareup/timessquare/CalendarGridView.java @@ -0,0 +1,103 @@ +// Copyright 2012 Square, Inc. +package com.squareup.timessquare; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import static android.view.View.MeasureSpec.AT_MOST; +import static android.view.View.MeasureSpec.EXACTLY; +import static android.view.View.MeasureSpec.makeMeasureSpec; + +/** + * ViewGroup that draws a grid of calendar cells. All children must be {@link CalendarRowView}s. + * The first row is assumed to be a header and no divider is drawn above it. + */ +public class CalendarGridView extends ViewGroup { + private final Paint dividerPaint = new Paint(); + private int oldWidthMeasureSize; + + public CalendarGridView(Context context, AttributeSet attrs) { + super(context, attrs); + dividerPaint.setColor(getResources().getColor(R.color.calendar_divider)); + } + + @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (getChildCount() == 0) { + ((CalendarRowView) child).setIsHeaderRow(true); + } + super.addView(child, index, params); + } + + @Override protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + final ViewGroup row = (ViewGroup) getChildAt(1); + int top = row.getTop(); + int bottom = getBottom(); + // Left side border. + final int left = row.getChildAt(0).getLeft() + getLeft(); + canvas.drawLine(left, top, left, bottom, dividerPaint); + + // Each cell's right-side border. + for (int c = 0; c < 7; c++) { + int x = left + row.getChildAt(c).getRight() - 1; + canvas.drawLine(x, top, x, bottom, dividerPaint); + } + } + + @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + final boolean retVal = super.drawChild(canvas, child, drawingTime); + // Draw a bottom border. + final int bottom = child.getBottom() - 1; + canvas.drawLine(child.getLeft(), bottom, child.getRight(), bottom, dividerPaint); + return retVal; + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Logr.d("Grid.onMeasure w=%s h=%s", MeasureSpec.toString(widthMeasureSpec), + MeasureSpec.toString(heightMeasureSpec)); + int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec); + if (oldWidthMeasureSize == widthMeasureSize) { + Logr.d("SKIP Grid.onMeasure"); + setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()); + return; + } + long start = System.currentTimeMillis(); + oldWidthMeasureSize = widthMeasureSize; + int cellSize = widthMeasureSize / 7; + // Remove any extra pixels since /7 is unlikely to give whole nums. + widthMeasureSize = cellSize * 7; + int totalHeight = 0; + final int rowWidthSpec = makeMeasureSpec(widthMeasureSize, EXACTLY); + final int rowHeightSpec = makeMeasureSpec(cellSize, EXACTLY); + for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) { + final View child = getChildAt(c); + if (child.getVisibility() == View.VISIBLE) { + if (c == 0) { // It's the header: height should be wrap_content. + measureChild(child, rowWidthSpec, makeMeasureSpec(cellSize, AT_MOST)); + } else { + measureChild(child, rowWidthSpec, rowHeightSpec); + } + totalHeight += child.getMeasuredHeight(); + } + } + final int measuredWidth = widthMeasureSize + 2; // Fudge factor to make the borders show up. + setMeasuredDimension(measuredWidth, totalHeight); + Logr.d("Grid.onMeasure %d ms", System.currentTimeMillis() - start); + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + long start = System.currentTimeMillis(); + top = 0; + for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) { + final View child = getChildAt(c); + final int rowHeight = child.getMeasuredHeight(); + child.layout(left, top, right, top + rowHeight); + top += rowHeight; + } + Logr.d("Grid.onLayout %d ms", System.currentTimeMillis() - start); + } +} diff --git a/Android/SquareLibrary/src/com/squareup/timessquare/CalendarPickerView.java b/Android/SquareLibrary/src/com/squareup/timessquare/CalendarPickerView.java new file mode 100755 index 0000000..80c5fd6 --- /dev/null +++ b/Android/SquareLibrary/src/com/squareup/timessquare/CalendarPickerView.java @@ -0,0 +1,662 @@ +// Copyright 2012 Square, Inc. +package com.squareup.timessquare; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.Toast; +import com.squareup.timessquare.MonthCellDescriptor.RangeState; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import static java.util.Calendar.DATE; +import static java.util.Calendar.DAY_OF_MONTH; +import static java.util.Calendar.DAY_OF_WEEK; +import static java.util.Calendar.HOUR_OF_DAY; +import static java.util.Calendar.MILLISECOND; +import static java.util.Calendar.MINUTE; +import static java.util.Calendar.MONTH; +import static java.util.Calendar.SECOND; +import static java.util.Calendar.YEAR; + +/** + * Android component to allow picking a date from a calendar view (a list of months). Must be + * initialized after inflation with {@link #init(Date, Date)} and can be customized with any of the + * {@link FluentInitializer} methods returned. The currently selected date can be retrieved with + * {@link #getSelectedDate()}. + */ +public class CalendarPickerView extends ListView { + public enum SelectionMode { + /** + * Only one date will be selectable. If there is already a selected date and you select a new + * one, the old date will be unselected. + */ + SINGLE, + /** Multiple dates will be selectable. Selecting an already-selected date will un-select it. */ + MULTIPLE, + /** + * Allows you to select a date range. Previous selections are cleared when you either: + *

+ */ + RANGE + } + + private final CalendarPickerView.MonthAdapter adapter; + private DateFormat monthNameFormat; + private DateFormat weekdayNameFormat; + private DateFormat fullDateFormat; + SelectionMode selectionMode; + final List months = new ArrayList(); + final List selectedCells = new ArrayList(); + final Calendar today = Calendar.getInstance(); + private final List>> cells = + new ArrayList>>(); + final List selectedCals = new ArrayList(); + private final Calendar minCal = Calendar.getInstance(); + private final Calendar maxCal = Calendar.getInstance(); + private final Calendar monthCounter = Calendar.getInstance(); + private final MonthView.Listener listener = new CellClickedListener(); + + private OnDateSelectedListener dateListener; + private DateSelectableFilter dateConfiguredListener; + private OnInvalidDateSelectedListener invalidDateListener = + new DefaultOnInvalidDateSelectedListener(); + + public CalendarPickerView(Context context, AttributeSet attrs) { + super(context, attrs); + adapter = new MonthAdapter(); + setDivider(null); + setDividerHeight(0); + final int bg = getResources().getColor(R.color.calendar_bg); + setBackgroundColor(bg); + setCacheColorHint(bg); + monthNameFormat = new SimpleDateFormat(context.getString(R.string.month_name_format)); + weekdayNameFormat = new SimpleDateFormat(context.getString(R.string.day_name_format)); + fullDateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM); + + if (isInEditMode()) { + Calendar nextYear = Calendar.getInstance(); + nextYear.add(Calendar.YEAR, 1); + + init(new Date(), nextYear.getTime()) // + .withSelectedDate(new Date()); + } + } + + /** + * Both date parameters must be non-null and their {@link Date#getTime()} must not return 0. Time + * of day will be ignored. For instance, if you pass in {@code minDate} as 11/16/2012 5:15pm and + * {@code maxDate} as 11/16/2013 4:30am, 11/16/2012 will be the first selectable date and + * 11/15/2013 will be the last selectable date ({@code maxDate} is exclusive). + *

+ * This will implicitly set the {@link SelectionMode} to {@link SelectionMode#SINGLE}. If you + * want a different selection mode, use {@link FluentInitializer#inMode(SelectionMode)} on the + * {@link FluentInitializer} this method returns. + * + * @param minDate Earliest selectable date, inclusive. Must be earlier than {@code maxDate}. + * @param maxDate Latest selectable date, exclusive. Must be later than {@code minDate}. + */ + public FluentInitializer init(Date minDate, Date maxDate) { + if (minDate == null || maxDate == null) { + throw new IllegalArgumentException( + "minDate and maxDate must be non-null. " + dbg(minDate, maxDate)); + } + if (minDate.after(maxDate)) { + throw new IllegalArgumentException( + "minDate must be before maxDate. " + dbg(minDate, maxDate)); + } + if (minDate.getTime() == 0 || maxDate.getTime() == 0) { + throw new IllegalArgumentException( + "minDate and maxDate must be non-zero. " + dbg(minDate, maxDate)); + } + this.selectionMode = SelectionMode.SINGLE; + // Clear out any previously-selected dates/cells. + selectedCals.clear(); + selectedCells.clear(); + + // Clear previous state. + cells.clear(); + months.clear(); + minCal.setTime(minDate); + maxCal.setTime(maxDate); + setMidnight(minCal); + setMidnight(maxCal); + + // maxDate is exclusive: bump back to the previous day so if maxDate is the first of a month, + // we don't accidentally include that month in the view. + maxCal.add(MINUTE, -1); + + // Now iterate between minCal and maxCal and build up our list of months to show. + monthCounter.setTime(minCal.getTime()); + final int maxMonth = maxCal.get(MONTH); + final int maxYear = maxCal.get(YEAR); + while ((monthCounter.get(MONTH) <= maxMonth // Up to, including the month. + || monthCounter.get(YEAR) < maxYear) // Up to the year. + && monthCounter.get(YEAR) < maxYear + 1) { // But not > next yr. + Date date = monthCounter.getTime(); + MonthDescriptor month = + new MonthDescriptor(monthCounter.get(MONTH), monthCounter.get(YEAR), date, + monthNameFormat.format(date)); + cells.add(getMonthCells(month, monthCounter)); + Logr.d("Adding month %s", month); + months.add(month); + monthCounter.add(MONTH, 1); + } + + validateAndUpdate(); + return new FluentInitializer(); + } + + public class FluentInitializer { + /** Override the {@link SelectionMode} from the default ({@link SelectionMode#SINGLE}). */ + public FluentInitializer inMode(SelectionMode mode) { + selectionMode = mode; + validateAndUpdate(); + return this; + } + + /** + * Set an initially-selected date. The calendar will scroll to that date if it's not already + * visible. + */ + public FluentInitializer withSelectedDate(Date selectedDates) { + return withSelectedDates(Arrays.asList(selectedDates)); + } + + /** + * Set multiple selected dates. This will throw an {@link IllegalArgumentException} if you + * pass in multiple dates and haven't already called {@link #inMode(SelectionMode)}. + */ + public FluentInitializer withSelectedDates(Collection selectedDates) { + if (selectionMode == SelectionMode.SINGLE && selectedDates.size() > 1) { + throw new IllegalArgumentException("SINGLE mode can't be used with multiple selectedDates"); + } + if (selectedDates != null) { + for (Date date : selectedDates) { + selectDate(date); + } + } + Integer selectedIndex = null; + Integer todayIndex = null; + Calendar today = Calendar.getInstance(); + for (int c = 0; c < months.size(); c++) { + MonthDescriptor month = months.get(c); + if (selectedIndex == null) { + for (Calendar selectedCal : selectedCals) { + if (sameMonth(selectedCal, month)) { + selectedIndex = c; + break; + } + } + if (selectedIndex == null && todayIndex == null && sameMonth(today, month)) { + todayIndex = c; + } + } + } + if (selectedIndex != null) { + scrollToSelectedMonth(selectedIndex); + } else if (todayIndex != null) { + scrollToSelectedMonth(todayIndex); + } + + validateAndUpdate(); + return this; + } + + /** Override default locale: specify a locale in which the calendar should be rendered. */ + public FluentInitializer withLocale(Locale locale) { + monthNameFormat = + new SimpleDateFormat(getContext().getString(R.string.month_name_format), locale); + for (MonthDescriptor month : months) { + month.setLabel(monthNameFormat.format(month.getDate())); + } + weekdayNameFormat = + new SimpleDateFormat(getContext().getString(R.string.day_name_format), locale); + fullDateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); + validateAndUpdate(); + return this; + } + } + + private void validateAndUpdate() { + if (getAdapter() == null) { + setAdapter(adapter); + } + adapter.notifyDataSetChanged(); + } + + private void scrollToSelectedMonth(final int selectedIndex) { + post(new Runnable() { + @Override + public void run() { + smoothScrollToPosition(selectedIndex); + } + }); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (months.isEmpty()) { + throw new IllegalStateException( + "Must have at least one month to display. Did you forget to call init()?"); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + public Date getSelectedDate() { + return (selectedCals.size() > 0 ? selectedCals.get(0).getTime() : null); + } + + public List getSelectedDates() { + List selectedDates = new ArrayList(); + for (MonthCellDescriptor cal : selectedCells) { + selectedDates.add(cal.getDate()); + } + Collections.sort(selectedDates); + return selectedDates; + } + + /** Returns a string summarizing what the client sent us for init() params. */ + private static String dbg(Date minDate, Date maxDate) { + return "minDate: " + minDate + "\nmaxDate: " + maxDate; + } + + /** Clears out the hours/minutes/seconds/millis of a Calendar. */ + static void setMidnight(Calendar cal) { + cal.set(HOUR_OF_DAY, 0); + cal.set(MINUTE, 0); + cal.set(SECOND, 0); + cal.set(MILLISECOND, 0); + } + + private class CellClickedListener implements MonthView.Listener { + @Override public void handleClick(MonthCellDescriptor cell) { + Date clickedDate = cell.getDate(); + + if (!betweenDates(clickedDate, minCal, maxCal) || !isDateSelectable(clickedDate)) { + if (invalidDateListener != null) { + invalidDateListener.onInvalidDateSelected(clickedDate); + } + } else { + boolean wasSelected = doSelectDate(clickedDate, cell); + + if (wasSelected && dateListener != null) { + dateListener.onDateSelected(clickedDate); + } + } + } + } + + /** + * Select a new date. Respects the {@link SelectionMode} this CalendarPickerView is configured + * with: if you are in {@link SelectionMode#SINGLE}, the previously selected date will be + * un-selected. In {@link SelectionMode#MULTIPLE}, the new date will be added to the list of + * selected dates. + *

+ * If the selection was made (selectable date, in range), the view will scroll to the newly + * selected date if it's not already visible. + * + * @return - whether we were able to set the date + */ + public boolean selectDate(Date date) { + if (date == null) { + throw new IllegalArgumentException("Selected date must be non-null. " + date); + } + if (date.getTime() == 0) { + throw new IllegalArgumentException("Selected date must be non-zero. " + date); + } + if (date.before(minCal.getTime()) || date.after(maxCal.getTime())) { + throw new IllegalArgumentException( + "selectedDate must be between minDate and maxDate. " + date); + } + MonthCellWithMonthIndex monthCellWithMonthIndex = getMonthCellWithIndexByDate(date); + if (monthCellWithMonthIndex == null || !isDateSelectable(date)) { + return false; + } + boolean wasSelected = doSelectDate(date, monthCellWithMonthIndex.cell); + if (wasSelected) { + scrollToSelectedMonth(monthCellWithMonthIndex.monthIndex); + } + return wasSelected; + } + + private boolean doSelectDate(Date date, MonthCellDescriptor cell) { + Calendar newlySelectedCal = Calendar.getInstance(); + newlySelectedCal.setTime(date); + // Sanitize input: clear out the hours/minutes/seconds/millis. + setMidnight(newlySelectedCal); + + // Clear any remaining range state. + for (MonthCellDescriptor selectedCell : selectedCells) { + selectedCell.setRangeState(RangeState.NONE); + } + + switch (selectionMode) { + case RANGE: + if (selectedCals.size() > 1) { + // We've already got a range selected: clear the old one. + clearOldSelections(); + } else if (selectedCals.size() == 1 && newlySelectedCal.before(selectedCals.get(0))) { + // We're moving the start of the range back in time: clear the old start date. + clearOldSelections(); + } + break; + + case MULTIPLE: + date = applyMultiSelect(date, newlySelectedCal); + break; + + case SINGLE: + clearOldSelections(); + break; + default: + throw new IllegalStateException("Unknown selectionMode " + selectionMode); + } + + if (date != null) { + // Select a new cell. + if (selectedCells.size() == 0 || !selectedCells.get(0).equals(cell)) { + selectedCells.add(cell); + cell.setSelected(true); + } + selectedCals.add(newlySelectedCal); + + if (selectionMode == SelectionMode.RANGE && selectedCells.size() > 1) { + // Select all days in between start and end. + Date start = selectedCells.get(0).getDate(); + Date end = selectedCells.get(1).getDate(); + selectedCells.get(0).setRangeState(MonthCellDescriptor.RangeState.FIRST); + selectedCells.get(1).setRangeState(MonthCellDescriptor.RangeState.LAST); + + for (List> month : cells) { + for (List week : month) { + for (MonthCellDescriptor singleCell : week) { + if (singleCell.getDate().after(start) + && singleCell.getDate().before(end) + && singleCell.isSelectable()) { + singleCell.setSelected(true); + singleCell.setRangeState(MonthCellDescriptor.RangeState.MIDDLE); + selectedCells.add(singleCell); + } + } + } + } + } + } + + // Update the adapter. + validateAndUpdate(); + return date != null; + } + + private void clearOldSelections() { + for (MonthCellDescriptor selectedCell : selectedCells) { + // De-select the currently-selected cell. + selectedCell.setSelected(false); + } + selectedCells.clear(); + selectedCals.clear(); + } + + private Date applyMultiSelect(Date date, Calendar selectedCal) { + for (MonthCellDescriptor selectedCell : selectedCells) { + if (selectedCell.getDate().equals(date)) { + // De-select the currently-selected cell. + selectedCell.setSelected(false); + selectedCells.remove(selectedCell); + date = null; + break; + } + } + for (Calendar cal : selectedCals) { + if (sameDate(cal, selectedCal)) { + selectedCals.remove(cal); + break; + } + } + return date; + } + + /** Hold a cell with a month-index. */ + private static class MonthCellWithMonthIndex { + public MonthCellDescriptor cell; + public int monthIndex; + + public MonthCellWithMonthIndex(MonthCellDescriptor cell, int monthIndex) { + this.cell = cell; + this.monthIndex = monthIndex; + } + } + + /** Return cell and month-index (for scrolling) for a given Date. */ + private MonthCellWithMonthIndex getMonthCellWithIndexByDate(Date date) { + int index = 0; + Calendar searchCal = Calendar.getInstance(); + searchCal.setTime(date); + Calendar actCal = Calendar.getInstance(); + + for (List> monthCells : cells) { + for (List weekCells : monthCells) { + for (MonthCellDescriptor actCell : weekCells) { + actCal.setTime(actCell.getDate()); + if (sameDate(actCal, searchCal) && actCell.isSelectable()) { + return new MonthCellWithMonthIndex(actCell, index); + } + } + } + index++; + } + return null; + } + + private class MonthAdapter extends BaseAdapter { + private final LayoutInflater inflater; + + private MonthAdapter() { + inflater = LayoutInflater.from(getContext()); + } + + @Override public boolean isEnabled(int position) { + // Disable selectability: each cell will handle that itself. + return false; + } + + @Override public int getCount() { + return months.size(); + } + + @Override public Object getItem(int position) { + return months.get(position); + } + + @Override public long getItemId(int position) { + return position; + } + + @Override public View getView(int position, View convertView, ViewGroup parent) { + MonthView monthView = (MonthView) convertView; + if (monthView == null) { + monthView = MonthView.create(parent, inflater, weekdayNameFormat, listener, today); + } + monthView.init(months.get(position), cells.get(position)); + return monthView; + } + } + + List> getMonthCells(MonthDescriptor month, Calendar startCal) { + Calendar cal = Calendar.getInstance(); + cal.setTime(startCal.getTime()); + List> cells = new ArrayList>(); + cal.set(DAY_OF_MONTH, 1); + int firstDayOfWeek = cal.get(DAY_OF_WEEK); + cal.add(DATE, cal.getFirstDayOfWeek() - firstDayOfWeek); + + Calendar minSelectedCal = minDate(selectedCals); + Calendar maxSelectedCal = maxDate(selectedCals); + + while ((cal.get(MONTH) < month.getMonth() + 1 || cal.get(YEAR) < month.getYear()) // + && cal.get(YEAR) <= month.getYear()) { + Logr.d("Building week row starting at %s", cal.getTime()); + List weekCells = new ArrayList(); + cells.add(weekCells); + for (int c = 0; c < 7; c++) { + Date date = cal.getTime(); + boolean isCurrentMonth = cal.get(MONTH) == month.getMonth(); + boolean isSelected = isCurrentMonth && containsDate(selectedCals, cal); + boolean isSelectable = + isCurrentMonth && betweenDates(cal, minCal, maxCal) && isDateSelectable(date); + boolean isToday = sameDate(cal, today); + int value = cal.get(DAY_OF_MONTH); + + MonthCellDescriptor.RangeState rangeState = MonthCellDescriptor.RangeState.NONE; + if (selectedCals != null && selectedCals.size() > 1) { + if (sameDate(minSelectedCal, cal)) { + rangeState = MonthCellDescriptor.RangeState.FIRST; + } else if (sameDate(maxDate(selectedCals), cal)) { + rangeState = MonthCellDescriptor.RangeState.LAST; + } else if (betweenDates(cal, minSelectedCal, maxSelectedCal)) { + rangeState = MonthCellDescriptor.RangeState.MIDDLE; + } + } + + weekCells.add( + new MonthCellDescriptor(date, isCurrentMonth, isSelectable, isSelected, isToday, value, + rangeState)); + cal.add(DATE, 1); + } + } + return cells; + } + + private static boolean containsDate(List selectedCals, Calendar cal) { + for (Calendar selectedCal : selectedCals) { + if (sameDate(cal, selectedCal)) { + return true; + } + } + return false; + } + + private static Calendar minDate(List selectedCals) { + if (selectedCals == null || selectedCals.size() == 0) { + return null; + } + Collections.sort(selectedCals); + return selectedCals.get(0); + } + + private static Calendar maxDate(List selectedCals) { + if (selectedCals == null || selectedCals.size() == 0) { + return null; + } + Collections.sort(selectedCals); + return selectedCals.get(selectedCals.size() - 1); + } + + private static boolean sameDate(Calendar cal, Calendar selectedDate) { + return cal.get(MONTH) == selectedDate.get(MONTH) + && cal.get(YEAR) == selectedDate.get(YEAR) + && cal.get(DAY_OF_MONTH) == selectedDate.get(DAY_OF_MONTH); + } + + private static boolean betweenDates(Calendar cal, Calendar minCal, Calendar maxCal) { + final Date date = cal.getTime(); + return betweenDates(date, minCal, maxCal); + } + + static boolean betweenDates(Date date, Calendar minCal, Calendar maxCal) { + final Date min = minCal.getTime(); + return (date.equals(min) || date.after(min)) // >= minCal + && date.before(maxCal.getTime()); // && < maxCal + } + + private static boolean sameMonth(Calendar cal, MonthDescriptor month) { + return (cal.get(MONTH) == month.getMonth() && cal.get(YEAR) == month.getYear()); + } + + private boolean isDateSelectable(Date date) { + if (dateConfiguredListener == null) { + return true; + } + return dateConfiguredListener.isDateSelectable(date); + } + + public void setOnDateSelectedListener(OnDateSelectedListener listener) { + dateListener = listener; + } + + /** + * Set a listener to react to user selection of a disabled date. + * + * @param listener the listener to set, or null for no reaction + */ + public void setOnInvalidDateSelectedListener(OnInvalidDateSelectedListener listener) { + invalidDateListener = listener; + } + + /** + * Set a listener used to discriminate between selectable and unselectable dates. Set this to + * disable arbitrary dates as they are rendered. + *

+ * Important: set this before you call {@link #init(Date, Date)} methods. If called afterwards, + * it will not be consistently applied. + */ + public void setDateSelectableFilter(DateSelectableFilter listener) { + dateConfiguredListener = listener; + } + + /** + * Interface to be notified when a new date is selected. This will only be called when the user + * initiates the date selection. If you call {@link #selectDate(Date)} this listener will not be + * notified. + * + * @see #setOnDateSelectedListener(OnDateSelectedListener) + */ + public interface OnDateSelectedListener { + void onDateSelected(Date date); + } + + /** + * Interface to be notified when an invalid date is selected by the user. This will only be + * called when the user initiates the date selection. If you call {@link #selectDate(Date)} this + * listener will not be notified. + * + * @see #setOnInvalidDateSelectedListener(OnInvalidDateSelectedListener) + */ + public interface OnInvalidDateSelectedListener { + void onInvalidDateSelected(Date date); + } + + /** + * Interface used for determining the selectability of a date cell when it is configured for + * display on the calendar. + * + * @see #setDateSelectableFilter(DateSelectableFilter) + */ + public interface DateSelectableFilter { + boolean isDateSelectable(Date date); + } + + private class DefaultOnInvalidDateSelectedListener implements OnInvalidDateSelectedListener { + @Override public void onInvalidDateSelected(Date date) { + String errMessage = + getResources().getString(R.string.invalid_date, fullDateFormat.format(minCal.getTime()), + fullDateFormat.format(maxCal.getTime())); + Toast.makeText(getContext(), errMessage, Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/Android/SquareLibrary/src/com/squareup/timessquare/CalendarRowView.java b/Android/SquareLibrary/src/com/squareup/timessquare/CalendarRowView.java new file mode 100755 index 0000000..b67732f --- /dev/null +++ b/Android/SquareLibrary/src/com/squareup/timessquare/CalendarRowView.java @@ -0,0 +1,73 @@ +// Copyright 2012 Square, Inc. +package com.squareup.timessquare; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import static android.view.View.MeasureSpec.AT_MOST; +import static android.view.View.MeasureSpec.EXACTLY; +import static android.view.View.MeasureSpec.makeMeasureSpec; + +/** TableRow that draws a divider between each cell. To be used with {@link CalendarGridView}. */ +public class CalendarRowView extends ViewGroup implements View.OnClickListener { + private boolean isHeaderRow; + private MonthView.Listener listener; + private int cellSize; + + public CalendarRowView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { + child.setOnClickListener(this); + super.addView(child, index, params); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + long start = System.currentTimeMillis(); + final int totalWidth = MeasureSpec.getSize(widthMeasureSpec); + cellSize = totalWidth / 7; + int cellWidthSpec = makeMeasureSpec(cellSize, EXACTLY); + int cellHeightSpec = isHeaderRow ? makeMeasureSpec(cellSize, AT_MOST) : cellWidthSpec; + int rowHeight = 0; + for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) { + final View child = getChildAt(c); + child.measure(cellWidthSpec, cellHeightSpec); + // The row height is the height of the tallest cell. + if (child.getMeasuredHeight() > rowHeight) { + rowHeight = child.getMeasuredHeight(); + } + } + final int widthWithPadding = totalWidth + getPaddingLeft() + getPaddingRight(); + final int heightWithPadding = rowHeight + getPaddingTop() + getPaddingBottom(); + setMeasuredDimension(widthWithPadding, heightWithPadding); + Logr.d("Row.onMeasure %d ms", System.currentTimeMillis() - start); + } + + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + long start = System.currentTimeMillis(); + int cellHeight = bottom - top; + for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) { + final View child = getChildAt(c); + child.layout(c * cellSize, 0, (c + 1) * cellSize, cellHeight); + } + Logr.d("Row.onLayout %d ms", System.currentTimeMillis() - start); + } + + public void setIsHeaderRow(boolean isHeaderRow) { + this.isHeaderRow = isHeaderRow; + } + + @Override public void onClick(View v) { + // Header rows don't have a click listener + if (listener != null) { + listener.handleClick((MonthCellDescriptor) v.getTag()); + } + } + + public void setListener(MonthView.Listener listener) { + this.listener = listener; + } +} diff --git a/Android/SquareLibrary/src/com/squareup/timessquare/Logr.java b/Android/SquareLibrary/src/com/squareup/timessquare/Logr.java new file mode 100755 index 0000000..d134b54 --- /dev/null +++ b/Android/SquareLibrary/src/com/squareup/timessquare/Logr.java @@ -0,0 +1,18 @@ +package com.squareup.timessquare; + +import android.util.Log; + +/** Log utility class to handle the log tag and DEBUG-only logging. */ +final class Logr { + public static void d(String message) { + if (BuildConfig.DEBUG) { + Log.d("TimesSquare", message); + } + } + + public static void d(String message, Object... args) { + if (BuildConfig.DEBUG) { + d(String.format(message, args)); + } + } +} diff --git a/Android/SquareLibrary/src/com/squareup/timessquare/MonthCellDescriptor.java b/Android/SquareLibrary/src/com/squareup/timessquare/MonthCellDescriptor.java new file mode 100755 index 0000000..90ed358 --- /dev/null +++ b/Android/SquareLibrary/src/com/squareup/timessquare/MonthCellDescriptor.java @@ -0,0 +1,86 @@ +// Copyright 2012 Square, Inc. + +package com.squareup.timessquare; + +import java.util.Date; + +/** Describes the state of a particular date cell in a {@link MonthView}. */ +class MonthCellDescriptor { + public enum RangeState { + NONE, FIRST, MIDDLE, LAST + } + + private final Date date; + private final int value; + private final boolean isCurrentMonth; + private boolean isSelected; + private final boolean isToday; + private final boolean isSelectable; + private RangeState rangeState; + + MonthCellDescriptor(Date date, boolean currentMonth, boolean selectable, boolean selected, + boolean today, int value, RangeState rangeState) { + this.date = date; + isCurrentMonth = currentMonth; + isSelectable = selectable; + isSelected = selected; + isToday = today; + this.value = value; + this.rangeState = rangeState; + } + + public Date getDate() { + return date; + } + + public boolean isCurrentMonth() { + return isCurrentMonth; + } + + public boolean isSelectable() { + return isSelectable; + } + + public boolean isSelected() { + return isSelected; + } + + public void setSelected(boolean selected) { + isSelected = selected; + } + + public boolean isToday() { + return isToday; + } + + public RangeState getRangeState() { + return rangeState; + } + + public void setRangeState(RangeState rangeState) { + this.rangeState = rangeState; + } + + public int getValue() { + return value; + } + + @Override public String toString() { + return "MonthCellDescriptor{" + + "date=" + + date + + ", value=" + + value + + ", isCurrentMonth=" + + isCurrentMonth + + ", isSelected=" + + isSelected + + ", isToday=" + + isToday + + ", isSelectable=" + + isSelectable + + ", rangeState=" + + rangeState + + '}'; + } +} diff --git a/Android/SquareLibrary/src/com/squareup/timessquare/MonthDescriptor.java b/Android/SquareLibrary/src/com/squareup/timessquare/MonthDescriptor.java new file mode 100755 index 0000000..c5014df --- /dev/null +++ b/Android/SquareLibrary/src/com/squareup/timessquare/MonthDescriptor.java @@ -0,0 +1,50 @@ +// Copyright 2012 Square, Inc. +package com.squareup.timessquare; + +import java.util.Date; + +class MonthDescriptor { + private final int month; + private final int year; + private final Date date; + private String label; + + public MonthDescriptor(int month, int year, Date date, String label) { + this.month = month; + this.year = year; + this.date = date; + this.label = label; + } + + public int getMonth() { + return month; + } + + public int getYear() { + return year; + } + + public Date getDate() { + return date; + } + + public String getLabel() { + return label; + } + + void setLabel(String label) { + this.label = label; + } + + @Override public String toString() { + return "MonthDescriptor{" + + "label='" + + label + + '\'' + + ", month=" + + month + + ", year=" + + year + + '}'; + } +} diff --git a/Android/SquareLibrary/src/com/squareup/timessquare/MonthView.java b/Android/SquareLibrary/src/com/squareup/timessquare/MonthView.java new file mode 100755 index 0000000..fb1f772 --- /dev/null +++ b/Android/SquareLibrary/src/com/squareup/timessquare/MonthView.java @@ -0,0 +1,83 @@ +// Copyright 2012 Square, Inc. +package com.squareup.timessquare; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; +import java.text.DateFormat; +import java.util.Calendar; +import java.util.List; + +public class MonthView extends LinearLayout { + TextView title; + CalendarGridView grid; + private Listener listener; + + public static MonthView create(ViewGroup parent, LayoutInflater inflater, + DateFormat weekdayNameFormat, Listener listener, Calendar today) { + final MonthView view = (MonthView) inflater.inflate(R.layout.month, parent, false); + + final int originalDayOfWeek = today.get(Calendar.DAY_OF_WEEK); + + int firstDayOfWeek = today.getFirstDayOfWeek(); + final CalendarRowView headerRow = (CalendarRowView) view.grid.getChildAt(0); + for (int offset = 0; offset < 7; offset++) { + today.set(Calendar.DAY_OF_WEEK, firstDayOfWeek + offset); + final TextView textView = (TextView) headerRow.getChildAt(offset); + textView.setText(weekdayNameFormat.format(today.getTime())); + } + today.set(Calendar.DAY_OF_WEEK, originalDayOfWeek); + view.listener = listener; + return view; + } + + public MonthView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override protected void onFinishInflate() { + super.onFinishInflate(); + title = (TextView) findViewById(R.id.title); + grid = (CalendarGridView) findViewById(R.id.calendar_grid); + } + + public void init(MonthDescriptor month, List> cells) { + Logr.d("Initializing MonthView (%d) for %s", System.identityHashCode(this), month); + long start = System.currentTimeMillis(); + title.setText(month.getLabel()); + + final int numRows = cells.size(); + for (int i = 0; i < 6; i++) { + CalendarRowView weekRow = (CalendarRowView) grid.getChildAt(i + 1); + weekRow.setListener(listener); + if (i < numRows) { + weekRow.setVisibility(VISIBLE); + List week = cells.get(i); + for (int c = 0; c < week.size(); c++) { + MonthCellDescriptor cell = week.get(c); + CalendarCellView cellView = (CalendarCellView) weekRow.getChildAt(c); + + cellView.setText(Integer.toString(cell.getValue())); + cellView.setEnabled(cell.isCurrentMonth()); + + cellView.setSelectable(cell.isSelectable()); + cellView.setSelected(cell.isSelected()); + cellView.setCurrentMonth(cell.isCurrentMonth()); + cellView.setToday(cell.isToday()); + cellView.setRangeState(cell.getRangeState()); + cellView.setTag(cell); + } + } else { + weekRow.setVisibility(GONE); + } + } + Logr.d("MonthView.init took %d ms", System.currentTimeMillis() - start); + } + + public interface Listener { + void handleClick(MonthCellDescriptor cell); + } +} diff --git a/Android/SquareLibrary/test/com/squareup/timessquare/CalendarPickerViewTest.java b/Android/SquareLibrary/test/com/squareup/timessquare/CalendarPickerViewTest.java new file mode 100755 index 0000000..3b03fb7 --- /dev/null +++ b/Android/SquareLibrary/test/com/squareup/timessquare/CalendarPickerViewTest.java @@ -0,0 +1,556 @@ +// Copyright 2012 Square, Inc. +package com.squareup.timessquare; + +import android.app.Activity; +import android.widget.TextView; +import com.squareup.timessquare.MonthCellDescriptor.RangeState; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import org.intellij.lang.annotations.MagicConstant; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import static com.squareup.timessquare.CalendarPickerView.SelectionMode.MULTIPLE; +import static com.squareup.timessquare.CalendarPickerView.SelectionMode.RANGE; +import static com.squareup.timessquare.CalendarPickerView.SelectionMode.SINGLE; +import static java.util.Calendar.APRIL; +import static java.util.Calendar.AUGUST; +import static java.util.Calendar.DAY_OF_MONTH; +import static java.util.Calendar.DAY_OF_WEEK; +import static java.util.Calendar.DECEMBER; +import static java.util.Calendar.FEBRUARY; +import static java.util.Calendar.JANUARY; +import static java.util.Calendar.JULY; +import static java.util.Calendar.JUNE; +import static java.util.Calendar.MARCH; +import static java.util.Calendar.MAY; +import static java.util.Calendar.MONTH; +import static java.util.Calendar.NOVEMBER; +import static java.util.Calendar.OCTOBER; +import static java.util.Calendar.SEPTEMBER; +import static java.util.Calendar.YEAR; +import static org.fest.assertions.api.ANDROID.assertThat; +import static org.fest.assertions.api.Assertions.assertThat; +import static org.fest.assertions.api.Assertions.fail; + +@RunWith(RobolectricTestRunner.class) +public class CalendarPickerViewTest { + private CalendarPickerView view; + private Calendar today; + private Date maxDate; + private Date minDate; + + @Before + public void setUp() throws Exception { + view = new CalendarPickerView(new Activity(), null); + today = Calendar.getInstance(); + today.set(2012, NOVEMBER, 16, 0, 0); + minDate = today.getTime(); + today.set(2013, NOVEMBER, 16, 0, 0); + maxDate = today.getTime(); + today.set(2012, NOVEMBER, 16, 0, 0); + Date startDate = today.getTime(); + view.today.setTime(startDate); + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDate(startDate); + } + + @Test + public void testInitDecember() throws Exception { + Calendar dec2012 = buildCal(2012, DECEMBER, 1); + Calendar dec2013 = buildCal(2013, DECEMBER, 1); + view.init(dec2012.getTime(), dec2013.getTime()) // + .inMode(SINGLE) // + .withSelectedDate(dec2012.getTime()); + assertThat(view.months).hasSize(12); + } + + @Test + public void testInitJanuary() throws Exception { + Calendar jan2012 = buildCal(2012, JANUARY, 1); + Calendar jan2013 = buildCal(2013, JANUARY, 1); + view.init(jan2012.getTime(), jan2013.getTime()) // + .inMode(SINGLE) // + .withSelectedDate(jan2012.getTime()); + assertThat(view.months).hasSize(12); + } + + @Test + public void testInitMidyear() throws Exception { + Calendar may2012 = buildCal(2012, MAY, 1); + Calendar may2013 = buildCal(2013, MAY, 1); + view.init(may2012.getTime(), may2013.getTime()) // + .inMode(SINGLE) // + .withSelectedDate(may2012.getTime()); + assertThat(view.months).hasSize(12); + } + + @Test + public void testOnlyShowingFourWeeks() throws Exception { + List> cells = selectDateAndGetCells(FEBRUARY, 2015, today); + assertThat(cells).hasSize(4); + + // Last cell should be 1. + assertCell(cells, 0, 0, 1, true, false, false, false, MonthCellDescriptor.RangeState.NONE); + + // Last cell should be 28. + assertCell(cells, 3, 6, 28, true, false, false, false, MonthCellDescriptor.RangeState.NONE); + } + + @Test + public void testOnlyShowingFiveWeeks() throws Exception { + List> cells = selectDateAndGetCells(FEBRUARY, 2013, today); + assertThat(cells).hasSize(5); + + // First cell is the 27th of January. + assertCell(cells, 0, 0, 27, false, false, false, false, MonthCellDescriptor.RangeState.NONE); + + // First day of Feb falls on the 5th cell. + assertCell(cells, 0, 5, 1, true, false, false, true, MonthCellDescriptor.RangeState.NONE); + + // Last day of Feb falls on the 5th row, 5th column. + assertCell(cells, 4, 4, 28, true, false, false, true, MonthCellDescriptor.RangeState.NONE); + + // Last cell should be March 2nd. + assertCell(cells, 4, 6, 2, false, false, false, false, RangeState.NONE); + } + + @Test + public void testWeirdOverlappingYear() throws Exception { + List> cells = selectDateAndGetCells(JANUARY, 2013, today); + assertThat(cells).hasSize(5); + } + + @Test + public void testShowingSixWeeks() throws Exception { + List> cells = selectDateAndGetCells(DECEMBER, 2012, today); + assertThat(cells).hasSize(6); + + // First cell is the 27th of November. + assertCell(cells, 0, 0, 25, false, false, false, false, MonthCellDescriptor.RangeState.NONE); + + // First day of December falls on the 6th cell. + assertCell(cells, 0, 6, 1, true, false, false, true, MonthCellDescriptor.RangeState.NONE); + + // Last day of December falls on the 6th row, 2nd column. + assertCell(cells, 5, 1, 31, true, false, false, true, MonthCellDescriptor.RangeState.NONE); + + // Last cell should be January 5th. + assertCell(cells, 5, 6, 5, false, false, false, false, RangeState.NONE); + } + + @Test + public void testIsSelected() throws Exception { + Calendar nov29 = buildCal(2012, NOVEMBER, 29); + + List> cells = selectDateAndGetCells(NOVEMBER, 2012, nov29); + assertThat(cells).hasSize(5); + // Make sure the cell is selected when it's in November. + assertCell(cells, 4, 4, 29, true, true, false, true, MonthCellDescriptor.RangeState.NONE); + + cells = selectDateAndGetCells(DECEMBER, 2012, nov29); + assertThat(cells).hasSize(6); + // Make sure the cell is not selected when it's in December. + assertCell(cells, 0, 4, 29, false, false, false, false, MonthCellDescriptor.RangeState.NONE); + } + + @Test + public void testTodayIsToday() throws Exception { + List> cells = selectDateAndGetCells(NOVEMBER, 2012, today); + assertCell(cells, 2, 5, 16, true, true, true, true, RangeState.NONE); + } + + @Test + public void testSelectabilityInFirstMonth() throws Exception { + List> cells = selectDateAndGetCells(NOVEMBER, 2012, today); + // 10/29 is not selectable because it's in the previous month. + assertCell(cells, 0, 0, 28, false, false, false, false, RangeState.NONE); + // 11/1 is not selectable because it's < minDate (11/16/12). + assertCell(cells, 0, 4, 1, true, false, false, false, MonthCellDescriptor.RangeState.NONE); + // 11/16 is selectable because it's == minDate (11/16/12). + assertCell(cells, 2, 5, 16, true, true, true, true, RangeState.NONE); + // 11/20 is selectable because it's > minDate (11/16/12). + assertCell(cells, 3, 2, 20, true, false, false, true, MonthCellDescriptor.RangeState.NONE); + // 12/1 is not selectable because it's in the next month. + assertCell(cells, 4, 6, 1, false, false, false, false, MonthCellDescriptor.RangeState.NONE); + } + + @Test + public void testSelectabilityInLastMonth() throws Exception { + List> cells = selectDateAndGetCells(NOVEMBER, 2013, today); + // 10/29 is not selectable because it's in the previous month. + assertCell(cells, 0, 0, 27, false, false, false, false, RangeState.NONE); + // 11/1 is selectable because it's < maxDate (11/16/13). + assertCell(cells, 0, 5, 1, true, false, false, true, MonthCellDescriptor.RangeState.NONE); + // 11/15 is selectable because it's one less than maxDate (11/16/13). + assertCell(cells, 2, 5, 15, true, false, false, true, RangeState.NONE); + // 11/16 is not selectable because it's > maxDate (11/16/13). + assertCell(cells, 2, 6, 16, true, false, false, false, MonthCellDescriptor.RangeState.NONE); + } + + @Test + public void testInitSingleWithMultipleSelections() throws Exception { + List selectedDates = new ArrayList(); + selectedDates.add(minDate); + // This one should work. + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDates(selectedDates); + + // Now add another date and try init'ing again in SINGLE mode. + Calendar secondSelection = buildCal(2012, NOVEMBER, 17); + selectedDates.add(secondSelection.getTime()); + try { + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDates(selectedDates); + fail("Should not have been able to init() with SINGLE mode && multiple selected dates"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testNullDates() throws Exception { + final Date validDate = today.getTime(); + try { + view.init(validDate, validDate) // + .inMode(SINGLE) // + .withSelectedDate(null); + fail("Should not have been able to pass in a null startDate"); + } catch (IllegalArgumentException expected) { + } + try { + view.init(null, validDate) // + .inMode(SINGLE) // + .withSelectedDate(validDate); + fail("Should not have been able to pass in a null minDate"); + } catch (IllegalArgumentException expected) { + } + try { + view.init(validDate, null) // + .inMode(SINGLE) // + .withSelectedDate(validDate); + fail("Should not have been able to pass in a null maxDate"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testZeroDates() throws Exception { + final Date validDate = today.getTime(); + final Date zeroDate = new Date(0L); + try { + view.init(validDate, validDate) // + .inMode(SINGLE) // + .withSelectedDate(zeroDate); + fail("Should not have been able to pass in a zero startDate"); + } catch (IllegalArgumentException expected) { + } + try { + view.init(zeroDate, validDate) // + .inMode(SINGLE) // + .withSelectedDate(validDate); + fail("Should not have been able to pass in a zero minDate"); + } catch (IllegalArgumentException expected) { + } + try { + view.init(validDate, zeroDate) // + .inMode(SINGLE) // + .withSelectedDate(validDate); + fail("Should not have been able to pass in a zero maxDate"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testMinAndMaxMixup() throws Exception { + final Date minDate = today.getTime(); + today.add(YEAR, -1); + final Date maxDate = today.getTime(); + try { + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDate(minDate); + fail("Should not have been able to pass in a maxDate < minDate"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSelectedNotInRange() throws Exception { + final Date minDate = today.getTime(); + today.add(YEAR, 1); + final Date maxDate = today.getTime(); + today.add(YEAR, 1); + Date selectedDate = today.getTime(); + try { + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDate(selectedDate); + fail("Should not have been able to pass in a selectedDate > maxDate"); + } catch (IllegalArgumentException expected) { + } + today.add(YEAR, -5); + selectedDate = today.getTime(); + try { + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDate(selectedDate); + fail("Should not have been able to pass in a selectedDate < minDate"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testNotCallingInit() throws Exception { + view = new CalendarPickerView(new Activity(), null); + try { + view.onMeasure(0, 0); + fail("Should have thrown an IllegalStateException!"); + } catch (IllegalStateException expected) { + } + } + + @Test + public void testShowingOnlyOneMonth() throws Exception { + Calendar feb1 = buildCal(2013, FEBRUARY, 1); + Calendar mar1 = buildCal(2013, MARCH, 1); + view.init(feb1.getTime(), mar1.getTime()) // + .inMode(SINGLE) // + .withSelectedDate(feb1.getTime()); + assertThat(view.months).hasSize(1); + } + + @Test + public void selectDateThrowsExceptionForDatesOutOfRange() { + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDate(today.getTime()); + Calendar outOfRange = buildCal(2015, FEBRUARY, 1); + try { + view.selectDate(outOfRange.getTime()); + fail("selectDate should've blown up with an out of range date"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void selectDateReturnsTrueForDateInRange() { + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDate(today.getTime()); + Calendar inRange = buildCal(2013, FEBRUARY, 1); + boolean wasAbleToSetDate = view.selectDate(inRange.getTime()); + assertThat(wasAbleToSetDate).isTrue(); + } + + @Test + public void selectDateDoesntSelectDisabledCell() { + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDate(today.getTime()); + Calendar jumpToCal = buildCal(2013, FEBRUARY, 1); + boolean wasAbleToSetDate = view.selectDate(jumpToCal.getTime()); + assertThat(wasAbleToSetDate).isTrue(); + assertThat(view.selectedCells.get(0).isSelectable()).isTrue(); + } + + @Test + public void testMultiselectWithNoInitialSelections() throws Exception { + view.init(minDate, maxDate) // + .inMode(MULTIPLE); + assertThat(view.selectionMode).isEqualTo(MULTIPLE); + assertThat(view.getSelectedDates()).isEmpty(); + + view.selectDate(minDate); + assertThat(view.getSelectedDates()).hasSize(1); + + Calendar secondSelection = buildCal(2012, NOVEMBER, 17); + view.selectDate(secondSelection.getTime()); + assertThat(view.getSelectedDates()).hasSize(2); + assertThat(view.getSelectedDates().get(1)).hasTime(secondSelection.getTimeInMillis()); + } + + @Test + public void testOnDateConfiguredListener() { + final Calendar testCal = Calendar.getInstance(); + view.setDateSelectableFilter(new CalendarPickerView.DateSelectableFilter() { + @Override public boolean isDateSelectable(Date date) { + testCal.setTime(date); + int dayOfWeek = testCal.get(DAY_OF_WEEK); + return dayOfWeek > 1 && dayOfWeek < 7; + } + }); + view.init(minDate, maxDate) // + .inMode(SINGLE) // + .withSelectedDate(today.getTime()); + Calendar jumpToCal = Calendar.getInstance(); + jumpToCal.add(MONTH, 2); + jumpToCal.set(DAY_OF_WEEK, 1); + boolean wasAbleToSetDate = view.selectDate(jumpToCal.getTime()); + assertThat(wasAbleToSetDate).isFalse(); + + jumpToCal.set(DAY_OF_WEEK, 2); + wasAbleToSetDate = view.selectDate(jumpToCal.getTime()); + assertThat(wasAbleToSetDate).isTrue(); + } + + @Test + public void testRangeSelectionWithNoInitialSelection() throws Exception { + view.init(minDate, maxDate) + .inMode(RANGE); + assertThat(view.selectedCals).hasSize(0); + assertThat(view.selectedCells).hasSize(0); + + Calendar nov18 = buildCal(2012, NOVEMBER, 18); + view.selectDate(nov18.getTime()); + assertOneDateSelected(); + + Calendar nov24 = buildCal(2012, NOVEMBER, 24); + view.selectDate(nov24.getTime()); + assertRangeSelected(); + + assertRangeSelectionBehavior(); + } + + private void assertRangeSelectionBehavior() { + // Start a new range in the middle of the current (Nov 18 - Nov 24) one. + Calendar nov20 = buildCal(2012, NOVEMBER, 20); + view.selectDate(nov20.getTime()); + assertOneDateSelected(); + + // Finish that range. + Calendar nov26 = buildCal(2012, NOVEMBER, 26); + view.selectDate(nov26.getTime()); + assertRangeSelected(); + + // Start a new range in the middle of the current (Nov 20 - Nov 26) one. + Calendar nov24 = buildCal(2012, NOVEMBER, 24); + view.selectDate(nov24.getTime()); + assertOneDateSelected(); + + // Only Nov 24 is selected: going backward should start a new range. + view.selectDate(nov20.getTime()); + assertOneDateSelected(); + } + + @Test + public void testRangeWithTwoInitialSelections() throws Exception { + Calendar nov18 = buildCal(2012, NOVEMBER, 18); + Calendar nov24 = buildCal(2012, NOVEMBER, 24); + List selectedDates = Arrays.asList(nov18.getTime(), nov24.getTime()); + view.init(minDate, maxDate) + .inMode(RANGE) + .withSelectedDates(selectedDates); + assertRangeSelected(); + + assertRangeSelectionBehavior(); + } + + @Test + public void testRangeWithOneInitialSelection() throws Exception { + Calendar nov18 = buildCal(2012, NOVEMBER, 18); + Calendar nov24 = buildCal(2012, NOVEMBER, 24); + List selectedDates = Arrays.asList(nov18.getTime()); + view.init(minDate, maxDate) + .inMode(RANGE) + .withSelectedDates(selectedDates); + assertOneDateSelected(); + + view.selectDate(nov24.getTime()); + assertRangeSelected(); + + assertRangeSelectionBehavior(); + } + + private void assertRangeSelected() { + assertThat(view.selectedCals).hasSize(2); + assertThat(view.selectedCells).hasSize(7); + assertThat(view.getSelectedDates()).hasSize(7); + } + + private void assertOneDateSelected() { + assertThat(view.selectedCals).hasSize(1); + assertThat(view.selectedCells).hasSize(1); + assertThat(view.getSelectedDates()).hasSize(1); + } + + @Test + public void testRangeStateOnDateSelections() { + Calendar startCal = buildCal(2012, NOVEMBER, 17); + Calendar endCal = buildCal(2012, NOVEMBER, 24); + + view.init(minDate, maxDate) // + .inMode(RANGE); + + boolean wasAbleToSetDate = view.selectDate(startCal.getTime()); + assertThat(wasAbleToSetDate).isTrue(); + + wasAbleToSetDate = view.selectDate(endCal.getTime()); + assertThat(wasAbleToSetDate).isTrue(); + + List> cells = getCells(NOVEMBER, 2012); + assertCell(cells, 2, 6, 17, true, true, false, true, MonthCellDescriptor.RangeState.FIRST); + assertCell(cells, 3, 0, 18, true, false, false, true, MonthCellDescriptor.RangeState.MIDDLE); + assertCell(cells, 3, 1, 19, true, false, false, true, MonthCellDescriptor.RangeState.MIDDLE); + assertCell(cells, 3, 2, 20, true, false, false, true, RangeState.MIDDLE); + assertCell(cells, 3, 3, 21, true, false, false, true, RangeState.MIDDLE); + assertCell(cells, 3, 4, 22, true, false, false, true, MonthCellDescriptor.RangeState.MIDDLE); + assertCell(cells, 3, 5, 23, true, false, false, true, MonthCellDescriptor.RangeState.MIDDLE); + assertCell(cells, 3, 6, 24, true, true, false, true, MonthCellDescriptor.RangeState.LAST); + } + + @Test + public void testLocaleSetting() throws Exception { + view.init(minDate, maxDate) // + .withLocale(Locale.GERMAN); + MonthView monthView = (MonthView) view.getAdapter().getView(1, null, null); + CalendarRowView header = (CalendarRowView) monthView.grid.getChildAt(0); + TextView firstDay = (TextView) header.getChildAt(0); + assertThat(firstDay).hasTextString("So"); // Sonntag = Sunday + assertThat(monthView.title).hasTextString("Dezember 2012"); + } + + private static void assertCell(List> cells, int row, int col, + int expectedVal, boolean expectedCurrentMonth, boolean expectedSelected, + boolean expectedToday, boolean expectedSelectable, MonthCellDescriptor.RangeState expectedRangeState) { + final MonthCellDescriptor cell = cells.get(row).get(col); + assertThat(cell.getValue()).isEqualTo(expectedVal); + assertThat(cell.isCurrentMonth()).isEqualTo(expectedCurrentMonth); + assertThat(cell.isSelected()).isEqualTo(expectedSelected); + assertThat(cell.isToday()).isEqualTo(expectedToday); + assertThat(cell.isSelectable()).isEqualTo(expectedSelectable); + assertThat(cell.getRangeState()).isEqualTo(expectedRangeState); + } + + private List> selectDateAndGetCells(int month, int year, + Calendar selectedDate) { + view.selectDate(selectedDate.getTime()); + return getCells(month, year); + } + + private List> getCells(int month, int year) { + Calendar cal = Calendar.getInstance(); + cal.set(DAY_OF_MONTH, 1); + cal.set(YEAR, year); + cal.set(MONTH, month); + return view.getMonthCells(new MonthDescriptor(month, year, cal.getTime(), "January 2012"), cal); + } + + private Calendar buildCal(int year, @MagicConstant(intValues = { + JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, + DECEMBER + }) int month, int day) { + Calendar jumpToCal = Calendar.getInstance(); + jumpToCal.set(year, month, day); + CalendarPickerView.setMidnight(jumpToCal); + return jumpToCal; + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..13cd94b --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2013 Benjamin Bahrenburg + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..d261cc7 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +

Ti.SquaredCalendar

+ +The SquaredCalendar project allows you to use [Square's](http://square.github.io) open source TimeSquare calendar picker in your Titanium app. + +

Coming Soon...

+Examples on how to use the CalendarView and associated code is coming soon.... please watch the project for updates. + + +

Twitter

+ +Please consider following the [@benCoding Twitter](http://www.twitter.com/benCoding) for updates +and more about Titanium. + +

Blog

+ +For module updates, Titanium tutorials and more please check out my blog at [benCoding.Com](http://benCoding.com). \ No newline at end of file diff --git a/iOS/.gitignore b/iOS/.gitignore new file mode 100644 index 0000000..a606ca9 --- /dev/null +++ b/iOS/.gitignore @@ -0,0 +1,4 @@ +tmp +bin +build +*.zip diff --git a/iOS/.project b/iOS/.project new file mode 100644 index 0000000..49a7f73 --- /dev/null +++ b/iOS/.project @@ -0,0 +1,23 @@ + + + CalendarView + + + + + + com.appcelerator.titanium.core.builder + + + + + com.aptana.ide.core.unifiedBuilder + + + + + + com.appcelerator.titanium.mobile.module.nature + com.aptana.projects.webnature + + diff --git a/iOS/CHANGELOG.txt b/iOS/CHANGELOG.txt new file mode 100644 index 0000000..de1e091 --- /dev/null +++ b/iOS/CHANGELOG.txt @@ -0,0 +1 @@ +Place your change log text here. This file will be incorporated with your app at package time. \ No newline at end of file diff --git a/iOS/CalendarView.xcodeproj/project.pbxproj b/iOS/CalendarView.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ec92b16 --- /dev/null +++ b/iOS/CalendarView.xcodeproj/project.pbxproj @@ -0,0 +1,477 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXAggregateTarget section */ + 24416B8111C4CA220047AFDD /* Build & Test */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 24416B8A11C4CA520047AFDD /* Build configuration list for PBXAggregateTarget "Build & Test" */; + buildPhases = ( + 24416B8011C4CA220047AFDD /* ShellScript */, + ); + dependencies = ( + 24416B8511C4CA280047AFDD /* PBXTargetDependency */, + ); + name = "Build & Test"; + productName = "Build & test"; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 24DD6CF91134B3F500162E58 /* TiSquaredcalendarModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 24DD6CF71134B3F500162E58 /* TiSquaredcalendarModule.h */; }; + 24DD6CFA1134B3F500162E58 /* TiSquaredcalendarModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 24DD6CF81134B3F500162E58 /* TiSquaredcalendarModule.m */; }; + 24DE9E1111C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.h in Headers */ = {isa = PBXBuildFile; fileRef = 24DE9E0F11C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.h */; }; + 24DE9E1211C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.m in Sources */ = {isa = PBXBuildFile; fileRef = 24DE9E1011C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.m */; }; + 436798FA176E9C060013CE2A /* TimesSquare-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 436798F0176E9C060013CE2A /* TimesSquare-Prefix.pch */; }; + 436798FB176E9C060013CE2A /* TimesSquare.h in Headers */ = {isa = PBXBuildFile; fileRef = 436798F1176E9C060013CE2A /* TimesSquare.h */; }; + 436798FC176E9C060013CE2A /* TSQCalendarCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 436798F2176E9C060013CE2A /* TSQCalendarCell.h */; }; + 436798FD176E9C060013CE2A /* TSQCalendarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 436798F3176E9C060013CE2A /* TSQCalendarCell.m */; }; + 436798FE176E9C060013CE2A /* TSQCalendarMonthHeaderCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 436798F4176E9C060013CE2A /* TSQCalendarMonthHeaderCell.h */; }; + 436798FF176E9C060013CE2A /* TSQCalendarMonthHeaderCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 436798F5176E9C060013CE2A /* TSQCalendarMonthHeaderCell.m */; }; + 43679900176E9C060013CE2A /* TSQCalendarRowCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 436798F6176E9C060013CE2A /* TSQCalendarRowCell.h */; }; + 43679901176E9C060013CE2A /* TSQCalendarRowCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 436798F7176E9C060013CE2A /* TSQCalendarRowCell.m */; }; + 43679902176E9C060013CE2A /* TSQCalendarView.h in Headers */ = {isa = PBXBuildFile; fileRef = 436798F8176E9C060013CE2A /* TSQCalendarView.h */; }; + 43679903176E9C060013CE2A /* TSQCalendarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 436798F9176E9C060013CE2A /* TSQCalendarView.m */; }; + 4367990A176E9C740013CE2A /* TiSquaredcalendarCalendarView.h in Headers */ = {isa = PBXBuildFile; fileRef = 43679908176E9C740013CE2A /* TiSquaredcalendarCalendarView.h */; }; + 4367990B176E9C740013CE2A /* TiSquaredcalendarCalendarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 43679909176E9C740013CE2A /* TiSquaredcalendarCalendarView.m */; }; + 4367990E176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 4367990C176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.h */; }; + 4367990F176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4367990D176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.m */; }; + 43679913176EAB4A0013CE2A /* TSQTACalendarRowCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 43679911176EAB490013CE2A /* TSQTACalendarRowCell.h */; }; + 43679914176EAB4A0013CE2A /* TSQTACalendarRowCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 43679912176EAB490013CE2A /* TSQTACalendarRowCell.m */; }; + AA747D9F0F9514B9006C5449 /* TiSquaredcalendar_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = AA747D9E0F9514B9006C5449 /* TiSquaredcalendar_Prefix.pch */; }; + AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AACBBE490F95108600F1A2B1 /* Foundation.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 24416B8411C4CA280047AFDD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2AAC07D0554694100DB518D; + remoteInfo = CalendarView; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 24DD6CF71134B3F500162E58 /* TiSquaredcalendarModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TiSquaredcalendarModule.h; path = Classes/TiSquaredcalendarModule.h; sourceTree = ""; }; + 24DD6CF81134B3F500162E58 /* TiSquaredcalendarModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TiSquaredcalendarModule.m; path = Classes/TiSquaredcalendarModule.m; sourceTree = ""; }; + 24DD6D1B1134B66800162E58 /* titanium.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = titanium.xcconfig; sourceTree = ""; }; + 24DE9E0F11C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TiSquaredcalendarModuleAssets.h; path = Classes/TiSquaredcalendarModuleAssets.h; sourceTree = ""; }; + 24DE9E1011C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TiSquaredcalendarModuleAssets.m; path = Classes/TiSquaredcalendarModuleAssets.m; sourceTree = ""; }; + 436798F0176E9C060013CE2A /* TimesSquare-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TimesSquare-Prefix.pch"; sourceTree = ""; }; + 436798F1176E9C060013CE2A /* TimesSquare.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimesSquare.h; sourceTree = ""; }; + 436798F2176E9C060013CE2A /* TSQCalendarCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSQCalendarCell.h; sourceTree = ""; }; + 436798F3176E9C060013CE2A /* TSQCalendarCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSQCalendarCell.m; sourceTree = ""; }; + 436798F4176E9C060013CE2A /* TSQCalendarMonthHeaderCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSQCalendarMonthHeaderCell.h; sourceTree = ""; }; + 436798F5176E9C060013CE2A /* TSQCalendarMonthHeaderCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSQCalendarMonthHeaderCell.m; sourceTree = ""; }; + 436798F6176E9C060013CE2A /* TSQCalendarRowCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSQCalendarRowCell.h; sourceTree = ""; }; + 436798F7176E9C060013CE2A /* TSQCalendarRowCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSQCalendarRowCell.m; sourceTree = ""; }; + 436798F8176E9C060013CE2A /* TSQCalendarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSQCalendarView.h; sourceTree = ""; }; + 436798F9176E9C060013CE2A /* TSQCalendarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSQCalendarView.m; sourceTree = ""; }; + 43679908176E9C740013CE2A /* TiSquaredcalendarCalendarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TiSquaredcalendarCalendarView.h; path = Classes/TiSquaredcalendarCalendarView.h; sourceTree = ""; }; + 43679909176E9C740013CE2A /* TiSquaredcalendarCalendarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TiSquaredcalendarCalendarView.m; path = Classes/TiSquaredcalendarCalendarView.m; sourceTree = ""; }; + 4367990C176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TiSquaredcalendarCalendarViewProxy.h; path = Classes/TiSquaredcalendarCalendarViewProxy.h; sourceTree = ""; }; + 4367990D176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TiSquaredcalendarCalendarViewProxy.m; path = Classes/TiSquaredcalendarCalendarViewProxy.m; sourceTree = ""; }; + 43679911176EAB490013CE2A /* TSQTACalendarRowCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TSQTACalendarRowCell.h; path = Classes/TSQTACalendarRowCell.h; sourceTree = ""; }; + 43679912176EAB490013CE2A /* TSQTACalendarRowCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSQTACalendarRowCell.m; path = Classes/TSQTACalendarRowCell.m; sourceTree = ""; }; + AA747D9E0F9514B9006C5449 /* TiSquaredcalendar_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiSquaredcalendar_Prefix.pch; sourceTree = SOURCE_ROOT; }; + AACBBE490F95108600F1A2B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + D2AAC07E0554694100DB518D /* libTiSquaredcalendar.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libTiSquaredcalendar.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D2AAC07C0554694100DB518D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + D2AAC07E0554694100DB518D /* libTiSquaredcalendar.a */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 /* CalendarView */ = { + isa = PBXGroup; + children = ( + 08FB77AEFE84172EC02AAC07 /* Classes */, + 32C88DFF0371C24200C91783 /* Other Sources */, + 0867D69AFE84028FC02AAC07 /* Frameworks */, + 034768DFFF38A50411DB9C8B /* Products */, + ); + name = CalendarView; + sourceTree = ""; + }; + 0867D69AFE84028FC02AAC07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + AACBBE490F95108600F1A2B1 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 08FB77AEFE84172EC02AAC07 /* Classes */ = { + isa = PBXGroup; + children = ( + 43679911176EAB490013CE2A /* TSQTACalendarRowCell.h */, + 43679912176EAB490013CE2A /* TSQTACalendarRowCell.m */, + 436798EF176E9C060013CE2A /* TimesSquare */, + 43679908176E9C740013CE2A /* TiSquaredcalendarCalendarView.h */, + 43679909176E9C740013CE2A /* TiSquaredcalendarCalendarView.m */, + 24DE9E0F11C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.h */, + 24DE9E1011C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.m */, + 24DD6CF71134B3F500162E58 /* TiSquaredcalendarModule.h */, + 24DD6CF81134B3F500162E58 /* TiSquaredcalendarModule.m */, + 4367990C176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.h */, + 4367990D176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.m */, + ); + name = Classes; + sourceTree = ""; + }; + 32C88DFF0371C24200C91783 /* Other Sources */ = { + isa = PBXGroup; + children = ( + AA747D9E0F9514B9006C5449 /* TiSquaredcalendar_Prefix.pch */, + 24DD6D1B1134B66800162E58 /* titanium.xcconfig */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 436798EF176E9C060013CE2A /* TimesSquare */ = { + isa = PBXGroup; + children = ( + 436798F0176E9C060013CE2A /* TimesSquare-Prefix.pch */, + 436798F1176E9C060013CE2A /* TimesSquare.h */, + 436798F2176E9C060013CE2A /* TSQCalendarCell.h */, + 436798F3176E9C060013CE2A /* TSQCalendarCell.m */, + 436798F4176E9C060013CE2A /* TSQCalendarMonthHeaderCell.h */, + 436798F5176E9C060013CE2A /* TSQCalendarMonthHeaderCell.m */, + 436798F6176E9C060013CE2A /* TSQCalendarRowCell.h */, + 436798F7176E9C060013CE2A /* TSQCalendarRowCell.m */, + 436798F8176E9C060013CE2A /* TSQCalendarView.h */, + 436798F9176E9C060013CE2A /* TSQCalendarView.m */, + ); + path = TimesSquare; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D2AAC07A0554694100DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + AA747D9F0F9514B9006C5449 /* TiSquaredcalendar_Prefix.pch in Headers */, + 24DD6CF91134B3F500162E58 /* TiSquaredcalendarModule.h in Headers */, + 24DE9E1111C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.h in Headers */, + 436798FA176E9C060013CE2A /* TimesSquare-Prefix.pch in Headers */, + 436798FB176E9C060013CE2A /* TimesSquare.h in Headers */, + 436798FC176E9C060013CE2A /* TSQCalendarCell.h in Headers */, + 436798FE176E9C060013CE2A /* TSQCalendarMonthHeaderCell.h in Headers */, + 43679900176E9C060013CE2A /* TSQCalendarRowCell.h in Headers */, + 43679902176E9C060013CE2A /* TSQCalendarView.h in Headers */, + 4367990A176E9C740013CE2A /* TiSquaredcalendarCalendarView.h in Headers */, + 4367990E176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.h in Headers */, + 43679913176EAB4A0013CE2A /* TSQTACalendarRowCell.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D2AAC07D0554694100DB518D /* CalendarView */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "CalendarView" */; + buildPhases = ( + D2AAC07A0554694100DB518D /* Headers */, + D2AAC07B0554694100DB518D /* Sources */, + D2AAC07C0554694100DB518D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CalendarView; + productName = CalendarView; + productReference = D2AAC07E0554694100DB518D /* libTiSquaredcalendar.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "CalendarView" */; + compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 0867D691FE84028FC02AAC07 /* CalendarView */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC07D0554694100DB518D /* CalendarView */, + 24416B8111C4CA220047AFDD /* Build & Test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 24416B8011C4CA220047AFDD /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# shell script goes here\n\npython \"${TITANIUM_SDK}/titanium.py\" run --dir=\"${PROJECT_DIR}\"\nexit $?\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D2AAC07B0554694100DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 24DD6CFA1134B3F500162E58 /* TiSquaredcalendarModule.m in Sources */, + 24DE9E1211C5FE74003F90F6 /* TiSquaredcalendarModuleAssets.m in Sources */, + 436798FD176E9C060013CE2A /* TSQCalendarCell.m in Sources */, + 436798FF176E9C060013CE2A /* TSQCalendarMonthHeaderCell.m in Sources */, + 43679901176E9C060013CE2A /* TSQCalendarRowCell.m in Sources */, + 43679903176E9C060013CE2A /* TSQCalendarView.m in Sources */, + 4367990B176E9C740013CE2A /* TiSquaredcalendarCalendarView.m in Sources */, + 4367990F176E9C9A0013CE2A /* TiSquaredcalendarCalendarViewProxy.m in Sources */, + 43679914176EAB4A0013CE2A /* TSQTACalendarRowCell.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 24416B8511C4CA280047AFDD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2AAC07D0554694100DB518D /* CalendarView */; + targetProxy = 24416B8411C4CA280047AFDD /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1DEB921F08733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24DD6D1B1134B66800162E58 /* titanium.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DSTROOT = /tmp/TiSquaredcalendar.dst; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = TiSquaredcalendar_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = "TI_VERSION=$(TI_VERSION)"; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_VERSION = ""; + GCC_WARN_ABOUT_RETURN_TYPE = NO; + GCC_WARN_MISSING_PARENTHESES = NO; + GCC_WARN_SHADOW = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = NO; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_PARAMETER = NO; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + INSTALL_PATH = /usr/local/lib; + LIBRARY_SEARCH_PATHS = ""; + OTHER_CFLAGS = ( + "-DDEBUG", + "-DTI_POST_1_2", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = TiSquaredcalendar; + PROVISIONING_PROFILE = ""; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + RUN_CLANG_STATIC_ANALYZER = NO; + SDKROOT = iphoneos; + USER_HEADER_SEARCH_PATHS = ""; + }; + name = Debug; + }; + 1DEB922008733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24DD6D1B1134B66800162E58 /* titanium.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DSTROOT = /tmp/TiSquaredcalendar.dst; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = TiSquaredcalendar_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = "TI_VERSION=$(TI_VERSION)"; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_VERSION = ""; + GCC_WARN_ABOUT_RETURN_TYPE = NO; + GCC_WARN_MISSING_PARENTHESES = NO; + GCC_WARN_SHADOW = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = NO; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_PARAMETER = NO; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + INSTALL_PATH = /usr/local/lib; + IPHONEOS_DEPLOYMENT_TARGET = 4.0; + LIBRARY_SEARCH_PATHS = ""; + OTHER_CFLAGS = "-DTI_POST_1_2"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = TiSquaredcalendar; + RUN_CLANG_STATIC_ANALYZER = NO; + SDKROOT = iphoneos; + USER_HEADER_SEARCH_PATHS = ""; + }; + name = Release; + }; + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24DD6D1B1134B66800162E58 /* titanium.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DSTROOT = /tmp/TiSquaredcalendar.dst; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = TiSquaredcalendar_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = "TI_VERSION=$(TI_VERSION)"; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_VERSION = ""; + GCC_WARN_ABOUT_RETURN_TYPE = NO; + GCC_WARN_MISSING_PARENTHESES = NO; + GCC_WARN_SHADOW = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = NO; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_PARAMETER = NO; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + INSTALL_PATH = /usr/local/lib; + OTHER_CFLAGS = ( + "-DDEBUG", + "-DTI_POST_1_2", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = TiSquaredcalendar; + PROVISIONING_PROFILE = ""; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + RUN_CLANG_STATIC_ANALYZER = NO; + SDKROOT = iphoneos; + USER_HEADER_SEARCH_PATHS = ""; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24DD6D1B1134B66800162E58 /* titanium.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DSTROOT = /tmp/TiSquaredcalendar.dst; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = TiSquaredcalendar_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = "TI_VERSION=$(TI_VERSION)"; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + GCC_VERSION = ""; + GCC_WARN_ABOUT_RETURN_TYPE = NO; + GCC_WARN_MISSING_PARENTHESES = NO; + GCC_WARN_SHADOW = NO; + GCC_WARN_STRICT_SELECTOR_MATCH = NO; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_PARAMETER = NO; + GCC_WARN_UNUSED_VALUE = NO; + GCC_WARN_UNUSED_VARIABLE = NO; + INSTALL_PATH = /usr/local/lib; + IPHONEOS_DEPLOYMENT_TARGET = 4.0; + OTHER_CFLAGS = "-DTI_POST_1_2"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = TiSquaredcalendar; + RUN_CLANG_STATIC_ANALYZER = NO; + SDKROOT = iphoneos; + USER_HEADER_SEARCH_PATHS = ""; + }; + name = Release; + }; + 24416B8211C4CA220047AFDD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24DD6D1B1134B66800162E58 /* titanium.xcconfig */; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + PRODUCT_NAME = "Build & test"; + }; + name = Debug; + }; + 24416B8311C4CA220047AFDD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24DD6D1B1134B66800162E58 /* titanium.xcconfig */; + buildSettings = { + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + PRODUCT_NAME = "Build & test"; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "CalendarView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB921F08733DC00010E9CD /* Debug */, + 1DEB922008733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "CalendarView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 24416B8A11C4CA520047AFDD /* Build configuration list for PBXAggregateTarget "Build & Test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 24416B8211C4CA220047AFDD /* Debug */, + 24416B8311C4CA220047AFDD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/iOS/CalendarView.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iOS/CalendarView.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..0ba52c1 --- /dev/null +++ b/iOS/CalendarView.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/iOS/CalendarView.xcodeproj/project.xcworkspace/xcuserdata/benjamin.xcuserdatad/UserInterfaceState.xcuserstate b/iOS/CalendarView.xcodeproj/project.xcworkspace/xcuserdata/benjamin.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..e7901d5 Binary files /dev/null and b/iOS/CalendarView.xcodeproj/project.xcworkspace/xcuserdata/benjamin.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist b/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist new file mode 100644 index 0000000..05301bc --- /dev/null +++ b/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist @@ -0,0 +1,5 @@ + + + diff --git a/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/Build & Test.xcscheme b/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/Build & Test.xcscheme new file mode 100644 index 0000000..533771e --- /dev/null +++ b/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/Build & Test.xcscheme @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/CalendarView.xcscheme b/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/CalendarView.xcscheme new file mode 100644 index 0000000..39d2be6 --- /dev/null +++ b/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/CalendarView.xcscheme @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/xcschememanagement.plist b/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..b165cf2 --- /dev/null +++ b/iOS/CalendarView.xcodeproj/xcuserdata/benjamin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,32 @@ + + + + + SchemeUserState + + Build & Test.xcscheme + + orderHint + 1 + + CalendarView.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 24416B8111C4CA220047AFDD + + primary + + + D2AAC07D0554694100DB518D + + primary + + + + + diff --git a/iOS/Classes/.gitignore b/iOS/Classes/.gitignore new file mode 100644 index 0000000..cee4fbf --- /dev/null +++ b/iOS/Classes/.gitignore @@ -0,0 +1,2 @@ +TiSquaredcalendar.h +TiSquaredcalendar.m diff --git a/iOS/Classes/TSQTACalendarRowCell.h b/iOS/Classes/TSQTACalendarRowCell.h new file mode 100755 index 0000000..f8b6799 --- /dev/null +++ b/iOS/Classes/TSQTACalendarRowCell.h @@ -0,0 +1,14 @@ +// +// TSQTACalendarRowCell.h +// TimesSquare +// +// Created by Jim Puls on 12/5/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TimesSquare.h" + +@interface TSQTACalendarRowCell : TSQCalendarRowCell + +@end diff --git a/iOS/Classes/TSQTACalendarRowCell.m b/iOS/Classes/TSQTACalendarRowCell.m new file mode 100755 index 0000000..55a3d57 --- /dev/null +++ b/iOS/Classes/TSQTACalendarRowCell.m @@ -0,0 +1,68 @@ +// +// TSQTACalendarRowCell.m +// TimesSquare +// +// Created by Jim Puls on 12/5/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQTACalendarRowCell.h" +#import "TiSquaredcalendarModule.h" +@implementation TSQTACalendarRowCell + + +- (void)layoutViewsForColumnAtIndex:(NSUInteger)index inRect:(CGRect)rect; +{ + // Move down for the row at the top + rect.origin.y += self.columnSpacing; + rect.size.height -= (self.bottomRow ? 2.0f : 1.0f) * self.columnSpacing; + [super layoutViewsForColumnAtIndex:index inRect:rect]; +} + +- (UIImage *)todayBackgroundImage; +{ + NSString *imagePath = [TiSquaredcalendarModule getPathToModuleAsset:@"CalendarTodaysDate.png"]; + UIImage *image = [[UIImage imageWithContentsOfFile:imagePath] stretchableImageWithLeftCapWidth:4 topCapHeight:4]; + if (image == nil) { + return nil; + } + return image; + //return [[UIImage imageNamed:@"CalendarTodaysDate.png"] stretchableImageWithLeftCapWidth:4 topCapHeight:4]; +} + +- (UIImage *)selectedBackgroundImage; +{ + NSString *imagePath = [TiSquaredcalendarModule getPathToModuleAsset:@"CalendarSelectedDate.png"]; + UIImage *image = [[UIImage imageWithContentsOfFile:imagePath] stretchableImageWithLeftCapWidth:4 topCapHeight:4]; + if (image == nil) { + return nil; + } + return image; + //return [[UIImage imageNamed:@"CalendarSelectedDate.png"] stretchableImageWithLeftCapWidth:4 topCapHeight:4]; +} + +- (UIImage *)notThisMonthBackgroundImage; +{ + NSString *imagePath = [TiSquaredcalendarModule getPathToModuleAsset:@"CalendarPreviousMonth.png"]; + UIImage *image = [[UIImage imageWithContentsOfFile:imagePath] stretchableImageWithLeftCapWidth:0 topCapHeight:0]; + if (image == nil) { + return nil; + } + return image; + //return [[UIImage imageNamed:@"CalendarPreviousMonth.png"] stretchableImageWithLeftCapWidth:0 topCapHeight:0]; +} + +- (UIImage *)backgroundImage; +{ + NSString *imagePath = [TiSquaredcalendarModule getPathToModuleAsset: + [NSString stringWithFormat:@"CalendarRow%@.png", self.bottomRow ? @"Bottom" : @""]]; + UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; + if (image == nil) { + return nil; + } + return image; + //return [UIImage imageNamed:[NSString stringWithFormat:@"CalendarRow%@.png", self.bottomRow ? @"Bottom" : @""]]; +} + +@end diff --git a/iOS/Classes/TiSquaredcalendarCalendarView.h b/iOS/Classes/TiSquaredcalendarCalendarView.h new file mode 100644 index 0000000..ec465cc --- /dev/null +++ b/iOS/Classes/TiSquaredcalendarCalendarView.h @@ -0,0 +1,17 @@ +/** + * benCoding.CalendarView + * Copyright (c) 2010-2014 by Ben Bahrenburg. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ +#import "TiUIView.h" +#import "TimesSquare.h" + + + +@interface TiSquaredcalendarCalendarView : TiUIView { +@private + TSQCalendarView *square; +} +-(void) showToday; +@end diff --git a/iOS/Classes/TiSquaredcalendarCalendarView.m b/iOS/Classes/TiSquaredcalendarCalendarView.m new file mode 100644 index 0000000..56d2a29 --- /dev/null +++ b/iOS/Classes/TiSquaredcalendarCalendarView.m @@ -0,0 +1,144 @@ +/** + * benCoding.CalendarView + * Copyright (c) 2010-2014 by Ben Bahrenburg. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +#import "TiSquaredcalendarCalendarView.h" +#import "TiUtils.h" +#import "TSQTACalendarRowCell.h" + +@interface TSQCalendarView (AccessingPrivateStuff) + +@property (nonatomic, readonly) UITableView *tableView; + +@end + +@implementation TiSquaredcalendarCalendarView + +-(TSQCalendarView*)square +{ + // Return the square view. If this is the first time then allocate and + // initialize it. + if (square == nil) { + NSLog(@"[CALENDARVIEW] square"); + + square = [[TSQCalendarView alloc] initWithFrame:[self frame]]; + square.rowCellClass = [TSQTACalendarRowCell class]; + square.firstDate = [NSDate dateWithTimeIntervalSinceNow:-60 * 60 * 24 * 365 * 1]; + square.lastDate = [NSDate dateWithTimeIntervalSinceNow:60 * 60 * 24 * 365 * 5]; + square.backgroundColor = [UIColor colorWithRed:0.84f green:0.85f blue:0.86f alpha:1.0f]; + square.pagingEnabled = YES; + CGFloat onePixel = 1.0f / [UIScreen mainScreen].scale; + square.contentInset = UIEdgeInsetsMake(0.0f, onePixel, 0.0f, onePixel); + square.delegate = self; + + [self addSubview:square]; + + } + + return square; +} + +-(void) showToday +{ + // Set the calendar view to show today date on start + [square scrollToDate:[NSDate date] animated:NO]; +} +-(BOOL)hasTouchableListener +{ + // since this guy only works with touch events, we always want them + // just always return YES no matter what listeners we have registered + return YES; +} + +#pragma mark View controller stuff + +-(void)setBackgroundColor_:(id)value +{ + NSLog(@"[CALENDARVIEW] Property Set: setBackgroundColor_"); + + // Use the TiUtils methods to get the values from the arguments + TiColor *newColor = [TiUtils colorValue:value]; + UIColor *clr = [newColor _color]; + UIView *sq = [self square]; + sq.backgroundColor = clr; +} +-(void)setPagingEnabled_:(id)value +{ + + [[self square] setPagingEnabled:[TiUtils boolValue:value]]; +} +-(NSDate*) getSelectedDate +{ + return [[self square] selectedDate]; +} +-(void)setSelectedDate_:(id)args +{ + ENSURE_SINGLE_ARG(args,NSDictionary); + NSInteger month = [TiUtils intValue:@"month" properties:args def:1]; + NSInteger day = [TiUtils intValue:@"day" properties:args def:1]; + NSInteger year = [TiUtils intValue:@"year" properties:args def:2000]; + + NSDateComponents *comps = [[NSDateComponents alloc] init]; + [comps setMonth:month]; + [comps setDay:day]; + [comps setYear:year]; + + NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; + [[self square] setSelectedDate:[gregorian dateFromComponents:comps]]; + + [comps release]; + [gregorian release]; +} + +-(void)setFirstDate_:(id)args +{ + ENSURE_SINGLE_ARG(args,NSDictionary); + NSInteger month = [TiUtils intValue:@"month" properties:args def:1]; + NSInteger day = [TiUtils intValue:@"day" properties:args def:1]; + NSInteger year = [TiUtils intValue:@"year" properties:args def:2000]; + + NSDateComponents *comps = [[NSDateComponents alloc] init]; + [comps setMonth:month]; + [comps setDay:day]; + [comps setYear:year]; + + NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; + [[self square] setFirstDate:[gregorian dateFromComponents:comps]]; + + [comps release]; + [gregorian release]; +} + + +-(void)setLastDate_:(id)args +{ + ENSURE_SINGLE_ARG(args,NSDictionary); + NSInteger month = [TiUtils intValue:@"month" properties:args def:1]; + NSInteger day = [TiUtils intValue:@"day" properties:args def:1]; + NSInteger year = [TiUtils intValue:@"year" properties:args def:2000]; + + NSDateComponents *comps = [[NSDateComponents alloc] init]; + [comps setMonth:month]; + [comps setDay:day]; + [comps setYear:year]; + + NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; + + [[self square] setLastDate:[gregorian dateFromComponents:comps]]; + + [comps release]; + [gregorian release]; +} + +- (BOOL)calendarView:(TSQCalendarView *)calendarView shouldSelectDate:(NSDate *)date +{ + +} +- (void)calendarView:(TSQCalendarView *)calendarView didSelectDate:(NSDate *)date +{ + //Add listener callout +} +@end diff --git a/iOS/Classes/TiSquaredcalendarCalendarViewProxy.h b/iOS/Classes/TiSquaredcalendarCalendarViewProxy.h new file mode 100644 index 0000000..26db510 --- /dev/null +++ b/iOS/Classes/TiSquaredcalendarCalendarViewProxy.h @@ -0,0 +1,14 @@ +/** + * benCoding.CalendarView + * Copyright (c) 2010-2014 by Ben Bahrenburg. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +#import "TiViewProxy.h" + +@interface TiSquaredcalendarCalendarViewProxy : TiViewProxy { + +} + +@end diff --git a/iOS/Classes/TiSquaredcalendarCalendarViewProxy.m b/iOS/Classes/TiSquaredcalendarCalendarViewProxy.m new file mode 100644 index 0000000..ad8c2e9 --- /dev/null +++ b/iOS/Classes/TiSquaredcalendarCalendarViewProxy.m @@ -0,0 +1,22 @@ +/** + * benCoding.CalendarView + * Copyright (c) 2010-2014 by Ben Bahrenburg. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +#import "TiSquaredcalendarCalendarViewProxy.h" +#import "TiUtils.h" +#import "TiSquaredcalendarCalendarView.h" +@implementation TiSquaredcalendarCalendarViewProxy + +-(void)viewDidAttach +{ + ENSURE_UI_THREAD_0_ARGS; + TiSquaredcalendarCalendarView * ourView = (TiSquaredcalendarCalendarView *)[self view]; + [ourView showToday]; + [super viewDidAttach]; +} + + +@end diff --git a/iOS/Classes/TiSquaredcalendarModule.h b/iOS/Classes/TiSquaredcalendarModule.h new file mode 100644 index 0000000..05b85d5 --- /dev/null +++ b/iOS/Classes/TiSquaredcalendarModule.h @@ -0,0 +1,16 @@ +/** + * benCoding.CalendarView + * Copyright (c) 2010-2014 by Ben Bahrenburg. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +#import "TiModule.h" + +@interface TiSquaredcalendarModule : TiModule { + +} + ++(NSString*)getPathToModuleAsset:(NSString*) fileName; + +@end diff --git a/iOS/Classes/TiSquaredcalendarModule.m b/iOS/Classes/TiSquaredcalendarModule.m new file mode 100644 index 0000000..dc05ac3 --- /dev/null +++ b/iOS/Classes/TiSquaredcalendarModule.m @@ -0,0 +1,77 @@ +/** + * benCoding.CalendarView + * Copyright (c) 2010-2014 by Ben Bahrenburg. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +#import "TiSquaredcalendarModule.h" +#import "TiBase.h" +#import "TiHost.h" +#import "TiUtils.h" + +@implementation TiSquaredcalendarModule + +#pragma mark Internal + +NSString *const _moduleId = @"ti.squaredcalendar"; + +// this is generated for your module, please do not change it +-(id)moduleGUID +{ + return @"2353b3f4-ecd8-42cc-b698-ffbba0de7ebe"; +} + +// this is generated for your module, please do not change it +-(NSString*)moduleId +{ + return _moduleId; +} + +#pragma mark Lifecycle + +-(void)startup +{ + // this method is called when the module is first loaded + // you *must* call the superclass + [super startup]; +} + +-(void)shutdown:(id)sender +{ + // this method is called when the module is being unloaded + // typically this is during shutdown. make sure you don't do too + // much processing here or the app will be quit forceably + + // you *must* call the superclass + [super shutdown:sender]; +} + +#pragma mark Cleanup + +-(void)dealloc +{ + // release any resources that have been retained by the module + [super dealloc]; +} + +#pragma mark Internal Memory Management + +-(void)didReceiveMemoryWarning:(NSNotification*)notification +{ + // optionally release any resources that can be dynamically + // reloaded once memory is available - such as caches + [super didReceiveMemoryWarning:notification]; +} ++(NSString*)getPathToModuleAsset:(NSString*) fileName +{ + // The module assets are copied to the application bundle into the folder pattern + // "module/". One way to access these assets is to build a path from the + // mainBundle of the application. + + NSString *pathComponent = [NSString stringWithFormat:@"modules/%@/%@", _moduleId, fileName]; + NSString *result = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:pathComponent]; + + return result; +} +@end diff --git a/iOS/Classes/TiSquaredcalendarModuleAssets.h b/iOS/Classes/TiSquaredcalendarModuleAssets.h new file mode 100644 index 0000000..20a4459 --- /dev/null +++ b/iOS/Classes/TiSquaredcalendarModuleAssets.h @@ -0,0 +1,14 @@ +/** + * benCoding.CalendarView + * Copyright (c) 2010-2014 by Ben Bahrenburg. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +@interface TiSquaredcalendarModuleAssets : NSObject +{ +} +- (NSData*) moduleAsset; +- (NSData*) resolveModuleAsset:(NSString*)path; + +@end diff --git a/iOS/Classes/TiSquaredcalendarModuleAssets.m b/iOS/Classes/TiSquaredcalendarModuleAssets.m new file mode 100644 index 0000000..857b680 --- /dev/null +++ b/iOS/Classes/TiSquaredcalendarModuleAssets.m @@ -0,0 +1,30 @@ +/** + * benCoding.CalendarView + * Copyright (c) 2010-2014 by Ben Bahrenburg. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +#import "TiSquaredcalendarModuleAssets.h" + +extern NSData* filterDataInRange(NSData* thedata, NSRange range); + +@implementation TiSquaredcalendarModuleAssets + +- (NSData*) moduleAsset +{ + //##TI_AUTOGEN_BEGIN asset + //Compiler generates code for asset here + return nil; // DEFAULT BEHAVIOR + //##TI_AUTOGEN_END asset +} + +- (NSData*) resolveModuleAsset:(NSString*)path +{ + //##TI_AUTOGEN_BEGIN resolve_asset + //Compiler generates code for asset resolution here + return nil; // DEFAULT BEHAVIOR + //##TI_AUTOGEN_END resolve_asset +} + +@end diff --git a/iOS/LICENSE b/iOS/LICENSE new file mode 100644 index 0000000..6ae867d --- /dev/null +++ b/iOS/LICENSE @@ -0,0 +1 @@ +TODO: place your license here and we'll include it in the module distribution diff --git a/iOS/LICENSE.txt b/iOS/LICENSE.txt new file mode 100644 index 0000000..4124b1d --- /dev/null +++ b/iOS/LICENSE.txt @@ -0,0 +1 @@ +Place your license text here. This file will be incorporated with your app at package time. \ No newline at end of file diff --git a/iOS/README b/iOS/README new file mode 100644 index 0000000..874f036 --- /dev/null +++ b/iOS/README @@ -0,0 +1,151 @@ +Appcelerator Titanium iPhone Module Project +=========================================== + +This is a skeleton Titanium Mobile iPhone module project. Modules can be +used to extend the functionality of Titanium by providing additional native +code that is compiled into your application at build time and can expose certain +APIs into JavaScript. + +MODULE NAMING +-------------- + +Choose a unique module id for your module. This ID usually follows a namespace +convention using DNS notation. For example, com.appcelerator.module.test. This +ID can only be used once by all public modules in Titanium. + + +COMPONENTS +----------- + +Components that are exposed by your module must follow a special naming convention. +A component (widget, proxy, etc) must be named with the pattern: + + TiProxy + +For example, if you component was called Foo, your proxy would be named: + + TiMyfirstFooProxy + +For view proxies or widgets, you must create both a view proxy and a view implementation. +If you widget was named proxy, you would create the following files: + + TiMyfirstFooProxy.h + TiMyfirstFooProxy.m + TiMyfirstFoo.h + TiMyfirstFoo.m + +The view implementation is named the same except it does contain the suffix `Proxy`. + +View implementations extend the Titanium base class `TiUIView`. View Proxies extend the +Titanium base class `TiUIViewProxy` or `TiUIWidgetProxy`. + +For proxies that are simply native objects that can be returned to JavaScript, you can +simply extend `TiProxy` and no view implementation is required. + + +GET STARTED +------------ + +1. Edit manifest with the appropriate details about your module. +2. Edit LICENSE to add your license details. +3. Place any assets (such as PNG files) that are required in the assets folder. +4. Edit the titanium.xcconfig and make sure you're building for the right Titanium version. +5. Code and build. + +BUILD TIME COMPILER CONFIG +-------------------------- + +You can edit the file `module.xcconfig` to include any build time settings that should be +set during application compilation that your module requires. This file will automatically get `#include` in the main application project. + +For more information about this file, please see the Apple documentation at: + + + + +DOCUMENTATION FOR YOUR MODULE +----------------------------- + +You should provide at least minimal documentation for your module in `documentation` folder using the Markdown syntax. + +For more information on the Markdown syntax, refer to this documentation at: + + + + +TEST HARNESS EXAMPLE FOR YOUR MODULE +------------------------------------ + +The `example` directory contains a skeleton application test harness that can be +used for testing and providing an example of usage to the users of your module. + + +INSTALL YOUR MODULE +-------------------- + +1. Run `build.py` which creates your distribution +2. cd to `/Library/Application Support/Titanium` +3. copy this zip file into the folder of your Titanium SDK + +REGISTER YOUR MODULE +--------------------- + +Register your module with your application by editing `tiapp.xml` and adding your module. +Example: + + + ti.squaredcalendar + + +When you run your project, the compiler will know automatically compile in your module +dependencies and copy appropriate image assets into the application. + +USING YOUR MODULE IN CODE +------------------------- + +To use your module in code, you will need to require it. + +For example, + + var my_module = require('ti.squaredcalendar'); + my_module.foo(); + +WRITING PURE JS NATIVE MODULES +------------------------------ + +You can write a pure JavaScript "natively compiled" module. This is nice if you +want to distribute a JS module pre-compiled. + +To create a module, create a file named ti.squaredcalendar.js under the assets folder. +This file must be in the Common JS format. For example: + + exports.echo = function(s) + { + return s; + }; + +Any functions and properties that are exported will be made available as part of your +module. All other code inside your JS will be private to your module. + +For pure JS module, you don't need to modify any of the Objective-C module code. You +can leave it as-is and build. + +TESTING YOUR MODULE +------------------- + +Run the `titanium.py` script to test your module or test from within XCode. +To test with the script, execute: + + titanium run --dir=YOURMODULEDIR + + +This will execute the app.js in the example folder as a Titanium application. + + +DISTRIBUTING YOUR MODULE +------------------------- + +Currently, you will need to manually distribution your module distribution zip file directly. However, in the near future, we will make module distribution and sharing built-in to Titanium Developer and in the Titanium Marketplace! + + +Cheers! diff --git a/iOS/TiSquaredcalendar_Prefix.pch b/iOS/TiSquaredcalendar_Prefix.pch new file mode 100644 index 0000000..8bec24f --- /dev/null +++ b/iOS/TiSquaredcalendar_Prefix.pch @@ -0,0 +1,4 @@ + +#ifdef __OBJC__ + #import +#endif diff --git a/iOS/TimesSquare/TSQCalendarCell.h b/iOS/TimesSquare/TSQCalendarCell.h new file mode 100755 index 0000000..8cd7648 --- /dev/null +++ b/iOS/TimesSquare/TSQCalendarCell.h @@ -0,0 +1,92 @@ +// +// TSQCalendarCell.h +// TimesSquare +// +// Created by Jim Puls on 11/15/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import + + +@class TSQCalendarView; + + +/** The `TSQCalendarCell` class is an abstract superclass to the two cell types used for display in a `TSQCalendarView`. + + Most of its interface deals with display properties. The most interesting method is `-layoutViewsForColumnAtIndex:inRect:`, which is a simple way of handling seven columns. + */ +@interface TSQCalendarCell : UITableViewCell + +/** @name State Properties Set by Calendar View */ + +/** The first day of the month this cell is currently representing. + + This can be useful for calculations and for display. + */ +@property (nonatomic, strong) NSDate *firstOfMonth; + +/** How many days there are in a week. + + This is usually 7. + */ +@property (nonatomic, readonly) NSUInteger daysInWeek; + +/** The calendar type we're displaying. + + This is whatever the owning `TSQCalendarView`'s `calendar` property is set to; it's likely `[NSCalendar currentCalendar]`. + */ +@property (nonatomic, strong) NSCalendar *calendar; + +/** The owning calendar view. + + This is a weak reference. + */ +@property (nonatomic, retain) TSQCalendarView *calendarView; + +/** @name Display Properties */ + +/** The preferred height for instances of this cell. + + The built-in implementation in `TSQCalendarCell` returns `46.0f`. Your subclass may want to return another value. + */ ++ (CGFloat) cellHeight; + +/** The text color. + + This is used for all text the cell draws; if a date is disabled, then it will draw in this color, but at 50% opacity. + */ +@property (nonatomic, strong) UIColor *textColor; + +/** The text shadow offset. + + This is as you would set on `UILabel`. + */ +@property (nonatomic) CGSize shadowOffset; + +/** The spacing between columns. + + This defaults to one pixel or `1.0 / [UIScreen mainScreen].scale`. + */ +@property (nonatomic) CGFloat columnSpacing; + +/** @name Initialization */ + +/** Initializes the cell. + + @param calendar The `NSCalendar` the cell is representing + @param reuseIdentifier A string reuse identifier, as used by `UITableViewCell` + */ +- (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseIdentifier; + +/** Seven-column layout helper. + + @param index The index of the column we're laying out, probably in the range [0..6] + @param rect The rect relative to the bounds of the cell's content view that represents the column. + + Feel free to adjust the rect before moving views and to vertically position them within the column. (In fact, you could ignore the rect entirely; it's just there to help.) + */ +- (void)layoutViewsForColumnAtIndex:(NSUInteger)index inRect:(CGRect)rect; + +@end diff --git a/iOS/TimesSquare/TSQCalendarCell.m b/iOS/TimesSquare/TSQCalendarCell.m new file mode 100755 index 0000000..99276c9 --- /dev/null +++ b/iOS/TimesSquare/TSQCalendarCell.m @@ -0,0 +1,124 @@ +// +// TSQCalendarCell.m +// TimesSquare +// +// Created by Jim Puls on 11/15/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQCalendarCell.h" +#import "TSQCalendarView.h" + + +@interface TSQCalendarCell () + +@property (nonatomic, assign) NSLocaleLanguageDirection layoutDirection; + +@end + + +@implementation TSQCalendarCell + +- (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseIdentifier; +{ + self = [self initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]; + if (!self) { + return nil; + } + + _calendar = calendar; + NSString *languageCode = [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode]; + self.layoutDirection = [NSLocale characterDirectionForLanguage:languageCode]; + self.backgroundColor = [UIColor colorWithRed:0.84f green:0.85f blue:0.86f alpha:1.0f]; + + CGFloat onePixel = 1.0f / [UIScreen mainScreen].scale; + + static CGSize shadowOffset; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shadowOffset = CGSizeMake(0.0f, onePixel); + }); + self.shadowOffset = shadowOffset; + self.columnSpacing = onePixel; + self.textColor = [UIColor colorWithRed:0.47f green:0.5f blue:0.53f alpha:1.0f]; + + return self; +} + ++ (CGFloat)cellHeight; +{ + return 46.0f; +} + +- (NSUInteger)daysInWeek; +{ + static NSUInteger daysInWeek = 0; + if (daysInWeek == 0) { + daysInWeek = [self.calendar maximumRangeOfUnit:NSWeekdayCalendarUnit].length; + } + return daysInWeek; +} + +- (UITableViewCellSelectionStyle)selectionStyle; +{ + return UITableViewCellSelectionStyleNone; +} + +- (void)setHighlighted:(BOOL)selected animated:(BOOL)animated; +{ + // do nothing +} + +- (void)setSelected:(BOOL)selected animated:(BOOL)animated; +{ + // do nothing +} + +- (void)layoutViewsForColumnAtIndex:(NSUInteger)index inRect:(CGRect)rect; +{ + // for subclass to implement +} + +- (void)layoutSubviews; +{ + [super layoutSubviews]; + + UIEdgeInsets insets = self.calendarView.contentInset; + + + CGRect insetRect = UIEdgeInsetsInsetRect(self.bounds, insets); + insetRect.origin.y = CGRectGetMinY(self.bounds); + insetRect.size.height = CGRectGetHeight(self.bounds); + CGFloat increment = (CGRectGetWidth(insetRect) - (self.daysInWeek - 1) * self.columnSpacing) / self.daysInWeek; + increment = roundf(increment); + CGFloat __block start = insets.left; + + CGFloat extraSpace = (CGRectGetWidth(insetRect) - (self.daysInWeek - 1) * self.columnSpacing) - (increment * self.daysInWeek); + + // Divide the extra space out over the outer columns in increments of the column spacing + NSInteger columnsWithExtraSpace = (NSInteger)fabsf(extraSpace / self.columnSpacing); + NSInteger columnsOnLeftWithExtraSpace = columnsWithExtraSpace / 2; + NSInteger columnsOnRightWithExtraSpace = columnsWithExtraSpace - columnsOnLeftWithExtraSpace; + + for (NSUInteger index = 0; index < self.daysInWeek; index++) { + CGFloat width = increment; + if (index < columnsOnLeftWithExtraSpace || index >= self.daysInWeek - columnsOnRightWithExtraSpace) { + width += (extraSpace / columnsWithExtraSpace); + } + + NSUInteger displayIndex = index; + if (self.layoutDirection == NSLocaleLanguageDirectionRightToLeft) { + displayIndex = self.daysInWeek - index - 1; + } + + CGRect columnBounds = self.bounds; + columnBounds.origin.x = start; + columnBounds.size.width = width; + [self layoutViewsForColumnAtIndex:displayIndex inRect:columnBounds]; + start += width + self.columnSpacing; + } + +} + +@end diff --git a/iOS/TimesSquare/TSQCalendarMonthHeaderCell.h b/iOS/TimesSquare/TSQCalendarMonthHeaderCell.h new file mode 100755 index 0000000..d51838b --- /dev/null +++ b/iOS/TimesSquare/TSQCalendarMonthHeaderCell.h @@ -0,0 +1,33 @@ +// +// TSQCalendarMonthHeaderCell.h +// TimesSquare +// +// Created by Jim Puls on 11/14/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQCalendarCell.h" + +/** The `TSQCalendarMonthHeaderCell` class displays the month name and day names at the top of a month's worth of weeks. + + By default, it lays out the day names in the bottom 20 points, the month name in the remainder of its height, and has a height of 65 points. You'll want to subclass it to change any of those things. + */ +@interface TSQCalendarMonthHeaderCell : TSQCalendarCell + +/** @name Day Labels */ + +/** The day header labels. + + The count is equal to the `daysInWeek` property, likely seven. You can position them in the call to `layoutViewsForColumnAtIndex:inRect:`. + */ +@property (nonatomic, strong) NSArray *headerLabels; + + +/** Creates the header labels. + + If you want the text in your header labels to be something other than the short day format ("Mon Tue Wed" etc.), override this method, call `super`, and loop through `self.headerLabels`, changing their text. + */ +- (void)createHeaderLabels; + +@end diff --git a/iOS/TimesSquare/TSQCalendarMonthHeaderCell.m b/iOS/TimesSquare/TSQCalendarMonthHeaderCell.m new file mode 100755 index 0000000..776f692 --- /dev/null +++ b/iOS/TimesSquare/TSQCalendarMonthHeaderCell.m @@ -0,0 +1,127 @@ +// +// TSQCalendarMonthHeaderCell.m +// TimesSquare +// +// Created by Jim Puls on 11/14/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQCalendarMonthHeaderCell.h" + + +static const CGFloat TSQCalendarMonthHeaderCellMonthsHeight = 20.f; + + +@interface TSQCalendarMonthHeaderCell () + +@property (nonatomic, strong) NSDateFormatter *monthDateFormatter; + +@end + + +@implementation TSQCalendarMonthHeaderCell + +- (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseIdentifier; +{ + self = [super initWithCalendar:calendar reuseIdentifier:reuseIdentifier]; + if (!self) { + return nil; + } + + [self createHeaderLabels]; + + return self; +} + + ++ (CGFloat)cellHeight; +{ + return 65.0f; +} + +- (NSDateFormatter *)monthDateFormatter; +{ + if (!_monthDateFormatter) { + _monthDateFormatter = [NSDateFormatter new]; + _monthDateFormatter.calendar = self.calendar; + + NSString *dateComponents = @"yyyyLLLL"; + _monthDateFormatter.dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:[NSLocale currentLocale]]; + } + return _monthDateFormatter; +} + +- (void)createHeaderLabels; +{ + NSDate *referenceDate = [NSDate dateWithTimeIntervalSinceReferenceDate:0]; + NSDateComponents *offset = [NSDateComponents new]; + offset.day = 1; + NSMutableArray *headerLabels = [NSMutableArray arrayWithCapacity:self.daysInWeek]; + + NSDateFormatter *dayFormatter = [NSDateFormatter new]; + dayFormatter.calendar = self.calendar; + dayFormatter.dateFormat = @"cccccc"; + + for (NSUInteger index = 0; index < self.daysInWeek; index++) { + [headerLabels addObject:@""]; + } + + for (NSUInteger index = 0; index < self.daysInWeek; index++) { + NSInteger ordinality = [self.calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:referenceDate]; + UILabel *label = [[UILabel alloc] initWithFrame:self.frame]; + label.textAlignment = UITextAlignmentCenter; + label.text = [dayFormatter stringFromDate:referenceDate]; + label.font = [UIFont boldSystemFontOfSize:12.f]; + label.backgroundColor = self.backgroundColor; + label.textColor = self.textColor; + label.shadowColor = [UIColor whiteColor]; + label.shadowOffset = self.shadowOffset; + [label sizeToFit]; + headerLabels[ordinality - 1] = label; + [self.contentView addSubview:label]; + + referenceDate = [self.calendar dateByAddingComponents:offset toDate:referenceDate options:0]; + } + + self.headerLabels = headerLabels; + self.textLabel.textAlignment = UITextAlignmentCenter; + self.textLabel.textColor = self.textColor; + self.textLabel.shadowColor = [UIColor whiteColor]; + self.textLabel.shadowOffset = self.shadowOffset; +} + +- (void)layoutSubviews; +{ + [super layoutSubviews]; + + CGRect bounds = self.contentView.bounds; + bounds.size.height -= TSQCalendarMonthHeaderCellMonthsHeight; + self.textLabel.frame = CGRectOffset(bounds, 0.0f, 5.0f); +} + +- (void)layoutViewsForColumnAtIndex:(NSUInteger)index inRect:(CGRect)rect; +{ + UILabel *label = self.headerLabels[index]; + CGRect labelFrame = rect; + labelFrame.size.height = TSQCalendarMonthHeaderCellMonthsHeight; + labelFrame.origin.y = self.bounds.size.height - TSQCalendarMonthHeaderCellMonthsHeight; + label.frame = labelFrame; +} + +- (void)setFirstOfMonth:(NSDate *)firstOfMonth; +{ + [super setFirstOfMonth:firstOfMonth]; + self.textLabel.text = [self.monthDateFormatter stringFromDate:firstOfMonth]; + self.accessibilityLabel = self.textLabel.text; +} + +- (void)setBackgroundColor:(UIColor *)backgroundColor; +{ + [super setBackgroundColor:backgroundColor]; + for (UILabel *label in self.headerLabels) { + label.backgroundColor = backgroundColor; + } +} + +@end diff --git a/iOS/TimesSquare/TSQCalendarRowCell.h b/iOS/TimesSquare/TSQCalendarRowCell.h new file mode 100755 index 0000000..7a37db4 --- /dev/null +++ b/iOS/TimesSquare/TSQCalendarRowCell.h @@ -0,0 +1,68 @@ +// +// TSQCalendarRowCell.h +// TimesSquare +// +// Created by Jim Puls on 11/14/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQCalendarCell.h" + +/** The `TSQCalendarRowCell` class is a cell that represents one week in the calendar. + + Each of the seven columns can represent a day that's in this month, a day that's not in this month, a selected day, today, or an unselected day. The cell uses several images placed strategically to achieve the effect. + */ +@interface TSQCalendarRowCell : TSQCalendarCell + +/** @name Images */ + +/** The background image for the entire row. + + This image should be as wide as the entire view and include the grid lines between the columns. It will probably also include the grid line at the top of the row, but not the one at the bottom. + + You might, however, return a different image that includes both the grid line at the top and the one at the bottom if the `bottomRow` property is set to `YES`. You might even adjust the `cellHeight`. + */ +@property (nonatomic, retain, readonly) UIImage *backgroundImage; + +/** The background image for a day that's selected. + + This is blue in the system's built-in Calendar app. You probably want to use a stretchable image. + */ +@property (nonatomic, retain, readonly) UIImage *selectedBackgroundImage; + +/** The background image for a day that's "today". + + This is dark gray in the system's built-in Calendar app. You probably want to use a stretchable image. + */ +@property (nonatomic, retain, readonly) UIImage *todayBackgroundImage; + +/** The background image for a day that's not this month. + + These are the trailing days from the previous month or the leading days from the following month. This can be `nil`. + */ +@property (nonatomic, retain, readonly) UIImage *notThisMonthBackgroundImage; + +/** @name State Properties Set by Calendar View */ + +/** The date at the beginning of the week for this cell. + + Notice that it might be before the `firstOfMonth` property or it might be after. + */ +@property (nonatomic, strong) NSDate *beginningDate; + +/** Whether this cell is the bottom row / last week for the month. + + You may find yourself using a different background image or laying out differently in the last row. + */ +@property (nonatomic, getter = isBottomRow) BOOL bottomRow; + +/** Method to select a specific date within the week. + + This is funneled through and called by the calendar view, to facilitate deselection of other rows. + + @param date The date to select, or nil to deselect all columns. + */ +- (void)selectColumnForDate:(NSDate *)date; + +@end diff --git a/iOS/TimesSquare/TSQCalendarRowCell.m b/iOS/TimesSquare/TSQCalendarRowCell.m new file mode 100755 index 0000000..312a7f2 --- /dev/null +++ b/iOS/TimesSquare/TSQCalendarRowCell.m @@ -0,0 +1,298 @@ +// +// TSQCalendarRowCell.m +// TimesSquare +// +// Created by Jim Puls on 11/14/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQCalendarRowCell.h" +#import "TSQCalendarView.h" + + +@interface TSQCalendarRowCell () + +@property (nonatomic, strong) NSArray *dayButtons; +@property (nonatomic, strong) NSArray *notThisMonthButtons; +@property (nonatomic, strong) UIButton *todayButton; +@property (nonatomic, strong) UIButton *selectedButton; + +@property (nonatomic, assign) NSInteger indexOfTodayButton; +@property (nonatomic, assign) NSInteger indexOfSelectedButton; + +@property (nonatomic, strong) NSDateFormatter *dayFormatter; +@property (nonatomic, strong) NSDateFormatter *accessibilityFormatter; + +@property (nonatomic, strong) NSDateComponents *todayDateComponents; +@property (nonatomic) NSInteger monthOfBeginningDate; + +@end + + +@implementation TSQCalendarRowCell + +- (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseIdentifier; +{ + self = [super initWithCalendar:calendar reuseIdentifier:reuseIdentifier]; + if (!self) { + return nil; + } + + return self; +} + +- (void)configureButton:(UIButton *)button; +{ + button.titleLabel.font = [UIFont boldSystemFontOfSize:19.f]; + button.titleLabel.shadowOffset = self.shadowOffset; + button.adjustsImageWhenDisabled = NO; + [button setTitleColor:self.textColor forState:UIControlStateNormal]; + [button setTitleShadowColor:[UIColor whiteColor] forState:UIControlStateNormal]; +} + +- (void)createDayButtons; +{ + NSMutableArray *dayButtons = [NSMutableArray arrayWithCapacity:self.daysInWeek]; + for (NSUInteger index = 0; index < self.daysInWeek; index++) { + UIButton *button = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + [button addTarget:self action:@selector(dateButtonPressed:) forControlEvents:UIControlEventTouchDown]; + [dayButtons addObject:button]; + [self.contentView addSubview:button]; + [self configureButton:button]; + [button setTitleColor:[self.textColor colorWithAlphaComponent:0.5f] forState:UIControlStateDisabled]; + } + self.dayButtons = dayButtons; +} + +- (void)createNotThisMonthButtons; +{ + NSMutableArray *notThisMonthButtons = [NSMutableArray arrayWithCapacity:self.daysInWeek]; + for (NSUInteger index = 0; index < self.daysInWeek; index++) { + UIButton *button = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + [notThisMonthButtons addObject:button]; + [self.contentView addSubview:button]; + [self configureButton:button]; + + button.enabled = NO; + UIColor *backgroundPattern = [UIColor colorWithPatternImage:[self notThisMonthBackgroundImage]]; + button.backgroundColor = backgroundPattern; + button.titleLabel.backgroundColor = backgroundPattern; + } + self.notThisMonthButtons = notThisMonthButtons; +} + +- (void)createTodayButton; +{ + self.todayButton = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + [self.contentView addSubview:self.todayButton]; + [self configureButton:self.todayButton]; + [self.todayButton addTarget:self action:@selector(todayButtonPressed:) forControlEvents:UIControlEventTouchDown]; + + [self.todayButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + [self.todayButton setBackgroundImage:[self todayBackgroundImage] forState:UIControlStateNormal]; + [self.todayButton setTitleShadowColor:[UIColor colorWithWhite:0.0f alpha:0.75f] forState:UIControlStateNormal]; + + self.todayButton.titleLabel.shadowOffset = CGSizeMake(0.0f, -1.0f / [UIScreen mainScreen].scale); +} + +- (void)createSelectedButton; +{ + self.selectedButton = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + [self.contentView addSubview:self.selectedButton]; + [self configureButton:self.selectedButton]; + + [self.selectedButton setAccessibilityTraits:UIAccessibilityTraitSelected|self.selectedButton.accessibilityTraits]; + + self.selectedButton.enabled = NO; + [self.selectedButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + [self.selectedButton setBackgroundImage:[self selectedBackgroundImage] forState:UIControlStateNormal]; + [self.selectedButton setTitleShadowColor:[UIColor colorWithWhite:0.0f alpha:0.75f] forState:UIControlStateNormal]; + + self.selectedButton.titleLabel.shadowOffset = CGSizeMake(0.0f, -1.0f / [UIScreen mainScreen].scale); + self.indexOfSelectedButton = -1; +} + +- (void)setBeginningDate:(NSDate *)date; +{ + _beginningDate = date; + + NSDateComponents *offset = [NSDateComponents new]; + offset.day = 1; + + self.todayButton.hidden = YES; + self.indexOfTodayButton = -1; + self.selectedButton.hidden = YES; + self.indexOfSelectedButton = -1; + + for (NSUInteger index = 0; index < self.daysInWeek; index++) { + NSString *title = [self.dayFormatter stringFromDate:date]; + NSString *accessibilityLabel = [self.accessibilityFormatter stringFromDate:date]; + [self.dayButtons[index] setTitle:title forState:UIControlStateNormal]; + [self.dayButtons[index] setAccessibilityLabel:accessibilityLabel]; + [self.notThisMonthButtons[index] setTitle:title forState:UIControlStateNormal]; + [self.notThisMonthButtons[index] setAccessibilityLabel:accessibilityLabel]; + + NSDateComponents *thisDateComponents = [self.calendar components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:date]; + + [self.dayButtons[index] setHidden:YES]; + [self.notThisMonthButtons[index] setHidden:YES]; + + NSInteger thisDayMonth = thisDateComponents.month; + if (self.monthOfBeginningDate != thisDayMonth) { + [self.notThisMonthButtons[index] setHidden:NO]; + } else { + + if ([self.todayDateComponents isEqual:thisDateComponents]) { + self.todayButton.hidden = NO; + [self.todayButton setTitle:title forState:UIControlStateNormal]; + [self.todayButton setAccessibilityLabel:accessibilityLabel]; + self.indexOfTodayButton = index; + } else { + UIButton *button = self.dayButtons[index]; + button.enabled = ![self.calendarView.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)] || [self.calendarView.delegate calendarView:self.calendarView shouldSelectDate:date]; + button.hidden = NO; + } + } + + date = [self.calendar dateByAddingComponents:offset toDate:date options:0]; + } +} + +- (void)setBottomRow:(BOOL)bottomRow; +{ + UIImageView *backgroundImageView = (UIImageView *)self.backgroundView; + if ([backgroundImageView isKindOfClass:[UIImageView class]] && _bottomRow == bottomRow) { + return; + } + + _bottomRow = bottomRow; + + self.backgroundView = [[UIImageView alloc] initWithImage:self.backgroundImage]; + + [self setNeedsLayout]; +} + +- (IBAction)dateButtonPressed:(id)sender; +{ + NSDateComponents *offset = [NSDateComponents new]; + offset.day = [self.dayButtons indexOfObject:sender]; + NSDate *selectedDate = [self.calendar dateByAddingComponents:offset toDate:self.beginningDate options:0]; + self.calendarView.selectedDate = selectedDate; +} + +- (IBAction)todayButtonPressed:(id)sender; +{ + NSDateComponents *offset = [NSDateComponents new]; + offset.day = self.indexOfTodayButton; + NSDate *selectedDate = [self.calendar dateByAddingComponents:offset toDate:self.beginningDate options:0]; + self.calendarView.selectedDate = selectedDate; +} + +- (void)layoutSubviews; +{ + if (!self.dayButtons) { + [self createDayButtons]; + [self createNotThisMonthButtons]; + [self createTodayButton]; + [self createSelectedButton]; + } + + if (!self.backgroundView) { + [self setBottomRow:NO]; + } + + [super layoutSubviews]; + + self.backgroundView.frame = self.bounds; +} + +- (void)layoutViewsForColumnAtIndex:(NSUInteger)index inRect:(CGRect)rect; +{ + UIButton *dayButton = self.dayButtons[index]; + UIButton *notThisMonthButton = self.notThisMonthButtons[index]; + + dayButton.frame = rect; + notThisMonthButton.frame = rect; + + if (self.indexOfTodayButton == (NSInteger)index) { + self.todayButton.frame = rect; + } + if (self.indexOfSelectedButton == (NSInteger)index) { + self.selectedButton.frame = rect; + } +} + +- (void)selectColumnForDate:(NSDate *)date; +{ + if (!date && self.indexOfSelectedButton == -1) { + return; + } + + NSInteger newIndexOfSelectedButton = -1; + if (date) { + NSInteger thisDayMonth = [self.calendar components:NSMonthCalendarUnit fromDate:date].month; + if (self.monthOfBeginningDate == thisDayMonth) { + newIndexOfSelectedButton = [self.calendar components:NSDayCalendarUnit fromDate:self.beginningDate toDate:date options:0].day; + if (newIndexOfSelectedButton >= (NSInteger)self.daysInWeek) { + newIndexOfSelectedButton = -1; + } + } + } + + self.indexOfSelectedButton = newIndexOfSelectedButton; + + if (newIndexOfSelectedButton >= 0) { + self.selectedButton.hidden = NO; + [self.selectedButton setTitle:[self.dayButtons[newIndexOfSelectedButton] currentTitle] forState:UIControlStateNormal]; + [self.selectedButton setAccessibilityLabel:[self.dayButtons[newIndexOfSelectedButton] accessibilityLabel]]; + } else { + self.selectedButton.hidden = YES; + } + + [self setNeedsLayout]; +} + +- (NSDateFormatter *)dayFormatter; +{ + if (!_dayFormatter) { + _dayFormatter = [NSDateFormatter new]; + _dayFormatter.calendar = self.calendar; + _dayFormatter.dateFormat = @"d"; + } + return _dayFormatter; +} + +- (NSDateFormatter *)accessibilityFormatter; +{ + if (!_accessibilityFormatter) { + _accessibilityFormatter = [NSDateFormatter new]; + _accessibilityFormatter.calendar = self.calendar; + _accessibilityFormatter.dateStyle = NSDateFormatterLongStyle; + } + return _accessibilityFormatter; +} + +- (NSInteger)monthOfBeginningDate; +{ + if (!_monthOfBeginningDate) { + _monthOfBeginningDate = [self.calendar components:NSMonthCalendarUnit fromDate:self.firstOfMonth].month; + } + return _monthOfBeginningDate; +} + +- (void)setFirstOfMonth:(NSDate *)firstOfMonth; +{ + [super setFirstOfMonth:firstOfMonth]; + self.monthOfBeginningDate = 0; +} + +- (NSDateComponents *)todayDateComponents; +{ + if (!_todayDateComponents) { + self.todayDateComponents = [self.calendar components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:[NSDate date]]; + } + return _todayDateComponents; +} + +@end diff --git a/iOS/TimesSquare/TSQCalendarView.h b/iOS/TimesSquare/TSQCalendarView.h new file mode 100755 index 0000000..bdfa962 --- /dev/null +++ b/iOS/TimesSquare/TSQCalendarView.h @@ -0,0 +1,134 @@ +// +// TSQCalendarState.h +// TimesSquare +// +// Created by Jim Puls on 11/14/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import + + +@protocol TSQCalendarViewDelegate; + + +/** The `TSQCalendarView` class displays a monthly calendar in a self-contained scrolling view. It supports any calendar that `NSCalendar` supports. + + The implementation and usage are very similar to `UITableView`: the app provides reusable cells via a data source and controls behavior via a delegate. See `TSQCalendarCell` for a cell superclass. + */ +@interface TSQCalendarView : UIView + +/** @name Date Setup */ + +/** The earliest month the calendar view displays. + + Set this property to any `NSDate`; `TSQCalendarView` will only look at the month and year. + Must be set for the calendar to be useful. + */ +@property (nonatomic, strong) NSDate *firstDate; + +/** The latest month the calendar view displays. + + Set this property to any `NSDate`; `TSQCalendarView` will only look at the month and year. + Must be set for the calendar to be useful. + */ +@property (nonatomic, strong) NSDate *lastDate; + +/** The currently-selected date on the calendar. + + Set this property to any `NSDate`; `TSQCalendarView` will only look at the month, day, and year. + You can read and write this property; the delegate method `calendarView:didSelectDate:` will be called both when a new date is selected from the UI and when this method is called manually. + */ +@property (nonatomic, strong) NSDate *selectedDate; + +/** @name Calendar Configuration */ + +/** The calendar type to use when displaying. + + If not set, this defaults to `[NSCalendar currentCalendar]`. + */ +@property (nonatomic, strong) NSCalendar *calendar; + +/** @name Visual Configuration */ + +/** The delegate of the calendar view. + + The delegate must adopt the `TSQCalendarViewDelegate` protocol. + The `TSQCalendarView` class, which does not retain the delegate, invokes each protocol method the delegate implements. + */ +@property (nonatomic, retain) id delegate; + +/** Whether to pin the header to the top of the view. + + If you're trying to emulate the built-in calendar app, set this to `YES`. Default value is `NO`. + */ +@property (nonatomic) BOOL pinsHeaderToTop; + +/** Whether or not the calendar snaps to begin a month at the top of its bounds. + + This property is roughly equivalent to the one defined on `UIScrollView` except the snapping is to months rather than integer multiples of the view's bounds. + */ +@property (nonatomic) BOOL pagingEnabled; + +/** The distance from the edges of the view to where the content begins. + + This property is equivalent to the one defined on `UIScrollView`. + */ +@property (nonatomic) UIEdgeInsets contentInset; + +/** The point on the calendar where the currently-visible region starts. + + This property is equivalent to the one defined on `UIScrollView`. + */ +@property (nonatomic) CGPoint contentOffset; + +/** The cell class to use for month headers. + + Since there's very little configuration to be done for each cell, this can be set as a shortcut to implementing a data source. + The class should be a subclass of `TSQCalendarMonthHeaderCell` or at least implement all of its methods. + */ +@property (nonatomic, strong) Class headerCellClass; + +/** The cell class to use for week rows. + + Since there's very little configuration to be done for each cell, this can be set as a shortcut to implementing a data source. + The class should be a subclass of `TSQCalendarRowCell` or at least implement all of its methods. + */ +@property (nonatomic, strong) Class rowCellClass; + +/** Scrolls the receiver until the specified date month is completely visible. + + @param date A date that identifies the month that will be visible. + @param animated YES if you want to animate the change in position, NO if it should be immediate. + */ +- (void)scrollToDate:(NSDate *)date animated:(BOOL)animated; + +@end + +/** The methods in the `TSQCalendarViewDelegate` protocol allow the adopting delegate to either prevent a day from being selected or respond to it. + */ +@protocol TSQCalendarViewDelegate + +@optional + +/** @name Responding to Selection */ + +/** Asks the delegate whether a particular date is selectable. + + This method should be relatively efficient, as it is called repeatedly to appropriate enable and disable individual days on the calendar view. + + @param calendarView The calendar view that is selecting a date. + @param date Midnight on the date being selected. + @return Whether or not the date is selectable. + */ +- (BOOL)calendarView:(TSQCalendarView *)calendarView shouldSelectDate:(NSDate *)date; + +/** Tells the delegate that a particular date was selected. + + @param calendarView The calendar view that is selecting a date. + @param date Midnight on the date being selected. + */ +- (void)calendarView:(TSQCalendarView *)calendarView didSelectDate:(NSDate *)date; + +@end diff --git a/iOS/TimesSquare/TSQCalendarView.m b/iOS/TimesSquare/TSQCalendarView.m new file mode 100755 index 0000000..fecaa5e --- /dev/null +++ b/iOS/TimesSquare/TSQCalendarView.m @@ -0,0 +1,328 @@ +// +// TSQCalendarState.m +// TimesSquare +// +// Created by Jim Puls on 11/14/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQCalendarView.h" +#import "TSQCalendarMonthHeaderCell.h" +#import "TSQCalendarRowCell.h" + +@interface TSQCalendarView () + +@property (nonatomic, strong) UITableView *tableView; +@property (nonatomic, strong) TSQCalendarMonthHeaderCell *headerView; // nil unless pinsHeaderToTop == YES + +@end + + +@implementation TSQCalendarView + +- (id)initWithCoder:(NSCoder *)aDecoder; +{ + self = [super initWithCoder:aDecoder]; + if (!self) { + return nil; + } + + [self _TSQCalendarView_commonInit]; + + return self; +} + +- (id)initWithFrame:(CGRect)frame; +{ + self = [super initWithFrame:frame]; + if (!self) { + return nil; + } + + [self _TSQCalendarView_commonInit]; + + return self; +} + +- (void)_TSQCalendarView_commonInit; +{ + _tableView = [[UITableView alloc] initWithFrame:self.bounds style:UITableViewStylePlain]; + _tableView.dataSource = self; + _tableView.delegate = self; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth; + [self addSubview:_tableView]; +} + +- (void)dealloc; +{ + _tableView.dataSource = nil; + _tableView.delegate = nil; + [super dealloc]; +} + +- (NSCalendar *)calendar; +{ + if (!_calendar) { + self.calendar = [NSCalendar currentCalendar]; + } + return _calendar; +} + +- (Class)headerCellClass; +{ + if (!_headerCellClass) { + self.headerCellClass = [TSQCalendarMonthHeaderCell class]; + } + return _headerCellClass; +} + +- (Class)rowCellClass; +{ + if (!_rowCellClass) { + self.rowCellClass = [TSQCalendarRowCell class]; + } + return _rowCellClass; +} + +- (Class)cellClassForRowAtIndexPath:(NSIndexPath *)indexPath; +{ + if (indexPath.row == 0 && !self.pinsHeaderToTop) { + return [self headerCellClass]; + } else { + return [self rowCellClass]; + } +} + +- (void)setBackgroundColor:(UIColor *)backgroundColor; +{ + [super setBackgroundColor:backgroundColor]; + [self.tableView setBackgroundColor:backgroundColor]; +} + +- (void)setPinsHeaderToTop:(BOOL)pinsHeaderToTop; +{ + _pinsHeaderToTop = pinsHeaderToTop; + [self setNeedsLayout]; +} + +- (void)setFirstDate:(NSDate *)firstDate; +{ + // clamp to the beginning of its month + _firstDate = [self clampDate:firstDate toComponents:NSMonthCalendarUnit|NSYearCalendarUnit]; +} + +- (void)setLastDate:(NSDate *)lastDate; +{ + // clamp to the end of its month + NSDate *firstOfMonth = [self clampDate:lastDate toComponents:NSMonthCalendarUnit|NSYearCalendarUnit]; + + NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; + offsetComponents.month = 1; + offsetComponents.day = -1; + _lastDate = [self.calendar dateByAddingComponents:offsetComponents toDate:firstOfMonth options:0]; +} + +- (void)setSelectedDate:(NSDate *)newSelectedDate; +{ + // clamp to beginning of its day + NSDate *startOfDay = [self clampDate:newSelectedDate toComponents:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit]; + + if ([self.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)] && ![self.delegate calendarView:self shouldSelectDate:startOfDay]) { + return; + } + + [[self cellForRowAtDate:_selectedDate] selectColumnForDate:nil]; + [[self cellForRowAtDate:startOfDay] selectColumnForDate:startOfDay]; + NSIndexPath *newIndexPath = [self indexPathForRowAtDate:startOfDay]; + CGRect newIndexPathRect = [self.tableView rectForRowAtIndexPath:newIndexPath]; + CGRect scrollBounds = self.tableView.bounds; + + if (self.pagingEnabled) { + CGRect sectionRect = [self.tableView rectForSection:newIndexPath.section]; + [self.tableView setContentOffset:sectionRect.origin animated:YES]; + } else { + if (CGRectGetMinY(scrollBounds) > CGRectGetMinY(newIndexPathRect)) { + [self.tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; + } else if (CGRectGetMaxY(scrollBounds) < CGRectGetMaxY(newIndexPathRect)) { + [self.tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; + } + } + + _selectedDate = startOfDay; + + if ([self.delegate respondsToSelector:@selector(calendarView:didSelectDate:)]) { + [self.delegate calendarView:self didSelectDate:startOfDay]; + } +} + +- (void)scrollToDate:(NSDate *)date animated:(BOOL)animated +{ + NSInteger section = [self sectionForDate:date]; + [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:animated]; +} + +- (TSQCalendarMonthHeaderCell *)makeHeaderCellWithIdentifier:(NSString *)identifier; +{ + TSQCalendarMonthHeaderCell *cell = [[[self headerCellClass] alloc] initWithCalendar:self.calendar reuseIdentifier:identifier]; + cell.backgroundColor = self.backgroundColor; + cell.calendarView = self; + return cell; +} + +#pragma mark Calendar calculations + +- (NSDate *)firstOfMonthForSection:(NSInteger)section; +{ + NSDateComponents *offset = [NSDateComponents new]; + offset.month = section; + return [self.calendar dateByAddingComponents:offset toDate:self.firstDate options:0]; +} + +- (TSQCalendarRowCell *)cellForRowAtDate:(NSDate *)date; +{ + return (TSQCalendarRowCell *)[self.tableView cellForRowAtIndexPath:[self indexPathForRowAtDate:date]]; +} + +- (NSInteger)sectionForDate:(NSDate *)date; +{ + return [self.calendar components:NSMonthCalendarUnit fromDate:self.firstDate toDate:date options:0].month; +} + +- (NSIndexPath *)indexPathForRowAtDate:(NSDate *)date; +{ + if (!date) { + return nil; + } + + NSInteger section = [self sectionForDate:date]; + NSDate *firstOfMonth = [self firstOfMonthForSection:section]; + NSInteger firstWeek = [self.calendar components:NSWeekOfYearCalendarUnit fromDate:firstOfMonth].weekOfYear; + NSInteger targetWeek = [self.calendar components:NSWeekOfYearCalendarUnit fromDate:date].weekOfYear; + if (targetWeek < firstWeek) { + targetWeek = [self.calendar maximumRangeOfUnit:NSWeekOfYearCalendarUnit].length; + } + return [NSIndexPath indexPathForRow:(self.pinsHeaderToTop ? 0 : 1) + targetWeek - firstWeek inSection:section]; +} + +#pragma mark UIView + +- (void)layoutSubviews; +{ + if (self.pinsHeaderToTop) { + if (!self.headerView) { + self.headerView = [self makeHeaderCellWithIdentifier:nil]; + if (self.tableView.visibleCells.count > 0) { + self.headerView.firstOfMonth = [self.tableView.visibleCells[0] firstOfMonth]; + } else { + self.headerView.firstOfMonth = self.firstDate; + } + [self addSubview:self.headerView]; + } + CGRect bounds = self.bounds; + CGRect headerRect; + CGRect tableRect; + CGRectDivide(bounds, &headerRect, &tableRect, [[self headerCellClass] cellHeight], CGRectMinYEdge); + self.headerView.frame = headerRect; + self.tableView.frame = tableRect; + } else { + if (self.headerView) { + [self.headerView removeFromSuperview]; + self.headerView = nil; + } + self.tableView.frame = self.bounds; + } +} + +#pragma mark UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; +{ + return 1 + [self.calendar components:NSMonthCalendarUnit fromDate:self.firstDate toDate:self.lastDate options:0].month; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; +{ + NSDate *firstOfMonth = [self firstOfMonthForSection:section]; + NSRange rangeOfWeeks = [self.calendar rangeOfUnit:NSWeekCalendarUnit inUnit:NSMonthCalendarUnit forDate:firstOfMonth]; + return (self.pinsHeaderToTop ? 0 : 1) + rangeOfWeeks.length; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; +{ + if (indexPath.row == 0 && !self.pinsHeaderToTop) { + // month header + static NSString *identifier = @"header"; + TSQCalendarMonthHeaderCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; + if (!cell) { + cell = [self makeHeaderCellWithIdentifier:identifier]; + } + return cell; + } else { + static NSString *identifier = @"row"; + TSQCalendarRowCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; + if (!cell) { + cell = [[[self rowCellClass] alloc] initWithCalendar:self.calendar reuseIdentifier:identifier]; + cell.backgroundColor = self.backgroundColor; + cell.calendarView = self; + } + return cell; + } +} + +#pragma mark UITableViewDelegate + +- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; +{ + NSDate *firstOfMonth = [self firstOfMonthForSection:indexPath.section]; + [(TSQCalendarCell *)cell setFirstOfMonth:firstOfMonth]; + if (indexPath.row > 0 || self.pinsHeaderToTop) { + NSInteger ordinalityOfFirstDay = [self.calendar ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:firstOfMonth]; + NSDateComponents *dateComponents = [NSDateComponents new]; + dateComponents.day = 1 - ordinalityOfFirstDay; + dateComponents.week = indexPath.row - (self.pinsHeaderToTop ? 0 : 1); + [(TSQCalendarRowCell *)cell setBeginningDate:[self.calendar dateByAddingComponents:dateComponents toDate:firstOfMonth options:0]]; + [(TSQCalendarRowCell *)cell selectColumnForDate:self.selectedDate]; + + BOOL isBottomRow = (indexPath.row == [self tableView:tableView numberOfRowsInSection:indexPath.section] - (self.pinsHeaderToTop ? 0 : 1)); + [(TSQCalendarRowCell *)cell setBottomRow:isBottomRow]; + } +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath; +{ + return [[self cellClassForRowAtIndexPath:indexPath] cellHeight]; +} + +#pragma mark UIScrollViewDelegate + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset; +{ + if (self.pagingEnabled) { + NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:*targetContentOffset]; + // If the target offset is at the third row or later, target the next month; otherwise, target the beginning of this month. + NSInteger section = indexPath.section; + if (indexPath.row > 2) { + section++; + } + CGRect sectionRect = [self.tableView rectForSection:section]; + *targetContentOffset = sectionRect.origin; + } +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView; +{ + if (self.pinsHeaderToTop && self.tableView.visibleCells.count > 0) { + TSQCalendarCell *cell = self.tableView.visibleCells[0]; + self.headerView.firstOfMonth = cell.firstOfMonth; + } +} + +- (NSDate *)clampDate:(NSDate *)date toComponents:(NSUInteger)unitFlags +{ + NSDateComponents *components = [self.calendar components:unitFlags fromDate:date]; + return [self.calendar dateFromComponents:components]; +} + +@end diff --git a/iOS/TimesSquare/TimesSquare-Prefix.pch b/iOS/TimesSquare/TimesSquare-Prefix.pch new file mode 100755 index 0000000..700840d --- /dev/null +++ b/iOS/TimesSquare/TimesSquare-Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'TimesSquare' target in the 'TimesSquare' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/iOS/TimesSquare/TimesSquare.h b/iOS/TimesSquare/TimesSquare.h new file mode 100755 index 0000000..addfc8a --- /dev/null +++ b/iOS/TimesSquare/TimesSquare.h @@ -0,0 +1,12 @@ +// +// TimesSquare.h +// TimesSquare +// +// Created by Jim Puls on 12/5/12. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQCalendarMonthHeaderCell.h" +#import "TSQCalendarRowCell.h" +#import "TSQCalendarView.h" diff --git a/iOS/assets/CalendarPreviousMonth.png b/iOS/assets/CalendarPreviousMonth.png new file mode 100755 index 0000000..35edb79 Binary files /dev/null and b/iOS/assets/CalendarPreviousMonth.png differ diff --git a/iOS/assets/CalendarPreviousMonth@2x.png b/iOS/assets/CalendarPreviousMonth@2x.png new file mode 100755 index 0000000..daf0db3 Binary files /dev/null and b/iOS/assets/CalendarPreviousMonth@2x.png differ diff --git a/iOS/assets/CalendarRow.png b/iOS/assets/CalendarRow.png new file mode 100755 index 0000000..9905881 Binary files /dev/null and b/iOS/assets/CalendarRow.png differ diff --git a/iOS/assets/CalendarRow@2x.png b/iOS/assets/CalendarRow@2x.png new file mode 100755 index 0000000..5446039 Binary files /dev/null and b/iOS/assets/CalendarRow@2x.png differ diff --git a/iOS/assets/CalendarRowBottom.png b/iOS/assets/CalendarRowBottom.png new file mode 100755 index 0000000..c5a4abc Binary files /dev/null and b/iOS/assets/CalendarRowBottom.png differ diff --git a/iOS/assets/CalendarRowBottom@2x.png b/iOS/assets/CalendarRowBottom@2x.png new file mode 100755 index 0000000..3496670 Binary files /dev/null and b/iOS/assets/CalendarRowBottom@2x.png differ diff --git a/iOS/assets/CalendarSelectedDate.png b/iOS/assets/CalendarSelectedDate.png new file mode 100755 index 0000000..ab2f830 Binary files /dev/null and b/iOS/assets/CalendarSelectedDate.png differ diff --git a/iOS/assets/CalendarSelectedDate@2x.png b/iOS/assets/CalendarSelectedDate@2x.png new file mode 100755 index 0000000..ffa18cc Binary files /dev/null and b/iOS/assets/CalendarSelectedDate@2x.png differ diff --git a/iOS/assets/CalendarTodaysDate.png b/iOS/assets/CalendarTodaysDate.png new file mode 100755 index 0000000..3551826 Binary files /dev/null and b/iOS/assets/CalendarTodaysDate.png differ diff --git a/iOS/assets/CalendarTodaysDate@2x.png b/iOS/assets/CalendarTodaysDate@2x.png new file mode 100755 index 0000000..e54c1a8 Binary files /dev/null and b/iOS/assets/CalendarTodaysDate@2x.png differ diff --git a/iOS/assets/Default-568h@2x.png b/iOS/assets/Default-568h@2x.png new file mode 100755 index 0000000..0891b7a Binary files /dev/null and b/iOS/assets/Default-568h@2x.png differ diff --git a/iOS/assets/Default.png b/iOS/assets/Default.png new file mode 100755 index 0000000..4c8ca6f Binary files /dev/null and b/iOS/assets/Default.png differ diff --git a/iOS/assets/Default@2x.png b/iOS/assets/Default@2x.png new file mode 100755 index 0000000..35b84cf Binary files /dev/null and b/iOS/assets/Default@2x.png differ diff --git a/iOS/assets/README b/iOS/assets/README new file mode 100644 index 0000000..2c9f6a4 --- /dev/null +++ b/iOS/assets/README @@ -0,0 +1,6 @@ +Place your assets like PNG files in this directory and they will be packaged with your module. + +If you create a file named ti.squaredcalendar.js in this directory, it will be +compiled and used as your module. This allows you to run pure Javascript +modules that are pre-compiled. + diff --git a/iOS/build.py b/iOS/build.py new file mode 100755 index 0000000..10c5191 --- /dev/null +++ b/iOS/build.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python +# +# Appcelerator Titanium Module Packager +# +# +import os, subprocess, sys, glob, string +import zipfile +from datetime import date + +cwd = os.path.abspath(os.path.dirname(sys._getframe(0).f_code.co_filename)) +os.chdir(cwd) +required_module_keys = ['name','version','moduleid','description','copyright','license','copyright','platform','minsdk'] +module_defaults = { + 'description':'My module', + 'author': 'Your Name', + 'license' : 'Specify your license', + 'copyright' : 'Copyright (c) %s by Your Company' % str(date.today().year), +} +module_license_default = "TODO: place your license here and we'll include it in the module distribution" + +def find_sdk(config): + sdk = config['TITANIUM_SDK'] + return os.path.expandvars(os.path.expanduser(sdk)) + +def replace_vars(config,token): + idx = token.find('$(') + while idx != -1: + idx2 = token.find(')',idx+2) + if idx2 == -1: break + key = token[idx+2:idx2] + if not config.has_key(key): break + token = token.replace('$(%s)' % key, config[key]) + idx = token.find('$(') + return token + + +def read_ti_xcconfig(): + contents = open(os.path.join(cwd,'titanium.xcconfig')).read() + config = {} + for line in contents.splitlines(False): + line = line.strip() + if line[0:2]=='//': continue + idx = line.find('=') + if idx > 0: + key = line[0:idx].strip() + value = line[idx+1:].strip() + config[key] = replace_vars(config,value) + return config + +def generate_doc(config): + docdir = os.path.join(cwd,'documentation') + if not os.path.exists(docdir): + print "Couldn't find documentation file at: %s" % docdir + return None + + try: + import markdown2 as markdown + except ImportError: + import markdown + documentation = [] + for file in os.listdir(docdir): + if file in ignoreFiles or os.path.isdir(os.path.join(docdir, file)): + continue + md = open(os.path.join(docdir,file)).read() + html = markdown.markdown(md) + documentation.append({file:html}); + return documentation + +def compile_js(manifest,config): + js_file = os.path.join(cwd,'assets','ti.squaredcalendar.js') + if not os.path.exists(js_file): return + + from compiler import Compiler + try: + import json + except: + import simplejson as json + + compiler = Compiler(cwd, manifest['moduleid'], manifest['name'], 'commonjs') + root_asset, module_assets = compiler.compile_module() + + root_asset_content = """ +%s + + return filterDataInRange([NSData dataWithBytesNoCopy:data length:sizeof(data) freeWhenDone:NO], ranges[0]); +""" % root_asset + + module_asset_content = """ +%s + + NSNumber *index = [map objectForKey:path]; + if (index == nil) { + return nil; + } + return filterDataInRange([NSData dataWithBytesNoCopy:data length:sizeof(data) freeWhenDone:NO], ranges[index.integerValue]); +""" % module_assets + + from tools import splice_code + + assets_router = os.path.join(cwd,'Classes','TiSquaredcalendarModuleAssets.m') + splice_code(assets_router, 'asset', root_asset_content) + splice_code(assets_router, 'resolve_asset', module_asset_content) + + # Generate the exports after crawling all of the available JS source + exports = open('metadata.json','w') + json.dump({'exports':compiler.exports }, exports) + exports.close() + +def die(msg): + print msg + sys.exit(1) + +def warn(msg): + print "[WARN] %s" % msg + +def validate_license(): + c = open(os.path.join(cwd,'LICENSE')).read() + if c.find(module_license_default)!=-1: + warn('please update the LICENSE file with your license text before distributing') + +def validate_manifest(): + path = os.path.join(cwd,'manifest') + f = open(path) + if not os.path.exists(path): die("missing %s" % path) + manifest = {} + for line in f.readlines(): + line = line.strip() + if line[0:1]=='#': continue + if line.find(':') < 0: continue + key,value = line.split(':') + manifest[key.strip()]=value.strip() + for key in required_module_keys: + if not manifest.has_key(key): die("missing required manifest key '%s'" % key) + if module_defaults.has_key(key): + defvalue = module_defaults[key] + curvalue = manifest[key] + if curvalue==defvalue: warn("please update the manifest key: '%s' to a non-default value" % key) + return manifest,path + +ignoreFiles = ['.DS_Store','.gitignore','libTitanium.a','titanium.jar','README'] +ignoreDirs = ['.DS_Store','.svn','.git','CVSROOT'] + +def zip_dir(zf,dir,basepath,ignoreExt=[]): + if not os.path.exists(dir): return + for root, dirs, files in os.walk(dir): + for name in ignoreDirs: + if name in dirs: + dirs.remove(name) # don't visit ignored directories + for file in files: + if file in ignoreFiles: continue + e = os.path.splitext(file) + if len(e) == 2 and e[1] in ignoreExt: continue + from_ = os.path.join(root, file) + to_ = from_.replace(dir, '%s/%s'%(basepath,dir), 1) + zf.write(from_, to_) + +def glob_libfiles(): + files = [] + for libfile in glob.glob('build/**/*.a'): + if libfile.find('Release-')!=-1: + files.append(libfile) + return files + +def build_module(manifest,config): + from tools import ensure_dev_path + ensure_dev_path() + + rc = os.system("xcodebuild -sdk iphoneos -configuration Release") + if rc != 0: + die("xcodebuild failed") + rc = os.system("xcodebuild -sdk iphonesimulator -configuration Release") + if rc != 0: + die("xcodebuild failed") + # build the merged library using lipo + moduleid = manifest['moduleid'] + libpaths = '' + for libfile in glob_libfiles(): + libpaths+='%s ' % libfile + + os.system("lipo %s -create -output build/lib%s.a" %(libpaths,moduleid)) + +def package_module(manifest,mf,config): + name = manifest['name'].lower() + moduleid = manifest['moduleid'].lower() + version = manifest['version'] + modulezip = '%s-iphone-%s.zip' % (moduleid,version) + if os.path.exists(modulezip): os.remove(modulezip) + zf = zipfile.ZipFile(modulezip, 'w', zipfile.ZIP_DEFLATED) + modulepath = 'modules/iphone/%s/%s' % (moduleid,version) + zf.write(mf,'%s/manifest' % modulepath) + libname = 'lib%s.a' % moduleid + zf.write('build/%s' % libname, '%s/%s' % (modulepath,libname)) + docs = generate_doc(config) + if docs!=None: + for doc in docs: + for file, html in doc.iteritems(): + filename = string.replace(file,'.md','.html') + zf.writestr('%s/documentation/%s'%(modulepath,filename),html) + zip_dir(zf,'assets',modulepath,['.pyc','.js']) + zip_dir(zf,'example',modulepath,['.pyc']) + zip_dir(zf,'platform',modulepath,['.pyc','.js']) + zf.write('LICENSE','%s/LICENSE' % modulepath) + zf.write('module.xcconfig','%s/module.xcconfig' % modulepath) + exports_file = 'metadata.json' + if os.path.exists(exports_file): + zf.write(exports_file, '%s/%s' % (modulepath, exports_file)) + zf.close() + + +if __name__ == '__main__': + manifest,mf = validate_manifest() + validate_license() + config = read_ti_xcconfig() + + sdk = find_sdk(config) + sys.path.insert(0,os.path.join(sdk,'iphone')) + sys.path.append(os.path.join(sdk, "common")) + + compile_js(manifest,config) + build_module(manifest,config) + package_module(manifest,mf,config) + sys.exit(0) + diff --git a/iOS/documentation/index.md b/iOS/documentation/index.md new file mode 100644 index 0000000..6d2840a --- /dev/null +++ b/iOS/documentation/index.md @@ -0,0 +1,39 @@ +# CalendarView Module + +## Description + +TODO: Enter your module description here + +## Accessing the CalendarView Module + +To access this module from JavaScript, you would do the following: + + var CalendarView = require("ti.squaredcalendar"); + +The CalendarView variable is a reference to the Module object. + +## Reference + +TODO: If your module has an API, you should document +the reference here. + +### ___PROJECTNAMEASIDENTIFIER__.function + +TODO: This is an example of a module function. + +### ___PROJECTNAMEASIDENTIFIER__.property + +TODO: This is an example of a module property. + +## Usage + +TODO: Enter your usage example here + +## Author + +TODO: Enter your author name, email and other contact +details you want to share here. + +## License + +TODO: Enter your license/legal information here. diff --git a/iOS/example/app.js b/iOS/example/app.js new file mode 100644 index 0000000..6defa22 --- /dev/null +++ b/iOS/example/app.js @@ -0,0 +1,39 @@ +// This is a test harness for your module +// You should do something interesting in this harness +// to test out the module and to provide instructions +// to users on how to use it by example. + + +// open a single window +var win = Ti.UI.createWindow({ + backgroundColor:'white' +}); +var label = Ti.UI.createLabel(); +win.add(label); +win.open(); + +// TODO: write your module tests here +var CalendarView = require('ti.squaredcalendar'); +Ti.API.info("module is => " + CalendarView); + +label.text = CalendarView.example(); + +Ti.API.info("module exampleProp is => " + CalendarView.exampleProp); +CalendarView.exampleProp = "This is a test value"; + +if (Ti.Platform.name == "android") { + var proxy = CalendarView.createExample({ + message: "Creating an example Proxy", + backgroundColor: "red", + width: 100, + height: 100, + top: 100, + left: 150 + }); + + proxy.printMessage("Hello world!"); + proxy.message = "Hi world!. It's me again."; + proxy.printMessage("Hello world!"); + win.add(proxy); +} + diff --git a/iOS/hooks/README b/iOS/hooks/README new file mode 100644 index 0000000..66b10a8 --- /dev/null +++ b/iOS/hooks/README @@ -0,0 +1 @@ +These files are not yet supported as of 1.4.0 but will be in a near future release. diff --git a/iOS/hooks/add.py b/iOS/hooks/add.py new file mode 100644 index 0000000..04e1c1d --- /dev/null +++ b/iOS/hooks/add.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# +# This is the module project add hook that will be +# called when your module is added to a project +# +import os, sys + +def dequote(s): + if s[0:1] == '"': + return s[1:-1] + return s + +def main(args,argc): + # You will get the following command line arguments + # in the following order: + # + # project_dir = the full path to the project root directory + # project_type = the type of project (desktop, mobile, ipad) + # project_name = the name of the project + # + project_dir = dequote(os.path.expanduser(args[1])) + project_type = dequote(args[2]) + project_name = dequote(args[3]) + + # TODO: write your add hook here (optional) + + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/iOS/hooks/install.py b/iOS/hooks/install.py new file mode 100644 index 0000000..b423fe9 --- /dev/null +++ b/iOS/hooks/install.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# +# This is the module install hook that will be +# called when your module is first installed +# +import os, sys + +def main(args,argc): + + # TODO: write your install hook here (optional) + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/iOS/hooks/remove.py b/iOS/hooks/remove.py new file mode 100644 index 0000000..f92a234 --- /dev/null +++ b/iOS/hooks/remove.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# +# This is the module project remove hook that will be +# called when your module is remove from a project +# +import os, sys + +def dequote(s): + if s[0:1] == '"': + return s[1:-1] + return s + +def main(args,argc): + # You will get the following command line arguments + # in the following order: + # + # project_dir = the full path to the project root directory + # project_type = the type of project (desktop, mobile, ipad) + # project_name = the name of the project + # + project_dir = dequote(os.path.expanduser(args[1])) + project_type = dequote(args[2]) + project_name = dequote(args[3]) + + # TODO: write your remove hook here (optional) + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/iOS/hooks/uninstall.py b/iOS/hooks/uninstall.py new file mode 100644 index 0000000..a7ffd91 --- /dev/null +++ b/iOS/hooks/uninstall.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# +# This is the module uninstall hook that will be +# called when your module is uninstalled +# +import os, sys + +def main(args,argc): + + # TODO: write your uninstall hook here (optional) + + # exit + sys.exit(0) + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/iOS/manifest b/iOS/manifest new file mode 100644 index 0000000..bc1a217 --- /dev/null +++ b/iOS/manifest @@ -0,0 +1,18 @@ +# +# this is your module manifest and used by Titanium +# during compilation, packaging, distribution, etc. +# +version: 0.1 +apiversion: 2 +description: My module +author: Benjamin Bahrenburg +license: Specify your license +copyright: Copyright (c) 2013 by Your Company + + +# these should not be edited +name: CalendarView +moduleid: ti.squaredcalendar +guid: 2353b3f4-ecd8-42cc-b698-ffbba0de7ebe +platform: iphone +minsdk: 3.1.0.GA diff --git a/iOS/module.xcconfig b/iOS/module.xcconfig new file mode 100644 index 0000000..80ca884 --- /dev/null +++ b/iOS/module.xcconfig @@ -0,0 +1,27 @@ +// +// PLACE ANY BUILD DEFINITIONS IN THIS FILE AND THEY WILL BE +// PICKED UP DURING THE APP BUILD FOR YOUR MODULE +// +// see the following webpage for instructions on the settings +// for this file: +// http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/XcodeBuildSystem/400-Build_Configurations/build_configs.html +// + +// +// How to add a Framework (example) +// +// OTHER_LDFLAGS=$(inherited) -framework Foo +// +// Adding a framework for a specific version(s) of iPhone: +// +// OTHER_LDFLAGS[sdk=iphoneos4*]=$(inherited) -framework Foo +// OTHER_LDFLAGS[sdk=iphonesimulator4*]=$(inherited) -framework Foo +// +// +// How to add a compiler define: +// +// OTHER_CFLAGS=$(inherited) -DFOO=1 +// +// +// IMPORTANT NOTE: always use $(inherited) in your overrides +// diff --git a/iOS/platform/README b/iOS/platform/README new file mode 100644 index 0000000..7ac991c --- /dev/null +++ b/iOS/platform/README @@ -0,0 +1,3 @@ +You can place platform-specific files here in sub-folders named "android" and/or "iphone", just as you can with normal Titanium Mobile SDK projects. Any folders and files you place here will be merged with the platform-specific files in a Titanium Mobile project that uses this module. + +When a Titanium Mobile project that uses this module is built, the files from this platform/ folder will be treated the same as files (if any) from the Titanium Mobile project's platform/ folder. diff --git a/iOS/timodule.xml b/iOS/timodule.xml new file mode 100644 index 0000000..6affb2f --- /dev/null +++ b/iOS/timodule.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/iOS/titanium.xcconfig b/iOS/titanium.xcconfig new file mode 100644 index 0000000..1e749bd --- /dev/null +++ b/iOS/titanium.xcconfig @@ -0,0 +1,19 @@ +// +// +// CHANGE THESE VALUES TO REFLECT THE VERSION (AND LOCATION IF DIFFERENT) +// OF YOUR TITANIUM SDK YOU'RE BUILDING FOR +// +// +TITANIUM_SDK_VERSION = 3.1.0.GA + + +// +// THESE SHOULD BE OK GENERALLY AS-IS +// +TITANIUM_SDK = ~/Library/Application Support/Titanium/mobilesdk/osx/$(TITANIUM_SDK_VERSION) +TITANIUM_BASE_SDK = "$(TITANIUM_SDK)/iphone/include" +TITANIUM_BASE_SDK2 = "$(TITANIUM_SDK)/iphone/include/TiCore" +HEADER_SEARCH_PATHS= $(TITANIUM_BASE_SDK) $(TITANIUM_BASE_SDK2) + + +