GigSign

Independent artists have to do many things to stay afloat financially and protect themselves from unsavory characters. This motivated our team at the API World 2017 Hackathon to create GigSign: contracts for the modern musician. GigSign connects the artist to their client by allowing the artist to quickly verify their phone number, send forms to be digitally signed, and send an invoice after the performance.

Verify phone numbers

GigSign allows you to quickly screen phone numbers for fraudulent activity associated with the number. To determine this risk, it uses the TeleSign Score API to convert the phone number to a rating from 0 (clear) to 1000 (fraudulent). If a high score is detected, the artist is instantly informed that this client’s phone number may be associated with fraud. The artist is still allowed to move forward, but they have been warned!

Send legal documentation and invoices quickly

Using the template and payment APIs from DocuSign, GigSign allows the artist to send legally binding paperwork to be digitally signed to their client by simply entering the recipient’s email address and checking a few boxes. The artist can also send an invoice to their client after performing a gig, which is connected directly to their bank account. The DocuSign Payment API works with 39 different currencies and can be paid using a major credit card or valid checking account information.

Take a look

Pitch Slides

Prototype

 

Unix-terminal refactored

This is an update to an old post mentioned below:

Unix-terminal splash page in JS

I added some default functionality to the Unix terminal on the index page of this website. Now when you type a command that it doesn’t understand an ‘Elizabot’ (GitHub) will respond questioningly. ‘Eliza’ was the name of one of the first natural language processing scripts and you can read more about it here.

There were two things that made this implementation difficult. First, I had to learn a lot about JavaScript’s asynchronicity. Since it takes some time to parse the text, I can’t shut down everything else that is going on in the background while the user waits around. JS uses ‘callbacks’ extensively which are basically blocks of code that call a function when they finish. Second, splitting up JS code into multiple files isn’t as trivial as it is in other languages. In fact, you need to import a library and use callbacks just to do it.

Ideas for improvement

  • Track returning users using cookies or login/logout
  • Add simple game functions. I will probably add simple card and gambling games (blackjack, poker, roulette). High scores would be a cool addition too and would let me practice database management. Would be even cooler if it synced up with cryptocurrency for ‘real’ gambling.
  • Text adventure like the emacs game
  • Connect with other people. Imagine if other people had this sort of robot on their website and they could talk to each other. It would also be neat to randomly connect to real people… but we all know what kind of problems anonymity can bring.

Using PRAW and NetflixRoulette API to make a Reddit Bot that responds when movies are mentioned

Reddit is a popular news/media/time-waster website where people share links and users democratically vote them up or down. I’m a big fan of movies and television, but I only subscribe to Netflix. When someone recommends a show, I would get slightly annoyed that I had to open a separate tab to determine if I could watch it. I made this bot that would automatically tell users like myself whether or not it’s available.

First import the necessary libraries, PRAW helps us scrape Reddit, NetflixRoulette tells us what’s available on Netflix, time will help us delay the bot, and re will let us work with regular expressions.

from NetflixRoulette import *
import time
import praw
import re

Now for the initial setup. We login using praw then move it to the movies and television subreddit.

r = praw.Reddit('test')
already_done = set()
r.login('Name_of_the_bot', 'password', disable_warning=True)
subreddit = r.get_subreddit('movies+television')

With our robot signed in and looking at the right subreddit, we can now make the main loop to check for posts that mention movies or tv that are on Netflix. The variable already_done will be used to record posts that we have already responded to to prevent double posting.

while True:
    try:
        subreddit_comments = subreddit.get_comments()
        flat_comments = praw.helpers.flatten_tree(subreddit_comments)
        for comment in flat_comments:
            m = re.search(r"is+.+on netflix", comment.body)
            if m and comment.id not in already_done:
                content_type = -1
                title = comment.body[m.start()+3:m.end()-11]
                try:
                    content_type = get_media_type(title)
                except urllib2.HTTPError,error:
                    contents = error.read()
                if (content_type >= 0):
                    comment.reply("Yes. ("+title+")")
                    print("Success! "+title)
                
            already_done.add(comment.id)
        #time.sleep(2)
    except:
        print("Error!")

This bot only works while the script is running, which can be set up using cron jobs on a certain schedule. You will need to export already_done to a file if you want to repeatedly use this, otherwise it forget the posts it already responded to. Notice I have time.sleep(2) commented out for debugging purposes. Be careful about the number of requests and posts because you can get your bot banned. Read the PRAW guidelines here from their official website before deploying a reddit bot.

These bots are interesting because you can also use them as simple web scrapers. It is not a huge leap to use a similar bot to collect data on what movies and television are most popular with reddit users.

More ideas for reddit bots:

  • Responds to people who mention dollar amounts with bitcoin amounts.
  • “Darkweb” link aggregator that keeps track of working/broken links.
  • Searches for your wordpress tags and if they are mentioned it either notifies your or replies with the relevant post.
  • Gambling/blackjack bot – will deal you a hand and you tell it ‘hit’ or ‘stay’.
  • Add in datasets or use APIs to authoritatively respond when certain topics are brought up.

Idea: A file that tells Dropbox to not upload certain files and folders

I really only backup my computer two ways – through a Time Machine external HD (that I don’t really trust) and Dropbox. Website files, old projects, work stuff, school stuff, half-finished stories, resumes, everything important on my computer that’s not gigantic I keep in my Dropbox folder. I’ve been forced to either pay up or delete items so many times from that folder I almost like doing it. It forces me to decide what’s really important in my life, but it’s getting to the point where I really need more than the free 10gb. Version control software has an ‘ignore’ file, which allows you to tell it not to track things like videos, large pictures, audio files, etc. This is important because these don’t really change after you add them to your project, while taking up the majority of the available space.

Wouldn’t it be cool if Dropbox could do the same thing? Unfortunately, this functionality is not built-in and apparently new releases broke “dbignore,” which may have worked in the past. Right now there is a workaround to ignore files by tricking selective sync, but it’s a bit labor intensive for me.

There is an extensive dropbox core api that works with multiple languages, but I assume the difficulty in implementing this is within the platform itself.

Run Grouper iOS Project

I went with a project-based approach to learning Swift and this is (a start to) what I came up with. I used to run with a group in Brooklyn and I would miss the meetups a lot because I would forget the meetup times and show up too early or too late. The running world is very punctual and this app targets runners who aren’t.

This project made me realize that I mostly build toy apps with extremely limited functionality. Besides my website I don’t have much experience with building for the long run, so this app turned out to be a challenge. I started by mocking it up in invision, a neat web tool you can use to prototype mobile apps. I learned about invision from a lecture about ‘UX’ at General Assembly in New York. UX, as far as I can tell, stands for User Experience and is a play on ‘UI’ (User Interface). The app market is saturated and at the slightest inconvenience many users will move on to the next one. You’re also not allowed to just push whatever garbage you came up with out to the iOS app store, so you have to make sure you follow proper design principles. UX is important because nobody has the time nor the patience to read instruction manuals anymore. So, software designers have to make functionality apparent based on imagery, placement, motion, color schemes, and dozens of other options. The idea is you want everything streamlined so the path of least resistance leads to favorable results or ‘goal conversions’ as Google Analytics calls it. For this app the ‘goal’ is to inform our users about the local meetups as easily as possible. Here’s the rough draft of the Run Grouper app, with zero functionality: Tada! Looks nice, right? Now for the hard part.

Apple computers come with Xcode, an IDE for developing native iOS apps. It has a photoshop-esque complexity and can be overwhelming with the number of features. The ‘storyboard’ mode has some unique drag and drop functionality where you link storyboard components like buttons, textfields, etc. by literally dragging them into your code. You can then manipulate the generated “IBOutlet variable” through your code.

Swift is very strongly-typed and complains a lot when you don’t follow the rules. It is definitely tricky transitioning from languages like Ruby, Python, JavaScript, etc. to Swift, but it should payoff in the long run. One common issue of weakly typed languages is passing the wrong variable type into a function causing unpredicted behavior. Swift should catch that kind of problem before compiling and generate more maintainable code.

I used this tutorial from Apple to get the app into its basic state. That gives our basic UI which consists of a list of ‘food items’ (soon to be run items) and functionality to add, delete, edit, and persist data. The next thing I did was replace the food-related stuff with run-related stuff and get rid of the rating system.  Although that might be a cool addition in the future.

We don’t want to force the user to enter their own runs, but we want the runs to load in based on their established group. The first step of the project is loading data from a website into the app. I opted to use the JSON format over XML to save myself some typing and because websites like this and this host JSON data freely. Here’s the Swift code to load in the JSON data, please pardon the weird syntax highlighting:

private func loadSampleRuns(){
        let url = URL(string: "mywebsite.com/run.json")
        URLSession.shared.dataTask(with:url!, completionHandler:
            {(data, response, error) in
                guard let data = data, error == nil else { return }
                do {
                    let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:Any]
                    if let runs_load = json["runs"] as? [[String: AnyObject]] {
                        for run in runs_load {
                            
                            let r_name = self.loadJSONString(jsonObject: run, name: "runName")
                            //Make sure that the run name is not already loaded
                            if !self.checkForRunName(name: r_name) {
                                let r_day = self.loadJSONString(jsonObject: run, name: "runDay")
                                let r_time = self.loadJSONString(jsonObject: run, name: "runTime")
                                let r_location = self.loadJSONString(jsonObject: run, name: "runLocation")
                                guard let current_run = Run(name:r_name, day: r_day, time: r_time, location: r_location) else {
                                    fatalError("Unable to instantiate run")
                                }
                                self.runs += [current_run]
                                self.tableView.reloadData()
                            }
                        }
                    }
                } catch let error as NSError {
                    print(error)
                }
        }).resume()
    }
    
    private func loadJSONString(jsonObject: [String:AnyObject], name: String) -> String {
        return(jsonObject[name] as? String! ?? "EMPTY")
    }
    private func checkForRunName(name: String) -> Bool {
        for run in runs {
            if run.name == name {
                return true
            }
        }
        return false
    }

Not the most attractive code, but it works well enough. Next we need  consistent, formatted time and date information. We mainly need the difference in time, so we can tell the reader that the meetup is in 3 hours, tomorrow, or next week. I found this helpful thread on stackexchange for differencing JSON formatted strings with Swift and here’s the code:

private func getDateFromString(_ dateString: String?) -> Date? {
        guard let dateString = dateString else {
            return nil
        }
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
        var date: Date?
        date = dateFormatter.date(from: dateString)
        if date == nil {
            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZ"
            date = dateFormatter.date(from: dateString)
        }
        return date
    }
    private func compareDates(date1 : Date, date2 : Date) -> DateComponents {
        let calendar = Calendar.current
        return calendar.dateComponents([.day, .hour, .minute], from: date1, to: date2)
    }

I’m still learning how to meet the  app store guidelines and debating whether it’s worth it to pay the 99$/year fee to deploy this onto the app store. I think the concept is novel enough, but this will not meet the stylistic guidelines yet. You can look at the github repository here, notice it’s still named “foodtracker” after the iOS tutorial.

Folder Action to Copy Dropbox Screenshots to a Different Directory using the built-in OSX Automator

For mac users who take a lot of Screenshots and also happen to have Dropbox, you may have noticed that the screenshots are automatically saved to the Dropbox/Screenshots subdirectory. Annoyingly, there is no way to change this destination within your dropbox preferences. My solution Here is a guide to setup the OS X Automator to take care of this for you, well, automatically.

The OSX Automator is a macro builder that comes standard with Apple computers. Macros are automated functions that can take care of many mindless repetitive tasks after being set up. I just found out about it recently when I came across this Dropbox issue and I want to share how I set it up.

Right click on the folder and select Services > Folder Actions Setup…

 

A modal box titled “Folder Actions Setup” should appear. Select “move – source to dest files.scpt” then click Attach.

The name of the folder should appear on the Folders With Actions list. Make sure it is checked. Now we are capable of adding macros to the folder using the Automator.

Open up Automator from Spotlight Search or the Applications folder.

Select New Workflow then click on Files & Folders from the left navigation menu

You should see an empty workspace with the message “Drag actions or files here to build your workflow.” The first thing to do is get the specified finder items, so find Get Specified Finder Items from the searchable action list and drag it over.

Click the Add button to find the folder from earlier.

Next, we want to duplicate the file so we don’t lose the original.

Drag that below the Get Specified… item in your workflow. Lastly, we want to move the duplicated file to the new directory. You can do this by using the Move Finder Items action.

Drag this below the Duplicate Finder Items action.

Select where you are moving the files from the dropdown “To” selector.

Your total workflow should look something like this:

Go ahead and click the Run button at the top and test it out by moving taking a few screenshots.

Hope this helped!

Unix-terminal splash page in JS

I finally got around to updating my out of style parallax splash page today and here’s what I came up with. I was inspired by the hours upon hours I’ve been forcing myself to sit in front of my computer this summer learning CS fundamentals. Anyways, it’s still very much a work in progress, but here’s the gist of it.

Step 1: the HTML

Apart from the navbar, the html is basically just an input box

<div class="input-group">
 $ <input type = "text" id="entertext" autofocus>
</div>

Take note of the ‘autofocus’ attribute there that clues the user in that they can type stuff.

Step 2: The CSS

The next part was styling the input box so that there’s no border

input, textarea:focus, input:focus {
  outline: none;
  border: none;
  width: 80%;
}

The width style is important for mobile support, otherwise the input may wrap around on small screens and it looks a bit off.

Step 3: The JavaScript

Next is the meat of the project, which I wrote in JavaScript using the jQuery library. Here’s the function for selecting the input after the user presses ‘return’, or ASCII value 13:

$(".input-group").on("keypress", function(e){
  if (e.which == 13) {
    // sanitize user input for injections
    var text = $("#entertext").val().replace(/[^a-z0-9\s]/gi, ' ').replace(/[_\s]/g, ' ')
    $(this).before(evaluateText(text))
    $("input").val("")
    document.body.scrollTop = document.body.scrollHeight;
  }
})

I found the fancy regex to sanitize user input from this thread on stackexchange. I noticed pretty quickly in development that you could modify html and scripts from user input, so that code strips away special characters before processing.

The before() function prepends the evaluated text so our input box stays at the bottom of the page.

Finally, we clear the text and move the window scroll to the input box. How do we evaluate the text? Well, here’s my big nasty if-else block for that purpose. Notice it has pretty limited functionality at this stage:

var hintGiven = false
function evaluateText(text) {
  var words = text.toLowerCase().split(" ")
  var userInput = "<h1 class='userEntered'>: "+text+"</h1>"
  if (words[0] === "echo" && words.length > 1) {
    if (words.slice(-1)[0].includes("path"))
      return pathlink()
    else
      return userInput + "<h1 class='userEntered'>" + words.slice(1).join() + "</h1>"
  }
  else if (words[0] === "clear")
    return clear()
  else if (words[0] == "help")
    return userInput + "<ul class='userEntered'>"+
    "<li>echo: repeat back</li>"+
    "<li>clear: removes previous commands</li>" +
    "<li>cd [dir]: change to directory (e.g., Reid/geology/)</li>" + "</ul>"
  else if (words[0] == "cd")
    return changeDirectory(words.pop())
  else {
    var str = "try typing 'help' for available commands"
    !hintGiven ? hintGiven = true : str = "command not found"
    return userInput + "<h1 class='userEntered'>" +
     str + "</h1>"
  }
}

The ‘hintGiven’ global variable declared at the top checks if the user has already received the prompt about the ‘help’ command. We then convert the words to lowercase (for simpler parsing) and split them into an array. Next, we check the first word in the array (words[0]) against available commands (echo, clear, help, cd) to determine the action.

Lastly, some helper functions for the actions

function changeDirectory(path) {
  var path = path.toLowerCase()
  if (path == "reid") {
    return window.location.href = "https://reidsherman.com/cv/"
  }
  else if (path == "wordpress") {
    return window.location.href = "https://reidsherman.com/wp/"
  }
  else if (path == "geology") {
    return window.location.href = "https://reidsherman.com/projects/"
  }
  else {
    return evaluateText('help')
  }

}
function pathlink() {
  return "<h1 class='userEntered'> Reid/geology/astrophysics/educator/<a href='https://reidsherman.com/projects/'>software-developer</a></h1>"
}
function clear() {
  $(".userEntered").remove()
}

A few things to note:

  • change directory (cd) uses the window.location.href function to automatically link to a different page.
  • All items that are added to the screen, including pathlink, echo commands, and ‘help’ items are given the html class ‘.userEntered’.
  • Because all items have the class ‘userEntered’, clearing the screen is trivial by selected all items with that class and then running the jQuery remove() function on that.

Well that’s it, I hope you enjoyed the read. There are a million ways to make this better and more robust, but I hope this may give you some insight on how to start if you’re building something similar. Here are some ideas for future/better implementations:

  • Add styling changes to invert colors, matrix theme, adjustable text size, old-school terminal mode, etc.
  • More commands! Perhaps even a text-style adventure like the emacs dunnet game.
  • Text effects (fade in/out) and sound.

 

 

Foray into Cryptography

Until recently my main experience with cryptography was fitting my password to the arbitrary requirements of whatever website I’m signing up for. ‘Crypto’ keeps popping up randomly in web apps, so I bought a used version of Understanding Cryptography to understand… cryptography. The previous owner extensively highlighted the introductory chapter, but abruptly stopped at around page 22. I was about ready to give up too, but the next page has this fantastic quote in the “Lessons Learned” summary section:

“Never ever develop your own crypto algorithm unless you have a team of experienced cryptanalysts checking your design.”

I can definitely get behind something that not only recommends being lazy, but is also one of the major tenets of the field. Despite the brilliance of a single person or small team working in isolation, they will probably never beat the time-tested, heavily researched methods. I love that in the job of hiding information, the experts are extremely open about how to hide it.

One of the first exercises in the book is to crack a deciphered message that uses the infamous Caesar cipher.

The Caesar Cipher, or Shift Cipher, encrypts a document by assigning each letter a numerical value then ‘shifting’ it by some amount. For example, if the key is 3 then ‘A’ becomes ‘D’, ‘B’ becomes ‘E’, and so on. Once you get to ‘Z’ it loops around, so ‘Y’ becomes ‘B’, ‘Z’ becomes ‘C’, etc.

The encryption function e for our cleartext (normal) letter x, looks like this:

e(x) = (x + k) % 26

The decryption function for the encrypted letter y, looks like this:

d(y) = (y - k) % 26

where is the key (the number of spaces to shift) and is the modulo operator.

This encryption method isn’t very secure because the encrypted data retains characteristics of English (or whatever language the cleartext is written in). Certain letters and words are statistically more likely than others, so through analysis of the encrypted text you can determine the encryption key. Here’s the encrypted text given in the book:

“xultpaajcxitltlxaarpjhtiwtgxktghidhipxciwtvgtpilpitghlxiwiwtxgqadds”

Now the plan for decryption (in ruby):

  1. Write helper functions to convert words to numbers where A = 0, B = 1, C = 2, and vice versa.
  2. Write a function that will retrieve the letter frequency from the text
  3. Compare the most frequent letters to the most frequent letters in English to determine the key.

Admittedly, that’s a lot of work when you could just count the letters to skip right to step 3. But let’s pretend we had to do this on thousands of pages worth of data.

To turn letters into numbers I added a method to the String class named numerify:

class String
  #makes it so that 'a' is 0, 'b' is 1, etc.
  def numerify
    num = []
    self.downcase.each_char {|c| num << c.ord - 97}
    num
  end
end

Notice that returns an Array of numbers, so to reverse that function I had to modify the Array class:

class Array
  #takes an array of encrypted letters (as numbers) and turns into lowercase letters
  def denumerify
    letters = ""
    self.each {|n| letters += (n + 97).chr}
    letters
  end
end

Now we write the encryption and decryption functions using the equations from above.

#input: string to be encrypted, key (int)
#output: encrypted string as a comma separated string
def encrypt(x, k)
  e = []
  x.numerify.each {|i| e << ((i + k) % 26)}
  e.denumerify
end
#input: takes in an encrypted string and the key,
#output returns the decrypted string
def decrypt(y, k)
  d = []
  y.numerify.each{|i| d << ((i - k) % 26)}
  d.denumerify
end

Finally, we have to count the letter frequency so we can actually decipher the message. Here was my original way of doing it, which returns a key => value hash (letter => count).

letter_freq = {}
encrypted_text.chars.each do |i|
  letter_freq[i].nil? ? letter_freq[i] = 1 : letter_freq[i] += 1
end

But there’s always a better way! I found this more rubyesque method on Stack Overflow which I could tack onto the ‘String’ class:

class String
  def frequency
    self.chomp.each_char.with_object(Hash.new(0)) do |letter, hash|
      hash[letter.downcase] += 1
    end
  end
  #makes it so that 'a' is 0, 'b' is 1, etc.
  def numerify
    num = []
    self.downcase.each_char {|c| num << c.ord - 97}
    num
  end
end

Almost there. Now we can call the frequency method on our encrypted string to get the letter count, then sort by occurrence:

letter_freq = encrypted_string.frequency
#sort by occurance
sorted_letter_frequency = []
letter_freq.each_pair do |i|
  sorted_letter_frequency << i
  sorted_letter_frequency.sort! do |i, j|
    j[1] <=> i[1]
  end
end

The most common letter in the encrypted text is t, then i, then x. Wikipedia has some nice graphs on letter frequency, but usually you can just assume the most common letter is ‘e’.

E(4) -> T(19) = 15 letters in between, so we print out the decryption with a key of 15, and there’s our answer.

p decrypt(encrypted_string, 15)

I’m not going to spoil this one, but let’s just say these crypto guys are a bit sick in the head. Anyways, it was fun pretending to be Jake Gyllenhaal in Zodiac and I hope to write more about encryption in the future.

 

Mental Exercise for Menial Chores

If there’s one thing I learned from teaching, it’s that one of the most mind numbing activities is test proctoring. Most proctors aren’t allowed to use their smartphone, read a book, draw, or even sit. You are supposed to walk around and “actively proctor,” whatever that means. One of my coping tricks was to determine the collatz sequence of a random starting number, mentally.

A collatz sequence is a simple algorithm that creates a chain of numbers based on a starting ‘seed’ number. The rule is simple:

If n is even then:

n –> n/2

If n is odd then:

n –> 3n + 1

For example if you start with 5, an odd number, the next number would be

3*5 + 1 = 16

Since 16 is even, we divide it by two, so the next number is 8. So far our sequence is

5 –> 16 –> 8

Since 8 is even, we divide by two again, giving us 4. Divide that, and on and on until you reach 1. The overall sequence contains 6 numbers:

5 — > 16 –> 8 –> 4 –> 2 –> 1

This is called the collatz conjecture because it’s unproven whether or not any starting number will always reach one, like in the example above. Here is the question from Project Euler  which I will try to solve.

Which starting number, under one million, produces the longest chain?

How I will approach this problem

  1. Write a helper function that will determine the next number in the sequence
  2. Write a function that will loop through every number keeping track of the number of iterations.
  3. Once the chain has finished, check if it has surpassed the last winner. If it has, then replace it as the current ‘leader’ .

First, the helper function to determine the next number:

def next_collatz(n)
  if (n%2 == 0)
    return n/2
  else
    return 3*n+1
  end
end

Then, declare the variables to keep track of the current leader (longest)

longest = 0
counter = 1
winning_num = 0
x = 1

Construct the loop to go through all numbers less than one million

while counter < 1000000
  x = counter
  chain = 1
  while x > 1
    x = next_collatz(x)
    chain+=1
  end
  #print("N: ", counter, " Had a chain of: ",chain,"\n")
  if (chain > longest)
    longest = chain
    winning_num=counter
  end
  counter+=1
end

Print the winner!

print("longest: ",longest," winningnum: ",winning_num,"\n") #Note this is the number that produces the longest chain, not the length of chain

 

Logging Workouts with the Reminders App (iOS)

I read an article on Nerd Fitness last year that went on and on about how important it is to log your progress. While I always saw the importance,    I frequently found myself without my notepad, pen, or both. I transitioned to my phone’s “Notes” app for a while, but the file soon became large and unorganized.

I never really found a use for the “Reminders” app. Doesn’t everyone use Calendar to remind them of stuff? After digging it out of the “maybe I’ll use it one day but probably not” discard folder I tried using it to log my workouts. After establishing a logging system, it turns out to be an effective way to track progress. There’s hundreds of exercise apps that can do something similar, but why give away your personal information, hard drive space, and/or money? Here’s my system that, depending on the complexity of your routine, shouldn’t take more than a few minutes to set up and almost no time to use.

Creation

Step 1: Create a new “List” by pushing the “+” symbol at the top right corner. If you do not see the “+” symbol, you might have to swipe down (it appears next to the search feature). Name it something descriptive and (optionally) give it a color code in ROYGBIV order. For example, I color coded my “Legs” workout Red because it is my first exercise of the week. Next comes “Chest” in Orange, then “Back” in yellow, and so on.

Step 2: Write in the different exercises for that day. For example “Squat 3×5 135” represents 3 sets of 5 reps at 135 lbs. Then on the next line, “Leg Curl 4×8 140,” and so on until you have set up every exercise for that routine.

Step 3: Before you go onto the next routine (list), add a new list item with the date you plan on doing this routine OR write the date in once you’re at the gym. It is important to keep track of dates otherwise you will have trouble remembering when you started this routine.I recommend keeping the dates as a separate list item to keep it clean.

Repeat Steps 1-3 for all of your routines. For example if you have five different routines, you should have five lists.

Separate by time

I separate my weeks with semicolons, “;”, so that it is clear to see the change every week. This works even if you have complex exercises with variable weights, sets, and/or reps because they are still semicolon separated.

After you set up your exercise you should not repeat the title, you just log what you did. For example:

Incline bench press 4×8 135; 4×8 140; 4×6 145; skipped for core work; 4×8 150; …

And so on. Notice how clearly the different days show up… Well, you’ll get used to it.

I can’t emphasize enough how motivating it is to start with 30 or so exercises and tick them off to zero. Well, until the end of the week that is, when you get to uncheck all your routines and start over 🙂

Ticking off boxes

Reminders makes it easy to track what you have left because you literally ‘tick’ off the exercises as you complete them. After you finish your sets you can also write a note to yourself for next week. For example, if I found the set easy I will write “easy” so I know to increase the weight next week. If I was not able to complete the exercise I will write “fail” so I know to decrease the weight or attempt it earlier in the routine next time.

 

Why it’s important to log progress

No matter how good your memory is, you will forget some specifics from the week before. Maybe you skipped the bench press because the line was around the block? Maybe you substituted core work for arm work because you weren’t going to the beach anytime soon. Perhaps you hit the treadmill for the first time because you found the confidence to be that guy who flirts at the gym. No matter your level, you need to log your activity otherwise you’ll spin your wheels with little progress. Happy lifting.