func appMain(driver gxui.Driver) {
	theme := flags.CreateTheme(driver)

	layout := theme.CreateLinearLayout()
	layout.SetSizeMode(gxui.Fill)

	addLabel := func(text string) {
		label := theme.CreateLabel()
		label.SetText(text)
		layout.AddChild(label)
	}

	addLabel("1. Fetch certificates from websites")
	addLabel("Insert IP or domain name here and press 'Fetch' button")

	urlTextBox := theme.CreateTextBox()
	urlTextBox.SetDesiredWidth(300)

	fetchButton := theme.CreateButton()
	fetchButton.SetText("Fetch")
	fetchButton.SetVisible(false)

	{
		lineLayout := theme.CreateLinearLayout()
		lineLayout.SetDirection(gxui.LeftToRight)
		lineLayout.AddChild(urlTextBox)
		lineLayout.AddChild(fetchButton)
		layout.AddChild(lineLayout)
	}

	statusLabel := theme.CreateLabel()
	statusLabel.SetMultiline(true)
	statusLabel.SetText("")
	layout.AddChild(statusLabel)

	certListAdapter := gxui.CreateDefaultAdapter()
	certList := theme.CreateList()
	certList.SetAdapter(certListAdapter)

	removeButton := theme.CreateButton()
	removeButton.SetText("Remove")

	{
		lineLayout := theme.CreateLinearLayout()
		lineLayout.SetDirection(gxui.LeftToRight)
		lineLayout.AddChild(certList)
		lineLayout.AddChild(removeButton)
		layout.AddChild(lineLayout)
	}

	addLabel("2. Select programmer serial port")

	portListAdapter := gxui.CreateDefaultAdapter()
	portList := theme.CreateList()
	portList.SetAdapter(portListAdapter)

	refreshButton := theme.CreateButton()
	refreshButton.SetText("Refresh List")

	{
		lineLayout := theme.CreateLinearLayout()
		lineLayout.SetDirection(gxui.LeftToRight)
		lineLayout.AddChild(portList)
		lineLayout.AddChild(refreshButton)
		layout.AddChild(lineLayout)
	}

	addLabel("3. Upload certificate to WiFi module")

	uploadButton := theme.CreateButton()
	uploadButton.SetText("Upload certificates")
	layout.AddChild(uploadButton)

	progressStatus := theme.CreateLabel()
	layout.AddChild(progressStatus)

	progressBar := theme.CreateProgressBar()
	size := math.MaxSize
	size.H = 20
	progressBar.SetDesiredSize(size)
	layout.AddChild(progressBar)

	// Business logic

	portSelected := false
	updateUploadButton := func() {
		visible := portSelected && certListAdapter.Count() > 0
		uploadButton.SetVisible(visible)
	}
	updateUploadButton()

	updateDownloadedCerts := func() {
		certListAdapter.SetItems(downloadedCerts)
		updateUploadButton()
	}

	downloadCert := func() {
		if uploading {
			return
		}
		url := urlTextBox.Text()
		if strings.Index(url, ":") == -1 {
			url += ":443"
		}
		data, err := certificates.EntryForAddress(url)
		if err != nil {
			log.Println("Error downloading certificate. " + err.Error())
			statusLabel.SetText("Error downloading certificate. " + err.Error())
			return
		} else {
			statusLabel.SetText("Download successful")
		}
		cert := &Cert{
			Label: url,
			Data:  data,
		}
		downloadedCerts = append(downloadedCerts, cert)
		urlTextBox.SetText("")
		updateDownloadedCerts()
	}

	urlTextBox.OnTextChanged(func([]gxui.TextBoxEdit) {
		isEmpty := (urlTextBox.Text() == "")
		fetchButton.SetVisible(!isEmpty)
	})
	urlTextBox.OnKeyPress(func(event gxui.KeyboardEvent) {
		char := event.Key
		if char == gxui.KeyEnter || char == gxui.KeyKpEnter {
			isEmpty := (urlTextBox.Text() == "")
			if !isEmpty {
				downloadCert()
			}
		}
	})

	fetchButton.OnClick(func(gxui.MouseEvent) {
		downloadCert()
	})

	removeButton.OnClick(func(gxui.MouseEvent) {
		if uploading {
			return
		}
		selected := certList.Selected()
		i := certList.Adapter().ItemIndex(selected)
		downloadedCerts = append(downloadedCerts[:i], downloadedCerts[i+1:]...)
		updateDownloadedCerts()
		removeButton.SetVisible(false)
	})
	certList.OnSelectionChanged(func(gxui.AdapterItem) {
		removeButton.SetVisible(true)
	})
	removeButton.SetVisible(false)

	refreshPortList := func() {
		if uploading {
			return
		}
		if list, err := serial.GetPortsList(); err != nil {
			log.Println("Error fetching serial ports" + err.Error())
		} else {
			portListAdapter.SetItems(list)
		}
	}
	refreshPortList()

	refreshButton.OnClick(func(gxui.MouseEvent) {
		refreshPortList()
		portSelected = false
		updateUploadButton()
	})
	portList.OnSelectionChanged(func(gxui.AdapterItem) {
		portSelected = true
		updateUploadButton()
	})

	updateProgress := func(msg string, percent int) {
		time.Sleep(time.Second)
		driver.CallSync(func() {
			if percent == -1 {
				progressStatus.SetColor(gxui.Red)
				progressBar.SetVisible(false)
			} else if percent == 100 {
				progressStatus.SetColor(gxui.Green)
				progressBar.SetVisible(false)
			} else {
				progressStatus.SetColor(gxui.White)
				progressBar.SetProgress(percent)
				progressBar.SetVisible(true)
			}
			progressStatus.SetText(msg)
		})
	}
	progressBar.SetVisible(false)

	uploadButton.OnClick(func(gxui.MouseEvent) {
		if uploading {
			return
		}
		port := portList.Selected().(string)
		uploading = true
		go uploadCertificates(port, driver, updateProgress)
		log.Println(port)
	})

	updateDownloadedCerts()

	window := theme.CreateWindow(800, 600, "Linear layout")
	window.SetTitle("WINC1500 SSL Certificate updater")
	window.SetScale(flags.DefaultScaleFactor)
	window.AddChild(layout)
	window.OnClose(driver.Terminate)
	window.SetPadding(math.Spacing{L: 10, T: 10, R: 10, B: 10})
	window.Relayout()
}