Eventail 2.2

After two months of coding I am pleased to announce a new version of Eventail.

This release brings some new features and a lot of polish into the settings screen.

Eventail Screenshots

The app now has some more personality, the preview is more prominent and looks like a preview too!

Colors

iPhone X users will be, hopefully, delighted by the new Black theme, which gives you a pure black background.

Black Theme

There are some good news for people who organize their life by colors too. A new Vivid color scheme puts accent on the calendar color and makes it easy to navigate through the day if you do a lot of context switching.

Black Theme

Weekends can get their own color too! A subtle background color indicates where the week ends.

Black Theme

Tweaks

The events now have a bold-colored stripe on the left, to highlight the calendar they are in.

Event invitations have distinct icons, it is easy to distinguish between invitations and own events; and also to see the RSVP status at a glance.

New Day View Icons

And more

I have squashed a number of bugs, and worked on the pestering memory issues. In most cases the widget should no longer fail to load, and the troubleshooting options will resolve the rest.

Without further ado, find Eventail on the App Store

Why there is no perfect email application

I have written this thought after listening to the Golden Cortex episode, however I forgot to remove the draft tag on it so it was never published. The latest episode on Triggers and email had made me re-discover it.

There is no perfect e-mail application because everybody has a different definition of perfection. Bland as it might seem, this is what makes it not viable to make an application that would be considered even remotely good by everyone.

Problems such as note taking or shopping list making are simple. Even with large competition, you can take the risk and develop an application that will work in some novel way. However, e-mail is complicated, which means that the application would have to be complex. It would be costly to develop and thus would have to be expensive, reducing the pool of potential clients.

Complex problem will only have good enough1 solutions because making a perfect solution would not be economically viable.


  1. It is important to note that perfection is not additive. If your application has every feature under the sun it means it will be less than desirable to many. [return]

Zoom out any page on mobile Safari

Just now I wanted to publish a new version of my app to testers. This is to be done on the iTunes connect website. I only had my phone, but according to Steve “iOS has full web experience” so this should not be a problem.

However, iTunes connect does not work exactly well on mobile. This is because the zoom level of the page is fixed on the “background” while the dialogs are in front. The result is that the button Submit is inaccessible.

After some amount of looking for a solution I have found several bookmarklets that should unlock the zoom. None of them worked. So I made my own solution.

Bookmark this page in Safari and change its URL to this:

javascript:(function(){document.getElementsByTagName("body")[0].style.zoom =0.5;})()

Then, on a page which has elements outside of the viewport, launch the bookmark from the bookmark menu. The page will be twice as small, but more of it will fit!

10 Laws of UX, digested

After reading the article on 10 Laws of UX and the associated comments on Hacker News, I have decided to try a shot at reformatting the page to a more legible format. In the form of a blog post—because I do not think that every idea on the internet needs its own domain.

I provide my own interpretation of the laws in the titles, they are not in the original article.

1. If a function needs to be accessed often and/or quickly, make the button big

The time to acquire a target is a function of the distance to and size of the target.

– Paul Fitts

Fitt’s Law on Wikipedia

2. Only provide choices when a good default does not exist

The time it takes to make a decision increases with the number and complexity of choices.

– William Edmund Hick and Ray Hyman

Hick’s Law on Wikipedia

3. Respect the platform’s conventions and interface guidelines

Users spend most of their time on other sites. This means that users prefer your site to work the same way as all the other sites they already know.

– Jakob Nielsen

Jakob’s Law on Nielsen Norman Group

4. Do not stuff too much detail into a small space

Law of Prägnanz:

People will perceive and interpret ambiguous or complex images as the simplest form possible, because it is the interpretation that requires the least cognitive effort of us.

The Laws of Figure/Ground, Prägnanz, Closure, and Common Fate on Interaction Design

5. Put actions that do similar things together

Law of Proximity:

Objects that are near, or proximate to each other, tend to be grouped together.

N.B.: The recent missile alert fiasco is a good example of why this is important

6. Reduce the number of things your users have to remember

The average person can only keep 7 (plus or minus 2) items in their working memory.

– George Miller

Miller’s Law on Wikipedia

7. Make tasks short, simple and with set deadlines

Any task will inflate until all of the available time is spent.

– Cyril Northcote Parkinson

Parkinson’s Law on Wikipedia

8. Put the important things at the beginning or at the end

Users have a propensity to best remember the first and last items in a series.

Serial Position Effect on Wikipedia

9. Removing features from your product may result in users not being able to achieve some goals

Tesler’s Law, also known as The Law of Conservation of Complexity, states that for any system there is a certain amount of complexity which cannot be reduced.

Larry Tesler’s Law of Conservation of Complexity on Wikipedia

10. Make the most important thing stand out

The Von Restorff effect, also known as The Isolation Effect, predicts that when multiple similar objects are present, the one that differs from the rest is most likely to be remembered.

Von Restorff Effect on Wikipedia

Alternatively: If you want to stand out from competition, find a feature which is always the same and make it different.

Bonus law

Do not put a leading zero on numbers that do not have to be aligned.

How I discovered how iOS calendar app chooses colors by digging into the icloud.com JavaScript code

While making an iOS calendar app, I needed to find out how Apple calculates background and text colors when displaying events in the Calendar app. I wanted to use the same algorithm for my display, in order to integrate well with the OS.

colors-iphone

I was hoping for some displayBacgkroundColor property on EKCalendar object or something similar, but it does not seem to exist. I almost decided to start approximating their algorithm while I realized one thing—the icloud.com site has a calendar “application”. The event colors there seem to follow the same logic as those in the Calendar app. They are not strictly the same but it could provide some insight into how the color switching works.

colors-icloud

iCloud.com being a modern web application, it runs a bunch of client side JavaScript. Since it is possible to add events, change calendar colors and so on, I assumed that the code which calculates the text and background colors for events must be somewhere in the publicly available JavaScript. There was a small possibility that they pre-computed the colors on the server but this seemed unlikely.

Homing in on the function

I decided to use Safari for this. It seems kind of appropriate and I am quite familiar with its web developer tools. I open the iCloud website, open the calendar page and look into what kind of JavaScript files are loaded.

Among others, this page loads mainly two big JavaScript files, both called javascript-packed.js. The other files are all called javascript-strings so I assume these load the localization files or something like that.

My first hypothesis was that the function is called when a color of an already present event is changed. This might happen when, for example, the calendar to which the event belongs is changed.

Luckily Safari can display minified JavaScript files in indented mode. However searching for functions for strings like “update” gave me too many results.

Looking at the HTML code of an event, I can observe that its color is inside an inline style attribute and that it changes when I change the calendar. This is good news, as the inspector lets me to put a breakpoint on that.

In the inspector I find the event element, right click on the div in the tree structure and set a breakpoint on Attribute Modified

set-breakpoint

Now I click on the event in the app, change the calendar… and voilà.

callstack

Walking up the call stack

At the very end of the call stack I can see that a property is assigned a value of #a66110. This is actually the text color for this calendar (I checked this in the inspector). My hope is that it will be calculated somewhere up the stack.

1 style: Assign #a66110 as as the color property of a CSS declaration
2 anonymous: Function that that applies the CSS style when called
3 access: This function is a sort of trampoline
4 css: This function generates the anonymous function in step 2, it does get the #a66110 as a parameter 'n'
5 updateProperty: calls the css function, and also gets the #a66110 as a parameter 'r'

When I get to the 6th level I finally hit something interesting. This is where the value #a66110 is created (inside a function called _update:

6 _update: This function seems to update the whole event, its title, state and most importantly, colors

p = t.get("textColor")

The get method seems to be doing something akin to the following (I did not really dig into this):

object.prototype.get = function(propName) {
    return call(this, this.properties[propName]);
}

I any case, the color value is derived from object t and its property textColor.

Finding the ‘Event’ object

I look into the t object’s properties and see this:

A cursory glance at the properties confirms to me that this is an ‘Event’ object. It has the usual ones such as owner, isAccepted and so on.

Intuitively this line then, in the _update function, does what we were searching for: Get the text color for an event and change the elements css representation to reflect that.

p = t.get("textColor"), this.updateProperty(n, o, ".text", p, "color")

I dig into this textColor property. I search the JavaScript file for it and look over the few results I get.

Within a piece of code which starts with Cal.Collection = CoreCal.Collection.extend({ I find this (among the other event properties):

textColor: function() {
    if (this.get("isBirthdayCalendar")) return "#4d5765";
    var e = this.get("_hexColor");
    return Cal.colorController.computeTextColor(e)
}.property("_hexColor").cacheable(),
backgroundColor: function() {
    if (this.get("isBirthdayCalendar")) return "#cfd3d9";
    var e = this.get("_hexColor");
    return Cal.colorController.computeBackgroundColor(e)
}.property("_hexColor").cacheable(),

Cal.colorController.computeTextColor(e) and Cal.colorController.computeBackgroundColor(e) and in both cases e is a color… finally I have struck gold!

Compute text and background color

The function iCloud uses to compute the text color is this:

computeTextColor: function(r) {
    r || CW.fatalError("Cannot provide a text color without a starting color"), r = r.toLowerCase();
    var i = this.get("specialColors"),
        s = SC.convertHexToHsv(r),
        o = s[0],
        u = s[1],
        a = s[2],
        f;
    if (i.isLight(o, u, a)) f = i.light.text;
    else if (i.isDark(o, u, a)) f = i.dark.text;
    else {
        var l = a - n,
            c = i.isGray(o, u, a),
            h = c ? 0 : Math.max(u, e);
        f = SC.convertHsvToHex(o, h, Math.max(l, t))
    }
    return f
},

By putting a breakpoint on the f = ... line I can deduce the constants n = 0.35, e = 0.5 and t = 0.3.

As for the background color:

computeBackgroundColor: function(e) {
    if (!e) {
        SC.error("Cannot provide a background color without a starting color");
        return
    }
    e = e.toLowerCase();
    var t = this.get("specialColors"),
        n = t.backgrounds[e];
    if (n) return n;
    var r = SC.convertHexToHsv(e),
        i = r[0],
        o = r[1],
        u = r[2];
    if (t.isLight(i, o, u)) n = t.light.background;
    else if (t.isDark(i, o, u)) n = t.dark.background;
    else {
        var a = t.isGray(i, o, u) ? 0 : s;
        n = SC.convertHsvToHex(i, a, t.bgBrightness(i, o, u))
    }
    return n
},

And the contents of the specialColors are here (comments are from my investigation of constants):

specialColors: {
    light: {
        main: "#dddddd",
        text: "#a8a8a8",
        background: "#f8f8f8"
    },
    dark: {
        main: "#1a1a1a",
        text: "#000000",
        background: "#bababa"
    },
    titles: {
        "#cc73e1": "#b14bc9",
        "#65db39": "#49bf1f",
        "#ffcc00": "#e0ac00",
        "#ff9500": "#ff7f00"
    },
    backgrounds: {
        "#a2845e": "#e0d3c1"
    },
    isLight: function(e, t, n) {
        var s = Cal.colorController;
        return n > r && t < i // r = 0.6, i = 0.1
    },
    isDark: function(e, n, r) {
        return r < t // t = 0.3
    },
    isGray: function(e, t, n) {
        return t < i // i = 0.1
    },
    bgBrightness: function(e, t, n) {
        return Math.min(n * 1.5, o)
    }
},

Then there is a bunch of color conversion functions that work in a pretty standard way.

  • convertHsvToHex takes a R, G, B triplet and returns a [H, S, V]
  • convertHexToHsv takes an array of three elements [H, S, V] and returns an array [R, G, B]
  • two regexes to check if a colors is in the HTML rgb(r, g, b) or #RRGGBB format
  • parseColor which converts either of the two formats and returns a #RRGGBB representation
  • expandColor which takes the #RRGGBB color and returns an array of [R, G, B]
  • toColorPart which takes a number in 0-255 range and transforms it to hex representation

I am not going to detail these functions here, but I have uploaded them here if you would like to look:

Original Apple Conversion Functions

Rewriting the functions into more human readable format

These functions were of course at least partly generated and minified. So I have rewritten them into a digestible format.

First, I have written a colorTools object which has the properties from the specialColors object on iCloud.

const colorTools = {
  light: {
    text: "#a8a8a8",
    background: "#f8f8f8"
  },
  dark: {
    text: "#000000",
    background: "#bababa"
  },
  backgrounds: {
    "#a2845e": "#e0d3c1"
  },
  isLight: function(h, s, v) {
    return v > 0.6 && s < 0.1
  },
  isDark: function(h, s, v) {
    return v < 0.3
  },
  isGray: function(h, s, v) {
    return s < 0.1
  },
  bgBrightness: function(h, s, v) {
    return Math.min(v * 1.5, 1)
  },
  /* color space conversion removed for brevity */

This object is then used as a helper for the two functions I was actually searching for in the beginning.

Text Color

If the color is too light or too dark then use a pre-computed text color. Otherwise cap the saturation to 0.5 (for grayish colors just set it at 0) and reduce the value by 0.35.

function computeTextColor(rgb) {
  const [h, s, v] = colorTools.convertHexToHsv(rgb);  
  if (colorTools.isLight(h, s, v)) {
    return colorTools.light.text;
  } else if (colorTools.isDark(h, s, v)) {
    return colorTools.dark.text;
  } else {
    const rs = colorTools.isGray(h, s, v) ? 0 : Math.max(s, 0.5);
    const rv = v - 0.35;
    return colorTools.convertHsvToHex(h, rs, Math.max(rv, 0.3))
  }
}

Background color

In a similar fashion, use a pre-computed colors if the background is too light or dark. Set the saturation to 0 for grayish colors.

function computeBackgroundColor(rgb) {
  if (colorTools.backgrounds[rgb]) {
    return colorTools.backgrounds[rgb];
  }
  const [h, s, v] = colorTools.convertHexToHsv(rgb);
  if (colorTools.isLight(h, s, v)) {
    return colorTools.light.background;
  } else if (colorTools.isDark(h, s, v)) {
    return colorTools.dark.background;
  } else {
    let rs = colorTools.isGray(h, s, v) ? 0 : s;
    return colorTools.convertHsvToHex(h, rs, colorTools.bgBrightness(h, s, v));
  } 
}

This was fun. Now, the colors on iCloud website are not strictly the same than in the iOS app, but that requires just a bit of tweaking with the constants.

You can download the complete JavaScript file I have extracted here:

Apple Calendar Colors

Some final words

There you have it. In the end I was actually surprised that it was pretty easy to extract these functions from the code. I expected the minified/obfuscated code to be quite hard to read. Interestingly, most function names did not get mangled. I suppose this is due to the fact that they are called through strings in some part of the framework Apple uses.

Of course, later I have converted these functions into Swift UIColor extensions so I can use them in my application.

I might publish them here sometime later, but if you are interested ask me on Twitter.

Thoughts about spam notifications in iOS

I try to keep my notifications minimal. Only those which are important should get through.

But when I receive notifications like this my blood boils:

Spam notification from Prime Video, for an episode I have already watched.

Amazon is not the sole culprit. When I received several notifications from The Fork I have confronted them on Twitter. They have promised to do something about it, and then a few days later I was spammed again… with the same notification.

The Fork is guilty too

Although this is forbidden by Apple App Store Review Guidelines, Design section 4.5.3, big firms do not care.

I think the main problem comes from the fact that these cost nothing and are invisible to apple. I think we should be able to report these to Apple, in the same way it is done for spam e-mail.

Where to put the report button?

Putting a button on each notification would be an overkill, it would be easy to hit it with a fat thumb.

It could go into the slide over or 3D touch menu in the notification center. But the problem is that if a notification pops up while the phone is unlocked, and you tap it by reflex, it gets a free pass. These are maddening.

My idea would be to put a section inside the Privacy tab. This view would display last few notifications you have received, with an option to report them to Apple and an option to ignore notifications with the same text from this application. At first, this would not have much impact, but over time Apple could use this data to pressure App publishers and maybe even start training Siri to act as a spam filter.

Pure CSS solution to retina images with a single file

Problem: You have an image and want it to display well in browsers on a high DPI display. There are many ways to do this, some of them more complicated than others.

My requirements were pretty simple:

  • I want to be able to publish “retina” images with default markdown syntax
  • I prefer images to be blurry on non-high DPI screens, they are blurry anyway so what’s the harm.
  • I do not care about wasted bandwidth by sending retina assets to clients with standard definition screens. Not having ads or huge javascript on this site makes up for this.
  • I do not want to use any javascript or server side hacks

So what’s the solution?

  1. Name @2x assets with @2x in their filename, for example image@2x.png
  2. Add this small snippet into my CSS file (thanks, Internet Explorer!)
img[src*='@2x'] {
  zoom:50%;
}

For an example usage look at my article about Qt Creator

Qt Creator Hidden Gems

When it comes to C++ IDEs I always reach for Qt Creator. I love its streamlined user interface which nevertheless exposes all screws and knobs if necessary.

But mostly I like how it manages to surprise me with every update.

Most recently I have updated to version 4.5.1 (from the 4.5 RC) and as I am hacking on the keyboard I see this:

QtCreator detects output parameters

Finally a work around the terrible decision to not explicitly mark function parameters used as a reference in C++.

What’s more, Qt Creator does support “relative” text style for this highlight. One saturation +0.30 later I have:

QtCreator can change text style relatively

Can’t wait for what the 4.6 will bring!

Setting File Permissions on Windows With C

Edit 2018-01-19

In my original article I have naively supposed that the group names are not localized. This is false. Always use the Well Known Security IDs when manipulating permissions on Windows.


I’ve spent good part of my morning trying to find a way how to set permissions to a particular group.

Finally while digging though Stack Overflow I have found this question which lead to another because of deprecation errors.

My final version of the code to add file read and write permissions to the group Users is this:

#include <Windows.h>
#include <AccCtrl.h>
#include <Aclapi.h>

const char* fileName = "C:/path/to/your_file.txt";

DWORD result = 0;

PACL pDACL = NULL;
PACL pOldDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea;
PSID pUsersSID = NULL;

const auto cleanup = [pDACL, pOldDACL, pSD, pUsersSID](){
  if (pOldDACL) LocalFree(pOldDACL);
  if (pDACL) LocalFree(pDACL);
  if (pSD) LocalFree(pSD);
  if (pUsersSID) FreeSid(pUsersSID);
};

result = GetNamedSecurityInfo(static_cast<LPTSTR>(fileName), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pOldDACL, NULL, &pSD);
if (result != ERROR_SUCCESS) {
  std::cerr << "Failed to get the security info [" << result << "]" << std::endl;
  cleanup();
  return EReturnCode_Error;
}

ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));

SID_IDENTIFIER_AUTHORITY SIDAuthNT = {SECURITY_NT_AUTHORITY};
if (!AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS, 0, 0, 0, 0, 0, 0, &pUsersSID)) {
  std::cerr << "Failed to allocate SID" << std::endl;
  cleanup();
  return EReturnCode_Error;
}

ea.grfAccessPermissions = FILE_GENERIC_WRITE | FILE_GENERIC_READ;
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea.Trustee.ptstrName = reinterpret_cast<LPTSTR>(pUsersSID);

result = SetEntriesInAcl(1, &ea, pOldDACL, &pDACL);
if (result != ERROR_SUCCESS) {
  std::cerr << "Failed to set entries in the access control list [" << result << "]" << std::endl;
  cleanup();
  return EReturnCode_Error;
}

result = SetNamedSecurityInfo(static_cast<LPTSTR>(fileName), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pDACL, NULL);
if (result != ERROR_SUCCESS) {
  std::cerr << "Failed to set the security info [" << result << "]" << std::endl;
  cleanup();
  return EReturnCode_Error;
}

cleanup();

Note: You will also need to link your program with Advapi32.lib.


My original code follows, do not use this:

const char* fileName = "C:/path/to/your_file.txt";
PACL pDACL = NULL;    
PACL pOldDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea;

GetNamedSecurityInfo(static_cast<LPTSTR>(fileName), SE_FILE_OBJECT, 
                     DACL_SECURITY_INFORMATION, NULL, NULL, 
                     &pOldDACL, NULL, &pSD);

ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));

char groupUsersName[6] = {"USERS"};
ea.grfAccessPermissions = FILE_GENERIC_WRITE | FILE_GENERIC_READ;
ea.grfAccessMode = SET_ACCESS;
ea.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
ea.Trustee.ptstrName = groupUsersName;

SetEntriesInAcl(1, &ea, pOldDACL, &pDACL);

SetNamedSecurityInfo(static_cast<LPTSTR>(fileName), SE_FILE_OBJECT, 
                     DACL_SECURITY_INFORMATION, NULL, NULL, pDACL, NULL);