Initial import

pull/30/head
Alinson S. Xavier 11 years ago
commit 477eb80109

32
.gitignore vendored

@ -0,0 +1,32 @@
#built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
local.properties
# Windows thumbnail db
Thumbs.db
# OSX files
.DS_Store
# Eclipse project files
.classpath
.project
# Android Studio
.idea
#.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
.gradle
build/

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.isoron.uhabits"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="20" />
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:name="com.activeandroid.app.Application"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light" >
<meta-data
android:name="AA_DB_NAME"
android:value="uhabits.db" />
<meta-data
android:name="AA_DB_VERSION"
android:value="2" />
<activity
android:name="org.isoron.uhabits.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="458"
height="458"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="ic_launcher.svg"
inkscape:export-filename="/home/isoron/Android/uHabits/art/ic_launcher.png"
inkscape:export-xdpi="300.06549"
inkscape:export-ydpi="300.06549">
<defs
id="defs4">
<linearGradient
id="linearGradient3005">
<stop
style="stop-color:#2c73be;stop-opacity:1;"
offset="0"
id="stop3007" />
<stop
style="stop-color:#003750;stop-opacity:1;"
offset="1"
id="stop3009" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3005"
id="linearGradient3011"
x1="302.45276"
y1="619.70703"
x2="302.45276"
y2="178.3795"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3005"
id="linearGradient3783"
gradientUnits="userSpaceOnUse"
x1="302.45276"
y1="619.70703"
x2="302.45276"
y2="178.3795" />
<filter
inkscape:collect="always"
id="filter3882"
color-interpolation-filters="sRGB">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.2443362"
id="feGaussianBlur3884" />
</filter>
<filter
inkscape:collect="always"
id="filter3943"
x="-0.11004367"
width="1.2200873"
y="-0.11004367"
height="1.2200873">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="5.7621265"
id="feGaussianBlur3945" />
</filter>
<filter
inkscape:collect="always"
id="filter4588">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.9052045"
id="feGaussianBlur4590" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="158.05316"
inkscape:cy="229.21828"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1364"
inkscape:window-height="747"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showguides="true"
inkscape:guide-bbox="true" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-143.04724,-297.18109)">
<path
transform="translate(61.047241,122.81891)"
d="m 540,403.36218 c 0,126.47321 -102.52679,229 -229,229 -126.47321,0 -229,-102.52679 -229,-229 0,-126.47321 102.52679,-229 229,-229 126.47321,0 229,102.52679 229,229 z"
sodipodi:ry="229"
sodipodi:rx="229"
sodipodi:cy="403.36218"
sodipodi:cx="311"
id="path3003"
style="fill:url(#linearGradient3011);fill-opacity:1.0;stroke:none"
sodipodi:type="arc" />
<flowRoot
xml:space="preserve"
id="flowRoot2985"
style="font-size:24px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:end;writing-mode:lr-tb;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;font-family:CCAstroCityInt;-inkscape-font-specification:augie Bold"><flowRegion
id="flowRegion2987"><rect
id="rect2989"
width="360"
height="345.71429"
x="251.42857"
y="540.93359"
style="font-size:24px" /></flowRegion><flowPara
id="flowPara2991" /></flowRoot> <path
sodipodi:type="arc"
style="fill:url(#linearGradient3783);fill-opacity:1;stroke:none"
id="path3781"
sodipodi:cx="311"
sodipodi:cy="403.36218"
sodipodi:rx="229"
sodipodi:ry="229"
d="m 540,403.36218 c 0,126.47321 -102.52679,229 -229,229 -126.47321,0 -229,-102.52679 -229,-229 0,-126.47321 102.52679,-229 229,-229 126.47321,0 229,102.52679 229,229 z"
transform="matrix(0.93886463,0,0,-0.93886463,80.06034,912.88358)" />
<flowRoot
xml:space="preserve"
id="flowRoot2993"
style="font-size:144px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:end;writing-mode:lr-tb;text-anchor:end;fill:#ff0000;fill-opacity:1;stroke:none;font-family:FontAwesome;-inkscape-font-specification:FontAwesome Bold"
transform="matrix(2.2412176,0,0,2.2406639,-134.49554,-529.80131)"><flowRegion
id="flowRegion2995"><rect
id="rect2997"
width="231.42857"
height="191.42857"
x="68.571426"
y="375.21933"
style="font-size:144px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#ff0000;font-family:FontAwesome;-inkscape-font-specification:FontAwesome Bold" /></flowRegion><flowPara
id="flowPara2999">aaahjoaaa</flowPara></flowRoot> <flowRoot
transform="matrix(1.0677632e-8,-2.2314814,2.2314814,1.0677632e-8,-30.518064,759.73426)"
style="font-size:144px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:end;writing-mode:lr-tb;text-anchor:end;fill:none;stroke:#000000;stroke-width:3.58506237000000016;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:GLYPHICONS;-inkscape-font-specification:GLYPHICONS Bold;opacity:0.50000000000000000;filter:url(#filter4588)"
id="flowRoot3931"
xml:space="preserve"><flowRegion
id="flowRegion3933"
style="stroke-width:2.24066401"><rect
style="font-size:144px;fill:none;stroke:#000000;stroke-width:3.58506237;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
y="79"
x="-221"
height="343"
width="383"
id="rect3935" /></flowRegion><flowPara
style="fill:none;stroke:#000000;stroke-width:3.58506237;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="flowPara3937"></flowPara></flowRoot> <flowRoot
xml:space="preserve"
id="flowRoot3923"
style="font-size:144px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:end;writing-mode:lr-tb;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke:none;font-family:GLYPHICONS;-inkscape-font-specification:GLYPHICONS Bold"
transform="matrix(1.0677632e-8,-2.2314814,2.2314814,1.0677632e-8,-30.518064,761.73426)"><flowRegion
id="flowRegion3925"><rect
id="rect3927"
width="383"
height="343"
x="-221"
y="79"
style="font-size:144px;fill:#ffffff;fill-opacity:1;stroke:none" /></flowRegion><flowPara
id="flowPara3929"
style="fill:#ffffff;fill-opacity:1;stroke:none"></flowPara></flowRoot> </g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Binary file not shown.

@ -0,0 +1,20 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

@ -0,0 +1,14 @@
# 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 edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-18

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false" android:state_enabled="true" android:drawable="@drawable/apptheme_textfield_default_holo_light" />
<item android:state_window_focused="false" android:state_enabled="false" android:drawable="@drawable/apptheme_textfield_disabled_holo_light" />
<item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/apptheme_textfield_activated_holo_light" />
<item android:state_enabled="true" android:state_activated="true" android:drawable="@drawable/apptheme_textfield_focused_holo_light" />
<item android:state_enabled="true" android:drawable="@drawable/apptheme_textfield_default_holo_light" />
<item android:state_focused="true" android:drawable="@drawable/apptheme_textfield_disabled_focused_holo_light" />
<item android:drawable="@drawable/apptheme_textfield_disabled_holo_light" />
</selector>

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project
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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" />

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:top="40dp">
<shape android:shape="rectangle" >
<gradient
android:startColor="#30000000"
android:endColor="#00000000"
android:angle="270"/>
</shape>
</item>
<item android:top="21dp" android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#ccffffff"
android:startColor="#ffffff" />
</shape>
</item>
<item android:bottom="21dp">
<shape android:shape="rectangle" >
<solid android:color="#ffffff" />
</shape>
</item>
</layer-list>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
</layer-list>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:bottom="28dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#00000000"
android:height="1px"
android:startColor="#08000000" />
</shape>
</item>
</layer-list>

@ -0,0 +1,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/habits_item_check_pressed" android:state_pressed="true"></item>
<item android:drawable="@drawable/habits_item_check_pressed" android:state_focused="true"></item>
<item android:drawable="@drawable/habits_item_check_normal"></item>
</selector>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="oval" >
<gradient
android:endColor="#00000000"
android:gradientRadius="@dimen/radius"
android:startColor="#50000000"
android:type="radial" />
</shape>
</item>
<item
android:bottom="6dp"
android:left="4dp"
android:right="4dp"
android:top="2dp">
<shape android:shape="oval" >
<solid android:color="#ffffff" />
</shape>
</item>
</layer-list>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="oval" >
<gradient
android:endColor="#00000000"
android:gradientRadius="@dimen/radius"
android:startColor="#50000000"
android:type="radial" />
</shape>
</item>
<item
android:bottom="6dp"
android:left="4dp"
android:right="4dp"
android:top="2dp">
<shape android:shape="oval" >
<solid android:color="#20000000" />
</shape>
</item>
</layer-list>

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project
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.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center" >
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:padding="28dp" >
<ProgressBar
android:id="@android:id/progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center" />
<com.android.colorpicker.ColorPickerPalette
android:id="@+id/color_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:visibility="gone" />
</FrameLayout>
</ScrollView>

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2013 The Android Open Source Project
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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="@+id/color_picker_swatch"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/color_picker_checkmark"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/ic_colorpicker_swatch_selected"
android:scaleType="fitXY"
android:visibility="gone" />
</merge>

@ -0,0 +1,135 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="org.isoron.uhabits.dialogs.EditHabitFragment"
tools:ignore="MergeRootFrame" >
<LinearLayout
android:id="@+id/formPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical"
android:padding="8dp" >
<LinearLayout
android:id="@+id/namePanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal" >
<EditText
android:id="@+id/input_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="10"
android:background="@drawable/apptheme_edit_text_holo_light"
android:ems="10"
android:hint="Name"
android:textColor="#cc2222" >
<requestFocus />
</EditText>
<ImageButton
android:id="@+id/button_pick_color"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_action_pick_color" />
</LinearLayout>
<EditText
android:id="@+id/input_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Description"
android:inputType="textMultiLine" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Repeat " />
<EditText
android:id="@+id/input_freq_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="7"
android:maxLength="2"
android:inputType="number"
android:gravity="center"
android:ems="2" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" times every " />
<EditText
android:id="@+id/input_freq_den"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="7"
android:maxLength="2"
android:inputType="number"
android:gravity="center"
android:ems="2" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" days" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/buttonPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="?android:attr/dividerHorizontal"
android:dividerPadding="0dip"
android:orientation="vertical"
android:showDividers="beginning" >
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:orientation="horizontal" >
<Button
android:id="@+id/button_discard"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Discard" />
<Button
android:id="@+id/button_save"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Save" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,15 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.isoron.uhabits.MainActivity"
tools:ignore="MergeRootFrame" >
<fragment
android:id="@+id/fragment1"
android:name="org.isoron.uhabits.dialogs.ShowHabitsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dslv="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff" >
<!-- <ListView -->
<!-- android:id="@+id/listView" -->
<!-- android:layout_width="match_parent" -->
<!-- android:layout_height="match_parent" -->
<!-- android:divider="#00000000" -->
<!-- android:dividerHeight="0dp"> -->
<!-- </ListView> -->
<com.mobeta.android.dslv.DragSortListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#00000000"
android:dividerHeight="0dp"
dslv:drag_enabled="true"
dslv:drag_handle_id="@drawable/habits_header_check"
dslv:drag_start_mode="onMove"
dslv:float_alpha="0.5"
dslv:sort_enabled="true"
dslv:track_drag_sort="false"
dslv:use_default_controller="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="@drawable/habits_header" >
<TextView
android:id="@+id/tvStarHeader"
android:layout_width="36dp"
android:layout_height="match_parent"
android:layout_marginTop="0dp"
android:gravity="center"
android:paddingTop="1dp" />
<TextView
android:id="@+id/tvNameHeader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:paddingBottom="6dp"
android:paddingLeft="0dp"
android:paddingRight="6dp"
android:paddingTop="6dp" />
<LinearLayout
android:id="@+id/llButtonsHeader"
android:layout_width="wrap_content"
android:layout_height="42dp"
android:orientation="horizontal" >
</LinearLayout>
</LinearLayout>
</RelativeLayout>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tvCheck"
android:layout_width="42dp"
android:layout_height="match_parent"
android:background="@drawable/habits_header_check"
android:focusable="false"
android:minHeight="42dp"
android:minWidth="42dp"
android:textSize="10sp" />

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/habits_item"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal" >
<TextView
android:id="@+id/tvStar"
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_marginTop="0dp"
android:gravity="center"
android:paddingTop="1dp"
android:textSize="16sp" />
<TextView
android:id="@+id/tvName"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingBottom="6dp"
android:paddingLeft="0dp"
android:paddingRight="6dp"
android:paddingTop="6dp" />
<LinearLayout
android:id="@+id/llButtons"
android:layout_width="wrap_content"
android:layout_height="42dp"
android:orientation="horizontal" />
</LinearLayout>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tvCheck"
android:layout_width="42dp"
android:layout_height="match_parent"
android:layout_marginRight="18dp"
android:minHeight="42dp"
android:minWidth="42dp"
android:focusable="false"
android:background="@drawable/habits_item_check" />

@ -0,0 +1,11 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.isoron.uhabits.MainActivity" >
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>
</menu>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_edit_habit"
android:title="@string/edit">
</item>
<item
android:id="@+id/action_delete_habit"
android:title="@string/delete">
</item>
</menu>

@ -0,0 +1,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.isoron.uhabits.MainActivity" >
<item
android:id="@+id/action_add"
android:title="@string/add_habit" android:showAsAction="always" android:icon="@drawable/ic_action_add"/>
</menu>

@ -0,0 +1,3 @@
<resources>
<item name="radius" format="float" type="dimen">30</item>
</resources>

@ -0,0 +1,3 @@
<resources>
<item name="radius" format="float" type="dimen">15</item>
</resources>

@ -0,0 +1,3 @@
<resources>
<item name="radius" format="float" type="dimen">20</item>
</resources>

@ -0,0 +1,11 @@
<resources>
<!--
Base application theme for API 11+. This theme completely replaces
AppBaseTheme from res/values/styles.xml on API 11+ devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
<!-- API 11 theme customizations can go here. -->
</style>
</resources>

@ -0,0 +1,12 @@
<resources>
<!--
Base application theme for API 14+. This theme completely replaces
AppBaseTheme from BOTH res/values/styles.xml and
res/values-v11/styles.xml on API 14+ devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<!-- API 14 theme customizations can go here. -->
</style>
</resources>

@ -0,0 +1,10 @@
<resources>
<!--
Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
-->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

@ -0,0 +1,3 @@
<resources>
<item name="radius" format="float" type="dimen">40</item>
</resources>

@ -0,0 +1,3 @@
<resources>
<item name="radius" format="float" type="dimen">60</item>
</resources>

@ -0,0 +1,3 @@
<resources>
<item name="radius" format="float" type="dimen">80</item>
</resources>

@ -0,0 +1,13 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<!-- Color picker -->
<dimen name="color_swatch_large">64dip</dimen>
<dimen name="color_swatch_small">48dip</dimen>
<dimen name="color_swatch_margins_large">8dip</dimen>
<dimen name="color_swatch_margins_small">4dip</dimen>
</resources>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<declare-styleable name="DragSortListView">
<attr name="collapsed_height" format="dimension" />
<attr name="drag_scroll_start" format="float" />
<attr name="max_drag_scroll_speed" format="float" />
<attr name="float_background_color" format="color" />
<attr name="remove_mode">
<enum name="clickRemove" value="0" />
<enum name="flingRemove" value="1" />
</attr>
<attr name="track_drag_sort" format="boolean"/>
<attr name="float_alpha" format="float"/>
<attr name="slide_shuffle_speed" format="float"/>
<attr name="remove_animation_duration" format="integer"/>
<attr name="drop_animation_duration" format="integer"/>
<attr name="drag_enabled" format="boolean" />
<attr name="sort_enabled" format="boolean" />
<attr name="remove_enabled" format="boolean" />
<attr name="drag_start_mode">
<enum name="onDown" value="0" />
<enum name="onMove" value="1" />
<enum name="onLongPress" value="2"/>
</attr>
<attr name="drag_handle_id" format="integer" />
<attr name="fling_handle_id" format="integer" />
<attr name="click_remove_id" format="integer" />
<attr name="use_default_controller" format="boolean" />
</declare-styleable>
</resources>

@ -0,0 +1,372 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="fa_glass">&#xf000;</string>
<string name="fa_music">&#xf001;</string>
<string name="fa_search">&#xf002;</string>
<string name="fa_envelope_o">&#xf003;</string>
<string name="fa_heart">&#xf004;</string>
<string name="fa_star">&#xf005;</string>
<string name="fa_star_o">&#xf006;</string>
<string name="fa_user">&#xf007;</string>
<string name="fa_film">&#xf008;</string>
<string name="fa_th_large">&#xf009;</string>
<string name="fa_th">&#xf00a;</string>
<string name="fa_th_list">&#xf00b;</string>
<string name="fa_check">&#xf00c;</string>
<string name="fa_times">&#xf00d;</string>
<string name="fa_search_plus">&#xf00e;</string>
<string name="fa_search_minus">&#xf010;</string>
<string name="fa_power_off">&#xf011;</string>
<string name="fa_signal">&#xf012;</string>
<string name="fa_cog">&#xf013;</string>
<string name="fa_trash_o">&#xf014;</string>
<string name="fa_home">&#xf015;</string>
<string name="fa_file_o">&#xf016;</string>
<string name="fa_clock_o">&#xf017;</string>
<string name="fa_road">&#xf018;</string>
<string name="fa_download">&#xf019;</string>
<string name="fa_arrow_circle_o_down">&#xf01a;</string>
<string name="fa_arrow_circle_o_up">&#xf01b;</string>
<string name="fa_inbox">&#xf01c;</string>
<string name="fa_play_circle_o">&#xf01d;</string>
<string name="fa_repeat">&#xf01e;</string>
<string name="fa_refresh">&#xf021;</string>
<string name="fa_list_alt">&#xf022;</string>
<string name="fa_lock">&#xf023;</string>
<string name="fa_flag">&#xf024;</string>
<string name="fa_headphones">&#xf025;</string>
<string name="fa_volume_off">&#xf026;</string>
<string name="fa_volume_down">&#xf027;</string>
<string name="fa_volume_up">&#xf028;</string>
<string name="fa_qrcode">&#xf029;</string>
<string name="fa_barcode">&#xf02a;</string>
<string name="fa_tag">&#xf02b;</string>
<string name="fa_tags">&#xf02c;</string>
<string name="fa_book">&#xf02d;</string>
<string name="fa_bookmark">&#xf02e;</string>
<string name="fa_print">&#xf02f;</string>
<string name="fa_camera">&#xf030;</string>
<string name="fa_font">&#xf031;</string>
<string name="fa_bold">&#xf032;</string>
<string name="fa_italic">&#xf033;</string>
<string name="fa_text_height">&#xf034;</string>
<string name="fa_text_width">&#xf035;</string>
<string name="fa_align_left">&#xf036;</string>
<string name="fa_align_center">&#xf037;</string>
<string name="fa_align_right">&#xf038;</string>
<string name="fa_align_justify">&#xf039;</string>
<string name="fa_list">&#xf03a;</string>
<string name="fa_outdent">&#xf03b;</string>
<string name="fa_indent">&#xf03c;</string>
<string name="fa_video_camera">&#xf03d;</string>
<string name="fa_picture_o">&#xf03e;</string>
<string name="fa_pencil">&#xf040;</string>
<string name="fa_map_marker">&#xf041;</string>
<string name="fa_adjust">&#xf042;</string>
<string name="fa_tint">&#xf043;</string>
<string name="fa_pencil_square_o">&#xf044;</string>
<string name="fa_share_square_o">&#xf045;</string>
<string name="fa_check_square_o">&#xf046;</string>
<string name="fa_arrows">&#xf047;</string>
<string name="fa_step_backward">&#xf048;</string>
<string name="fa_fast_backward">&#xf049;</string>
<string name="fa_backward">&#xf04a;</string>
<string name="fa_play">&#xf04b;</string>
<string name="fa_pause">&#xf04c;</string>
<string name="fa_stop">&#xf04d;</string>
<string name="fa_forward">&#xf04e;</string>
<string name="fa_fast_forward">&#xf050;</string>
<string name="fa_step_forward">&#xf051;</string>
<string name="fa_eject">&#xf052;</string>
<string name="fa_chevron_left">&#xf053;</string>
<string name="fa_chevron_right">&#xf054;</string>
<string name="fa_plus_circle">&#xf055;</string>
<string name="fa_minus_circle">&#xf056;</string>
<string name="fa_times_circle">&#xf057;</string>
<string name="fa_check_circle">&#xf058;</string>
<string name="fa_question_circle">&#xf059;</string>
<string name="fa_info_circle">&#xf05a;</string>
<string name="fa_crosshairs">&#xf05b;</string>
<string name="fa_times_circle_o">&#xf05c;</string>
<string name="fa_check_circle_o">&#xf05d;</string>
<string name="fa_ban">&#xf05e;</string>
<string name="fa_arrow_left">&#xf060;</string>
<string name="fa_arrow_right">&#xf061;</string>
<string name="fa_arrow_up">&#xf062;</string>
<string name="fa_arrow_down">&#xf063;</string>
<string name="fa_share">&#xf064;</string>
<string name="fa_expand">&#xf065;</string>
<string name="fa_compress">&#xf066;</string>
<string name="fa_plus">&#xf067;</string>
<string name="fa_minus">&#xf068;</string>
<string name="fa_asterisk">&#xf069;</string>
<string name="fa_exclamation_circle">&#xf06a;</string>
<string name="fa_gift">&#xf06b;</string>
<string name="fa_leaf">&#xf06c;</string>
<string name="fa_fire">&#xf06d;</string>
<string name="fa_eye">&#xf06e;</string>
<string name="fa_eye_slash">&#xf070;</string>
<string name="fa_exclamation_triangle">&#xf071;</string>
<string name="fa_plane">&#xf072;</string>
<string name="fa_calendar">&#xf073;</string>
<string name="fa_random">&#xf074;</string>
<string name="fa_comment">&#xf075;</string>
<string name="fa_magnet">&#xf076;</string>
<string name="fa_chevron_up">&#xf077;</string>
<string name="fa_chevron_down">&#xf078;</string>
<string name="fa_retweet">&#xf079;</string>
<string name="fa_shopping_cart">&#xf07a;</string>
<string name="fa_folder">&#xf07b;</string>
<string name="fa_folder_open">&#xf07c;</string>
<string name="fa_arrows_v">&#xf07d;</string>
<string name="fa_arrows_h">&#xf07e;</string>
<string name="fa_bar_chart_o">&#xf080;</string>
<string name="fa_twitter_square">&#xf081;</string>
<string name="fa_facebook_square">&#xf082;</string>
<string name="fa_camera_retro">&#xf083;</string>
<string name="fa_key">&#xf084;</string>
<string name="fa_cogs">&#xf085;</string>
<string name="fa_comments">&#xf086;</string>
<string name="fa_thumbs_o_up">&#xf087;</string>
<string name="fa_thumbs_o_down">&#xf088;</string>
<string name="fa_star_half">&#xf089;</string>
<string name="fa_heart_o">&#xf08a;</string>
<string name="fa_sign_out">&#xf08b;</string>
<string name="fa_linkedin_square">&#xf08c;</string>
<string name="fa_thumb_tack">&#xf08d;</string>
<string name="fa_external_link">&#xf08e;</string>
<string name="fa_sign_in">&#xf090;</string>
<string name="fa_trophy">&#xf091;</string>
<string name="fa_github_square">&#xf092;</string>
<string name="fa_upload">&#xf093;</string>
<string name="fa_lemon_o">&#xf094;</string>
<string name="fa_phone">&#xf095;</string>
<string name="fa_square_o">&#xf096;</string>
<string name="fa_bookmark_o">&#xf097;</string>
<string name="fa_phone_square">&#xf098;</string>
<string name="fa_twitter">&#xf099;</string>
<string name="fa_facebook">&#xf09a;</string>
<string name="fa_github">&#xf09b;</string>
<string name="fa_unlock">&#xf09c;</string>
<string name="fa_credit_card">&#xf09d;</string>
<string name="fa_rss">&#xf09e;</string>
<string name="fa_hdd_o">&#xf0a0;</string>
<string name="fa_bullhorn">&#xf0a1;</string>
<string name="fa_bell">&#xf0f3;</string>
<string name="fa_certificate">&#xf0a3;</string>
<string name="fa_hand_o_right">&#xf0a4;</string>
<string name="fa_hand_o_left">&#xf0a5;</string>
<string name="fa_hand_o_up">&#xf0a6;</string>
<string name="fa_hand_o_down">&#xf0a7;</string>
<string name="fa_arrow_circle_left">&#xf0a8;</string>
<string name="fa_arrow_circle_right">&#xf0a9;</string>
<string name="fa_arrow_circle_up">&#xf0aa;</string>
<string name="fa_arrow_circle_down">&#xf0ab;</string>
<string name="fa_globe">&#xf0ac;</string>
<string name="fa_wrench">&#xf0ad;</string>
<string name="fa_tasks">&#xf0ae;</string>
<string name="fa_filter">&#xf0b0;</string>
<string name="fa_briefcase">&#xf0b1;</string>
<string name="fa_arrows_alt">&#xf0b2;</string>
<string name="fa_users">&#xf0c0;</string>
<string name="fa_link">&#xf0c1;</string>
<string name="fa_cloud">&#xf0c2;</string>
<string name="fa_flask">&#xf0c3;</string>
<string name="fa_scissors">&#xf0c4;</string>
<string name="fa_files_o">&#xf0c5;</string>
<string name="fa_paperclip">&#xf0c6;</string>
<string name="fa_floppy_o">&#xf0c7;</string>
<string name="fa_square">&#xf0c8;</string>
<string name="fa_bars">&#xf0c9;</string>
<string name="fa_list_ul">&#xf0ca;</string>
<string name="fa_list_ol">&#xf0cb;</string>
<string name="fa_strikethrough">&#xf0cc;</string>
<string name="fa_underline">&#xf0cd;</string>
<string name="fa_table">&#xf0ce;</string>
<string name="fa_magic">&#xf0d0;</string>
<string name="fa_truck">&#xf0d1;</string>
<string name="fa_pinterest">&#xf0d2;</string>
<string name="fa_pinterest_square">&#xf0d3;</string>
<string name="fa_google_plus_square">&#xf0d4;</string>
<string name="fa_google_plus">&#xf0d5;</string>
<string name="fa_money">&#xf0d6;</string>
<string name="fa_caret_down">&#xf0d7;</string>
<string name="fa_caret_up">&#xf0d8;</string>
<string name="fa_caret_left">&#xf0d9;</string>
<string name="fa_caret_right">&#xf0da;</string>
<string name="fa_columns">&#xf0db;</string>
<string name="fa_sort">&#xf0dc;</string>
<string name="fa_sort_asc">&#xf0dd;</string>
<string name="fa_sort_desc">&#xf0de;</string>
<string name="fa_envelope">&#xf0e0;</string>
<string name="fa_linkedin">&#xf0e1;</string>
<string name="fa_undo">&#xf0e2;</string>
<string name="fa_gavel">&#xf0e3;</string>
<string name="fa_tachometer">&#xf0e4;</string>
<string name="fa_comment_o">&#xf0e5;</string>
<string name="fa_comments_o">&#xf0e6;</string>
<string name="fa_bolt">&#xf0e7;</string>
<string name="fa_sitemap">&#xf0e8;</string>
<string name="fa_umbrella">&#xf0e9;</string>
<string name="fa_clipboard">&#xf0ea;</string>
<string name="fa_lightbulb_o">&#xf0eb;</string>
<string name="fa_exchange">&#xf0ec;</string>
<string name="fa_cloud_download">&#xf0ed;</string>
<string name="fa_cloud_upload">&#xf0ee;</string>
<string name="fa_user_md">&#xf0f0;</string>
<string name="fa_stethoscope">&#xf0f1;</string>
<string name="fa_suitcase">&#xf0f2;</string>
<string name="fa_bell_o">&#xf0a2;</string>
<string name="fa_coffee">&#xf0f4;</string>
<string name="fa_cutlery">&#xf0f5;</string>
<string name="fa_file_text_o">&#xf0f6;</string>
<string name="fa_building_o">&#xf0f7;</string>
<string name="fa_hospital_o">&#xf0f8;</string>
<string name="fa_ambulance">&#xf0f9;</string>
<string name="fa_medkit">&#xf0fa;</string>
<string name="fa_fighter_jet">&#xf0fb;</string>
<string name="fa_beer">&#xf0fc;</string>
<string name="fa_h_square">&#xf0fd;</string>
<string name="fa_plus_square">&#xf0fe;</string>
<string name="fa_angle_double_left">&#xf100;</string>
<string name="fa_angle_double_right">&#xf101;</string>
<string name="fa_angle_double_up">&#xf102;</string>
<string name="fa_angle_double_down">&#xf103;</string>
<string name="fa_angle_left">&#xf104;</string>
<string name="fa_angle_right">&#xf105;</string>
<string name="fa_angle_up">&#xf106;</string>
<string name="fa_angle_down">&#xf107;</string>
<string name="fa_desktop">&#xf108;</string>
<string name="fa_laptop">&#xf109;</string>
<string name="fa_tablet">&#xf10a;</string>
<string name="fa_mobile">&#xf10b;</string>
<string name="fa_circle_o">&#xf10c;</string>
<string name="fa_quote_left">&#xf10d;</string>
<string name="fa_quote_right">&#xf10e;</string>
<string name="fa_spinner">&#xf110;</string>
<string name="fa_circle">&#xf111;</string>
<string name="fa_reply">&#xf112;</string>
<string name="fa_github_alt">&#xf113;</string>
<string name="fa_folder_o">&#xf114;</string>
<string name="fa_folder_open_o">&#xf115;</string>
<string name="fa_smile_o">&#xf118;</string>
<string name="fa_frown_o">&#xf119;</string>
<string name="fa_meh_o">&#xf11a;</string>
<string name="fa_gamepad">&#xf11b;</string>
<string name="fa_keyboard_o">&#xf11c;</string>
<string name="fa_flag_o">&#xf11d;</string>
<string name="fa_flag_checkered">&#xf11e;</string>
<string name="fa_terminal">&#xf120;</string>
<string name="fa_code">&#xf121;</string>
<string name="fa_reply_all">&#xf122;</string>
<string name="fa_mail_reply_all">&#xf122;</string>
<string name="fa_star_half_o">&#xf123;</string>
<string name="fa_location_arrow">&#xf124;</string>
<string name="fa_crop">&#xf125;</string>
<string name="fa_code_fork">&#xf126;</string>
<string name="fa_chain_broken">&#xf127;</string>
<string name="fa_question">&#xf128;</string>
<string name="fa_info">&#xf129;</string>
<string name="fa_exclamation">&#xf12a;</string>
<string name="fa_superscript">&#xf12b;</string>
<string name="fa_subscript">&#xf12c;</string>
<string name="fa_eraser">&#xf12d;</string>
<string name="fa_puzzle_piece">&#xf12e;</string>
<string name="fa_microphone">&#xf130;</string>
<string name="fa_microphone_slash">&#xf131;</string>
<string name="fa_shield">&#xf132;</string>
<string name="fa_calendar_o">&#xf133;</string>
<string name="fa_fire_extinguisher">&#xf134;</string>
<string name="fa_rocket">&#xf135;</string>
<string name="fa_maxcdn">&#xf136;</string>
<string name="fa_chevron_circle_left">&#xf137;</string>
<string name="fa_chevron_circle_right">&#xf138;</string>
<string name="fa_chevron_circle_up">&#xf139;</string>
<string name="fa_chevron_circle_down">&#xf13a;</string>
<string name="fa_html5">&#xf13b;</string>
<string name="fa_css3">&#xf13c;</string>
<string name="fa_anchor">&#xf13d;</string>
<string name="fa_unlock_alt">&#xf13e;</string>
<string name="fa_bullseye">&#xf140;</string>
<string name="fa_ellipsis_h">&#xf141;</string>
<string name="fa_ellipsis_v">&#xf142;</string>
<string name="fa_rss_square">&#xf143;</string>
<string name="fa_play_circle">&#xf144;</string>
<string name="fa_ticket">&#xf145;</string>
<string name="fa_minus_square">&#xf146;</string>
<string name="fa_minus_square_o">&#xf147;</string>
<string name="fa_level_up">&#xf148;</string>
<string name="fa_level_down">&#xf149;</string>
<string name="fa_check_square">&#xf14a;</string>
<string name="fa_pencil_square">&#xf14b;</string>
<string name="fa_external_link_square">&#xf14c;</string>
<string name="fa_share_square">&#xf14d;</string>
<string name="fa_compass">&#xf14e;</string>
<string name="fa_caret_square_o_down">&#xf150;</string>
<string name="fa_caret_square_o_up">&#xf151;</string>
<string name="fa_caret_square_o_right">&#xf152;</string>
<string name="fa_eur">&#xf153;</string>
<string name="fa_gbp">&#xf154;</string>
<string name="fa_usd">&#xf155;</string>
<string name="fa_inr">&#xf156;</string>
<string name="fa_jpy">&#xf157;</string>
<string name="fa_rub">&#xf158;</string>
<string name="fa_krw">&#xf159;</string>
<string name="fa_btc">&#xf15a;</string>
<string name="fa_file">&#xf15b;</string>
<string name="fa_file_text">&#xf15c;</string>
<string name="fa_sort_alpha_asc">&#xf15d;</string>
<string name="fa_sort_alpha_desc">&#xf15e;</string>
<string name="fa_sort_amount_asc">&#xf160;</string>
<string name="fa_sort_amount_desc">&#xf161;</string>
<string name="fa_sort_numeric_asc">&#xf162;</string>
<string name="fa_sort_numeric_desc">&#xf163;</string>
<string name="fa_thumbs_up">&#xf164;</string>
<string name="fa_thumbs_down">&#xf165;</string>
<string name="fa_youtube_square">&#xf166;</string>
<string name="fa_youtube">&#xf167;</string>
<string name="fa_xing">&#xf168;</string>
<string name="fa_xing_square">&#xf169;</string>
<string name="fa_youtube_play">&#xf16a;</string>
<string name="fa_dropbox">&#xf16b;</string>
<string name="fa_stack_overflow">&#xf16c;</string>
<string name="fa_instagram">&#xf16d;</string>
<string name="fa_flickr">&#xf16e;</string>
<string name="fa_adn">&#xf170;</string>
<string name="fa_bitbucket">&#xf171;</string>
<string name="fa_bitbucket_square">&#xf172;</string>
<string name="fa_tumblr">&#xf173;</string>
<string name="fa_tumblr_square">&#xf174;</string>
<string name="fa_long_arrow_down">&#xf175;</string>
<string name="fa_long_arrow_up">&#xf176;</string>
<string name="fa_long_arrow_left">&#xf177;</string>
<string name="fa_long_arrow_right">&#xf178;</string>
<string name="fa_apple">&#xf179;</string>
<string name="fa_windows">&#xf17a;</string>
<string name="fa_android">&#xf17b;</string>
<string name="fa_linux">&#xf17c;</string>
<string name="fa_dribbble">&#xf17d;</string>
<string name="fa_skype">&#xf17e;</string>
<string name="fa_foursquare">&#xf180;</string>
<string name="fa_trello">&#xf181;</string>
<string name="fa_female">&#xf182;</string>
<string name="fa_male">&#xf183;</string>
<string name="fa_gittip">&#xf184;</string>
<string name="fa_sun_o">&#xf185;</string>
<string name="fa_moon_o">&#xf186;</string>
<string name="fa_archive">&#xf187;</string>
<string name="fa_bug">&#xf188;</string>
<string name="fa_vk">&#xf189;</string>
<string name="fa_weibo">&#xf18a;</string>
<string name="fa_renren">&#xf18b;</string>
<string name="fa_pagelines">&#xf18c;</string>
<string name="fa_stack_exchange">&#xf18d;</string>
<string name="fa_arrow_circle_o_right">&#xf18e;</string>
<string name="fa_arrow_circle_o_left">&#xf190;</string>
<string name="fa_caret_square_o_left">&#xf191;</string>
<string name="fa_dot_circle_o">&#xf192;</string>
<string name="fa_wheelchair">&#xf193;</string>
<string name="fa_vimeo_square">&#xf194;</string>
<string name="fa_try">&#xf195;</string>
<string name="fa_plus_square_o">&#xf196;</string>
</resources>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">μHabits</string>
<string name="action_settings">Settings</string>
<string name="edit">Edit</string>
<string name="delete">Delete</string>
<string name="add_habit">Add habit</string>
<string name="color_picker_default_title">Select a Color</string>
<string name="color_swatch_description">Color <xliff:g id="color_index" example="14">%1$d</xliff:g></string>
<string name="color_swatch_description_selected">Color <xliff:g id="color_index" example="14">%1$d</xliff:g> selected</string>
<string name="toast_habit_created">Habit created.</string>
<string name="toast_habit_deleted">Habit deleted.</string>
<string name="toast_nothing_to_undo">Nothing to undo.</string>
<string name="toast_nothing_to_redo">Nothing to redo.</string>
<string name="toast_habit_changed">Habit changed.</string>
<string name="toast_habit_changed_back">Habit changed back.</string>
<string name="toast_repetition_toggled">Repetition toggled.</string>
<string name="habit_key"></string>
<string name="offset_key"></string>
</resources>

@ -0,0 +1,20 @@
<resources>
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Holo.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
</resources>

@ -0,0 +1,203 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.colorpicker;
import org.isoron.uhabits.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
/**
* A dialog which takes in as input an array of colors and creates a palette allowing the user to
* select a specific color swatch, which invokes a listener.
*/
public class ColorPickerDialog extends DialogFragment implements OnColorSelectedListener {
public static final int SIZE_LARGE = 1;
public static final int SIZE_SMALL = 2;
protected AlertDialog mAlertDialog;
protected static final String KEY_TITLE_ID = "title_id";
protected static final String KEY_COLORS = "colors";
protected static final String KEY_SELECTED_COLOR = "selected_color";
protected static final String KEY_COLUMNS = "columns";
protected static final String KEY_SIZE = "size";
protected int mTitleResId = R.string.color_picker_default_title;
protected int[] mColors = null;
protected int mSelectedColor;
protected int mColumns;
protected int mSize;
private ColorPickerPalette mPalette;
private ProgressBar mProgress;
protected OnColorSelectedListener mListener;
public ColorPickerDialog() {
// Empty constructor required for dialog fragments.
}
public static ColorPickerDialog newInstance(int titleResId, int[] colors, int selectedColor,
int columns, int size) {
ColorPickerDialog ret = new ColorPickerDialog();
ret.initialize(titleResId, colors, selectedColor, columns, size);
return ret;
}
public void initialize(int titleResId, int[] colors, int selectedColor, int columns, int size) {
setArguments(titleResId, columns, size);
setColors(colors, selectedColor);
}
public void setArguments(int titleResId, int columns, int size) {
Bundle bundle = new Bundle();
bundle.putInt(KEY_TITLE_ID, titleResId);
bundle.putInt(KEY_COLUMNS, columns);
bundle.putInt(KEY_SIZE, size);
setArguments(bundle);
}
public void setOnColorSelectedListener(OnColorSelectedListener listener) {
mListener = listener;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mTitleResId = getArguments().getInt(KEY_TITLE_ID);
mColumns = getArguments().getInt(KEY_COLUMNS);
mSize = getArguments().getInt(KEY_SIZE);
}
if (savedInstanceState != null) {
mColors = savedInstanceState.getIntArray(KEY_COLORS);
mSelectedColor = (Integer) savedInstanceState.getSerializable(KEY_SELECTED_COLOR);
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
View view = LayoutInflater.from(getActivity()).inflate(R.layout.color_picker_dialog, null);
mProgress = (ProgressBar) view.findViewById(android.R.id.progress);
mPalette = (ColorPickerPalette) view.findViewById(R.id.color_picker);
mPalette.init(mSize, mColumns, this);
if (mColors != null) {
showPaletteView();
}
mAlertDialog = new AlertDialog.Builder(activity)
.setTitle(mTitleResId)
.setView(view)
.create();
return mAlertDialog;
}
@Override
public void onColorSelected(int color) {
if (mListener != null) {
mListener.onColorSelected(color);
}
if (getTargetFragment() instanceof OnColorSelectedListener) {
final OnColorSelectedListener listener =
(OnColorSelectedListener) getTargetFragment();
listener.onColorSelected(color);
}
if (color != mSelectedColor) {
mSelectedColor = color;
// Redraw palette to show checkmark on newly selected color before dismissing.
mPalette.drawPalette(mColors, mSelectedColor);
}
dismiss();
}
public void showPaletteView() {
if (mProgress != null && mPalette != null) {
mProgress.setVisibility(View.GONE);
refreshPalette();
mPalette.setVisibility(View.VISIBLE);
}
}
public void showProgressBarView() {
if (mProgress != null && mPalette != null) {
mProgress.setVisibility(View.VISIBLE);
mPalette.setVisibility(View.GONE);
}
}
public void setColors(int[] colors, int selectedColor) {
if (mColors != colors || mSelectedColor != selectedColor) {
mColors = colors;
mSelectedColor = selectedColor;
refreshPalette();
}
}
public void setColors(int[] colors) {
if (mColors != colors) {
mColors = colors;
refreshPalette();
}
}
public void setSelectedColor(int color) {
if (mSelectedColor != color) {
mSelectedColor = color;
refreshPalette();
}
}
private void refreshPalette() {
if (mPalette != null && mColors != null) {
mPalette.drawPalette(mColors, mSelectedColor);
}
}
public int[] getColors() {
return mColors;
}
public int getSelectedColor() {
return mSelectedColor;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putIntArray(KEY_COLORS, mColors);
outState.putSerializable(KEY_SELECTED_COLOR, mSelectedColor);
}
}

@ -0,0 +1,186 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.colorpicker;
import org.isoron.uhabits.R;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TableLayout;
import android.widget.TableRow;
import com.android.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
/**
* A color picker custom view which creates an grid of color squares. The number of squares per
* row (and the padding between the squares) is determined by the user.
*/
public class ColorPickerPalette extends TableLayout {
public OnColorSelectedListener mOnColorSelectedListener;
private String mDescription;
private String mDescriptionSelected;
private int mSwatchLength;
private int mMarginSize;
private int mNumColumns;
public ColorPickerPalette(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ColorPickerPalette(Context context) {
super(context);
}
/**
* Initialize the size, columns, and listener. Size should be a pre-defined size (SIZE_LARGE
* or SIZE_SMALL) from ColorPickerDialogFragment.
*/
public void init(int size, int columns, OnColorSelectedListener listener) {
mNumColumns = columns;
Resources res = getResources();
if (size == ColorPickerDialog.SIZE_LARGE) {
mSwatchLength = res.getDimensionPixelSize(R.dimen.color_swatch_large);
mMarginSize = res.getDimensionPixelSize(R.dimen.color_swatch_margins_large);
} else {
mSwatchLength = res.getDimensionPixelSize(R.dimen.color_swatch_small);
mMarginSize = res.getDimensionPixelSize(R.dimen.color_swatch_margins_small);
}
mOnColorSelectedListener = listener;
mDescription = res.getString(R.string.color_swatch_description);
mDescriptionSelected = res.getString(R.string.color_swatch_description_selected);
}
private TableRow createTableRow() {
TableRow row = new TableRow(getContext());
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
row.setLayoutParams(params);
return row;
}
/**
* Adds swatches to table in a serpentine format.
*/
public void drawPalette(int[] colors, int selectedColor) {
if (colors == null) {
return;
}
this.removeAllViews();
int tableElements = 0;
int rowElements = 0;
int rowNumber = 0;
// Fills the table with swatches based on the array of colors.
TableRow row = createTableRow();
for (int color : colors) {
tableElements++;
View colorSwatch = createColorSwatch(color, selectedColor);
setSwatchDescription(rowNumber, tableElements, rowElements, color == selectedColor,
colorSwatch);
addSwatchToRow(row, colorSwatch, rowNumber);
rowElements++;
if (rowElements == mNumColumns) {
addView(row);
row = createTableRow();
rowElements = 0;
rowNumber++;
}
}
// Create blank views to fill the row if the last row has not been filled.
if (rowElements > 0) {
while (rowElements != mNumColumns) {
addSwatchToRow(row, createBlankSpace(), rowNumber);
rowElements++;
}
addView(row);
}
}
/**
* Appends a swatch to the end of the row for even-numbered rows (starting with row 0),
* to the beginning of a row for odd-numbered rows.
*/
private static void addSwatchToRow(TableRow row, View swatch, int rowNumber) {
if (rowNumber % 2 == 0) {
row.addView(swatch);
} else {
row.addView(swatch, 0);
}
}
/**
* Add a content description to the specified swatch view. Because the colors get added in a
* snaking form, every other row will need to compensate for the fact that the colors are added
* in an opposite direction from their left->right/top->bottom order, which is how the system
* will arrange them for accessibility purposes.
*/
private void setSwatchDescription(int rowNumber, int index, int rowElements, boolean selected,
View swatch) {
int accessibilityIndex;
if (rowNumber % 2 == 0) {
// We're in a regular-ordered row
accessibilityIndex = index;
} else {
// We're in a backwards-ordered row.
int rowMax = ((rowNumber + 1) * mNumColumns);
accessibilityIndex = rowMax - rowElements;
}
String description;
if (selected) {
description = String.format(mDescriptionSelected, accessibilityIndex);
} else {
description = String.format(mDescription, accessibilityIndex);
}
swatch.setContentDescription(description);
}
/**
* Creates a blank space to fill the row.
*/
private ImageView createBlankSpace() {
ImageView view = new ImageView(getContext());
TableRow.LayoutParams params = new TableRow.LayoutParams(mSwatchLength, mSwatchLength);
params.setMargins(mMarginSize, mMarginSize, mMarginSize, mMarginSize);
view.setLayoutParams(params);
return view;
}
/**
* Creates a color swatch.
*/
private ColorPickerSwatch createColorSwatch(int color, int selectedColor) {
ColorPickerSwatch view = new ColorPickerSwatch(getContext(), color,
color == selectedColor, mOnColorSelectedListener);
TableRow.LayoutParams params = new TableRow.LayoutParams(mSwatchLength, mSwatchLength);
params.setMargins(mMarginSize, mMarginSize, mMarginSize, mMarginSize);
view.setLayoutParams(params);
return view;
}
}

@ -0,0 +1,82 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.colorpicker;
import org.isoron.uhabits.R;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
/**
* Creates a circular swatch of a specified color. Adds a checkmark if marked as checked.
*/
public class ColorPickerSwatch extends FrameLayout implements View.OnClickListener {
private int mColor;
private ImageView mSwatchImage;
private ImageView mCheckmarkImage;
private OnColorSelectedListener mOnColorSelectedListener;
/**
* Interface for a callback when a color square is selected.
*/
public interface OnColorSelectedListener {
/**
* Called when a specific color square has been selected.
*/
public void onColorSelected(int color);
}
public ColorPickerSwatch(Context context, int color, boolean checked,
OnColorSelectedListener listener) {
super(context);
mColor = color;
mOnColorSelectedListener = listener;
LayoutInflater.from(context).inflate(R.layout.color_picker_swatch, this);
mSwatchImage = (ImageView) findViewById(R.id.color_picker_swatch);
mCheckmarkImage = (ImageView) findViewById(R.id.color_picker_checkmark);
setColor(color);
setChecked(checked);
setOnClickListener(this);
}
protected void setColor(int color) {
Drawable[] colorDrawable = new Drawable[]
{getContext().getResources().getDrawable(R.drawable.color_picker_swatch)};
mSwatchImage.setImageDrawable(new ColorStateDrawable(colorDrawable, color));
}
private void setChecked(boolean checked) {
if (checked) {
mCheckmarkImage.setVisibility(View.VISIBLE);
} else {
mCheckmarkImage.setVisibility(View.GONE);
}
}
@Override
public void onClick(View v) {
if (mOnColorSelectedListener != null) {
mOnColorSelectedListener.onColorSelected(mColor);
}
}
}

@ -0,0 +1,72 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.colorpicker;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
/**
* A drawable which sets its color filter to a color specified by the user, and changes to a
* slightly darker color when pressed or focused.
*/
public class ColorStateDrawable extends LayerDrawable {
private static final float PRESSED_STATE_MULTIPLIER = 0.70f;
private int mColor;
public ColorStateDrawable(Drawable[] layers, int color) {
super(layers);
mColor = color;
}
@Override
protected boolean onStateChange(int[] states) {
boolean pressedOrFocused = false;
for (int state : states) {
if (state == android.R.attr.state_pressed || state == android.R.attr.state_focused) {
pressedOrFocused = true;
break;
}
}
if (pressedOrFocused) {
super.setColorFilter(getPressedColor(mColor), PorterDuff.Mode.SRC_ATOP);
} else {
super.setColorFilter(mColor, PorterDuff.Mode.SRC_ATOP);
}
return super.onStateChange(states);
}
/**
* Given a particular color, adjusts its value by a multiplier.
*/
private static int getPressedColor(int color) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] = hsv[2] * PRESSED_STATE_MULTIPLIER;
return Color.HSVToColor(hsv);
}
@Override
public boolean isStateful() {
return true;
}
}

@ -0,0 +1,61 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.colorpicker;
import java.util.Comparator;
import android.graphics.Color;
/**
* A color comparator which compares based on hue, saturation, and value.
*/
public class HsvColorComparator implements Comparator<Integer> {
@Override
public int compare(Integer lhs, Integer rhs) {
float[] hsv = new float[3];
Color.colorToHSV(lhs, hsv);
float hue1 = hsv[0];
float sat1 = hsv[1];
float val1 = hsv[2];
float[] hsv2 = new float[3];
Color.colorToHSV(rhs, hsv2);
float hue2 = hsv2[0];
float sat2 = hsv2[1];
float val2 = hsv2[2];
if (hue1 < hue2) {
return 1;
} else if (hue1 > hue2) {
return -1;
} else {
if (sat1 < sat2) {
return 1;
} else if (sat1 > sat2) {
return -1;
} else {
if (val1 < val2) {
return 1;
} else if (val1 > val2) {
return -1;
}
}
}
return 0;
}
}

@ -0,0 +1,471 @@
package com.mobeta.android.dslv;
import android.graphics.Point;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AdapterView;
/**
* Class that starts and stops item drags on a {@link DragSortListView}
* based on touch gestures. This class also inherits from
* {@link SimpleFloatViewManager}, which provides basic float View
* creation.
*
* An instance of this class is meant to be passed to the methods
* {@link DragSortListView#setTouchListener()} and
* {@link DragSortListView#setFloatViewManager()} of your
* {@link DragSortListView} instance.
*/
public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener, GestureDetector.OnGestureListener {
/**
* Drag init mode enum.
*/
public static final int ON_DOWN = 0;
public static final int ON_DRAG = 1;
public static final int ON_LONG_PRESS = 2;
private int mDragInitMode = ON_DOWN;
private boolean mSortEnabled = true;
/**
* Remove mode enum.
*/
public static final int CLICK_REMOVE = 0;
public static final int FLING_REMOVE = 1;
/**
* The current remove mode.
*/
private int mRemoveMode;
private boolean mRemoveEnabled = false;
private boolean mIsRemoving = false;
private GestureDetector mDetector;
private GestureDetector mFlingRemoveDetector;
private int mTouchSlop;
public static final int MISS = -1;
private int mHitPos = MISS;
private int mFlingHitPos = MISS;
private int mClickRemoveHitPos = MISS;
private int[] mTempLoc = new int[2];
private int mItemX;
private int mItemY;
private int mCurrX;
private int mCurrY;
private boolean mDragging = false;
private float mFlingSpeed = 500f;
private int mDragHandleId;
private int mClickRemoveId;
private int mFlingHandleId;
private boolean mCanDrag;
private DragSortListView mDslv;
private int mPositionX;
/**
* Calls {@link #DragSortController(DragSortListView, int)} with a
* 0 drag handle id, FLING_RIGHT_REMOVE remove mode,
* and ON_DOWN drag init. By default, sorting is enabled, and
* removal is disabled.
*
* @param dslv The DSLV instance
*/
public DragSortController(DragSortListView dslv) {
this(dslv, 0, ON_DOWN, FLING_REMOVE);
}
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode) {
this(dslv, dragHandleId, dragInitMode, removeMode, 0);
}
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode, int clickRemoveId) {
this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0);
}
/**
* By default, sorting is enabled, and removal is disabled.
*
* @param dslv The DSLV instance
* @param dragHandleId The resource id of the View that represents
* the drag handle in a list item.
*/
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
int removeMode, int clickRemoveId, int flingHandleId) {
super(dslv);
mDslv = dslv;
mDetector = new GestureDetector(dslv.getContext(), this);
mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
mFlingRemoveDetector.setIsLongpressEnabled(false);
mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
mDragHandleId = dragHandleId;
mClickRemoveId = clickRemoveId;
mFlingHandleId = flingHandleId;
setRemoveMode(removeMode);
setDragInitMode(dragInitMode);
}
public int getDragInitMode() {
return mDragInitMode;
}
/**
* Set how a drag is initiated. Needs to be one of
* {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
*
* @param mode The drag init mode.
*/
public void setDragInitMode(int mode) {
mDragInitMode = mode;
}
/**
* Enable/Disable list item sorting. Disabling is useful if only item
* removal is desired. Prevents drags in the vertical direction.
*
* @param enabled Set <code>true</code> to enable list
* item sorting.
*/
public void setSortEnabled(boolean enabled) {
mSortEnabled = enabled;
}
public boolean isSortEnabled() {
return mSortEnabled;
}
/**
* One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE},
* {@link FLING_LEFT_REMOVE},
* {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
*/
public void setRemoveMode(int mode) {
mRemoveMode = mode;
}
public int getRemoveMode() {
return mRemoveMode;
}
/**
* Enable/Disable item removal without affecting remove mode.
*/
public void setRemoveEnabled(boolean enabled) {
mRemoveEnabled = enabled;
}
public boolean isRemoveEnabled() {
return mRemoveEnabled;
}
/**
* Set the resource id for the View that represents the drag
* handle in a list item.
*
* @param id An android resource id.
*/
public void setDragHandleId(int id) {
mDragHandleId = id;
}
/**
* Set the resource id for the View that represents the fling
* handle in a list item.
*
* @param id An android resource id.
*/
public void setFlingHandleId(int id) {
mFlingHandleId = id;
}
/**
* Set the resource id for the View that represents click
* removal button.
*
* @param id An android resource id.
*/
public void setClickRemoveId(int id) {
mClickRemoveId = id;
}
/**
* Sets flags to restrict certain motions of the floating View
* based on DragSortController settings (such as remove mode).
* Starts the drag on the DragSortListView.
*
* @param position The list item position (includes headers).
* @param deltaX Touch x-coord minus left edge of floating View.
* @param deltaY Touch y-coord minus top edge of floating View.
*
* @return True if drag started, false otherwise.
*/
public boolean startDrag(int position, int deltaX, int deltaY) {
int dragFlags = 0;
if (mSortEnabled && !mIsRemoving) {
dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
}
if (mRemoveEnabled && mIsRemoving) {
dragFlags |= DragSortListView.DRAG_POS_X;
dragFlags |= DragSortListView.DRAG_NEG_X;
}
mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX,
deltaY);
return mDragging;
}
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) {
return false;
}
mDetector.onTouchEvent(ev);
if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) {
mFlingRemoveDetector.onTouchEvent(ev);
}
int action = ev.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
mCurrX = (int) ev.getX();
mCurrY = (int) ev.getY();
break;
case MotionEvent.ACTION_UP:
if (mRemoveEnabled && mIsRemoving) {
int x = mPositionX >= 0 ? mPositionX : -mPositionX;
int removePoint = mDslv.getWidth() / 2;
if (x > removePoint) {
mDslv.stopDragWithVelocity(true, 0);
}
}
case MotionEvent.ACTION_CANCEL:
mIsRemoving = false;
mDragging = false;
break;
}
return false;
}
/**
* Overrides to provide fading when slide removal is enabled.
*/
@Override
public void onDragFloatView(View floatView, Point position, Point touch) {
if (mRemoveEnabled && mIsRemoving) {
mPositionX = position.x;
}
}
/**
* Get the position to start dragging based on the ACTION_DOWN
* MotionEvent. This function simply calls
* {@link #dragHandleHitPosition(MotionEvent)}. Override
* to change drag handle behavior;
* this function is called internally when an ACTION_DOWN
* event is detected.
*
* @param ev The ACTION_DOWN MotionEvent.
*
* @return The list position to drag if a drag-init gesture is
* detected; MISS if unsuccessful.
*/
public int startDragPosition(MotionEvent ev) {
return dragHandleHitPosition(ev);
}
public int startFlingPosition(MotionEvent ev) {
return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS;
}
/**
* Checks for the touch of an item's drag handle (specified by
* {@link #setDragHandleId(int)}), and returns that item's position
* if a drag handle touch was detected.
*
* @param ev The ACTION_DOWN MotionEvent.
* @return The list position of the item whose drag handle was
* touched; MISS if unsuccessful.
*/
public int dragHandleHitPosition(MotionEvent ev) {
return viewIdHitPosition(ev, mDragHandleId);
}
public int flingHandleHitPosition(MotionEvent ev) {
return viewIdHitPosition(ev, mFlingHandleId);
}
public int viewIdHitPosition(MotionEvent ev, int id) {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers
final int numHeaders = mDslv.getHeaderViewsCount();
final int numFooters = mDslv.getFooterViewsCount();
final int count = mDslv.getCount();
// Log.d("mobeta", "touch down on position " + itemnum);
// We're only interested if the touch was on an
// item that's not a header or footer.
if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders
&& touchPos < (count - numFooters)) {
final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
final int rawX = (int) ev.getRawX();
final int rawY = (int) ev.getRawY();
View dragBox = id == 0 ? item : (View) item.findViewById(id);
if (dragBox != null) {
dragBox.getLocationOnScreen(mTempLoc);
if (rawX > mTempLoc[0] && rawY > mTempLoc[1] &&
rawX < mTempLoc[0] + dragBox.getWidth() &&
rawY < mTempLoc[1] + dragBox.getHeight()) {
mItemX = item.getLeft();
mItemY = item.getTop();
return touchPos;
}
}
}
return MISS;
}
@Override
public boolean onDown(MotionEvent ev) {
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId);
}
mHitPos = startDragPosition(ev);
if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY);
}
mIsRemoving = false;
mCanDrag = true;
mPositionX = 0;
mFlingHitPos = startFlingPosition(ev);
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if(e1 == null) return false;
if(e2 == null) return false;
final int x1 = (int) e1.getX();
final int y1 = (int) e1.getY();
final int x2 = (int) e2.getX();
final int y2 = (int) e2.getY();
final int deltaX = x2 - mItemX;
final int deltaY = y2 - mItemY;
if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) {
if (mHitPos != MISS) {
if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) {
startDrag(mHitPos, deltaX, deltaY);
}
else if (mDragInitMode != ON_DOWN && Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled)
{
mIsRemoving = true;
startDrag(mFlingHitPos, deltaX, deltaY);
}
} else if (mFlingHitPos != MISS) {
if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) {
mIsRemoving = true;
startDrag(mFlingHitPos, deltaX, deltaY);
} else if (Math.abs(y2 - y1) > mTouchSlop) {
mCanDrag = false; // if started to scroll the list then
// don't allow sorting nor fling-removing
}
}
}
// return whatever
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// Log.d("mobeta", "lift listener long pressed");
if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
}
}
// complete the OnGestureListener interface
@Override
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
// complete the OnGestureListener interface
@Override
public boolean onSingleTapUp(MotionEvent ev) {
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
if (mClickRemoveHitPos != MISS) {
mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount());
}
}
return true;
}
// complete the OnGestureListener interface
@Override
public void onShowPress(MotionEvent ev) {
// do nothing
}
private GestureDetector.OnGestureListener mFlingRemoveListener =
new GestureDetector.SimpleOnGestureListener() {
@Override
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// Log.d("mobeta", "on fling remove called");
if (mRemoveEnabled && mIsRemoving) {
int w = mDslv.getWidth();
int minPos = w / 5;
if (velocityX > mFlingSpeed) {
if (mPositionX > -minPos) {
mDslv.stopDragWithVelocity(true, velocityX);
}
} else if (velocityX < -mFlingSpeed) {
if (mPositionX < minPos) {
mDslv.stopDragWithVelocity(true, velocityX);
}
}
mIsRemoving = false;
}
return false;
}
};
}

@ -0,0 +1,241 @@
package com.mobeta.android.dslv;
import java.util.ArrayList;
import android.content.Context;
import android.database.Cursor;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import android.support.v4.widget.CursorAdapter;
/**
* A subclass of {@link android.widget.CursorAdapter} that provides
* reordering of the elements in the Cursor based on completed
* drag-sort operations. The reordering is a simple mapping of
* list positions into Cursor positions (the Cursor is unchanged).
* To persist changes made by drag-sorts, one can retrieve the
* mapping with the {@link #getCursorPositions()} method, which
* returns the reordered list of Cursor positions.
*
* An instance of this class is passed
* to {@link DragSortListView#setAdapter(ListAdapter)} and, since
* this class implements the {@link DragSortListView.DragSortListener}
* interface, it is automatically set as the DragSortListener for
* the DragSortListView instance.
*/
public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener {
public static final int REMOVED = -1;
/**
* Key is ListView position, value is Cursor position
*/
private SparseIntArray mListMapping = new SparseIntArray();
private ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>();
public DragSortCursorAdapter(Context context, Cursor c) {
super(context, c);
}
public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
}
public DragSortCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
/**
* Swaps Cursor and clears list-Cursor mapping.
*
* @see android.widget.CursorAdapter#swapCursor(android.database.Cursor)
*/
@Override
public Cursor swapCursor(Cursor newCursor) {
Cursor old = super.swapCursor(newCursor);
resetMappings();
return old;
}
/**
* Changes Cursor and clears list-Cursor mapping.
*
* @see android.widget.CursorAdapter#changeCursor(android.database.Cursor)
*/
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
resetMappings();
}
/**
* Resets list-cursor mapping.
*/
public void reset() {
resetMappings();
notifyDataSetChanged();
}
private void resetMappings() {
mListMapping.clear();
mRemovedCursorPositions.clear();
}
@Override
public Object getItem(int position) {
return super.getItem(mListMapping.get(position, position));
}
@Override
public long getItemId(int position) {
return super.getItemId(mListMapping.get(position, position));
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return super.getDropDownView(mListMapping.get(position, position), convertView, parent);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return super.getView(mListMapping.get(position, position), convertView, parent);
}
/**
* On drop, this updates the mapping between Cursor positions
* and ListView positions. The Cursor is unchanged. Retrieve
* the current mapping with {@link getCursorPositions()}.
*
* @see DragSortListView.DropListener#drop(int, int)
*/
@Override
public void drop(int from, int to) {
if (from != to) {
int cursorFrom = mListMapping.get(from, from);
if (from > to) {
for (int i = from; i > to; --i) {
mListMapping.put(i, mListMapping.get(i - 1, i - 1));
}
} else {
for (int i = from; i < to; ++i) {
mListMapping.put(i, mListMapping.get(i + 1, i + 1));
}
}
mListMapping.put(to, cursorFrom);
cleanMapping();
notifyDataSetChanged();
}
}
/**
* On remove, this updates the mapping between Cursor positions
* and ListView positions. The Cursor is unchanged. Retrieve
* the current mapping with {@link getCursorPositions()}.
*
* @see DragSortListView.RemoveListener#remove(int)
*/
@Override
public void remove(int which) {
int cursorPos = mListMapping.get(which, which);
if (!mRemovedCursorPositions.contains(cursorPos)) {
mRemovedCursorPositions.add(cursorPos);
}
int newCount = getCount();
for (int i = which; i < newCount; ++i) {
mListMapping.put(i, mListMapping.get(i + 1, i + 1));
}
mListMapping.delete(newCount);
cleanMapping();
notifyDataSetChanged();
}
/**
* Does nothing. Just completes DragSortListener interface.
*/
@Override
public void drag(int from, int to) {
// do nothing
}
/**
* Remove unnecessary mappings from sparse array.
*/
private void cleanMapping() {
ArrayList<Integer> toRemove = new ArrayList<Integer>();
int size = mListMapping.size();
for (int i = 0; i < size; ++i) {
if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) {
toRemove.add(mListMapping.keyAt(i));
}
}
size = toRemove.size();
for (int i = 0; i < size; ++i) {
mListMapping.delete(toRemove.get(i));
}
}
@Override
public int getCount() {
return super.getCount() - mRemovedCursorPositions.size();
}
/**
* Get the Cursor position mapped to by the provided list position
* (given all previously handled drag-sort
* operations).
*
* @param position List position
*
* @return The mapped-to Cursor position
*/
public int getCursorPosition(int position) {
return mListMapping.get(position, position);
}
/**
* Get the current order of Cursor positions presented by the
* list.
*/
public ArrayList<Integer> getCursorPositions() {
ArrayList<Integer> result = new ArrayList<Integer>();
for (int i = 0; i < getCount(); ++i) {
result.add(mListMapping.get(i, i));
}
return result;
}
/**
* Get the list position mapped to by the provided Cursor position.
* If the provided Cursor position has been removed by a drag-sort,
* this returns {@link #REMOVED}.
*
* @param cursorPosition A Cursor position
* @return The mapped-to list position or REMOVED
*/
public int getListPosition(int cursorPosition) {
if (mRemovedCursorPositions.contains(cursorPosition)) {
return REMOVED;
}
int index = mListMapping.indexOfValue(cursorPosition);
if (index < 0) {
return cursorPosition;
} else {
return mListMapping.keyAt(index);
}
}
}

@ -0,0 +1,100 @@
package com.mobeta.android.dslv;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.util.Log;
/**
* Lightweight ViewGroup that wraps list items obtained from user's
* ListAdapter. ItemView expects a single child that has a definite
* height (i.e. the child's layout height is not MATCH_PARENT).
* The width of
* ItemView will always match the width of its child (that is,
* the width MeasureSpec given to ItemView is passed directly
* to the child, and the ItemView measured width is set to the
* child's measured width). The height of ItemView can be anything;
* the
*
*
* The purpose of this class is to optimize slide
* shuffle animations.
*/
public class DragSortItemView extends ViewGroup {
private int mGravity = Gravity.TOP;
public DragSortItemView(Context context) {
super(context);
// always init with standard ListView layout params
setLayoutParams(new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
//setClipChildren(true);
}
public void setGravity(int gravity) {
mGravity = gravity;
}
public int getGravity() {
return mGravity;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final View child = getChildAt(0);
if (child == null) {
return;
}
if (mGravity == Gravity.TOP) {
child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight());
} else {
child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight());
}
}
/**
*
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final View child = getChildAt(0);
if (child == null) {
setMeasuredDimension(0, width);
return;
}
if (child.isLayoutRequested()) {
// Always let child be as tall as it wants.
measureChild(child, widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
ViewGroup.LayoutParams lp = getLayoutParams();
if (lp.height > 0) {
height = lp.height;
} else {
height = child.getMeasuredHeight();
}
}
setMeasuredDimension(width, height);
}
}

@ -0,0 +1,55 @@
package com.mobeta.android.dslv;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.Checkable;
import android.util.Log;
/**
* Lightweight ViewGroup that wraps list items obtained from user's
* ListAdapter. ItemView expects a single child that has a definite
* height (i.e. the child's layout height is not MATCH_PARENT).
* The width of
* ItemView will always match the width of its child (that is,
* the width MeasureSpec given to ItemView is passed directly
* to the child, and the ItemView measured width is set to the
* child's measured width). The height of ItemView can be anything;
* the
*
*
* The purpose of this class is to optimize slide
* shuffle animations.
*/
public class DragSortItemViewCheckable extends DragSortItemView implements Checkable {
public DragSortItemViewCheckable(Context context) {
super(context);
}
@Override
public boolean isChecked() {
View child = getChildAt(0);
if (child instanceof Checkable)
return ((Checkable) child).isChecked();
else
return false;
}
@Override
public void setChecked(boolean checked) {
View child = getChildAt(0);
if (child instanceof Checkable)
((Checkable) child).setChecked(checked);
}
@Override
public void toggle() {
View child = getChildAt(0);
if (child instanceof Checkable)
((Checkable) child).toggle();
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,133 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mobeta.android.dslv;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
// taken from v4 rev. 10 ResourceCursorAdapter.java
/**
* Static library support version of the framework's {@link android.widget.ResourceCursorAdapter}.
* Used to write apps that run on platforms prior to Android 3.0. When running
* on Android 3.0 or above, this implementation is still used; it does not try
* to switch to the framework's implementation. See the framework SDK
* documentation for a class overview.
*/
public abstract class ResourceDragSortCursorAdapter extends DragSortCursorAdapter {
private int mLayout;
private int mDropDownLayout;
private LayoutInflater mInflater;
/**
* Constructor the enables auto-requery.
*
* @deprecated This option is discouraged, as it results in Cursor queries
* being performed on the application's UI thread and thus can cause poor
* responsiveness or even Application Not Responding errors. As an alternative,
* use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
*/
@Deprecated
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) {
super(context, c);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor with default behavior as per
* {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
* you not use this, but instead {@link #ResourceCursorAdapter(Context, int, Cursor, int)}.
* When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
* will always be set.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
* @param c The cursor from which to get the data.
* @param autoRequery If true the adapter will call requery() on the
* cursor whenever it changes so the most recent
* data is always displayed. Using true here is discouraged.
*/
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Standard constructor.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout Resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
* @param c The cursor from which to get the data.
* @param flags Flags used to determine the behavior of the adapter,
* as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
*/
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, int flags) {
super(context, c, flags);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Inflates view(s) from the specified XML file.
*
* @see android.widget.CursorAdapter#newView(android.content.Context,
* android.database.Cursor, ViewGroup)
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mLayout, parent, false);
}
@Override
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mDropDownLayout, parent, false);
}
/**
* <p>Sets the layout resource of the item views.</p>
*
* @param layout the layout resources used to create item views
*/
public void setViewResource(int layout) {
mLayout = layout;
}
/**
* <p>Sets the layout resource of the drop down views.</p>
*
* @param dropDownLayout the layout resources used to create drop down views
*/
public void setDropDownViewResource(int dropDownLayout) {
mDropDownLayout = dropDownLayout;
}
}

@ -0,0 +1,422 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mobeta.android.dslv;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.view.View;
import android.widget.TextView;
import android.widget.ImageView;
// taken from sdk/sources/android-16/android/widget/SimpleCursorAdapter.java
/**
* An easy adapter to map columns from a cursor to TextViews or ImageViews
* defined in an XML file. You can specify which columns you want, which
* views you want to display the columns, and the XML file that defines
* the appearance of these views.
*
* Binding occurs in two phases. First, if a
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
* is invoked. If the returned value is true, binding has occured. If the
* returned value is false and the view to bind is a TextView,
* {@link #setViewText(TextView, String)} is invoked. If the returned value
* is false and the view to bind is an ImageView,
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
* binding can be found, an {@link IllegalStateException} is thrown.
*
* If this adapter is used with filtering, for instance in an
* {@link android.widget.AutoCompleteTextView}, you can use the
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
* {@link android.widget.FilterQueryProvider} interfaces
* to get control over the filtering process. You can refer to
* {@link #convertToString(android.database.Cursor)} and
* {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
*/
public class SimpleDragSortCursorAdapter extends ResourceDragSortCursorAdapter {
/**
* A list of columns containing the data to bind to the UI.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected int[] mFrom;
/**
* A list of View ids representing the views to which the data must be bound.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected int[] mTo;
private int mStringConversionColumn = -1;
private CursorToStringConverter mCursorToStringConverter;
private ViewBinder mViewBinder;
String[] mOriginalFrom;
/**
* Constructor the enables auto-requery.
*
* @deprecated This option is discouraged, as it results in Cursor queries
* being performed on the application's UI thread and thus can cause poor
* responsiveness or even Application Not Responding errors. As an alternative,
* use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*/
@Deprecated
public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c);
mTo = to;
mOriginalFrom = from;
findColumns(c, from);
}
/**
* Standard constructor.
*
* @param context The context where the ListView associated with this
* SimpleListItemFactory is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. The layout file should include at least
* those named views defined in "to"
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null
* if the cursor is not available yet.
* @param to The views that should display column in the "from" parameter.
* These should all be TextViews. The first N views in this list
* are given the values of the first N columns in the from
* parameter. Can be null if the cursor is not available yet.
* @param flags Flags used to determine the behavior of the adapter,
* as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
*/
public SimpleDragSortCursorAdapter(Context context, int layout,
Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, flags);
mTo = to;
mOriginalFrom = from;
findColumns(c, from);
}
/**
* Binds all of the field names passed into the "to" parameter of the
* constructor with their corresponding cursor columns as specified in the
* "from" parameter.
*
* Binding occurs in two phases. First, if a
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
* is invoked. If the returned value is true, binding has occured. If the
* returned value is false and the view to bind is a TextView,
* {@link #setViewText(TextView, String)} is invoked. If the returned value is
* false and the view to bind is an ImageView,
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
* binding can be found, an {@link IllegalStateException} is thrown.
*
* @throws IllegalStateException if binding cannot occur
*
* @see android.widget.CursorAdapter#bindView(android.view.View,
* android.content.Context, android.database.Cursor)
* @see #getViewBinder()
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
* @see #setViewImage(ImageView, String)
* @see #setViewText(TextView, String)
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewBinder binder = mViewBinder;
final int count = mTo.length;
final int[] from = mFrom;
final int[] to = mTo;
for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, cursor, from[i]);
}
if (!bound) {
String text = cursor.getString(from[i]);
if (text == null) {
text = "";
}
if (v instanceof TextView) {
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
setViewImage((ImageView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleCursorAdapter");
}
}
}
}
}
/**
* Returns the {@link ViewBinder} used to bind data to views.
*
* @return a ViewBinder or null if the binder does not exist
*
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
*/
public ViewBinder getViewBinder() {
return mViewBinder;
}
/**
* Sets the binder used to bind data to views.
*
* @param viewBinder the binder used to bind data to views, can be null to
* remove the existing binder
*
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see #getViewBinder()
*/
public void setViewBinder(ViewBinder viewBinder) {
mViewBinder = viewBinder;
}
/**
* Called by bindView() to set the image for an ImageView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to an ImageView.
*
* By default, the value will be treated as an image resource. If the
* value cannot be used as an image resource, the value is used as an
* image Uri.
*
* Intended to be overridden by Adapters that need to filter strings
* retrieved from the database.
*
* @param v ImageView to receive an image
* @param value the value retrieved from the cursor
*/
public void setViewImage(ImageView v, String value) {
try {
v.setImageResource(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
v.setImageURI(Uri.parse(value));
}
}
/**
* Called by bindView() to set the text for a TextView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to a TextView.
*
* Intended to be overridden by Adapters that need to filter strings
* retrieved from the database.
*
* @param v TextView to receive text
* @param text the text to be set for the TextView
*/
public void setViewText(TextView v, String text) {
v.setText(text);
}
/**
* Return the index of the column used to get a String representation
* of the Cursor.
*
* @return a valid index in the current Cursor or -1
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #setStringConversionColumn(int)
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public int getStringConversionColumn() {
return mStringConversionColumn;
}
/**
* Defines the index of the column in the Cursor used to get a String
* representation of that Cursor. The column is used to convert the
* Cursor to a String only when the current CursorToStringConverter
* is null.
*
* @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
* conversion mechanism
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #getStringConversionColumn()
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public void setStringConversionColumn(int stringConversionColumn) {
mStringConversionColumn = stringConversionColumn;
}
/**
* Returns the converter used to convert the filtering Cursor
* into a String.
*
* @return null if the converter does not exist or an instance of
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
*
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public CursorToStringConverter getCursorToStringConverter() {
return mCursorToStringConverter;
}
/**
* Sets the converter used to convert the filtering Cursor
* into a String.
*
* @param cursorToStringConverter the Cursor to String converter, or
* null to remove the converter
*
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
mCursorToStringConverter = cursorToStringConverter;
}
/**
* Returns a CharSequence representation of the specified Cursor as defined
* by the current CursorToStringConverter. If no CursorToStringConverter
* has been set, the String conversion column is used instead. If the
* conversion column is -1, the returned String is empty if the cursor
* is null or Cursor.toString().
*
* @param cursor the Cursor to convert to a CharSequence
*
* @return a non-null CharSequence representing the cursor
*/
@Override
public CharSequence convertToString(Cursor cursor) {
if (mCursorToStringConverter != null) {
return mCursorToStringConverter.convertToString(cursor);
} else if (mStringConversionColumn > -1) {
return cursor.getString(mStringConversionColumn);
}
return super.convertToString(cursor);
}
/**
* Create a map from an array of strings to an array of column-id integers in cursor c.
* If c is null, the array will be discarded.
*
* @param c the cursor to find the columns from
* @param from the Strings naming the columns of interest
*/
private void findColumns(Cursor c, String[] from) {
if (c != null) {
int i;
int count = from.length;
if (mFrom == null || mFrom.length != count) {
mFrom = new int[count];
}
for (i = 0; i < count; i++) {
mFrom[i] = c.getColumnIndexOrThrow(from[i]);
}
} else {
mFrom = null;
}
}
@Override
public Cursor swapCursor(Cursor c) {
// super.swapCursor() will notify observers before we have
// a valid mapping, make sure we have a mapping before this
// happens
findColumns(c, mOriginalFrom);
return super.swapCursor(c);
}
/**
* Change the cursor and change the column-to-view mappings at the same time.
*
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null
* if the cursor is not available yet.
* @param to The views that should display column in the "from" parameter.
* These should all be TextViews. The first N views in this list
* are given the values of the first N columns in the from
* parameter. Can be null if the cursor is not available yet.
*/
public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
mOriginalFrom = from;
mTo = to;
// super.changeCursor() will notify observers before we have
// a valid mapping, make sure we have a mapping before this
// happens
findColumns(c, mOriginalFrom);
super.changeCursor(c);
}
/**
* This class can be used by external clients of SimpleCursorAdapter
* to bind values fom the Cursor to views.
*
* You should use this class to bind values from the Cursor to views
* that are not directly supported by SimpleCursorAdapter or to
* change the way binding occurs for views supported by
* SimpleCursorAdapter.
*
* @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see SimpleCursorAdapter#setViewImage(ImageView, String)
* @see SimpleCursorAdapter#setViewText(TextView, String)
*/
public static interface ViewBinder {
/**
* Binds the Cursor column defined by the specified index to the specified view.
*
* When binding is handled by this ViewBinder, this method must return true.
* If this method returns false, SimpleCursorAdapter will attempts to handle
* the binding on its own.
*
* @param view the view to bind the data to
* @param cursor the cursor to get the data from
* @param columnIndex the column at which the data can be found in the cursor
*
* @return true if the data was bound to the view, false otherwise
*/
boolean setViewValue(View view, Cursor cursor, int columnIndex);
}
/**
* This class can be used by external clients of SimpleCursorAdapter
* to define how the Cursor should be converted to a String.
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public static interface CursorToStringConverter {
/**
* Returns a CharSequence representing the specified Cursor.
*
* @param cursor the cursor for which a CharSequence representation
* is requested
*
* @return a non-null CharSequence representing the cursor
*/
CharSequence convertToString(Cursor cursor);
}
}

@ -0,0 +1,89 @@
package com.mobeta.android.dslv;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Color;
import android.widget.ListView;
import android.widget.ImageView;
import android.view.View;
import android.view.ViewGroup;
import android.util.Log;
/**
* Simple implementation of the FloatViewManager class. Uses list
* items as they appear in the ListView to create the floating View.
*/
public class SimpleFloatViewManager implements DragSortListView.FloatViewManager {
private Bitmap mFloatBitmap;
private ImageView mImageView;
private int mFloatBGColor = Color.BLACK;
private ListView mListView;
public SimpleFloatViewManager(ListView lv) {
mListView = lv;
}
public void setBackgroundColor(int color) {
mFloatBGColor = color;
}
/**
* This simple implementation creates a Bitmap copy of the
* list item currently shown at ListView <code>position</code>.
*/
@Override
public View onCreateFloatView(int position) {
// Guaranteed that this will not be null? I think so. Nope, got
// a NullPointerException once...
View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition());
if (v == null) {
return null;
}
v.setPressed(false);
// Create a copy of the drawing cache so that it does not get
// recycled by the framework when the list tries to clean up memory
//v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
v.setDrawingCacheEnabled(true);
mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache());
v.setDrawingCacheEnabled(false);
if (mImageView == null) {
mImageView = new ImageView(mListView.getContext());
}
mImageView.setBackgroundColor(mFloatBGColor);
mImageView.setPadding(0, 0, 0, 0);
mImageView.setImageBitmap(mFloatBitmap);
mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight()));
return mImageView;
}
/**
* This does nothing
*/
@Override
public void onDragFloatView(View floatView, Point position, Point touch) {
// do nothing
}
/**
* Removes the Bitmap from the ImageView created in
* onCreateFloatView() and tells the system to recycle it.
*/
@Override
public void onDestroyFloatView(View floatView) {
((ImageView) floatView).setImageDrawable(null);
mFloatBitmap.recycle();
mFloatBitmap = null;
}
}

@ -0,0 +1,15 @@
package org.isoron.helpers;
public abstract class Command {
public abstract void execute();
public abstract void undo();
public Integer getExecuteStringId() {
return null;
}
public Integer getUndoStringId() {
return null;
}
}

@ -0,0 +1,96 @@
package org.isoron.helpers;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class DateHelper
{
public static int millisecondsInOneDay = 24 * 60 * 60 * 1000;
public static long getLocalTime()
{
TimeZone tz = TimeZone.getDefault();
long now = new Date().getTime();
return now + tz.getOffset(now);
}
public static long getStartOfDay(long timestamp)
{
return (timestamp / millisecondsInOneDay) * millisecondsInOneDay;
}
// public static Date getStartOfDay(Date date)
// {
// Calendar calendar = Calendar.getInstance();
// calendar.setTime(date);
// calendar.set(Calendar.HOUR_OF_DAY, 0);
// calendar.set(Calendar.MINUTE, 0);
// calendar.set(Calendar.SECOND, 0);
// calendar.set(Calendar.MILLISECOND, 0);
// return calendar.getTime();
// }
public static int differenceInDays(Date from, Date to)
{
long milliseconds = getStartOfDay(to.getTime()) - getStartOfDay(from.getTime());
int days = (int) (milliseconds / millisecondsInOneDay);
return days;
}
public static String differenceInWords(Date from, Date to)
{
Integer days = differenceInDays(from, to);
boolean negative = (days < 0);
days = Math.abs(days);
Integer weeks = (int) Math.round(days / 7.0);
Double months = days / 30.4;
Double years = days / 365.0;
StringBuffer s = new StringBuffer();
DecimalFormat df = new DecimalFormat("#.#");
if(months > 18)
{
s.append(df.format(years));
s.append(" years");
}
else if(weeks > 6)
{
s.append(df.format(months));
s.append(" months");
}
else if(days > 13)
{
s.append(weeks);
s.append(" weeks");
}
else if(days > 6)
{
s.append(days);
s.append(" days");
}
else
{
if(days == 0)
s.append("Today");
else if(days == 1 && negative)
s.append("Yesterday");
else if(days == 1 && !negative)
s.append("Tomorrow");
else
{
if(negative)
s.append("past ");
s.append(new SimpleDateFormat("EEEE").format(to));
}
}
if(negative && days > 6)
s.append(" ago");
return s.toString();
}
}

@ -0,0 +1,41 @@
package org.isoron.helpers;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
public abstract class DialogHelper
{
// public static AlertDialog alert(Activity context, String title, String message, OnClickListener positiveClickListener) {
// return new AlertDialog.Builder(context)
// .setTitle(title)
// .setMessage(message)
// .setPositiveButton(android.R.string.yes, positiveClickListener)
// .setNegativeButton(android.R.string.no, null).show();
// }
public static abstract class SimpleClickListener implements OnClickListener
{
public abstract void onClick();
public void onClick(DialogInterface dialog, int whichButton)
{
onClick();
}
}
public static interface OnSavedListener
{
public void onSaved(Command command);
}
public static void showSoftKeyboard(View view)
{
InputMethodManager imm = (InputMethodManager)
view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
}

@ -0,0 +1,129 @@
package org.isoron.uhabits;
import java.util.LinkedList;
import org.isoron.helpers.Command;
import org.isoron.uhabits.dialogs.ShowHabitsFragment;
import org.isoron.uhabits.models.Habit;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
public class MainActivity extends Activity
{
private ShowHabitsFragment showHabitsFragment;
private LinkedList<Command> undoList;
private LinkedList<Command> redoList;
private static int MAX_UNDO_LEVEL = 15;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Creation *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Habit.rebuildOrder();
// Habit.roundTimestamps();
setContentView(R.layout.main_activity);
showHabitsFragment = (ShowHabitsFragment) getFragmentManager().findFragmentById(
R.id.fragment1);
undoList = new LinkedList<Command>();
redoList = new LinkedList<Command>();
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Action Bar Menu *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.main_activity, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if(id == R.id.action_settings)
{
return true;
}
return super.onOptionsItemSelected(item);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Commands, Undo, Redo *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public void executeCommand(Command command, boolean datasetChanged)
{
executeCommand(command, datasetChanged, true);
}
public void executeCommand(Command command, boolean datasetChanged, boolean clearRedoStack)
{
undoList.push(command);
if(undoList.size() > MAX_UNDO_LEVEL)
undoList.removeLast();
if(clearRedoStack)
redoList.clear();
command.execute();
showToast(command.getExecuteStringId());
if(datasetChanged)
{
showHabitsFragment.notifyDataSetChanged();
}
}
public void undo()
{
if(undoList.isEmpty())
{
showToast(R.string.toast_nothing_to_undo);
return;
}
Command last = undoList.pop();
redoList.push(last);
last.undo();
showToast(last.getUndoStringId());
showHabitsFragment.notifyDataSetChanged();
}
public void redo()
{
if(redoList.isEmpty())
{
showToast(R.string.toast_nothing_to_redo);
return;
}
Command last = redoList.pop();
executeCommand(last, true, false);
}
private Toast toast;
private void showToast(Integer stringId)
{
if(stringId == null)
return;
if(toast == null)
toast = Toast.makeText(this, stringId, Toast.LENGTH_SHORT);
else
toast.setText(stringId);
toast.show();
}
}

@ -0,0 +1,209 @@
package org.isoron.uhabits.dialogs;
import org.isoron.helpers.Command;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import android.app.DialogFragment;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import com.android.colorpicker.ColorPickerDialog;
import com.android.colorpicker.ColorPickerSwatch;
public class EditHabitFragment extends DialogFragment implements OnClickListener
{
private int mode;
static final int EDIT_MODE = 0;
static final int CREATE_MODE = 1;
private OnSavedListener onSavedListener;
private Habit originalHabit, modifiedHabit;
private TextView tvName, tvDescription, tvFreqNum, tvFreqDen;
static class SolidColorMatrix extends ColorMatrix
{
public SolidColorMatrix(int color)
{
float matrix[] = { 0.0f, 0.0f, 0.0f, 0.0f, Color.red(color), 0.0f, 0.0f, 0.0f, 0.0f,
Color.green(color), 0.0f, 0.0f, 0.0f, 0.0f, Color.blue(color), 0.0f, 0.0f,
0.0f, 1.0f, 0 };
set(matrix);
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Factory *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static EditHabitFragment editSingleHabitFragment(long id)
{
EditHabitFragment frag = new EditHabitFragment();
Bundle args = new Bundle();
args.putLong("habitId", id);
args.putInt("editMode", EDIT_MODE);
frag.setArguments(args);
return frag;
}
static EditHabitFragment createHabitFragment()
{
EditHabitFragment frag = new EditHabitFragment();
Bundle args = new Bundle();
args.putInt("editMode", CREATE_MODE);
frag.setArguments(args);
return frag;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Creation *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.edit_habit, container, false);
tvName = (TextView) view.findViewById(R.id.input_name);
tvDescription = (TextView) view.findViewById(R.id.input_description);
tvFreqNum = (TextView) view.findViewById(R.id.input_freq_num);
tvFreqDen = (TextView) view.findViewById(R.id.input_freq_den);
Button buttonSave = (Button) view.findViewById(R.id.button_save);
Button buttonDiscard = (Button) view.findViewById(R.id.button_discard);
buttonSave.setOnClickListener(this);
buttonDiscard.setOnClickListener(this);
ImageButton buttonPickColor = (ImageButton) view.findViewById(R.id.button_pick_color);
Bundle args = getArguments();
mode = (Integer) args.get("editMode");
if(mode == CREATE_MODE)
{
getDialog().setTitle("Create habit");
modifiedHabit = new Habit();
}
else if(mode == EDIT_MODE)
{
originalHabit = Habit.get((Long) args.get("habitId"));
modifiedHabit = new Habit(originalHabit);
getDialog().setTitle("Edit habit");
tvName.append(modifiedHabit.name);
tvDescription.append(modifiedHabit.description);
tvFreqNum.setText(null);
tvFreqDen.setText(null);
tvFreqNum.append(modifiedHabit.freq_num.toString());
tvFreqDen.append(modifiedHabit.freq_den.toString());
}
changeColor(modifiedHabit.color);
buttonPickColor.setOnClickListener(new OnClickListener()
{
public void onClick(View view)
{
ColorPickerDialog picker = ColorPickerDialog.newInstance(
R.string.color_picker_default_title,
Habit.colors, modifiedHabit.color, 4, ColorPickerDialog.SIZE_SMALL);
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
{
public void onColorSelected(int color)
{
modifiedHabit.color = color;
changeColor(color);
}
});
picker.show(getFragmentManager(), "picker");
}
});
return view;
}
private void changeColor(Integer color)
{
SolidColorMatrix matrix = new SolidColorMatrix(color);
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
Drawable background = getActivity().getResources().getDrawable(
R.drawable.apptheme_edit_text_holo_light);
background.setColorFilter(filter);
tvName.setBackgroundDrawable(background);
tvName.setTextColor(color);
}
public void setOnSavedListener(OnSavedListener onSavedListener)
{
this.onSavedListener = onSavedListener;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Callback *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
public void onClick(View v)
{
int id = v.getId();
/* Save button */
if(id == R.id.button_save)
{
Command command = null;
modifiedHabit.name = tvName.getText().toString().trim();
modifiedHabit.description = tvDescription.getText().toString().trim();
modifiedHabit.freq_num = Integer.parseInt(tvFreqNum.getText().toString());
modifiedHabit.freq_den = Integer.parseInt(tvFreqDen.getText().toString());
Boolean valid = true;
if(modifiedHabit.name.length() == 0)
{
tvName.setError("Name cannot be blank.");
valid = false;
}
if(modifiedHabit.freq_num <= 0)
{
tvFreqNum.setError("Frequency has to be positive.");
valid = false;
}
if(!valid)
return;
if(mode == EDIT_MODE)
command = originalHabit.new EditCommand(modifiedHabit);
if(mode == CREATE_MODE)
command = new Habit.CreateCommand(modifiedHabit);
if(onSavedListener != null)
onSavedListener.onSaved(command);
dismiss();
}
/* Discard button */
if(id == R.id.button_discard)
{
dismiss();
}
}
}

@ -0,0 +1,357 @@
package org.isoron.uhabits.dialogs;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.uhabits.MainActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import android.app.Fragment;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Vibrator;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView;
import com.mobeta.android.dslv.DragSortListView.DropListener;
public class ShowHabitsFragment extends Fragment implements OnSavedListener, OnItemClickListener,
OnLongClickListener, DropListener
{
private int tvNameWidth;
private int button_count;
ShowHabitsAdapter adapter;
DragSortListView listView;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Adapter *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
class ShowHabitsAdapter extends BaseAdapter
{
private Context context;
private LayoutInflater inflater;
private Typeface fontawesome;
String habits[] = { "wake up early", "work out", "meditate", "take vitamins",
"go to school",
"cook dinner & lunch" };
public ShowHabitsAdapter(Context context)
{
this.context = context;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
}
@Override
public int getCount()
{
return Habit.getCount() + 1;
}
@Override
public Object getItem(int position)
{
if(position == 0)
return null;
return Habit.getByPosition(position - 1);
}
@Override
public long getItemId(int position)
{
if(position == 0)
return 0;
return ((Habit) getItem(position)).getId();
}
@Override
public View getView(int position, View view, ViewGroup parent)
{
final Habit habit = (Habit) getItem(position);
if(view == null)
{
view = inflater.inflate(R.layout.show_habits_item, null);
((TextView) view.findViewById(R.id.tvStar)).setTypeface(fontawesome);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(tvNameWidth,
LayoutParams.WRAP_CONTENT, 1);
((TextView) view.findViewById(R.id.tvName)).setLayoutParams(params);
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
for (int i = 0; i < button_count; i++)
{
View check = inflater.inflate(R.layout.show_habits_item_check, null);
Button btCheck = (Button) check.findViewById(R.id.tvCheck);
btCheck.setTypeface(fontawesome);
btCheck.setOnLongClickListener(ShowHabitsFragment.this);
((LinearLayout) view.findViewById(R.id.llButtons)).addView(check);
}
}
TextView tvStar = (TextView) view.findViewById(R.id.tvStar);
TextView tvName = (TextView) view.findViewById(R.id.tvName);
if(habit == null)
{
tvName.setText(null);
return view;
}
int inactiveColor = Color.rgb(230, 230, 230);
int activeColor = habit.color;
tvName.setText(habit.name);
tvName.setTextColor(activeColor);
int score = habit.getScore();
if(score < 5999000)
{
tvStar.setText(context.getString(R.string.fa_star_o));
tvStar.setTextColor(Color.LTGRAY);
}
else if(score < 12973000)
{
tvStar.setText(context.getString(R.string.fa_star_half_o));
tvStar.setTextColor(Color.LTGRAY);
}
else
{
tvStar.setText(context.getString(R.string.fa_star));
tvStar.setTextColor(activeColor);
}
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
int m = llButtons.getChildCount();
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long dateFrom = dateTo - m * DateHelper.millisecondsInOneDay;
int isChecked[] = habit.getReps(dateFrom, dateTo);
for (int i = 0; i < m; i++)
{
Button tvCheck = (Button) llButtons.getChildAt(i);
tvCheck.setTag(R.string.habit_key, habit.getId());
tvCheck.setTag(R.string.offset_key, i);
switch(isChecked[i])
{
case 2:
tvCheck.setText(R.string.fa_check);
tvCheck.setTextColor(activeColor);
break;
case 1:
tvCheck.setText(R.string.fa_check);
tvCheck.setTextColor(inactiveColor);
break;
case 0:
tvCheck.setText(R.string.fa_times);
tvCheck.setTextColor(inactiveColor);
break;
}
}
return view;
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Creation *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.show_habits, container, false);
DisplayMetrics dm = getResources().getDisplayMetrics();
int width = (int) (dm.widthPixels / dm.density);
button_count = (int) ((width - 160) / 42);
tvNameWidth = (int) ((width - 30 - button_count * 42) * dm.density);
adapter = new ShowHabitsAdapter(getActivity());
listView = (DragSortListView) view.findViewById(R.id.listView);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
registerForContextMenu(listView);
listView.setDropListener(this);
DragSortController controller = new DragSortController(listView);
controller.setDragHandleId(R.id.tvStar);
controller.setRemoveEnabled(false);
controller.setSortEnabled(true);
controller.setDragInitMode(1);
listView.setFloatViewManager(controller);
listView.setOnTouchListener(controller);
listView.setDragEnabled(true);
GregorianCalendar day = new GregorianCalendar();
day.setTimeInMillis(DateHelper.getLocalTime());
for (int i = 0; i < button_count; i++)
{
View check = inflater.inflate(R.layout.show_habits_header_check, null);
Button btCheck = (Button) check.findViewById(R.id.tvCheck);
btCheck.setText(day.getDisplayName(GregorianCalendar.DAY_OF_WEEK,
GregorianCalendar.SHORT, Locale.US) + "\n"
+ Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH)));
((LinearLayout) view.findViewById(R.id.llButtonsHeader)).addView(check);
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
}
setHasOptionsMenu(true);
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
inflater.inflate(R.menu.show_habits_options, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
getActivity().getMenuInflater().inflate(R.menu.show_habits_context, menu);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Callback *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if(id == R.id.action_add)
{
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "dialog");
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onContextItemSelected(MenuItem menuItem)
{
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuItem.getMenuInfo();
final int id = menuItem.getItemId();
final Habit habit = Habit.get(info.id);
if(id == R.id.action_edit_habit)
{
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(habit.getId());
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "dialog");
return true;
}
return super.onContextItemSelected(menuItem);
}
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3)
{
}
@Override
public void onSaved(Command command)
{
executeCommand(command);
}
public void notifyDataSetChanged()
{
adapter.notifyDataSetChanged();
}
@Override
public boolean onLongClick(View v)
{
int id = v.getId();
if(id == R.id.tvCheck)
{
Habit habit = Habit.get((Long) v.getTag(R.string.habit_key));
int offset = (Integer) v.getTag(R.string.offset_key);
long timestamp = DateHelper.getStartOfDay(DateHelper.getLocalTime() - offset
* DateHelper.millisecondsInOneDay);
executeCommand(habit.new ToggleRepetitionCommand(timestamp));
Vibrator vb = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
vb.vibrate(100);
adapter.notifyDataSetChanged();
return true;
}
return false;
}
private void executeCommand(Command c)
{
((MainActivity) getActivity()).executeCommand(c, false);
adapter.notifyDataSetChanged();
}
@Override
public void drop(int from, int to)
{
Habit.reorder(from - 1, to - 1);
adapter.notifyDataSetChanged();
}
}

@ -0,0 +1,427 @@
package org.isoron.uhabits.models;
import java.util.Date;
import java.util.List;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import android.annotation.SuppressLint;
import android.graphics.Color;
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
import com.activeandroid.query.Delete;
import com.activeandroid.query.From;
import com.activeandroid.query.Select;
import com.activeandroid.query.Update;
import com.activeandroid.util.SQLiteUtils;
@Table(name = "Habits")
public class Habit extends Model
{
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Fields *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public static final int colors[] = { Color.parseColor("#900000"),
Color.parseColor("#c54100"), Color.parseColor("#c0ab00"),
Color.parseColor("#8db600"), Color.parseColor("#117209"),
Color.parseColor("#06965b"), Color.parseColor("#069a95"),
Color.parseColor("#114896"), Color.parseColor("#501394"),
Color.parseColor("#872086"), Color.parseColor("#c31764"),
Color.parseColor("#000000"), Color.parseColor("#aaaaaa") };
@Column(name = "name")
public String name;
@Column(name = "description")
public String description;
@Column(name = "freq_num")
public Integer freq_num;
@Column(name = "freq_den")
public Integer freq_den;
@Column(name = "color")
public Integer color;
@Column(name = "position")
public Integer position;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Commands *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public static class CreateCommand extends Command
{
private Habit model;
private Long savedId;
public CreateCommand(Habit model)
{
this.model = model;
}
@Override
public void execute()
{
Habit savedHabit = new Habit(model);
if(savedId == null)
{
savedHabit.save();
savedId = savedHabit.getId();
}
else
{
savedHabit.save(savedId);
}
}
@Override
public void undo()
{
Habit.get(savedId).delete();
}
@Override
public Integer getExecuteStringId()
{
return R.string.toast_habit_created;
}
@Override
public Integer getUndoStringId()
{
return R.string.toast_habit_deleted;
}
}
public class EditCommand extends Command
{
private Habit original;
private Habit modified;
private long savedId;
private boolean hasIntervalChanged;
public EditCommand(Habit modified)
{
this.savedId = getId();
this.modified = new Habit(modified);
this.original = new Habit(Habit.this);
hasIntervalChanged = (this.original.freq_den != this.modified.freq_den
|| this.original.freq_num != this.modified.freq_num);
}
public void execute()
{
Habit habit = Habit.get(savedId);
habit.copyAttributes(modified);
habit.save();
if(hasIntervalChanged)
habit.deleteScoresNewerThan(0);
}
public void undo()
{
Habit habit = Habit.get(savedId);
habit.copyAttributes(original);
habit.save();
if(hasIntervalChanged)
habit.deleteScoresNewerThan(0);
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_changed;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_changed_back;
}
}
public class ToggleRepetitionCommand extends Command
{
private Long offset;
public ToggleRepetitionCommand(long offset)
{
this.offset = offset;
}
@Override
public void execute()
{
toggleRepetition(offset);
}
@Override
public void undo()
{
execute();
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Accessors *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public Habit(Habit model)
{
copyAttributes(model);
}
public void copyAttributes(Habit model)
{
this.name = model.name;
this.description = model.description;
this.freq_num = model.freq_num;
this.freq_den = model.freq_den;
this.color = model.color;
this.position = model.position;
}
public Habit()
{
this.color = colors[11];
this.position = Habit.getCount();
}
public static Habit get(Long id)
{
return Habit.load(Habit.class, id);
}
public void save(Long id)
{
save();
Habit.updateId(getId(), id);
}
@SuppressLint("DefaultLocale")
public static void updateId(long oldId, long newId)
{
SQLiteUtils.execSql(String.format(
"update Habits set Id = %d where Id = %d", newId, oldId));
}
protected static From select()
{
return new Select().from(Habit.class).orderBy("position");
}
public static int getCount()
{
return select().count();
}
public static Habit getByPosition(int position)
{
return select().offset(position).executeSingle();
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Repetitions *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
protected From selectReps()
{
return new Select().from(Repetition.class).where("habit = ?", getId())
.orderBy("timestamp");
}
protected From selectRepsFromTo(long timeFrom, long timeTo)
{
return selectReps().and("timestamp >= ?", timeFrom).and(
"timestamp <= ?", timeTo);
}
public boolean hasRep(long timestamp)
{
int count = selectReps().where("timestamp = ?", timestamp).count();
return (count > 0);
}
public void deleteReps(long timestamp)
{
new Delete().from(Repetition.class).where("habit = ?", getId())
.and("timestamp = ?", timestamp).execute();
}
public int[] getReps(long timeFrom, long timeTo)
{
long timeFromExtended = timeFrom - freq_den * DateHelper.millisecondsInOneDay;
List<Repetition> reps = selectRepsFromTo(timeFromExtended, timeTo).execute();
int nDaysExtended = (int) ((timeTo - timeFromExtended) / DateHelper.millisecondsInOneDay);
int checkExtended[] = new int[nDaysExtended + 1];
int nDays = (int) ((timeTo - timeFrom) / DateHelper.millisecondsInOneDay);
// mark explicit checks
for (Repetition rep : reps)
{
int offset = (int) ((rep.timestamp - timeFrom) / DateHelper.millisecondsInOneDay);
checkExtended[nDays - offset] = 2;
}
// marks implicit checks
for(int i=0; i<nDays; i++)
{
int counter = 0;
for(int j=0; j<freq_den; j++)
if(checkExtended[i+j] == 2) counter++;
if(counter >= freq_num)
checkExtended[i] = Math.max(checkExtended[i], 1);
}
int check[] = new int[nDays + 1];
for(int i=0; i<nDays+1; i++)
check[i] = checkExtended[i];
return check;
}
public Repetition getOldestRep()
{
return (Repetition) selectReps().limit(1).executeSingle();
}
public void toggleRepetition(long timestamp)
{
if(hasRep(timestamp))
{
deleteReps(timestamp);
}
else
{
Repetition rep = new Repetition();
rep.habit = this;
rep.timestamp = timestamp;
rep.save();
}
deleteScoresNewerThan(timestamp);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Scoring *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public Score getNewestScore()
{
return new Select().from(Score.class).where("habit = ?", getId())
.orderBy("timestamp desc").limit(1).executeSingle();
}
public void deleteScoresNewerThan(long timestamp)
{
new Delete().from(Score.class).where("habit = ?", getId())
.and("timestamp >= ?", timestamp).execute();
}
public Integer getScore()
{
int beginningScore;
long beginningTime;
long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long day = DateHelper.millisecondsInOneDay;
double freq = ((double) freq_num) / freq_den;
double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
Score newestScore = getNewestScore();
if(newestScore == null)
{
Repetition oldestRep = getOldestRep();
if(oldestRep == null)
return 0;
beginningTime = oldestRep.timestamp;
beginningScore = 0;
}
else
{
beginningTime = newestScore.timestamp + day;
beginningScore = newestScore.score;
}
long nDays = (today - beginningTime) / day;
if(nDays < 0)
return newestScore.score;
int reps[] = getReps(beginningTime, today);
int lastScore = beginningScore;
for (int i = 0; i < reps.length; i++)
{
Score s = new Score();
s.habit = this;
s.timestamp = beginningTime + day * i;
s.score = (int) (lastScore * multiplier);
if(reps[reps.length-i-1] == 2) {
s.score += 1000000;
s.score = Math.min(s.score, 19259500);
}
s.save();
lastScore = s.score;
}
return lastScore;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Ordering *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public static void reorder(int from, int to)
{
if(from == to)
return;
Habit h = Habit.getByPosition(from);
if(to < from)
new Update(Habit.class).set("position = position + 1")
.where("position >= ? and position < ?", to, from)
.execute();
else
new Update(Habit.class).set("position = position - 1")
.where("position > ? and position <= ?", from, to)
.execute();
h.position = to;
h.save();
}
public static void rebuildOrder()
{
List<Habit> habits = select().execute();
int i = 0;
for (Habit h : habits)
{
h.position = i++;
h.save();
}
}
public static void roundTimestamps()
{
List<Repetition> reps = new Select().from(Repetition.class).execute();
for (Repetition r : reps)
{
r.timestamp = DateHelper.getStartOfDay(r.timestamp);
r.save();
}
}
}

@ -0,0 +1,15 @@
package org.isoron.uhabits.models;
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
@Table(name = "Repetitions")
public class Repetition extends Model {
@Column(name = "habit")
public Habit habit;
@Column(name = "timestamp")
public Long timestamp;
}

@ -0,0 +1,18 @@
package org.isoron.uhabits.models;
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
@Table(name = "Score")
public class Score extends Model
{
@Column(name = "habit")
public Habit habit;
@Column(name = "timestamp")
public Long timestamp;
@Column(name = "score")
public Integer score;
}
Loading…
Cancel
Save