motion-tickspot 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +114 -0
- data/lib/motion-tickspot.rb +16 -0
- data/lib/tick/base.rb +152 -0
- data/lib/tick/client.rb +8 -0
- data/lib/tick/entry.rb +48 -0
- data/lib/tick/project.rb +70 -0
- data/lib/tick/session.rb +111 -0
- data/lib/tick/task.rb +11 -0
- data/lib/tick/timer.rb +135 -0
- data/lib/tick/version.rb +3 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7c1750234a64316c38c16789b8eb82c7eda4f1c3
|
4
|
+
data.tar.gz: c66fb44d97c5b63883542ff6de3128684c748fa4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff3586ed69f69201d794b7caafc66fbef0b4fa0e6a9df04f29096e05002f514cf0278d0c931e792d25edfa095dbaf6a59e2ca86e44165f8834d1a1bd74bb358e
|
7
|
+
data.tar.gz: bece59d96ac4aae2410c8a8124184422c3eecb10350597cfbbd756b383ee352d4e5fe12955b147bfa24d2cce109e81f8bb00a76ee6cd20526c9bb52d767bb54c
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# Motion-Tickspot
|
2
|
+
|
3
|
+
[](https://travis-ci.org/81designs/motion-tickspot)
|
4
|
+
[](https://codeclimate.com/github/81designs/motion-tickspot)
|
5
|
+
|
6
|
+
A [RubyMotion](http://www.rubymotion.com) wrapper for the
|
7
|
+
[Tick](http://www.tickspot.com)'s [API](http://www.tickspot.com/api)
|
8
|
+
that works on iOS and OS X.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem "motion-tickspot"
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
$ bundle
|
22
|
+
```
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
$ gem install motion-tickspot
|
28
|
+
```
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
### Authentication
|
33
|
+
|
34
|
+
Authentication is handled by `Tick::Session` with some convenience methods.
|
35
|
+
|
36
|
+
[SSKeychain](https://github.com/soffes/sskeychain) is being used to
|
37
|
+
save the user's password to the iOS or OS X keychain.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
Tick.logged_in? # true/false
|
41
|
+
|
42
|
+
Tick.log_in("company/subdomain", "email@example.com", "password") do |session|
|
43
|
+
if session
|
44
|
+
# Success!
|
45
|
+
else
|
46
|
+
# Failed to log in
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Tick.log_out
|
51
|
+
```
|
52
|
+
|
53
|
+
### Tick::Client
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
Tick::Client.list do |clients|
|
57
|
+
# Returns array of Tick::Clients
|
58
|
+
# Raises Tick::AuthenticationError if not logged in
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
### Tick::Entry
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
Tick::Entry.create({
|
66
|
+
task_id: 1,
|
67
|
+
hours: 2.5,
|
68
|
+
date: "2008-03-17",
|
69
|
+
notes: "She can't take much more of this Captain"
|
70
|
+
}) do |entry|
|
71
|
+
# Returns the saved Tick::Entry
|
72
|
+
# Raises Tick::AuthenticationError if not logged in
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Tick::Entry.list do |entries|
|
78
|
+
# Returns array of Tick::Entries
|
79
|
+
# Raises Tick::AuthenticationError if not logged in
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
### Tick::Project
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
Tick::Project.list do |projects|
|
87
|
+
# Returns array of Tick::Projects
|
88
|
+
# Raises Tick::AuthenticationError if not logged in
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
### Tick::Session
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
Tick::Session.current
|
96
|
+
# Returns Tick::Session instance or nil
|
97
|
+
```
|
98
|
+
|
99
|
+
### Tick::Task
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
Tick::Task.list do |tasks|
|
103
|
+
# Returns array of Tick::Tasks
|
104
|
+
# Raises Tick::AuthenticationError if not logged in
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
1. Fork it
|
111
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
112
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
113
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
114
|
+
5. Create new Pull Request
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "motion-cocoapods"
|
2
|
+
|
3
|
+
unless defined?(Motion::Project::Config)
|
4
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
5
|
+
end
|
6
|
+
|
7
|
+
lib_dir_path = File.dirname(File.expand_path(__FILE__))
|
8
|
+
Motion::Project::App.setup do |app|
|
9
|
+
app.files.unshift(Dir.glob(File.join(lib_dir_path, "tick/**/*.rb")))
|
10
|
+
|
11
|
+
app.pods do
|
12
|
+
pod "AFNetworking", "~> 2.0.1"
|
13
|
+
pod "GDataXML-HTML", "~> 1.1.0"
|
14
|
+
pod "SSKeychain", "~> 1.2.1"
|
15
|
+
end
|
16
|
+
end
|
data/lib/tick/base.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
module Tick
|
2
|
+
|
3
|
+
DATE_FORMAT = "yyyy-MM-dd"
|
4
|
+
DATETIME_FORMAT = "EE, dd MMM yyyy HH:mm:ss ZZZ"
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def log_in(company, email, password, &block)
|
9
|
+
params = {
|
10
|
+
company: company,
|
11
|
+
email: email,
|
12
|
+
password: password
|
13
|
+
}
|
14
|
+
Session.create(params) do |session|
|
15
|
+
block.call(session) if block
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias_method :login, :log_in
|
19
|
+
|
20
|
+
def log_out
|
21
|
+
Session.current.destroy if Session.current
|
22
|
+
end
|
23
|
+
alias_method :logout, :log_out
|
24
|
+
|
25
|
+
def logged_in?
|
26
|
+
Session.logged_in?
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class Base
|
32
|
+
attr_accessor :id, :created_at, :updated_at
|
33
|
+
attr_reader :api_name, :api_path
|
34
|
+
|
35
|
+
def set_properties_from_xml_node(xml_node)
|
36
|
+
self.class::XML_PROPERTIES.each do |property|
|
37
|
+
xml_elements = xml_node.elementsForName(property)
|
38
|
+
value = xml_elements ? get_xml_element_value(xml_elements.first) : nil
|
39
|
+
self.send("#{property}=", value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.api_name
|
44
|
+
self.to_s.split('::').last.downcase
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.api_path
|
48
|
+
"/api/#{api_name}s"
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.list(options={}, &block)
|
52
|
+
url = "https://#{current_session.company}.tickspot.com#{api_path}"
|
53
|
+
|
54
|
+
params = authentication_params.merge!(options)
|
55
|
+
|
56
|
+
request_manager.GET(url, parameters:params, success:lambda{|operation, result|
|
57
|
+
objects = []
|
58
|
+
|
59
|
+
# Parse XML
|
60
|
+
error = Pointer.new(:object)
|
61
|
+
xml = GDataXMLDocument.alloc.initWithXMLString(result.to_s, error:error)
|
62
|
+
|
63
|
+
# Create the objects
|
64
|
+
error = Pointer.new(:object)
|
65
|
+
xml_nodes = xml.nodesForXPath("//#{api_name}", error:error)
|
66
|
+
|
67
|
+
xml_nodes.each do |xml_node|
|
68
|
+
object = new
|
69
|
+
object.set_properties_from_xml_node(xml_node)
|
70
|
+
objects << object
|
71
|
+
end
|
72
|
+
|
73
|
+
block.call(objects) if block
|
74
|
+
}, failure:lambda{|operation, error|
|
75
|
+
block.call(error) if block
|
76
|
+
})
|
77
|
+
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def get_xml_element_value(xml_element)
|
84
|
+
type = xml_element.attributeForName("type")
|
85
|
+
type = type.stringValue if type
|
86
|
+
case type
|
87
|
+
when "boolean"
|
88
|
+
xml_element.stringValue.boolValue
|
89
|
+
when "date"
|
90
|
+
date_from_string(xml_element.stringValue)
|
91
|
+
when "datetime"
|
92
|
+
datetime_from_string(xml_element.stringValue)
|
93
|
+
when "float"
|
94
|
+
xml_element.stringValue.floatValue
|
95
|
+
when "integer"
|
96
|
+
xml_element.stringValue.intValue
|
97
|
+
else
|
98
|
+
value = xml_element.stringValue
|
99
|
+
if value == "true"
|
100
|
+
true
|
101
|
+
elsif value == "false"
|
102
|
+
false
|
103
|
+
else
|
104
|
+
value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def date_from_string(string)
|
110
|
+
dateFormatter = NSDateFormatter.new
|
111
|
+
dateFormatter.setDateFormat(DATE_FORMAT)
|
112
|
+
dateFormatter.dateFromString(string)
|
113
|
+
end
|
114
|
+
|
115
|
+
def datetime_from_string(string)
|
116
|
+
dateFormatter = NSDateFormatter.new
|
117
|
+
dateFormatter.setDateFormat(DATETIME_FORMAT)
|
118
|
+
dateFormatter.dateFromString(string)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.authentication_params
|
122
|
+
{
|
123
|
+
email: current_session.email,
|
124
|
+
password: current_session.password
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.current_session
|
129
|
+
if Session.current
|
130
|
+
Session.current
|
131
|
+
else
|
132
|
+
raise AuthenticationError.new("User is not logged in.")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.request_manager
|
137
|
+
manager = AFHTTPRequestOperationManager.manager
|
138
|
+
|
139
|
+
request_serializer = AFHTTPRequestSerializer.serializer
|
140
|
+
request_serializer.setValue("application/xml", forHTTPHeaderField:"Content-type")
|
141
|
+
manager.requestSerializer = request_serializer
|
142
|
+
|
143
|
+
response_serializer = AFHTTPResponseSerializer.serializer
|
144
|
+
response_serializer.acceptableContentTypes = NSSet.setWithObjects("application/xml", nil)
|
145
|
+
manager.responseSerializer = response_serializer
|
146
|
+
|
147
|
+
manager
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
data/lib/tick/client.rb
ADDED
data/lib/tick/entry.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Tick
|
2
|
+
|
3
|
+
class Entry < Tick::Base
|
4
|
+
attr_accessor :billable, :billed, :budget, :client_name,
|
5
|
+
:date, :hours, :notes, :project_name, :sum_hours,
|
6
|
+
:task_id, :task_name, :user_email, :user_id
|
7
|
+
|
8
|
+
XML_PROPERTIES = %w( id billable billed budget client_name created_at date hours notes
|
9
|
+
project_name sum_hours task_id task_name updated_at user_email user_id )
|
10
|
+
|
11
|
+
def self.api_path
|
12
|
+
"/api/entries"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.create(options={}, &block)
|
16
|
+
url = "https://#{current_session.company}.tickspot.com/api/create_entry"
|
17
|
+
|
18
|
+
params = {
|
19
|
+
email: current_session.email,
|
20
|
+
password: current_session.password
|
21
|
+
}.merge!(options)
|
22
|
+
|
23
|
+
if params[:date].is_a?(NSDate)
|
24
|
+
dateFormatter = NSDateFormatter.new
|
25
|
+
dateFormatter.setDateFormat(DATE_FORMAT)
|
26
|
+
params[:date] = dateFormatter.stringFromDate(params[:date])
|
27
|
+
end
|
28
|
+
|
29
|
+
request_manager.GET(url, parameters:params, success:lambda{|operation, result|
|
30
|
+
error = Pointer.new(:object)
|
31
|
+
xml = GDataXMLDocument.alloc.initWithXMLString(result.to_s, error:error)
|
32
|
+
|
33
|
+
# Create the entry object from xml
|
34
|
+
error = Pointer.new(:object)
|
35
|
+
entry_node = xml.nodesForXPath("//entry", error:error).first
|
36
|
+
entry = new
|
37
|
+
entry.set_properties_from_xml_node(entry_node)
|
38
|
+
block.call(entry) if block
|
39
|
+
}, failure:lambda{|operation, error|
|
40
|
+
block.call(error) if block
|
41
|
+
})
|
42
|
+
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/lib/tick/project.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Tick
|
2
|
+
|
3
|
+
class Project < Tick::Base
|
4
|
+
attr_accessor :budget, :client_id, :client_name, :closed_on,
|
5
|
+
:name, :opened_on, :owner_id, :sum_hours, :tasks,
|
6
|
+
:user_count
|
7
|
+
|
8
|
+
XML_PROPERTIES = %w( id budget client_id client_name closed_on created_at
|
9
|
+
name opened_on owner_id sum_hours updated_at user_count )
|
10
|
+
|
11
|
+
def self.list(options={}, &block)
|
12
|
+
url = "https://#{current_session.company}.tickspot.com/api/projects"
|
13
|
+
|
14
|
+
params = authentication_params.merge!(options)
|
15
|
+
|
16
|
+
request_manager.GET(url, parameters:params, success:lambda{|operation, result|
|
17
|
+
projects = []
|
18
|
+
|
19
|
+
# Parse XML
|
20
|
+
error = Pointer.new(:object)
|
21
|
+
xml = GDataXMLDocument.alloc.initWithXMLString(result.to_s, error:error)
|
22
|
+
|
23
|
+
# Create the project objects
|
24
|
+
error = Pointer.new(:object)
|
25
|
+
project_nodes = xml.nodesForXPath("//project", error:error)
|
26
|
+
|
27
|
+
project_nodes.each do |project_node|
|
28
|
+
project = new
|
29
|
+
project.set_properties_from_xml_node(project_node)
|
30
|
+
project.tasks = get_tasks_from_xml_node(project_node)
|
31
|
+
project.tasks.each do |task|
|
32
|
+
task.project = project
|
33
|
+
end
|
34
|
+
projects << project
|
35
|
+
end
|
36
|
+
|
37
|
+
block.call(projects) if block
|
38
|
+
}, failure:lambda{|operation, error|
|
39
|
+
block.call(error) if block
|
40
|
+
})
|
41
|
+
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def self.get_tasks_from_xml_node(xml_node)
|
48
|
+
tasks = []
|
49
|
+
|
50
|
+
# Seems to be mixed results when parsing the XML where
|
51
|
+
# sometimes the tasks element doesn't exist
|
52
|
+
tasks_elements = xml_node.elementsForName("tasks")
|
53
|
+
if tasks_elements
|
54
|
+
task_nodes = tasks_elements.first.elementsForName("task")
|
55
|
+
else
|
56
|
+
task_nodes = xml_node.elementsForName("task")
|
57
|
+
end
|
58
|
+
|
59
|
+
task_nodes.each do |task_node|
|
60
|
+
task = Task.new
|
61
|
+
task.set_properties_from_xml_node(task_node)
|
62
|
+
tasks << task
|
63
|
+
end
|
64
|
+
|
65
|
+
tasks
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/tick/session.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
module Tick
|
2
|
+
|
3
|
+
class AuthenticationError < Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
class Session < Tick::Base
|
7
|
+
attr_accessor :company, :email, :first_name, :last_name, :password
|
8
|
+
|
9
|
+
SERVICE_NAME = "Tick Timer"
|
10
|
+
|
11
|
+
def company
|
12
|
+
@company ||= storage.objectForKey("company")
|
13
|
+
end
|
14
|
+
|
15
|
+
def company=(value)
|
16
|
+
@company = value
|
17
|
+
storage.setObject(value, forKey:"company")
|
18
|
+
storage.synchronize
|
19
|
+
@company
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy
|
23
|
+
storage.removeObjectForKey("company")
|
24
|
+
storage.removeObjectForKey("email")
|
25
|
+
SSKeychain.deletePasswordForService(SERVICE_NAME, account:email)
|
26
|
+
self.class.current = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def email
|
30
|
+
@email ||= storage.objectForKey("email")
|
31
|
+
end
|
32
|
+
|
33
|
+
def email=(value)
|
34
|
+
@email = value
|
35
|
+
storage.setObject(value, forKey:"email")
|
36
|
+
storage.synchronize
|
37
|
+
@email
|
38
|
+
end
|
39
|
+
|
40
|
+
def first_name
|
41
|
+
@first_name ||= storage.objectForKey("first_name")
|
42
|
+
end
|
43
|
+
|
44
|
+
def first_name=(value)
|
45
|
+
@first_name = value
|
46
|
+
storage.setObject(value, forKey:"first_name")
|
47
|
+
storage.synchronize
|
48
|
+
@first_name
|
49
|
+
end
|
50
|
+
|
51
|
+
def last_name
|
52
|
+
@last_name ||= storage.objectForKey("last_name")
|
53
|
+
end
|
54
|
+
|
55
|
+
def last_name=(value)
|
56
|
+
@last_name = value
|
57
|
+
storage.setObject(value, forKey:"last_name")
|
58
|
+
storage.synchronize
|
59
|
+
@last_name
|
60
|
+
end
|
61
|
+
|
62
|
+
def password
|
63
|
+
@password ||= SSKeychain.passwordForService(SERVICE_NAME, account:email)
|
64
|
+
end
|
65
|
+
|
66
|
+
def password=(value)
|
67
|
+
@password = value
|
68
|
+
SSKeychain.setPassword(value, forService:SERVICE_NAME, account:email)
|
69
|
+
@password
|
70
|
+
end
|
71
|
+
|
72
|
+
def storage
|
73
|
+
NSUserDefaults.standardUserDefaults
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.create(params, &block)
|
77
|
+
url = "https://#{params[:company]}.tickspot.com/api/users"
|
78
|
+
|
79
|
+
company = params[:company]
|
80
|
+
params.delete(:company)
|
81
|
+
|
82
|
+
request_manager.GET(url, parameters:params, success:lambda{|operation, result|
|
83
|
+
# TODO: Save first and last name
|
84
|
+
@current = new
|
85
|
+
@current.company = company
|
86
|
+
@current.email = params[:email]
|
87
|
+
@current.password = params[:password]
|
88
|
+
block.call(@current) if block
|
89
|
+
}, failure:lambda{|operation, error|
|
90
|
+
ap error
|
91
|
+
block.call(nil) if block
|
92
|
+
})
|
93
|
+
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.current
|
98
|
+
@current || (logged_in? ? new : nil)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.current=(value)
|
102
|
+
@current = value
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.logged_in?
|
106
|
+
(@current && @current.company && @current.email && @current.password) ? true : false
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
data/lib/tick/task.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module Tick
|
2
|
+
|
3
|
+
class Task < Tick::Base
|
4
|
+
attr_accessor :billable, :budget, :closed_on, :name, :opened_on,
|
5
|
+
:position, :project, :project_id, :sum_hours, :user_count
|
6
|
+
|
7
|
+
XML_PROPERTIES = %w( id billable budget closed_on name
|
8
|
+
opened_on position project_id sum_hours user_count )
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
data/lib/tick/timer.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
module Tick
|
2
|
+
|
3
|
+
class Timer
|
4
|
+
attr_accessor :start_time, :task, :time_spans
|
5
|
+
|
6
|
+
def clear
|
7
|
+
self.start_time = nil
|
8
|
+
self.time_spans = []
|
9
|
+
self.class.list.delete(self)
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def displayed_time
|
14
|
+
hours = self.time_elapsed_in_hours.to_i
|
15
|
+
minutes = (self.time_elapsed_in_seconds / 60).to_i - (hours * 60)
|
16
|
+
|
17
|
+
hours = hours.to_s
|
18
|
+
hours = "0#{hours}" if hours.length == 1
|
19
|
+
|
20
|
+
minutes = minutes.to_s
|
21
|
+
minutes = "0#{minutes}" if minutes.length == 1
|
22
|
+
|
23
|
+
"#{hours}:#{minutes}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
self.start
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_paused
|
32
|
+
self.start_time.nil?
|
33
|
+
end
|
34
|
+
alias_method :paused, :is_paused
|
35
|
+
alias_method :is_stopped, :is_paused
|
36
|
+
alias_method :stopped, :is_paused
|
37
|
+
|
38
|
+
def is_running
|
39
|
+
!self.start_time.nil?
|
40
|
+
end
|
41
|
+
alias_method :running, :is_running
|
42
|
+
alias_method :is_started, :is_running
|
43
|
+
alias_method :started, :is_running
|
44
|
+
|
45
|
+
def start
|
46
|
+
# Stop the current timer if it exists
|
47
|
+
current_timer = self.class.current
|
48
|
+
current_timer.stop if current_timer
|
49
|
+
|
50
|
+
# Start the timer and add it to the
|
51
|
+
# list of timers if it doesn't exist
|
52
|
+
self.start_time = Time.now
|
53
|
+
unless self.class.list.include?(self)
|
54
|
+
self.class.list << self
|
55
|
+
end
|
56
|
+
|
57
|
+
true
|
58
|
+
end
|
59
|
+
alias_method :resume, :start
|
60
|
+
|
61
|
+
def stop
|
62
|
+
self.time_spans << Time.now - self.start_time
|
63
|
+
self.start_time = nil
|
64
|
+
true
|
65
|
+
end
|
66
|
+
alias_method :pause, :stop
|
67
|
+
|
68
|
+
def submit!(options={}, &block)
|
69
|
+
dateFormatter = NSDateFormatter.new
|
70
|
+
dateFormatter.setDateFormat(DATE_FORMAT)
|
71
|
+
|
72
|
+
params = {
|
73
|
+
task_id: self.task.id,
|
74
|
+
hours: self.time_elapsed_in_hours,
|
75
|
+
date: Time.now
|
76
|
+
}.merge!(options)
|
77
|
+
|
78
|
+
entry = Entry.create(params) do |result|
|
79
|
+
self.clear
|
80
|
+
block.call(result) if block
|
81
|
+
end
|
82
|
+
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def time_elapsed_in_seconds
|
87
|
+
time_elapsed_in_seconds = 0
|
88
|
+
|
89
|
+
# Add up time spans
|
90
|
+
self.time_spans.each do |seconds|
|
91
|
+
time_elapsed_in_seconds += seconds
|
92
|
+
end
|
93
|
+
|
94
|
+
# Add the current running time
|
95
|
+
if self.start_time
|
96
|
+
time_elapsed_in_seconds += Time.now - self.start_time
|
97
|
+
end
|
98
|
+
|
99
|
+
time_elapsed_in_seconds
|
100
|
+
end
|
101
|
+
|
102
|
+
def time_elapsed_in_hours
|
103
|
+
self.time_elapsed_in_seconds / 60 / 60
|
104
|
+
end
|
105
|
+
|
106
|
+
def time_spans
|
107
|
+
@time_spans ||= []
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.current
|
111
|
+
list.select{|timer|
|
112
|
+
timer.is_running
|
113
|
+
}.first
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.list
|
117
|
+
@@list ||= []
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.start_with_task(task)
|
121
|
+
timer = list.select{|timer|
|
122
|
+
timer.task.id == task.id
|
123
|
+
}.first
|
124
|
+
|
125
|
+
if timer.nil?
|
126
|
+
timer = new
|
127
|
+
timer.task = task
|
128
|
+
end
|
129
|
+
|
130
|
+
timer
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
data/lib/tick/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: motion-tickspot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Pattison
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: motion-cocoapods
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.4.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: awesome_print_motion
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: guard-motion
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.1.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: motion-redgreen
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.1.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.1.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: RackMotion
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: terminal-notifier-guard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.5.3
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.5.3
|
111
|
+
description: Motion Tickspot is a RubyMotion wrapper for accessing the Tick time tracking
|
112
|
+
service using the http://tickspot.com API.
|
113
|
+
email:
|
114
|
+
- brian@brianpattison.com
|
115
|
+
executables: []
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- README.md
|
120
|
+
- lib/motion-tickspot.rb
|
121
|
+
- lib/tick/base.rb
|
122
|
+
- lib/tick/client.rb
|
123
|
+
- lib/tick/entry.rb
|
124
|
+
- lib/tick/project.rb
|
125
|
+
- lib/tick/session.rb
|
126
|
+
- lib/tick/task.rb
|
127
|
+
- lib/tick/timer.rb
|
128
|
+
- lib/tick/version.rb
|
129
|
+
homepage: https://github.com/81designs/motion-tickspot
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 2.2.2
|
150
|
+
signing_key:
|
151
|
+
specification_version: 4
|
152
|
+
summary: A RubyMotion wrapper for the http://tickspot.com API
|
153
|
+
test_files: []
|
154
|
+
has_rdoc:
|