{"id":1198,"date":"2025-05-20T13:20:19","date_gmt":"2025-05-20T10:20:19","guid":{"rendered":"https:\/\/www.juustila.com\/antti\/?p=1198"},"modified":"2025-05-20T13:24:34","modified_gmt":"2025-05-20T10:24:34","slug":"plz-halp-dedlinz","status":"publish","type":"post","link":"https:\/\/www.juustila.com\/antti\/2025\/05\/20\/plz-halp-dedlinz\/","title":{"rendered":"Plz halp dedlinz"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Apparently I like coding with the Course Deadline Counter app, now renamed to <a href=\"https:\/\/github.com\/anttijuu\/CourseDeadlineCounter\">Course Deadlines app<\/a>. I really, really should stop doing this, and focus on something else more acute and important &#8212; that&#8217;s why the call for help. I need to be stopped!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[<em>I am aware of this being my own responsibility, thank you so much, so plz do not send halp.<\/em>]<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As a new feature, the app now has a timeline View:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"806\" src=\"https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/timeline-view-1024x806.png\" alt=\"A screenshot of a timeline view. Image has course names, under each deadlines, so that the deadlines are placed on a timescale advancing from left to right. Deadlines have a goal and date time, as well as a symbol.\" class=\"wp-image-1199\" srcset=\"https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/timeline-view-1024x806.png 1024w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/timeline-view-300x236.png 300w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/timeline-view-768x604.png 768w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/timeline-view-1536x1208.png 1536w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/timeline-view-1200x944.png 1200w, https:\/\/www.juustila.com\/antti\/wp-content\/uploads\/2025\/05\/timeline-view.png 2016w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Where you can view the deadlines of courses currently ongoing. The view does not show courses that haven&#8217;t yet begun or courses where the last deadline already passed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a student user, viewing all ongoing courses is more important, to be able to view the deadlines of the courses she is currently taking and how they relate between all the work she needs to do. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a teacher, it is more relevant to show the deadlines to students in a single course. So limiting the view to just that one course is more important, using the drop down list at the top of the view.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Considering the needs and usage patterns of different groups of users is taught in our GUI design and programming course (Programming 4). So obviously I have to be able to do this stuff myself. Consider that done, at least in this case.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For drawing the course and course deadlines, I used the SwiftUI <code>Canvas<\/code> and for the gradient background, <code>MeshGradient<\/code>. I should still test the colors with both light and dark modes and find a usable combination of colors and an overlay to mute the colors when and if they are in disagreement with the text.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Drawing on the canvas is just simple drawing operations using the <code>GraphicsContext<\/code>, plotting the text and symbols on calculated coordinates. For example, if there are no ongoing courses, the code draws only the text &#8220;No courses to show&#8221;, in the middle of the view:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Canvas { context, size in\n\n   var origin = CGPoint(x: 0, y: 0)\n   let text = Text(\"No courses to show\").font(.title).bold()\n   let resolved = context.resolve(text)\n\n   if coursesToPlot.isEmpty {\n      \/\/ Draw info that there is no ongoing courses to show\n      var textSize = size\n      textSize = resolved.measure(in: size)\n      origin.x = size.width \/ 2 - textSize.width \/ 2\n      origin.y = size.height \/ 2 - textSize.height \/ 2\n      let rect = CGRect(origin: origin, size: textSize)\n      context.draw(resolved, in: rect)\n   } else {\n\/\/ ...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">With the <code>Picker<\/code>, choosing to show all courses or just one, I searched for different solutions from the internet, and chose this one:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@State private var selectedCourse: Course? = nil\n\/\/ ...\nPicker(\"Show ongoing courses\", selection: $selectedCourse) {\n   Text(\"All\").tag(Optional&lt;Course&gt;(nil))\n   ForEach(deadlines.ongoing, id: \\.self) { course in\n      Text(course.name).tag(Optional(course))\n   }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">So there is the &#8220;show all&#8221; option where the course selection is then set to <code>nil<\/code>. Otherwise, the selection is the selected course. This enables &#8220;no selection&#8221; or as it is handled in this case, &#8220;all are selected&#8221;.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I think I got to use the Swift <a href=\"https:\/\/docs.swift.org\/swift-book\/documentation\/the-swift-programming-language\/controlflow#If\"><code>if<\/code> expression<\/a> (different from the usual <code>if<\/code> conditional statement) here the first time, handling the above picker selection:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>let coursesToPlot = if let selectedCourse {\n   &#91;selectedCourse]\n} else {\n   deadlines.ongoing.sorted(by: { $0.startDate &lt; $1.startDate } )\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In plain English: if the <code>selectedCourse<\/code> is not null, let the <code>coursesToPlot<\/code> be an array containing only the <code>selectedCourse<\/code>. Otherwise, get all the ongoing courses from the <code>deadlines<\/code>, sorted by starting date ascending, and let the <code>coursesToPlot<\/code> be those courses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The next time I touch this app should really be in the end of August or beginning of September, when I decide on the deadlines of the courses I am responsible for.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the other news, I was today awarded as the Distinguished Teacher of the Year (again) by the study program student guild Blanko. Thank you!  I am so humbled to receive this appreciation for the work we are doing here.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Apparently I like coding with the Course Deadline Counter app, now renamed to Course Deadlines app. I really, really should stop doing this, and focus on something else more acute and important &#8212; that&#8217;s why the call for help. I need to be stopped! [I am aware of this being my own responsibility, thank you &hellip; <a href=\"https:\/\/www.juustila.com\/antti\/2025\/05\/20\/plz-halp-dedlinz\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Plz halp dedlinz&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[2],"tags":[182,185,186,184,70,46,47],"class_list":["post-1198","post","type-post","status-publish","format-standard","hentry","category-coding","tag-canvas","tag-if-expression","tag-mesh-gradient","tag-picker","tag-programming","tag-swift","tag-swiftui"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/posts\/1198","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/comments?post=1198"}],"version-history":[{"count":2,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/posts\/1198\/revisions"}],"predecessor-version":[{"id":1202,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/posts\/1198\/revisions\/1202"}],"wp:attachment":[{"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/media?parent=1198"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/categories?post=1198"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.juustila.com\/antti\/wp-json\/wp\/v2\/tags?post=1198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}