From 3776e85bc0e59c280075130cfd241de1f7072ba0 Mon Sep 17 00:00:00 2001 From: Nyeogmi Date: Sat, 1 Jul 2023 20:55:45 -0700 Subject: [PATCH] Initial commit --- .gitignore | 40 +++++++++ .idea/encodings.xml | 7 ++ .idea/kotlinc.xml | 6 ++ .idea/misc.xml | 13 +++ .idea/vcs.xml | 6 ++ .idea/workspace.xml | 115 +++++++++++++++++++++++++ pom.xml | 104 +++++++++++++++++++++++ src/main/kotlin/AppModel.kt | 53 ++++++++++++ src/main/kotlin/AppView.kt | 125 ++++++++++++++++++++++++++++ src/main/kotlin/ButtonArt.kt | 42 ++++++++++ src/main/kotlin/ImageButton.kt | 63 ++++++++++++++ src/main/kotlin/Main.kt | 71 ++++++++++++++++ src/main/kotlin/Util.kt | 16 ++++ src/main/resources/demoIcon.png | Bin 0 -> 22380 bytes src/main/resources/move_lit.png | Bin 0 -> 1659 bytes src/main/resources/move_pressed.png | Bin 0 -> 1204 bytes src/main/resources/move_unlit.png | Bin 0 -> 1213 bytes 17 files changed, 661 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 pom.xml create mode 100644 src/main/kotlin/AppModel.kt create mode 100644 src/main/kotlin/AppView.kt create mode 100644 src/main/kotlin/ButtonArt.kt create mode 100644 src/main/kotlin/ImageButton.kt create mode 100644 src/main/kotlin/Main.kt create mode 100644 src/main/kotlin/Util.kt create mode 100644 src/main/resources/demoIcon.png create mode 100644 src/main/resources/move_lit.png create mode 100644 src/main/resources/move_pressed.png create mode 100644 src/main/resources/move_unlit.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5dcb755 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +*.kt.old + +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..942f3a2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..b1077fb --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e8d124d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..835d7ad --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + "keyToString": { + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "SHARE_PROJECT_CONFIGURATION_FILES": "true", + "last_opened_file_path": "C:/Nyeogit/taskzap/taskzap/src/main/resources" + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1688177283964 + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..58190bf --- /dev/null +++ b/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + taskzap + app.chromaticdragon + 1.0-SNAPSHOT + jar + + consoleApp + + + UTF-8 + official + 1.8 + + + + + mavenCentral + https://repo1.maven.org/maven2/ + + + + + src/main/kotlin + src/test/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + 1.7.10 + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + maven-surefire-plugin + 2.22.2 + + + maven-failsafe-plugin + 2.22.2 + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + MainKt + + + + org.apache.maven.plugins + maven-compiler-plugin + + 6 + 6 + + + + + + + + org.jetbrains.kotlin + kotlin-test-junit5 + 1.7.10 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.8.2 + test + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + 1.7.10 + + + org.jetbrains.kotlinx + kotlinx-datetime-jvm + 0.4.0 + + + + \ No newline at end of file diff --git a/src/main/kotlin/AppModel.kt b/src/main/kotlin/AppModel.kt new file mode 100644 index 0000000..2da57e7 --- /dev/null +++ b/src/main/kotlin/AppModel.kt @@ -0,0 +1,53 @@ +import kotlinx.datetime.LocalDateTime +import java.awt.image.BufferedImage +import java.util.* +import kotlin.collections.HashMap + +class AppModel { + var version: Long = 0 + private set + private var tasks: HashMap = HashMap() + + fun updateTask(task: TaskRecord): TaskRecord? { // returns the old version + version += 1 + val old = tasks[task.uuid] + tasks[task.uuid] = task + return old + } + + fun getTask(uuid: UUID): TaskRecord? { + return tasks[uuid] + } + + fun removeTask(uuid: UUID): TaskRecord? { + version += 1 + return tasks.remove(uuid) + } + + fun getTasksByBlinkAt(): ArrayList { + val list = ArrayList() + for (t in tasks) { list.add(t.value) } + list.sortBy { t -> t.blinkAt } + return list + } +} + +class TaskRecord( + val uuid: UUID, + val icon: IconRecord, + val task: String, + val link: LinkRecord?, + val createdAt: LocalDateTime, + val blinkAt: LocalDateTime, +) + +class LinkRecord( + val icon: IconRecord, + val window: Int, + val pid: Int, + val filename: String, +) + +class IconRecord(val image: BufferedImage) { + val cachedButtonArt = ButtonArt.fromArbitraryImage(image) // don't persist this, just have it always ready +} \ No newline at end of file diff --git a/src/main/kotlin/AppView.kt b/src/main/kotlin/AppView.kt new file mode 100644 index 0000000..7147808 --- /dev/null +++ b/src/main/kotlin/AppView.kt @@ -0,0 +1,125 @@ +import java.awt.* +import javax.swing.BoxLayout +import javax.swing.JDialog +import javax.swing.JPanel +import javax.swing.border.EmptyBorder + +private val GRID_SIZE = 64; +private val GRID_SIZE_INNER = 56; +private val GRID_INSET = (GRID_SIZE - GRID_SIZE_INNER) / 2 + +class AppView(model: AppModel): JDialog() { + private val viewModel = AppViewModel(model) + + private val controlPanel = buildControlPanel() + private val iconPanel = JPanel() + // TODO: Replace these with icons that have clear meaning + + init { + isAlwaysOnTop = true + isUndecorated = true + background = TRANSPARENT + bounds = Rectangle(0, 0, Toolkit.getDefaultToolkit().screenSize.width, 64) + + contentPane.layout = BorderLayout() + contentPane.add(controlPanel, BorderLayout.WEST) + contentPane.add(iconPanel, BorderLayout.CENTER) + + iconPanel.layout = FlowLayout(FlowLayout.LEFT, 0, 0) + + controlPanel.background = TRANSPARENT; + iconPanel.background = TRANSPARENT; + } + + fun touch() { + viewModel.ifDirty(contentPane) { + val ipHeight = iconPanel.height + val iconSize = ipHeight * 3/4; + val meterHeight = ipHeight - iconSize; + + iconPanel.removeAll() + + val tasks = viewModel.model.getTasksByBlinkAt() + for (task in tasks) { + iconPanel.add(TaskView(task)) + } + } + } + + private fun buildControlPanel(): JPanel { + val moveButton = ImageButton( + ButtonArt( + getImage("/move_unlit.png")!!, + getImage("/move_lit.png")!!, + getImage("/move_pressed.png")!!, + ), + null + ) + // TODO: Do something + moveButton.preferredSize = Dimension(GRID_SIZE, GRID_SIZE) + moveButton.border = EmptyBorder(GRID_INSET, GRID_INSET, GRID_INSET, GRID_INSET) + moveButton.callback = { print("test") } + + val controlPanel = JPanel() + controlPanel.layout = BoxLayout(controlPanel, BoxLayout.PAGE_AXIS) + + controlPanel.add(moveButton) + + return controlPanel + } +} + +// for now: just reexpose the underlying model +// we don't have a layer of state beyond what can be kept in the UI +class AppViewModel(val model: AppModel) { + private var cpHeight: Int = -1 + private var version: Long = -1 + + fun ifDirty(contentPane: Container, body: () -> Unit) { + if (version != model.version || contentPane.height != cpHeight) { + body() + cpHeight = contentPane.height + version = model.version + } + } +} + +class TaskView( + private val task: TaskRecord, +): JPanel() { + private var button: ImageButton + init { + layout = BorderLayout() + preferredSize = Dimension(GRID_SIZE, GRID_SIZE) + border = EmptyBorder(GRID_INSET, GRID_INSET, GRID_INSET, GRID_INSET) + background = TRANSPARENT + + button = ImageButton( + task.icon.cachedButtonArt, + null + ) + + add(button) + } + + /* + override fun paintComponent(gnull: Graphics?) { + super.paintComponent(gnull) + + val g = gnull!! + + // icon occupies top of space + g.drawImage(task.icon.image, 0, 0, iconSize, iconSize, Color(0, 0, 0, 0)) { _, _, _, _, _, _ -> false } + + // bar occupies middle 1/2 of space + val realBarHeight = meterHeight / 2 + val barY = iconSize + (meterHeight - realBarHeight)/2 + val arcSize = realBarHeight / 2 + val barX = arcSize + val realBarWidth = iconSize - arcSize * 2 + + g.color = Color(0, 255, 0, 255) + g.fillRoundRect(barX, barY, realBarWidth, realBarHeight, arcSize, arcSize) + } + */ +} \ No newline at end of file diff --git a/src/main/kotlin/ButtonArt.kt b/src/main/kotlin/ButtonArt.kt new file mode 100644 index 0000000..6f1ee37 --- /dev/null +++ b/src/main/kotlin/ButtonArt.kt @@ -0,0 +1,42 @@ +import java.awt.Graphics2D +import java.awt.image.BufferedImage +import java.awt.image.RescaleOp + +// val MAX_SIZE: Int = 256 + +// TODO: Enforce MAX_SIZE +// TODO: Cookie cutter out a roundrect + +class ButtonArt( + val base: BufferedImage, + val highlighted: BufferedImage, + val pressed: BufferedImage +) { + companion object { + fun fromArbitraryImage(base: BufferedImage): ButtonArt { + return ButtonArt( + base, + base.toHighlighted(), + base.toPressed(), + ) + } + } +} + +private fun BufferedImage.toHighlighted(): BufferedImage { + val highlighted = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + val g = highlighted.graphics as Graphics2D + val op = RescaleOp(0.75f, 128f, null) + g.drawImage(this, 0, 0, width, height, 0, 0, width, height, null) + op.filter(highlighted, highlighted) + return highlighted +} + +private fun BufferedImage.toPressed(): BufferedImage { + val pressed = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + val g = pressed.graphics as Graphics2D + val op = RescaleOp(2.0f, 0.0f, null) + g.drawImage(this, 0, 0, width, height, 0, 0, width, height, null) + op.filter(pressed, pressed) + return pressed +} diff --git a/src/main/kotlin/ImageButton.kt b/src/main/kotlin/ImageButton.kt new file mode 100644 index 0000000..49813b9 --- /dev/null +++ b/src/main/kotlin/ImageButton.kt @@ -0,0 +1,63 @@ +import java.awt.Dimension +import java.awt.Graphics +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import java.awt.event.MouseListener +import java.awt.image.BufferedImage +import javax.swing.JPanel + +class ImageButton( + private val art: ButtonArt, + var callback: (() -> Unit)?, +): JPanel() { + private var mouseOver = false + private var mouseDown = false + + init { + background = TRANSPARENT + preferredSize = Dimension(art.base.width, art.base.height) + this.addMouseListener(object: MouseAdapter() { + override fun mouseEntered(e: MouseEvent?) { + super.mouseEntered(e) + mouseOver = true + repaint() + } + + override fun mouseExited(e: MouseEvent?) { + super.mouseExited(e) + mouseOver = false + repaint() + } + + override fun mousePressed(e: MouseEvent?) { + super.mousePressed(e) + mouseDown = true + repaint() + } + + override fun mouseReleased(e: MouseEvent?) { + super.mouseReleased(e) + mouseDown = false + repaint() + } + + override fun mouseClicked(e: MouseEvent?) { + super.mouseClicked(e) + callback?.invoke() + } + }) + } + + override fun paintComponent(gnull: Graphics?) { + super.paintComponent(gnull) + + val g = gnull!! + val img = if (mouseDown) { art.pressed } else if (mouseOver) { art.highlighted } else { art.base } + + val insets = insets + val dx = width - (insets.left + insets.right) + val dy = height - (insets.top + insets.bottom) + + g.drawImage(img, insets.left, insets.top, dx, dy, null) + } +} \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt new file mode 100644 index 0000000..879dea2 --- /dev/null +++ b/src/main/kotlin/Main.kt @@ -0,0 +1,71 @@ +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent +import java.awt.image.BufferedImage +import javax.swing.JFrame +import javax.swing.Timer +import kotlin.system.exitProcess +import kotlinx.datetime.* +import kotlinx.datetime.TimeZone +import java.awt.* +import java.io.ByteArrayInputStream +import java.util.* +import javax.imageio.ImageIO +import javax.swing.JButton +import javax.swing.JLabel +import javax.swing.JPanel + +fun main(args: Array) { + val app = App() + + app.start() +} + +class App { + private var model = AppModel() + + private var running: Boolean = false + private val worker = createWorker() + private val appView = createAppView() + + fun start() { + running = true + worker.start() + + val demoIcon = getImage("/demoIcon.png")!! + + for (i in 0..2) { + model.updateTask( + TaskRecord( + uuid = UUID.randomUUID(), + icon = IconRecord(demoIcon), + task = "FIX THE DOG", + link = null, + createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()), + blinkAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + ) + ) + } + } + + private fun createWorker(): Timer { + return Timer(33) { onFrame() } + } + + private fun createAppView(): AppView { + val av = AppView(model) + av.addWindowListener(object: WindowAdapter() { + override fun windowClosing(e: WindowEvent?) { + running = false + } + }) + + return av + } + + private fun onFrame() { + if (!running) { exitProcess(0) } + + if (running) appView.isVisible = true + appView.touch() + } +} diff --git a/src/main/kotlin/Util.kt b/src/main/kotlin/Util.kt new file mode 100644 index 0000000..9f84240 --- /dev/null +++ b/src/main/kotlin/Util.kt @@ -0,0 +1,16 @@ +import java.awt.AlphaComposite +import java.awt.Color +import java.awt.Composite +import java.awt.Graphics2D +import java.awt.image.BufferedImage +import java.awt.image.RescaleOp +import java.io.ByteArrayInputStream +import java.net.URL +import javax.imageio.ImageIO + +val TRANSPARENT: Color = Color(0, 0, 0, 0) + +fun ByteArray.toImage(): BufferedImage = ImageIO.read(ByteArrayInputStream(this)) +fun getImage(name: String): BufferedImage? = getResource(name)?.readBytes()?.toImage() +fun getResource(name: String): URL? = App::class.java.getResource(name) + diff --git a/src/main/resources/demoIcon.png b/src/main/resources/demoIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..06b1d7bbd32d2a736872e8918bcb03611c75be68 GIT binary patch literal 22380 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/move_lit.png b/src/main/resources/move_lit.png new file mode 100644 index 0000000000000000000000000000000000000000..a9e4932cf0edf4e96dee5f8e95339b262c17d94e GIT binary patch literal 1659 zcmZWpYgAKb7X5C#G2A2~1PG#7(n!K9gjaRY0SP1_U<6_s1p1IbMIH(S49JL-3-u8M zOQ=YoJZph60s_U3SYClJh{OTJ(Si?55P{JZ!FB{nqBQ;8AN%aH&N*xU*=wHyK`3tv zemfojuq8N%BQ!GlCrmaQt7?CY+DLG`e~3Q-9mNDg3>ElJwMLi6`O! za4g9w@eNW5EYF2~Tu8OT{at2qtSOYtDU_BvlLrD4U2eP-|2pZHFe$I^^2T4s;&RE& zrp&RQ_jTd(HCtuPg_o}umS#l@6U?8VyRG+Ls!?UHLIItlqRx1{V7EHRVK$c8@4V7S zMLpKc*rM3Rn!Ai++NkK&{|0ARYolgzav!2XcN>jG!9saTd3ewoH0P8uA- z#Vz147$y}Hjr?F7*+{vOQg&)`a$JfOu+!sWq;W?nrxK+J6kc$MAR^Dy+Gu}3Fvnk% zbwpR4obs4Ou&aYnl7blke9$MPr8?oAs_%Jc*wer+RsvlF)i_XUJ?oJ_=VG6WU+7Op?Coz`M+;A&hv!tq=}MD6w(`y zIKg5(jJ(fGE~`;v#R!$)QwcX<998`n1=ao);K8Mcp4nPmR0X#HT-oB47vE)DPY|z- zTeXbP!_kYIqoUt16kmM=5p%mQRzx+J$fqY(%5N3l9VIeZhj%^kUJX~2Jhk~fcazul zDeL66PWd=(kdA6^2cN%7qJkeaU6I+XXGcH!42Zt#mBd`*9^K&#uCfl6=0sc?^FD|! z4z5!NBR;6ekbx4c_IYIoTQq+GD|0@&x$Fs(`lBC%m7<<;-Iu4rQ2gp;DVdK>n@`)3 zHKZiYhxavg`hrm(4&bB1?=s#wt|`J*a67!0#2MUbtt5$Qvh!Qbjcm_SUO>c1ER8kL zFGAk!@K+2*__Y|dGB&cM58iT?a-T&nhG=#z-P58y5aQoqv+ON9IJRT zr(?z2h4AGyPy;`nQ=hG6qO>em|2+m0#2EMjan>k|>U{?U*nSb%m0K0B`C7L8kD=VM&w*!jV@t|(7!JR% z-d^q`Y5Zj7l0jd4vkLq|-a&$zc(zWG98bf_F8UVtO!A(>`;+Jv zLGwpU7Qbgg)0f30gNFiTAdLfdPT=LRglg!(aQN<4?-QV__T|Lf9>AlOn??tqy(CAa z!dx7Vv04*iPF!IjeN|w|UhtKhlhO;Z)T;3kw>)P9-&9wF6d7K~JlBqYt*gb!ECbS9 z33Wm58siCnO{rNJt0yN65GXZhmcHgMF_#gRt%Xx?%_6pI(fD#eFgKKQJwTlQ2V&L2 A5&!@I literal 0 HcmV?d00001 diff --git a/src/main/resources/move_pressed.png b/src/main/resources/move_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..54a3ac7733c3d9f05483d3e52e456c4cae54c487 GIT binary patch literal 1204 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSEa{HEjtmSN`?>!lvNA9*wq`mz z2Y5O=D-;yvr)B1(GB9XNtet4dPo>h8&lx~Ejc2W1F8Zax0n;{CkC)!!MN zHlGUFoX!HklV#JLPxk?f!FY=aUTwjGf)gZ2e`F#Jpp5 z$_=%pwF;+LtXU!Tl-qK;=(*@QSF`4BeSb1uLMKC8a@}PqS0--{C00SBS1T59aSPru z4f0v?(&pVm`NBV%MJJ!EYM7YJXu4v8jQtP!Kl9c`&Y4*@xv5a$L(*;e#}4oHE@a*K z_I+dSc0Y#y#sy1D|7=wg_^q<{;>APm2OgLe6yCk~#P&fEyF}e3Rp%lVd)Fn#pPgG<+jp5=!FzMmK1M#i#!jKs_0@ZJudmMg|82|j@65kn6y00@N1zxO zL2OCh?k+(3Kg0j5n+ftj7H5G+Bv3|A8H5=tmfDvA1=&kHeO=j~vokRniqG73SPCd4 zS>hT|;+&tGo0?a`;9QiNSdyBeP@Y+mq2TW68xY>eC(gjYRPE{F7*cWT?On&b#|8o} zfvexur=M4vtM{knCtKBaw|6O=n_Q2eP?t7_TiVxQ7FmO(L z8@f!4?OzwugoK^+)o4)-K-;lJ}Jof0F@5Tqbc<)zmR=?+O)HbR6 z{I~n>d?q{2gagm6_8dRp-ypH6*fL%AfuBP2wzcWHEO!~xZk#!^XX*iMMqZhSnL9;l zSXV5(>{a}o=^Tq$MM&xl$JTPS57r+Z37`6XmZi%Y5mji(q=n(r_CJhY XJGQ)@Wc31+rWrh4{an^LB{Ts5nc)Yl literal 0 HcmV?d00001 diff --git a/src/main/resources/move_unlit.png b/src/main/resources/move_unlit.png new file mode 100644 index 0000000000000000000000000000000000000000..0f6dfe1a96f7bec5efd2db611d2cfd635888010e GIT binary patch literal 1213 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSEa{HEjtmSN`?>!lvNA9*wq`mz z2Y5O=D-;yvr)B1(GB9XNtet4dPo>h8&lx~Ejc2W1F8Zax0n;{CkC)!!MN zHlGUFoX!HklV#JLPxk?f!FY=aUTwjGf)gZ2e`F#Jpp5 z$_=%pwF;+LtXU!Tl-qK;=(*@QSF`4BeSb1uLMKC8a@}PqS0--{C00SBS1T59aSPru z4f0v?(&pVm`NBV%MJJ!EYM7YJXu4v8jQtP!Kl9c`&Y4*@xv5a$L(*;e#}4oHE@a*K z_I+dSc0Y#y#sy1D|7=wg_^q<{;>APm2OgLe6yCk~#P&fEyF}e3Rp%lVd)Fn#pPgG<+jp5=!FzMmK1M#i#!jKs_0@ZJudmMg|82|j@65kn6y00@N1zxO zL2OCh?k+(3Kg0j5n+ftj7H5G+Bv3|A8H5=tmfDvA1=&kHeO=j~vokU2S*MvSOa}@{ zmbgZgIOpf)rskC}I2WZRmZYXAlxLP?D7bt2281{Ai8C-TwRpNXhE&{od)F{b#!$dD z@JIHcnR|8juw8RKvFObeMs}Xdca^X1w^s_eTD^ScL@(QZ`}ytm(=L}%mAKTP$U)S-#`)LXf%Dn|X>{jLYaaV}t zPaY>z-F6nn{n127AAJ0!oaHXp)9d~BC*Mxi+F{G0fUw!@-?@FxVXF=w(F6l ziABEsn}0Dqy>l;ZdTUd?pa16DO$Y7GmhV@7v3jJ9^y#A`D+uF2$0)tA#a`8-EnLn&78PgM5Z!H$QYwGU2E4%kt z$@L74Ej9>`dPHygefPjs{l)LLP2YG|IN>SV`yCUvKHc9SD!uo$-m`7<8N;%EoQu8@ zUC$A-@Z;|J@j3r>AIxJ=lZos7AyvUI5bURS{0(yqXUe6GUsv=l&_3WZJ5pl1aE01} zPG)W8?M>$yOiQCO-$WjmctEXxRj(b771NzBmPbD>Z`{t1BN4RLg6|*SiD`ykXH_<= zXUN^PIKWiklYPSNJk6`VEVV2JAX