I solved the problem I mentioned in my last post about the interval timer not working properly when the app is paused in the middle of a timing session. I completely overhauled the programming for this and the app's been working fine since. I barely remember how the original approach worked. I remember I used some (in hindsight) whacky system where I was subtracting interval seconds from the total seconds elapsed plus the amount of time paused, or something like that. It was yet another example of me programming by the seat of my pants without thinking it through ahead of time. But honestly, that's the way I like to program. I have a firm goal in mind and I just "have at it" until I reach the goal. I find this an exhilarating quest. Usually things work out fine, but occasionally - as in this case - they don't. A reasonable trade-off as far as I'm concerned.
The new approach is a simple one that occurred to me on a long walk as I was pondering the problem. It uses the the time on the clock as the starting point for figuring out when each new interval should end. So, if the next next interval is 20 seconds long and it begins when the clock reads 0:30, obviously the alert should sound at the 0:50 mark. If the person pauses the timer, the display is frozen until the Restart button is pressed. I still had to do some transposing of time based on the theSeconds function, but the logic was sound.
I had also mentioned in my previous post that the vibrate alert option seemed to really throw things off. It turned out that an extra second was added to the current interval every time the vibrate option was used. Given that the pattern of adding one and only one extra second never changed, I decided to just add the hack of subtracting a second when the vibrate option was used. I accomplished this with a variable I created named varIntervalFudge (in my mind fudge = hack).
Yeah, programming a hack rather than tracing down the actual problem is the lazy way to do things. But hey, I'm a busy guy.
Here is a review of the programming engine for this app. MainEventLoop, as its name suggests, is the main programming loop that allows the timer to run while the user is making choices, such as starting, stopping, and resetting the timer:
1: on MainEventLoop
2: if varRunMainLoop is true then
3: lock screen
4: LloydStopWatch
5: unlock screen
6: end if
7: put the pendingMessages into tMessages
8: repeat for each line aLine in tMessages
9: if item 3 of aLine is "mainEventLoop" then cancel item 1 of aLine
10: end repeat
11: send "mainEventLoop" to me in 50 milliseconds
12: end MainEventLoop
Line 4 triggers the procedure LloydStopWatch to run every 50 ms. The LloydStopWatch procedure contains the programming that operates the timer and checks on the intervals:
1: on LloydStopWatch
2: put ((varIminutes1*60) + varIseconds1 - varIntervalFudge) into varInterval1
3: put ((varIminutes2*60) + varIseconds2 - varIntervalFudge) into varInterval2
4: put ((varIminutes3*60) + varIseconds3 - varIntervalFudge) into varInterval3
5: put ((varIminutes4*60) + varIseconds4 - varIntervalFudge) into varInterval4
6: if varIntervalTurn =1 then put varInterval1 into varIntervalCurrent
7: if varIntervalTurn =2 then put varInterval2 into varIntervalCurrent
8: if varIntervalTurn =3 then put varInterval3 into varIntervalCurrent
9: if varIntervalTurn =4 then put varInterval4 into varIntervalCurrent
10:
11: put the seconds into varSecondsEnd
12: put (varSecondsEnd - varSecondsBegin) + varTimeElapsedPrevious into varTimeElapsed //Modified December 6, 2020
13: put varSecondsEnd - varSecondsBeginInterval into varTimeElapsedInterval
14:
15: //Display the time elapsed
16: put trunc(varTimeElapsed/60) into varDisplayMinutes
17: put (varTimeElapsed)-(varDisplayMinutes*60) into varDisplaySeconds
18: if the length of varDisplaySeconds < 2 then put "0"&varDisplaySeconds into varDisplaySeconds
19: put varDisplayMinutes&":"&varDisplaySeconds into line 1 of field "my time"
20:
21: if varIntervalCurrent = 0 then exit LloydStopWatch //Only when happens when all internal fields are 0
22: if varIntervalTurn = 1 and varIntervalCheck = true then //December 6, 2020
23: put varInterval1 + varTimeElapsed into varTimeOfNextAlert
24: end if
25: if varIntervalTurn = 2 and varIntervalCheck = true then //December 6, 2020
26: put varInterval2 + varTimeElapsed into varTimeOfNextAlert
27: end if
28: if varIntervalTurn = 3 and varIntervalCheck = true then //December 6, 2020
29: put varInterval3 + varTimeElapsed into varTimeOfNextAlert
30: end if
31: if varIntervalTurn = 4 and varIntervalCheck = true then //December 6, 2020
32: put varInterval4 + varTimeElapsed into varTimeOfNextAlert
33: end if
34: put false into varIntervalCheck
35:
36: if varTimeElapsed = varTimeOfNextAlert then
37: if varIntervalTurn = 1 and varChime1 <> "vibrate" then play specialFolderPath("engine")&varChime1
38: if varIntervalTurn = 1 and varChime1 = "vibrate" then mobileVibrate 2
39: if varIntervalTurn = 2 and varChime2 <> "vibrate" then play specialFolderPath("engine")&varChime2
40: if varIntervalTurn = 2 and varChime2 = "vibrate" then mobileVibrate 2
41: if varIntervalTurn = 3 and varChime3 <> "vibrate" then play specialFolderPath("engine")&varChime3
42: if varIntervalTurn = 3 and varChime3 = "vibrate" then mobileVibrate 2
43: if varIntervalTurn = 4 and varChime4 <> "vibrate" then play specialFolderPath("engine")&varChime4
44: if varIntervalTurn = 4 and varChime4 = "vibrate" then mobileVibrate 2
45:
46: //Hack when vibrate alert is used - December 6, 2020
47: //Reduce the next interval time by 1 second
48: put 0 into varIntervalFudge
49: if varIntervalTurn = 1 and varChime1 = "vibrate" then put 1 into varIntervalFudge
50: if varIntervalTurn = 2 and varChime2 = "vibrate" then put 1 into varIntervalFudge
51: if varIntervalTurn = 3 and varChime3 = "vibrate" then put 1 into varIntervalFudge
52: if varIntervalTurn = 4 and varChime4 = "vibrate" then put 1 into varIntervalFudge
53:
54: add 1 to varIntervalTurn
55: //Next 3 lines account for when either interval 2 or 3 are empty; if interval 2 is empty, then code assumes interval 3 is empty (even if isn't)
56: if varInterval2 = 0 then put 1 into varIntervalTurn
57: if varIntervalTurn = 3 and varInterval3 = 0 then put 1 into varIntervalTurn
58: if varIntervalTurn = 4 and varInterval4 = 0 then put 1 into varIntervalTurn
59: if varIntervalTurn > 4 then put 1 into varIntervalTurn
60:
61: put true into varIntervalCheck
62: end if
63:
64: end LloydStopWatch
Note the use of the variable varIntervalFudge when accounting for the vibrate alert.
Option to Save the Current Settings
One nice little feature I added was giving the user the option to save the current settings. This is really helpful if you have a main use for the app, such as teeth brushing, and want to revert to those settings after using the app to do other things. The solution to this is similar to the idea of a "cookie." The idea is to save a small data file on the iPhone that can be retrieved later. Here's a link to a good tutorial on how to set up something like this:
https://lessons.livecode.com/m/4069/l/14301-how-do-i-read-write-to-files-on-mobile
Here is the code for saving the current settings located in the button "Save current settings":
1: on mousedown
2: put empty into field "appdata" on card "data"
3: Put "Interval Data" into line 1 of field "appdata" on card "data"
4: put "Interval 1,"&field "IMinutes1"&comma&field "ISeconds1"&comma&field "chime1" into line 2 of field "appdata" on card "data"
5: put "Interval 2,"&field "IMinutes2"&comma&field "ISeconds2"&comma&field "chime2" into line 3 of field "appdata" on card "data"
6: put "Interval 3,"&field "IMinutes3"&comma&field "ISeconds3"&comma&field "chime3" into line 4 of field "appdata" on card "data"
7: put "Interval 4,"&field "IMinutes4"&comma&field "ISeconds4"&comma&field "chime4" into line 5 of field "appdata" on card "data"
8:
9: //Activate the following line to actually save the data
10: put field "appdata" on card "data" into URL ("file:data.txt")
11: wait 1 second
12: show image "checkmark"
13: wait 1 second
14: hide image "checkmark"
15: end mousedown
Lines 4-7 put the data for each of the four intervals into the text field "appdata" on card "data."
Line 10 saves everything in field "appdata" into a text file named "data.txt"
I decided to give the user feedback that the file was saved by displaying a very small graphic checkmark. It looks better if there is a pause for one second before it is displayed. I then hide the checkmark after another second.
Here is the code in the button "Revert to saved settings" that retrieves the settings data:
1: on mousedown
2:
3: put empty into field "appdata" on card "data"
4: set the defaultFolder to specialFolderPath("Documents")
5: put URL ("file:data.txt") into field "appdata" on card "data"
6:
7: if field "appdata" on card "data" is empty then
8: put "No saved settings" into field "notes"
9: put "0" into field ISeconds1
10: put "0" into field ISeconds2
11: put "0" into field ISeconds3
12: put "0" into field ISeconds4
13: put "0" into field IMinutes1
14: put "0" into field IMinutes2
15: put "0" into field IMinutes3
16: put "0" into field IMinutes4
17: put "Chimes" into field "chime1"
18: put "Chimes" into field "chime2"
19: put "Chimes" into field "chime3"
20: put "Chimes" into field "chime4"
21: put "/sounds for toothbrush app/chimes.wav" into varChime1
22: put "/sounds for toothbrush app/chimes.wav" into varChime2
23: put "/sounds for toothbrush app/chimes.wav" into varChime3
24: put "/sounds for toothbrush app/chimes.wav" into varChime4
25: else
26: put item 2 of line 2 of field "appdata" on card "data" into field IMinutes1
27: put item 3 of line 2 of field "appdata" on card "data" into field ISeconds1
28: put item 4 of line 2 of field "appdata" on card "data" into field "Chime1"
29:
30: put item 2 of line 3 of field "appdata" on card "data" into field IMinutes2
31: put item 3 of line 3 of field "appdata" on card "data" into field ISeconds2
32: put item 4 of line 3 of field "appdata" on card "data" into field "Chime2"
33:
34: put item 2 of line 4 of field "appdata" on card "data" into field IMinutes3
35: put item 3 of line 4 of field "appdata" on card "data" into field ISeconds3
36: put item 4 of line 4 of field "appdata" on card "data" into field "Chime3"
37:
38: put item 2 of line 5 of field "appdata" on card "data" into field IMinutes4
39: put item 3 of line 5 of field "appdata" on card "data" into field ISeconds4
40: put item 4 of line 5 of field "appdata" on card "data" into field "Chime4"
41:
42: repeat with i = 1 to 4
43: put "chime"&i into varI
44: if field varI is "Vibrate" then put "vibrate" into varChimeLocal
45: if field varI is "Applause" then put "/sounds for toothbrush app/applause.wav" into varChimeLocal
46: if field varI is "Bell" then put "/sounds for toothbrush app/bell.wav" into varChimeLocal
47: if field varI is "Bicycle Bell" then put "/sounds for toothbrush app/bicycle_bell.wav" into varChimeLocal
48: if field varI is "Chimes" then put "/sounds for toothbrush app/chimes.wav" into varChimeLocal
49: if field varI is "Ding" then put "/sounds for toothbrush app/ding.wav" into varChimeLocal
50: if field varI is "Laser" then put "/sounds for toothbrush app/laser_x.wav" into varChimeLocal
51: if field varI is "Oops" then put "/sounds for toothbrush app/oops.wav" into varChimeLocal
52: if field varI is "Slide Whistle" then put "/sounds for toothbrush app/slide_whistle_up.wav" into varChimeLocal
53: if field varI is "Tada" then put "/sounds for toothbrush app/tada2.wav" into varChimeLocal
54: if field varI is "Trumpet" then put "/sounds for toothbrush app/trumpet_x.wav" into varChimeLocal
55:
56: if i = 1 then put varChimeLocal into varChime1
57: if i = 2 then put varChimeLocal into varChime2
58: if i = 3 then put varChimeLocal into varChime3
59: if i = 4 then put varChimeLocal into varChime4
60:
61: end repeat
62: end if
63:
64: end mousedown
Yeah, there is quite a bit to this script, but that's because I had to take into account the chance that no settings had yet been saved. Line 5 is the key line. It takes whatever data are saved and puts it back into the text field "appdata" on card "data." From there, I can set all of the intervals to the saved settings properly.
Playing Alerts When the iPhone is Locked
Unlike tooth brushing, many other tasks may take quite a long time. The example that comes to mind is giving a 20 or 30 minute presentation where I'd like to have (most likely vibrate) alerts with about three or five minutes to go, then with a minute left, and then a final alert with 15 seconds to go so I can get off the stage for the next person. It is easy to see that the iPhone will likely automatically lock during task like this. I have my iPhone auto-lock after five minutes if not used. I want to be assured that the alerts will come whether the phone is locked or not. Unfortunately, right now although the timer keeps working, the alerts never sound.
I did a search on the LiveCode forums about this and found what should be the solution, the command iphoneSetAudioCategory. Here is a good web page with information about this command:
https://livecode.fandom.com/wiki/Iphonesetaudiocategory
The problem should be solved just by adding the command iphoneSetAudioCategory "playback" to my code. Unfortunately, it isn't working. I'll continue playing around with it. If I don't have any luck soon, I'll reach out to the LiveCode community in the forums.
Creating an Home Icon for the App
Despite my lack of graphic design skills - something I've written about frequently in this blog - I still enjoy the challenge of graphic design. Here is the design I've come up with for the app's icon on the iPhone home screen:
Though trite, a clock face is obviously synonymous with time. The four different colored squares give the clock face a distinctive look while also symbolizing the four opportunities to set four different times with unique alert sounds. I created this icon - and I'm almost embarrassed to admit it - in PowerPoint. Of all of the graphic tools out there, I find those in PowerPoint to be the most intuitive and simple to use. It's then easy to copy and paste the graphical object into Photoshop for exporting as a PNG file. My motto is always to use the tools that work for me and get the job done.
I also started toying around with some better names for this app other than "Lloyd's Toothbrush Timer App." Brushing my teeth was just the inspiration for the need for this app after all. I have a very bad track record of naming my apps, hence the names "Lloyd's Video Analysis Tool" and "Lloyd's Q Sort Tool." So, yes, I may just wind up calling this something like "Lloyd's Interval Timer App."
But, in an effort to force some extra drops of creativity, I thought I might name this the "This, Then That Timer." I played around with the alliteration of the four Ts in an earlier version of the app's icon, but none of those looked very good. Still, I think this name has potential. Of course, the boring - but descriptive - name of "Multiple Interval Timer" might win the day.
Just a Few Remaining Tasks
Very little is left on my punch list for this app other than solving the auto-lock issue just described. Here is the short list of the remaining tasks:
- Continue to improve the graphical interface.
- Improve the timer display to also include the number of seconds until when the next alert will be sounded. A "countdown" displays like this seems like a good idea to me.
No comments:
Post a Comment