Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

24.12.2024

Дев-адвент 24: форма додавання проби

Освіжив форму додавання проби — ліворуч те як вона виглядала вже багато місяців, праворуч перша ітерація покращень.

(Взагалі, за досвідом, ця форма з усіма тегами ледве влазить в екран телефону. Особливо якщо відкрити клавіатуру — тому в мене зараз пошук тегів відкривається окремим модалом. Наступним кроком хочу його прибрати.)

PS. Найважче в реалізації дизайнів на SwiftUI - це знати інструментарій. Мабуть, так само як і з CSS. Бо коли не знаєш, наприклад, що можна отримати системний колір третинного фону як .background.tertiary, або як все ж працює викладка елементів (зовсім не як в CSS!), то виходитиме складно та незграбно.


23.12.2024

Дев-адвент 23: графік розкладу тегу

🌶️ Ще один з гостро потрібних графіків — це схема тижневого розкладу тегу. Ідея в тому, що за ним можна зрозуміти, де справжній розклад не збігається з бажаним, та скоригувати.

🔘 В мене вже був такий графік, але я малював на ньому кожну пробу окремою крапочкою. За “хмарою” крапок можна було побачити ціле, але коли їх стає забагато, то вже нічого не зрозуміло — тому хотів замінити цей графік на теплову карту.

З першого погляду, це легко — підрахував проби за днями та годинами та й показуй — це як пряма заміна крапок на їхню кількість. Втім, як видно зверху справа, абсолютна кількість нам мало корисна. Краще відносна кількість, тобто як часто використовують цей тег в дану годину, порівняно з іншими тегами

Можемо витягнути відносну кількість з SQL одним запитом: COUNT(NULLIF(tagId != 123, TRUE))/CAST(COUNT(*) AS FLOAT). Взагалі останнім часом починаю роботу в клієнті SQLite, а потім вже переношу запити в застосунок — в SQL швидше виходить ітерувати.

🌚 Графік посередині — вже значно ясніше, з нього відразу зрозуміло, що вночі я сплю. Зліва — те ж саме, але я прибрав мітки з частотою до 5%. На мою думку, так менше шуму, але: з іншими тегами такої ж чіткості не буває, все через вільний графік.

До речі, якщо дивитися на частоту, то також обовʼязково обмежувати дані за часом, бо чим довше вибірка, тим більше розбіжність та менше буде відносна частота кожного тегу (окрім тих, що мають суворий графік, тобто відбуваються точно в однаковий час.) Тому можна показувати, скажімо, дані за останній місяць, а з решти збудувати тренд, що буде ще корисніше.


22.12.2024

Дев-адвент 22: дрібні, але важливі виправлення

Хоч мій трекер збудований на швидкому введенні тегів в несподіваний момент — мушу зізнатися, що вже довгий час (може, з пів року чи більше) сповіщення не відпрацьовували. Тобто, вони зʼявлялися, але перехід в застосунок відбувався без відкриття форми додавання проби. Ба більше, довгий час відкриття застосунку зі сповіщення взагалі викидувало через критичну помилку. 😳

З помилкою я вже цього місяця розібрався. Це була одна з тих бісячих ситуацій у Swift, коли застосунок отримує EXC_BREAKPOINT в асемблерному коді (тобто системному, без вихідного тексту) - та піди зʼясуй, в чому справа. Виявилося, що версія метода делегату didReceive з async не працює з моделлю рівночасності. (Класична проблема з офіційними API, на жаль.) Тому, контрінтуїтивно, повернувся до старої версії — з колбеком — та це мене врятувало. (На жаль, щоб це дізнатися, довелося, як в пазлі, пробувати всі комбінації по черзі.)

Але форма все ще не відкривалася. Причому код, що її відкриває зі сповіщення, був той самий, що й зі списку — тобто проблема була десь посередині. Думав, що в бізнес-логіці — ну, може, я погано шукаю пробу, яку треба відкрити. Може, не встигаю створити (бо створюються тільки проби за минуле, тому по відкриттю на сповіщення про зараз проба створюється “just-in-time”.)

Та виявилося, ні. Це була ще одне непорозуміння життєвого циклу, цього разу — всього застосунку. Бо застосунок у SwiftUI - це нащадок структури App. А в ній, як і в компонентах, в конструкторі стан @State ще не має привʼязки. Та якщо в конструкторі створити делегат сповіщень та передати йому той стан, що є — то він не зможе редагувати справжній стан.

Розвʼязалося все просто (коли вже знаєш, як воно працює!) Переніс створення делегату до обробника onAppear - тобто з конструктора в body. І все! Форма відкривається вчасно.


21.12.2024

Дев-адвент 21: що робити з часом?

Нарешті наважився запустити застосунок на телефоні — там, де справжні дані. (Для того потрібна була стабільна схема бази, щоб не страждати потім з міграціями.) Що можу сказати — чудові покращення! Залишилось додати трохи сполучної тканини, та можна випускати.

Але поки задумався: як же ж дійсно залучити зібрані дані, щоб краще витрачати свої 24 години, 365 днів? Бо я саме заради цього все роблю. Ось кілька думок.

PS. Знаєте Steam Replay - звіт про те, в що грав цього року? В мене він не дуже обʼєктивний — по-перше, обліковку ділю з сином, тому в топі ігор Celeste та Super Meat Boy. По-друге, не всі мої ігри були в Steam… Зате всі є в тезі 🎮 gaming! Хочу зробити аналогічний звіт, але з даних трекера.


20.12.2024

Дев-адвент 20: анімація з Transition

Так, дійсно, анімацію принципово легше зробити через вбудовані засоби. А саме .animation - для змін стану, та .transition - для появи та зникнення елементів. В моєму випадку, оскільки нові шари мапи саме зʼявляються, то тут потрібний .transition.

Порівняно з React+CSS, мені подобається, що видалений елемент автоматично зберігається аж доки не відпрацює перехід. Просто додаєш/прибираєш елемент з промальовки, та сам запускається перехід. Навіть помітив, що якщо на кінець переходу він не зникає з екрана через прозорість або позицію, то некрасиво зависає ще на пару секунд.

Для конфігурації Transition задається “активний (чомусь) стан”, а насправді стан “неіснування”. От в моєму випадку тег зʼявляється зі своєї позиції в попередньому батьку, тому код переходу виглядає так:

.transition(
    .offset(pathElement.offsetOfOriginalPosition)
    .combined(with: .scale(pathElement.scaleOfOriginalPosition))
    .combined(with: .opacity))

З хорошого — що в основі це працює чудово, навіть без всіх особливих налаштувань. З поганого — що все ж доводиться обчислювати відносне позиціювання та розміри, бо нутрощі мапи я малюю все ще через Canvas (а кожен рівень навігації є новим Canvas, з власними вкладеними тегами.) Ну й ще можна полірувати параметри анімації… нескінченно.


19.12.2024

Дев-адвент 19: дерево, тепер з анімацією

Сьогодні замотався з роботою, хотілося зробити щось веселе — а що може бути веселіше анімації? Занурився в анімаційні можливості SwiftUI та… щось навіть вийшло.

Як можна побачити зверху, я придумав ось такий спосіб навігації деревом: обираєш тег, він збільшується — але зберігає пропорції. Так легше відстежити звʼязок даних, ніж коли кожний вкладений тег розтягується (як тут) на весь екран. Тепер достатньо один раз викласти дерево, а вкладені рівні малювати суто через лінійні перетворення. Далі почалася сувора 2D-графіка без нормальних векторних типів…

Для такої задачі важливо не починати з кінця. Тому спочатку я тільки замінив логіку переходу, щоб малювати обрані теги зверху попереднього рівня — ще без масштабування та точно без анімації.

Найскладнішою частиною було правильно виконати перетворення, щоб тег зʼявлявся посередині екрану. Я обчислюю та запамʼятовую для кожного рівня зсув та масштаб відносно базової викладки (яку отримав ще на самому початку.) Їх потрібно врахувати й для малювання, й під час обробки натискань.

Це все вже нормально працює, а ось анімацію я тільки почав робити. Для неї є гарний компонент TimelineView - він перемальовує власний зміст за розкладом, хоч кожного кадру. Залишалося тільки досипати до моєї векторної алгебри ще й обчислення проміжного значення між “маленьким” та збільшеним розміром тегу. А ще проміжного значення прозорості фону. А ще лінійний перехід виглядає абсолютно ненатурально, та треба робити easing

Ото сиджу тепер та думаю, марно це все було. Треба було кожний шар робити окремим Canvas, позиціювати та масштабувати їх засобами SwiftUI, та анімувати їх теж через SwiftUI, як я колись робив пейджинатор. Тоді брудної роботи було б значно менше.


18.12.2024

Дев-адвент 18: життєвий цикл у SwiftUI

😵‍💫 Розбирався з ситуацією, що після зміни налаштувань не оновлюються запити, які ті налаштування зчитують. (Не з бази зчитують, а зі змінних.) Для того довелося все ж вивчити життєвий цикл компонент у SwiftUI. Знайшов прегарну книжку Thinking in SwiftUI (це справжній “посібник, який забули покласти”!) та з її допомогою вдалося второпати.

(Примітка. Я звик до реактивного UI в моделі React, особливо — з хуками. Все ще вважаю, що цей API, разом з лінтером для Rules of Hooks, найкращий у своєму класі. Там в мене ніколи не стає питання, що саме буде виконано при рендері, коли будуть викликані хуки, та який стан є тимчасовим, а який — сталим. У SwiftUI не все так гарно.)

😶‍🌫️ У SwiftUI компоненти є нащадками структури View. Недосвідченому у Swift програмісту може здатись, що наявність структури робить компонент сталою сутністю, бо це “щось на кшталт класу”. Але така інтуїція неправильна, бо навпаки, структура у Swift - це “тип-значення”, а відповідно, нащадки View не здатні зберігати стан.

🏭 Натомість кожен View є “фабрикою” для компонент. Центром її є атрибут body - він аналогічний до функціональної компоненти в React. А все, що оголошено поза body, є допоміжною логікою. Головним чином, ця логіка встановлює привʼязку до стану. Як і в React, стан зберігається… “десь там”, а ми до нього можемо звернутися — обгорткою @State тощо. Важливо памʼятати, що весь зміст View - це тільки аргументи до функції рендеру body, та вони не є сталими.

Ось такий цікавий патерн Swift - структура є фактично замиканням навколо функції та її параметрів. А тільки що відкрив вікіпедію та побачив, що таке використання буквально називається замиканням! А я-то думав, що замиканням може бути тільки функція.

💣 А тепер, підступ. Під час виконання конструктора View ще не має доступу до реального стану! Бо це ж тільки фабрика будується, а не компонент. Тому в конструкторі можна задати тільки початковий стан @State, або, наприклад @Query з GRDB. Це зовсім не очевидно, бо конструктор дійсно викликається на кожну перемальовку, та в ньому можна присвоїти значення для змінної @State, але працювати воно не буде. Та ані компілятор, ані лінтер не попереджають.

🪨 В моєму випадку з запитами знайшовся режим Query(constant:in:). В ньому, як я це розумію, замість стану, який можна змінювати, запит стає “константою” - а це значить, парадоксально, що коли його параметри в конструкторі змінюються, то й результат теж — а не тільки початковий стан. Таке.


17.12.2024

Дев-адвент 17: часограми

🏡 Поки задоволений головним екраном. На ньому ще колись зʼявляться навчальні підказки. Та ще гарно було б в понеділок бачити підсумки попереднього тижня — бо тільки по закінченню тижня його результати стають повними, та поки в цей самий момент вони зникають з виду.

🌈 Поставив у стовпчик часограми для багатьох тижнів і вони почали складатися в щось цікаве! Мені такий вигляд більше подобається, ніж лінійна діаграма. Взагалі в часограмах ще виводяться емодзі. Та так, придумав для цих смужок назву “часограма”.

𝓱 Придумав, щоб показувати приблизне значення годин, але підкреслити, що воно саме приблизне, писати не “h”, а “𝒽”. Ця дрібничка важлива для усвідомлення. Десь я такий спосіб бачив в математиці або фізиці, але не можу згадати, де. До речі, цей символ з блоку математичних символів в Unicode, та там ще багато “стильних” креслень для всіх латинських літер: “𝐡 ℎ 𝒉 𝗁 𝗵 𝘩 𝙝 𝒽 𝓱 𝔥 𝖍 𝚑 𝕙”, які доступні в більшості якісних шрифтів.


16.12.2024

Дев-адвент 16: домашній екран, та "спектрограма"

Поступово заповнюю домашній екран.

❎ Блок з незаповненими пробами — це дуже корисно (порівняно з просто список всіх проб, як в мене було.) Не тільки тому, що їх гарно видно, а й тому, що я можу приховати ті проби, які вже точно не зможу заповнити, та вони більше не мулятимуть очі. Бо зі списком я без кінця переглядав останні проби, щоб не забути про щось.

📝 Також зʼявилися нові ідеї про форму проби: там гарно було б показувати попередню та наступну проби, якщо такі є та заповнені. В мене вже попередня показується — бо зручно копіювати з неї теги, якщо вони повторюються. Але коли заповнюєш пропущені, то й наступна теж важлива, а також відстань між ними.

⚖️ А з показом поточної інформації доведеться йти по тонкому краю: з одного боку, хочеться бути корисним; з іншого — вся наша інформація приблизна, та чим “поточніше” вона, тим й приблизніша. Думаю, не буде нічого поганого, якщо просто показувати, скільки цього тижня було проб обраних тегів.

🤏 Тому мені подобається оцей новий прогрес-бар, який можна бачити вище. 100% на ньому — це тиждень. Сьогодні понеділок, отже, він майже не заповнений. Всі теги за сьогодні втиснуті в вузький простір. Це наче й не наочно, але: інформація за один день все одно статистично не значуща. Не треба її детально розглядувати. Чим більше заповниться прогрес-бар, тим більше буде на що дивитися.

(PS: вже зробив можливість виключити з графіків сон та незаповнені проби. А ще прогрес-бар наповнюється тією ж логікою, що й деревна мапа — зручно!)

🌈 Чим мені ще подобається така смуга — з неї можна зробити історичний перегляд — якщо скласти вертикально смуги для кожного тижня. А ще вона нагадує спектрограму (ту, що зі світла, а не зі звуку.) Та й за сенсом така собі “спектрограма життя”, га?


15.12.2024

Дев-адвент 15: домашній екран

👋 Займаюся дизайном головного екрану. По цей день мій стохастичний таймтрекер відкривається на список останніх проб. Частіше за все я відкриваю таймтрекер, щоб заповнити пропущені проби, тому це логічно. Але не дуже зручно: зокрема, старіші пропущені просто губляться, хоча інколи є можливість їх відновити. (Часто я просто маю змогу записати пробу в моменті та тримаю в голові на майбутнє.)

⚠️ Та й взагалі, список то сумний та нецікавий головний екран. А щоб зробити краще, треба спочатку зрозуміти потреби користувача. Одну з них я вже згадав — це заповнення пропущених проб. Тільки краще виводити лише пропущені, щоб їх не шукати. Та ще й наголосити візуально.

📌 Також, нарешті, треба придумати, як витягати користь з результатів, щодня. Це складніше, ніж здається. На щастя, в мене вже є бізнес-канва з цілями та проблемами користувачів. З цього (та власного досвіду!) придумав можливість “закріпити” тег та бачити його статистику на головному екрані.

💹 Ну й нарешті, хотілося б графік якийсь, щоб було яскраво. Можливо, це буде та сама деревна мапа поточного тижня, тільки стиснута. А ще є ідея її “лінеаризувати”, тобто перетворити на такий собі прогрес-бар.