mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: persistent bottom navigation using shell routing and centralize feed display logic

+3496 -3143
+627 -476
docs/designs/home.html
··· 1 - <!DOCTYPE html> 1 + <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 - <title>Home - Lazurite</title> 7 - <link rel="preconnect" href="https://fonts.googleapis.com"> 8 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 - <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet"> 11 - <link rel="stylesheet" href="styles.css"> 12 - <style> 13 - .feed-container { 14 - padding-bottom: 88px; 15 - } 16 - 17 - .compose-box { 18 - padding: 16px; 19 - border-bottom: 1px solid var(--border); 20 - background-color: var(--bg); 21 - } 22 - 23 - .compose-inner { 24 - display: flex; 25 - gap: 12px; 26 - } 27 - 28 - .compose-input-wrapper { 29 - flex: 1; 30 - } 31 - 32 - .compose-input { 33 - width: 100%; 34 - border: none; 35 - background: transparent; 36 - color: var(--text-primary); 37 - font-size: 16px; 38 - resize: none; 39 - outline: none; 40 - font-family: inherit; 41 - min-height: 24px; 42 - } 43 - 44 - .compose-input::placeholder { 45 - color: var(--text-muted); 46 - } 47 - 48 - .compose-actions { 49 - display: flex; 50 - justify-content: space-between; 51 - align-items: center; 52 - margin-top: 12px; 53 - padding-left: 60px; 54 - } 55 - 56 - .compose-tools { 57 - display: flex; 58 - gap: 8px; 59 - } 60 - 61 - .compose-tool { 62 - width: 36px; 63 - height: 36px; 64 - border-radius: 50%; 65 - border: none; 66 - background: transparent; 67 - color: var(--accent-primary); 68 - cursor: pointer; 69 - display: flex; 70 - align-items: center; 71 - justify-content: center; 72 - transition: background-color 0.2s ease; 73 - } 74 - 75 - .compose-tool:hover { 76 - background-color: rgba(0, 102, 255, 0.1); 77 - } 78 - 79 - .compose-tool svg { 80 - width: 20px; 81 - height: 20px; 82 - } 83 - 84 - .compose-submit { 85 - padding: 8px 20px; 86 - border-radius: 9999px; 87 - border: none; 88 - background-color: var(--accent-primary); 89 - color: white; 90 - font-weight: 600; 91 - font-size: 14px; 92 - cursor: pointer; 93 - transition: background-color 0.2s ease; 94 - } 95 - 96 - .compose-submit:hover { 97 - background-color: var(--accent-primary-hover); 98 - } 99 - 100 - .feed-tabs { 101 - display: flex; 102 - border-bottom: 1px solid var(--border); 103 - background-color: var(--bg); 104 - } 105 - 106 - .feed-tab { 107 - flex: 1; 108 - padding: 16px; 109 - text-align: center; 110 - font-weight: 600; 111 - font-size: 15px; 112 - color: var(--text-secondary); 113 - cursor: pointer; 114 - border-bottom: 2px solid transparent; 115 - transition: all 0.2s ease; 116 - background: none; 117 - border-top: none; 118 - border-left: none; 119 - border-right: none; 120 - } 121 - 122 - .feed-tab:hover { 123 - background-color: var(--surface); 124 - color: var(--text-primary); 125 - } 126 - 127 - .feed-tab.active { 128 - color: var(--text-primary); 129 - border-bottom-color: var(--accent-primary); 130 - } 131 - 132 - .post-facet-mention { 133 - color: var(--accent-primary); 134 - text-decoration: none; 135 - font-weight: 500; 136 - } 137 - 138 - .post-facet-mention:hover { 139 - text-decoration: underline; 140 - } 141 - 142 - .post-facet-hashtag { 143 - color: var(--accent-secondary); 144 - text-decoration: none; 145 - font-weight: 500; 146 - } 147 - 148 - .post-facet-hashtag:hover { 149 - text-decoration: underline; 150 - } 151 - 152 - .post-facet-link { 153 - color: var(--accent-primary); 154 - text-decoration: underline; 155 - } 156 - 157 - .post-embed { 158 - margin-top: 12px; 159 - border: 1px solid var(--border); 160 - border-radius: 12px; 161 - overflow: hidden; 162 - } 163 - 164 - .post-embed-image { 165 - width: 100%; 166 - height: 200px; 167 - background: linear-gradient(135deg, var(--surface) 0%, var(--surface-variant) 100%); 168 - display: flex; 169 - align-items: center; 170 - justify-content: center; 171 - color: var(--text-muted); 172 - } 173 - 174 - .post-embed-content { 175 - padding: 12px; 176 - } 177 - 178 - .post-embed-title { 179 - font-weight: 600; 180 - color: var(--text-primary); 181 - font-size: 14px; 182 - margin-bottom: 4px; 183 - } 184 - 185 - .post-embed-url { 186 - color: var(--text-muted); 187 - font-size: 12px; 188 - } 189 - </style> 190 - </head> 191 - <body> 192 - <div class="mobile-container"> 193 - 194 - <!-- Header --> 195 - <header class="header"> 196 - <h1 class="header-title">Home</h1> 197 - <button class="header-action">Settings</button> 198 - </header> 199 - 200 - <div class="feed-container"> 201 - 202 - <!-- Compose Box --> 203 - <div class="compose-box"> 204 - <div class="compose-inner"> 205 - <div class="avatar avatar-sm">JD</div> 206 - <div class="compose-input-wrapper"> 207 - <textarea class="compose-input" placeholder="What's on your mind?" rows="2"></textarea> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> 6 + <title>Home - Lazurite</title> 7 + <link rel="preconnect" href="https://fonts.googleapis.com" /> 8 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 9 + <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet" /> 10 + <link 11 + href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" 12 + rel="stylesheet" /> 13 + <link rel="stylesheet" href="styles.css" /> 14 + <style> 15 + .feed-container { 16 + padding-bottom: 88px; 17 + } 18 + 19 + .compose-box { 20 + padding: 16px; 21 + border-bottom: 1px solid var(--border); 22 + background-color: var(--bg); 23 + } 24 + 25 + .compose-inner { 26 + display: flex; 27 + gap: 12px; 28 + } 29 + 30 + .compose-input-wrapper { 31 + flex: 1; 32 + } 33 + 34 + .compose-input { 35 + width: 100%; 36 + border: none; 37 + background: transparent; 38 + color: var(--text-primary); 39 + font-size: 16px; 40 + resize: none; 41 + outline: none; 42 + font-family: inherit; 43 + min-height: 24px; 44 + } 45 + 46 + .compose-input::placeholder { 47 + color: var(--text-muted); 48 + } 49 + 50 + .compose-actions { 51 + display: flex; 52 + justify-content: space-between; 53 + align-items: center; 54 + margin-top: 12px; 55 + padding-left: 60px; 56 + } 57 + 58 + .compose-tools { 59 + display: flex; 60 + gap: 8px; 61 + } 62 + 63 + .compose-tool { 64 + width: 36px; 65 + height: 36px; 66 + border-radius: 50%; 67 + border: none; 68 + background: transparent; 69 + color: var(--accent-primary); 70 + cursor: pointer; 71 + display: flex; 72 + align-items: center; 73 + justify-content: center; 74 + transition: background-color 0.2s ease; 75 + } 76 + 77 + .compose-tool:hover { 78 + background-color: rgba(0, 102, 255, 0.1); 79 + } 80 + 81 + .compose-tool svg { 82 + width: 20px; 83 + height: 20px; 84 + } 85 + 86 + .compose-submit { 87 + padding: 8px 20px; 88 + border-radius: 9999px; 89 + border: none; 90 + background-color: var(--accent-primary); 91 + color: white; 92 + font-weight: 600; 93 + font-size: 14px; 94 + cursor: pointer; 95 + transition: background-color 0.2s ease; 96 + } 97 + 98 + .compose-submit:hover { 99 + background-color: var(--accent-primary-hover); 100 + } 101 + 102 + .feed-tabs { 103 + display: flex; 104 + border-bottom: 1px solid var(--border); 105 + background-color: var(--bg); 106 + } 107 + 108 + .feed-tab { 109 + flex: 1; 110 + padding: 16px; 111 + text-align: center; 112 + font-weight: 600; 113 + font-size: 15px; 114 + color: var(--text-secondary); 115 + cursor: pointer; 116 + border-bottom: 2px solid transparent; 117 + transition: all 0.2s ease; 118 + background: none; 119 + border-top: none; 120 + border-left: none; 121 + border-right: none; 122 + } 123 + 124 + .feed-tab:hover { 125 + background-color: var(--surface); 126 + color: var(--text-primary); 127 + } 128 + 129 + .feed-tab.active { 130 + color: var(--text-primary); 131 + border-bottom-color: var(--accent-primary); 132 + } 133 + 134 + .post-facet-mention { 135 + color: var(--accent-primary); 136 + text-decoration: none; 137 + font-weight: 500; 138 + } 139 + 140 + .post-facet-mention:hover { 141 + text-decoration: underline; 142 + } 143 + 144 + .post-facet-hashtag { 145 + color: var(--accent-secondary); 146 + text-decoration: none; 147 + font-weight: 500; 148 + } 149 + 150 + .post-facet-hashtag:hover { 151 + text-decoration: underline; 152 + } 153 + 154 + .post-facet-link { 155 + color: var(--accent-primary); 156 + text-decoration: underline; 157 + } 158 + 159 + .post-embed { 160 + margin-top: 12px; 161 + border: 1px solid var(--border); 162 + border-radius: 12px; 163 + overflow: hidden; 164 + } 165 + 166 + .post-embed-image { 167 + width: 100%; 168 + height: 200px; 169 + background: linear-gradient(135deg, var(--surface) 0%, var(--surface-variant) 100%); 170 + display: flex; 171 + align-items: center; 172 + justify-content: center; 173 + color: var(--text-muted); 174 + } 175 + 176 + .post-embed-content { 177 + padding: 12px; 178 + } 179 + 180 + .post-embed-title { 181 + font-weight: 600; 182 + color: var(--text-primary); 183 + font-size: 14px; 184 + margin-bottom: 4px; 185 + } 186 + 187 + .post-embed-url { 188 + color: var(--text-muted); 189 + font-size: 12px; 190 + } 191 + </style> 192 + </head> 193 + <body> 194 + <div class="mobile-container"> 195 + <!-- Header --> 196 + <header class="header"> 197 + <h1 class="header-title">Home</h1> 198 + <button class="header-action">Settings</button> 199 + </header> 200 + 201 + <div class="feed-container"> 202 + <!-- Compose Box --> 203 + <div class="compose-box"> 204 + <div class="compose-inner"> 205 + <div class="avatar avatar-sm">JD</div> 206 + <div class="compose-input-wrapper"> 207 + <textarea class="compose-input" placeholder="What's on your mind?" rows="2"></textarea> 208 + </div> 209 + </div> 210 + <div class="compose-actions"> 211 + <div class="compose-tools"> 212 + <button class="compose-tool" title="Add image"> 213 + <svg 214 + viewBox="0 0 24 24" 215 + fill="none" 216 + stroke="currentColor" 217 + stroke-width="2" 218 + stroke-linecap="round" 219 + stroke-linejoin="round"> 220 + <rect x="3" y="3" width="18" height="18" rx="2" ry="2" /> 221 + <circle cx="8.5" cy="8.5" r="1.5" /> 222 + <polyline points="21 15 16 10 5 21" /> 223 + </svg> 224 + </button> 225 + <button class="compose-tool" title="Add GIF"> 226 + <svg 227 + viewBox="0 0 24 24" 228 + fill="none" 229 + stroke="currentColor" 230 + stroke-width="2" 231 + stroke-linecap="round" 232 + stroke-linejoin="round"> 233 + <text x="4" y="16" font-family="inherit" font-size="12" font-weight="bold" fill="currentColor"> 234 + GIF 235 + </text> 236 + </svg> 237 + </button> 238 + <button class="compose-tool" title="Add poll"> 239 + <svg 240 + viewBox="0 0 24 24" 241 + fill="none" 242 + stroke="currentColor" 243 + stroke-width="2" 244 + stroke-linecap="round" 245 + stroke-linejoin="round"> 246 + <line x1="18" y1="20" x2="18" y2="10" /> 247 + <line x1="12" y1="20" x2="12" y2="4" /> 248 + <line x1="6" y1="20" x2="6" y2="14" /> 249 + </svg> 250 + </button> 251 + <button class="compose-tool" title="Emoji"> 252 + <svg 253 + viewBox="0 0 24 24" 254 + fill="none" 255 + stroke="currentColor" 256 + stroke-width="2" 257 + stroke-linecap="round" 258 + stroke-linejoin="round"> 259 + <circle cx="12" cy="12" r="10" /> 260 + <path d="M8 14s1.5 2 4 2 4-2 4-2" /> 261 + <line x1="9" y1="9" x2="9.01" y2="9" /> 262 + <line x1="15" y1="9" x2="15.01" y2="9" /> 263 + </svg> 264 + </button> 265 + </div> 266 + <button class="compose-submit">Post</button> 208 267 </div> 209 268 </div> 210 - <div class="compose-actions"> 211 - <div class="compose-tools"> 212 - <button class="compose-tool" title="Add image"> 213 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 214 - <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/> 215 - <circle cx="8.5" cy="8.5" r="1.5"/> 216 - <polyline points="21 15 16 10 5 21"/> 269 + 270 + <!-- Feed Tabs --> 271 + <div class="feed-tabs"> 272 + <button class="feed-tab active">Following</button> 273 + <button class="feed-tab">Discover</button> 274 + </div> 275 + 276 + <!-- Post 1 --> 277 + <article class="post-card"> 278 + <div class="post-header"> 279 + <div class="avatar">AS</div> 280 + <div class="post-author"> 281 + <div class="post-author-name">Alice Smith</div> 282 + <div class="post-author-handle">@alice.bsky.social · <span class="post-timestamp">2h</span></div> 283 + </div> 284 + </div> 285 + 286 + <div class="post-content"> 287 + Just launched my new project! 🚀 Check out the demo at <a href="#" class="post-facet-link">example.com</a>. 288 + Special thanks to <a href="#" class="post-facet-mention">@bob.bsky.social</a> for the help! 289 + <a href="#" class="post-facet-hashtag">#buildinpublic</a> <a href="#" class="post-facet-hashtag">#dev</a> 290 + </div> 291 + 292 + <div class="post-embed"> 293 + <div class="post-embed-image">[Project Screenshot]</div> 294 + <div class="post-embed-content"> 295 + <div class="post-embed-title">My Awesome Project</div> 296 + <div class="post-embed-url">example.com</div> 297 + </div> 298 + </div> 299 + 300 + <div class="post-actions"> 301 + <button class="post-action"> 302 + <svg 303 + viewBox="0 0 24 24" 304 + fill="none" 305 + stroke="currentColor" 306 + stroke-width="2" 307 + stroke-linecap="round" 308 + stroke-linejoin="round"> 309 + <path 310 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 311 + </svg> 312 + 12 313 + </button> 314 + <button class="post-action"> 315 + <svg 316 + viewBox="0 0 24 24" 317 + fill="none" 318 + stroke="currentColor" 319 + stroke-width="2" 320 + stroke-linecap="round" 321 + stroke-linejoin="round"> 322 + <polyline points="17 1 21 5 17 9" /> 323 + <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 324 + <polyline points="7 23 3 19 7 15" /> 325 + <path d="M21 13v2a4 4 0 0 1-4 4H3" /> 326 + </svg> 327 + 5 328 + </button> 329 + <button class="post-action"> 330 + <svg 331 + viewBox="0 0 24 24" 332 + fill="none" 333 + stroke="currentColor" 334 + stroke-width="2" 335 + stroke-linecap="round" 336 + stroke-linejoin="round"> 337 + <path 338 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 339 + </svg> 340 + 48 341 + </button> 342 + <button class="post-action"> 343 + <svg 344 + viewBox="0 0 24 24" 345 + fill="none" 346 + stroke="currentColor" 347 + stroke-width="2" 348 + stroke-linecap="round" 349 + stroke-linejoin="round"> 350 + <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 351 + <polyline points="16 6 12 2 8 6" /> 352 + <line x1="12" y1="2" x2="12" y2="15" /> 217 353 </svg> 218 354 </button> 219 - <button class="compose-tool" title="Add GIF"> 220 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 221 - <text x="4" y="16" font-family="inherit" font-size="12" font-weight="bold" fill="currentColor">GIF</text> 355 + </div> 356 + </article> 357 + 358 + <!-- Post 2 --> 359 + <article class="post-card"> 360 + <div class="post-header"> 361 + <div class="avatar">BJ</div> 362 + <div class="post-author"> 363 + <div class="post-author-name">Bob Johnson</div> 364 + <div class="post-author-handle">@bob.bsky.social · <span class="post-timestamp">4h</span></div> 365 + </div> 366 + </div> 367 + 368 + <div class="post-content"> 369 + Working on some exciting features for the next release! Here's a sneak peek of what's coming: improved 370 + search, better notifications, and dark mode support. <a href="#" class="post-facet-hashtag">#bluesky</a> 371 + <a href="#" class="post-facet-hashtag">#atproto</a> 372 + </div> 373 + 374 + <div class="post-actions"> 375 + <button class="post-action"> 376 + <svg 377 + viewBox="0 0 24 24" 378 + fill="none" 379 + stroke="currentColor" 380 + stroke-width="2" 381 + stroke-linecap="round" 382 + stroke-linejoin="round"> 383 + <path 384 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 222 385 </svg> 386 + 8 223 387 </button> 224 - <button class="compose-tool" title="Add poll"> 225 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 226 - <line x1="18" y1="20" x2="18" y2="10"/> 227 - <line x1="12" y1="20" x2="12" y2="4"/> 228 - <line x1="6" y1="20" x2="6" y2="14"/> 388 + <button class="post-action"> 389 + <svg 390 + viewBox="0 0 24 24" 391 + fill="none" 392 + stroke="currentColor" 393 + stroke-width="2" 394 + stroke-linecap="round" 395 + stroke-linejoin="round"> 396 + <polyline points="17 1 21 5 17 9" /> 397 + <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 398 + <polyline points="7 23 3 19 7 15" /> 399 + <path d="M21 13v2a4 4 0 0 1-4 4H3" /> 229 400 </svg> 401 + 24 230 402 </button> 231 - <button class="compose-tool" title="Emoji"> 232 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 233 - <circle cx="12" cy="12" r="10"/> 234 - <path d="M8 14s1.5 2 4 2 4-2 4-2"/> 235 - <line x1="9" y1="9" x2="9.01" y2="9"/> 236 - <line x1="15" y1="9" x2="15.01" y2="9"/> 403 + <button class="post-action"> 404 + <svg 405 + viewBox="0 0 24 24" 406 + fill="none" 407 + stroke="currentColor" 408 + stroke-width="2" 409 + stroke-linecap="round" 410 + stroke-linejoin="round"> 411 + <path 412 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 413 + </svg> 414 + 156 415 + </button> 416 + <button class="post-action"> 417 + <svg 418 + viewBox="0 0 24 24" 419 + fill="none" 420 + stroke="currentColor" 421 + stroke-width="2" 422 + stroke-linecap="round" 423 + stroke-linejoin="round"> 424 + <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 425 + <polyline points="16 6 12 2 8 6" /> 426 + <line x1="12" y1="2" x2="12" y2="15" /> 237 427 </svg> 238 428 </button> 239 429 </div> 240 - <button class="compose-submit">Post</button> 241 - </div> 242 - </div> 243 - 244 - <!-- Feed Tabs --> 245 - <div class="feed-tabs"> 246 - <button class="feed-tab active">Following</button> 247 - <button class="feed-tab">Discover</button> 248 - </div> 249 - 250 - <!-- Post 1 --> 251 - <article class="post-card"> 252 - <div class="post-header"> 253 - <div class="avatar">AS</div> 254 - <div class="post-author"> 255 - <div class="post-author-name">Alice Smith</div> 256 - <div class="post-author-handle">@alice.bsky.social · <span class="post-timestamp">2h</span></div> 430 + </article> 431 + 432 + <!-- Post 3 - Rich Facets --> 433 + <article class="post-card"> 434 + <div class="post-header"> 435 + <div class="avatar">CW</div> 436 + <div class="post-author"> 437 + <div class="post-author-name">Carol White</div> 438 + <div class="post-author-handle">@carol.dev · <span class="post-timestamp">6h</span></div> 439 + </div> 257 440 </div> 258 - </div> 259 - 260 - <div class="post-content"> 261 - Just launched my new project! 🚀 Check out the demo at <a href="#" class="post-facet-link">example.com</a>. Special thanks to <a href="#" class="post-facet-mention">@bob.bsky.social</a> for the help! <a href="#" class="post-facet-hashtag">#buildinpublic</a> <a href="#" class="post-facet-hashtag">#dev</a> 262 - </div> 263 - 264 - <div class="post-embed"> 265 - <div class="post-embed-image">[Project Screenshot]</div> 266 - <div class="post-embed-content"> 267 - <div class="post-embed-title">My Awesome Project</div> 268 - <div class="post-embed-url">example.com</div> 441 + 442 + <div class="post-content"> 443 + The <a href="#" class="post-facet-mention">@atproto</a> team has been doing amazing work! Read their latest 444 + blog post about federation: <a href="#" class="post-facet-link">atproto.com/blog/federation</a> 445 + 446 + <br /><br /> 447 + 448 + Key highlights: • Self-hosting guides • PDS (Personal Data Server) setup • Relay infrastructure 449 + 450 + <br /><br /> 451 + 452 + <a href="#" class="post-facet-hashtag">#atprotocol</a> 453 + <a href="#" class="post-facet-hashtag">#decentralized</a> 454 + <a href="#" class="post-facet-hashtag">#socialweb</a> 269 455 </div> 270 - </div> 271 - 272 - <div class="post-actions"> 273 - <button class="post-action"> 274 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 275 - <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/> 276 - </svg> 277 - 12 278 - </button> 279 - <button class="post-action"> 280 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 281 - <polyline points="17 1 21 5 17 9"/> 282 - <path d="M3 11V9a4 4 0 0 1 4-4h14"/> 283 - <polyline points="7 23 3 19 7 15"/> 284 - <path d="M21 13v2a4 4 0 0 1-4 4H3"/> 285 - </svg> 286 - 5 287 - </button> 288 - <button class="post-action"> 289 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 290 - <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/> 291 - </svg> 292 - 48 293 - </button> 294 - <button class="post-action"> 295 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 296 - <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/> 297 - <polyline points="16 6 12 2 8 6"/> 298 - <line x1="12" y1="2" x2="12" y2="15"/> 299 - </svg> 300 - </button> 301 - </div> 302 - </article> 303 - 304 - <!-- Post 2 --> 305 - <article class="post-card"> 306 - <div class="post-header"> 307 - <div class="avatar">BJ</div> 308 - <div class="post-author"> 309 - <div class="post-author-name">Bob Johnson</div> 310 - <div class="post-author-handle">@bob.bsky.social · <span class="post-timestamp">4h</span></div> 456 + 457 + <div class="post-actions"> 458 + <button class="post-action"> 459 + <svg 460 + viewBox="0 0 24 24" 461 + fill="none" 462 + stroke="currentColor" 463 + stroke-width="2" 464 + stroke-linecap="round" 465 + stroke-linejoin="round"> 466 + <path 467 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 468 + </svg> 469 + 34 470 + </button> 471 + <button class="post-action"> 472 + <svg 473 + viewBox="0 0 24 24" 474 + fill="none" 475 + stroke="currentColor" 476 + stroke-width="2" 477 + stroke-linecap="round" 478 + stroke-linejoin="round"> 479 + <polyline points="17 1 21 5 17 9" /> 480 + <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 481 + <polyline points="7 23 3 19 7 15" /> 482 + <path d="M21 13v2a4 4 0 0 1-4 4H3" /> 483 + </svg> 484 + 12 485 + </button> 486 + <button class="post-action"> 487 + <svg 488 + viewBox="0 0 24 24" 489 + fill="none" 490 + stroke="currentColor" 491 + stroke-width="2" 492 + stroke-linecap="round" 493 + stroke-linejoin="round"> 494 + <path 495 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 496 + </svg> 497 + 289 498 + </button> 499 + <button class="post-action"> 500 + <svg 501 + viewBox="0 0 24 24" 502 + fill="none" 503 + stroke="currentColor" 504 + stroke-width="2" 505 + stroke-linecap="round" 506 + stroke-linejoin="round"> 507 + <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 508 + <polyline points="16 6 12 2 8 6" /> 509 + <line x1="12" y1="2" x2="12" y2="15" /> 510 + </svg> 511 + </button> 311 512 </div> 312 - </div> 313 - 314 - <div class="post-content"> 315 - Working on some exciting features for the next release! Here's a sneak peek of what's coming: improved search, better notifications, and dark mode support. <a href="#" class="post-facet-hashtag">#bluesky</a> <a href="#" class="post-facet-hashtag">#atproto</a> 316 - </div> 317 - 318 - <div class="post-actions"> 319 - <button class="post-action"> 320 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 321 - <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/> 322 - </svg> 323 - 8 324 - </button> 325 - <button class="post-action"> 326 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 327 - <polyline points="17 1 21 5 17 9"/> 328 - <path d="M3 11V9a4 4 0 0 1 4-4h14"/> 329 - <polyline points="7 23 3 19 7 15"/> 330 - <path d="M21 13v2a4 4 0 0 1-4 4H3"/> 331 - </svg> 332 - 24 333 - </button> 334 - <button class="post-action"> 335 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 336 - <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/> 337 - </svg> 338 - 156 339 - </button> 340 - <button class="post-action"> 341 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 342 - <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/> 343 - <polyline points="16 6 12 2 8 6"/> 344 - <line x1="12" y1="2" x2="12" y2="15"/> 345 - </svg> 346 - </button> 347 - </div> 348 - </article> 349 - 350 - <!-- Post 3 - Rich Facets --> 351 - <article class="post-card"> 352 - <div class="post-header"> 353 - <div class="avatar">CW</div> 354 - <div class="post-author"> 355 - <div class="post-author-name">Carol White</div> 356 - <div class="post-author-handle">@carol.dev · <span class="post-timestamp">6h</span></div> 513 + </article> 514 + 515 + <!-- Post 4 --> 516 + <article class="post-card"> 517 + <div class="post-header"> 518 + <div class="avatar">DM</div> 519 + <div class="post-author"> 520 + <div class="post-author-name">David Miller</div> 521 + <div class="post-author-handle">@davidm.bsky.social · <span class="post-timestamp">8h</span></div> 522 + </div> 523 + </div> 524 + 525 + <div class="post-content"> 526 + Beautiful sunset from my hike today! 🌅 <a href="#" class="post-facet-hashtag">#nature</a> 527 + <a href="#" class="post-facet-hashtag">#photography</a> <a href="#" class="post-facet-hashtag">#hiking</a> 528 + </div> 529 + 530 + <div class="post-embed"> 531 + <div class="post-embed-image">[Sunset Photo]</div> 357 532 </div> 358 - </div> 359 - 360 - <div class="post-content"> 361 - The <a href="#" class="post-facet-mention">@atproto</a> team has been doing amazing work! Read their latest blog post about federation: <a href="#" class="post-facet-link">atproto.com/blog/federation</a> 362 - 363 - <br><br> 364 - 365 - Key highlights: 366 - • Self-hosting guides 367 - • PDS (Personal Data Server) setup 368 - • Relay infrastructure 369 - 370 - <br><br> 371 - 372 - <a href="#" class="post-facet-hashtag">#atprotocol</a> <a href="#" class="post-facet-hashtag">#decentralized</a> <a href="#" class="post-facet-hashtag">#socialweb</a> 373 - </div> 374 - 375 - <div class="post-actions"> 376 - <button class="post-action"> 377 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 378 - <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/> 379 - </svg> 380 - 34 381 - </button> 382 - <button class="post-action"> 383 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 384 - <polyline points="17 1 21 5 17 9"/> 385 - <path d="M3 11V9a4 4 0 0 1 4-4h14"/> 386 - <polyline points="7 23 3 19 7 15"/> 387 - <path d="M21 13v2a4 4 0 0 1-4 4H3"/> 388 - </svg> 389 - 12 390 - </button> 391 - <button class="post-action"> 392 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 393 - <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/> 394 - </svg> 395 - 289 396 - </button> 397 - <button class="post-action"> 398 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 399 - <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/> 400 - <polyline points="16 6 12 2 8 6"/> 401 - <line x1="12" y1="2" x2="12" y2="15"/> 402 - </svg> 403 - </button> 404 - </div> 405 - </article> 406 - 407 - <!-- Post 4 --> 408 - <article class="post-card"> 409 - <div class="post-header"> 410 - <div class="avatar">DM</div> 411 - <div class="post-author"> 412 - <div class="post-author-name">David Miller</div> 413 - <div class="post-author-handle">@davidm.bsky.social · <span class="post-timestamp">8h</span></div> 533 + 534 + <div class="post-actions"> 535 + <button class="post-action"> 536 + <svg 537 + viewBox="0 0 24 24" 538 + fill="none" 539 + stroke="currentColor" 540 + stroke-width="2" 541 + stroke-linecap="round" 542 + stroke-linejoin="round"> 543 + <path 544 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 545 + </svg> 546 + 6 547 + </button> 548 + <button class="post-action"> 549 + <svg 550 + viewBox="0 0 24 24" 551 + fill="none" 552 + stroke="currentColor" 553 + stroke-width="2" 554 + stroke-linecap="round" 555 + stroke-linejoin="round"> 556 + <polyline points="17 1 21 5 17 9" /> 557 + <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 558 + <polyline points="7 23 3 19 7 15" /> 559 + <path d="M21 13v2a4 4 0 0 1-4 4H3" /> 560 + </svg> 561 + 18 562 + </button> 563 + <button class="post-action"> 564 + <svg 565 + viewBox="0 0 24 24" 566 + fill="none" 567 + stroke="currentColor" 568 + stroke-width="2" 569 + stroke-linecap="round" 570 + stroke-linejoin="round"> 571 + <path 572 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 573 + </svg> 574 + 423 575 + </button> 576 + <button class="post-action"> 577 + <svg 578 + viewBox="0 0 24 24" 579 + fill="none" 580 + stroke="currentColor" 581 + stroke-width="2" 582 + stroke-linecap="round" 583 + stroke-linejoin="round"> 584 + <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 585 + <polyline points="16 6 12 2 8 6" /> 586 + <line x1="12" y1="2" x2="12" y2="15" /> 587 + </svg> 588 + </button> 414 589 </div> 415 - </div> 416 - 417 - <div class="post-content"> 418 - Beautiful sunset from my hike today! 🌅 <a href="#" class="post-facet-hashtag">#nature</a> <a href="#" class="post-facet-hashtag">#photography</a> <a href="#" class="post-facet-hashtag">#hiking</a> 419 - </div> 420 - 421 - <div class="post-embed"> 422 - <div class="post-embed-image">[Sunset Photo]</div> 423 - </div> 424 - 425 - <div class="post-actions"> 426 - <button class="post-action"> 427 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 428 - <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/> 429 - </svg> 430 - 6 431 - </button> 432 - <button class="post-action"> 433 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 434 - <polyline points="17 1 21 5 17 9"/> 435 - <path d="M3 11V9a4 4 0 0 1 4-4h14"/> 436 - <polyline points="7 23 3 19 7 15"/> 437 - <path d="M21 13v2a4 4 0 0 1-4 4H3"/> 438 - </svg> 439 - 18 440 - </button> 441 - <button class="post-action"> 442 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 443 - <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/> 444 - </svg> 445 - 423 446 - </button> 447 - <button class="post-action"> 448 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 449 - <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/> 450 - <polyline points="16 6 12 2 8 6"/> 451 - <line x1="12" y1="2" x2="12" y2="15"/> 452 - </svg> 453 - </button> 454 - </div> 455 - </article> 456 - 590 + </article> 591 + </div> 592 + 593 + <!-- Bottom Navigation --> 594 + <nav class="nav-bar"> 595 + <a href="home.html" class="nav-item active"> 596 + <svg 597 + viewBox="0 0 24 24" 598 + fill="none" 599 + stroke="currentColor" 600 + stroke-width="2" 601 + stroke-linecap="round" 602 + stroke-linejoin="round"> 603 + <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> 604 + <polyline points="9 22 9 12 15 12 15 22" /> 605 + </svg> 606 + <span>Home</span> 607 + </a> 608 + 609 + <a href="profile.html" class="nav-item"> 610 + <svg 611 + viewBox="0 0 24 24" 612 + fill="none" 613 + stroke="currentColor" 614 + stroke-width="2" 615 + stroke-linecap="round" 616 + stroke-linejoin="round"> 617 + <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> 618 + <circle cx="12" cy="7" r="4" /> 619 + </svg> 620 + <span>Profile</span> 621 + </a> 622 + 623 + <a href="settings.html" class="nav-item"> 624 + <svg 625 + viewBox="0 0 24 24" 626 + fill="none" 627 + stroke="currentColor" 628 + stroke-width="2" 629 + stroke-linecap="round" 630 + stroke-linejoin="round"> 631 + <circle cx="12" cy="12" r="3" /> 632 + <path 633 + d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" /> 634 + </svg> 635 + <span>Settings</span> 636 + </a> 637 + </nav> 457 638 </div> 458 - 459 - <!-- Bottom Navigation --> 460 - <nav class="nav-bar"> 461 - <a href="home.html" class="nav-item active"> 462 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 463 - <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/> 464 - <polyline points="9 22 9 12 15 12 15 22"/> 465 - </svg> 466 - <span>Home</span> 467 - </a> 468 - 469 - <a href="profile.html" class="nav-item"> 470 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 471 - <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 472 - <circle cx="12" cy="7" r="4"/> 473 - </svg> 474 - <span>Profile</span> 475 - </a> 476 - 477 - <a href="settings.html" class="nav-item"> 478 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 479 - <circle cx="12" cy="12" r="3"/> 480 - <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/> 481 - </svg> 482 - <span>Settings</span> 483 - </a> 484 - </nav> 485 - 486 - </div> 487 - 488 - <script> 489 - // Theme toggle functionality for demo 490 - if (localStorage.getItem('theme') === 'dark') { 491 - document.documentElement.setAttribute('data-theme', 'dark'); 492 - } 493 - </script> 494 - </body> 639 + 640 + <script> 641 + if (localStorage.getItem("theme") === "dark") { 642 + document.documentElement.setAttribute("data-theme", "dark"); 643 + } 644 + </script> 645 + </body> 495 646 </html>
+3 -5
docs/designs/logs.html
··· 9 9 <link 10 10 href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" 11 11 rel="stylesheet" /> 12 - <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet" /> 12 + <link 13 + href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" 14 + rel="stylesheet" /> 13 15 <link rel="stylesheet" href="styles.css" /> 14 16 <style> 15 17 .logs-container { ··· 555 557 </div> 556 558 557 559 <script> 558 - // Theme init 559 560 const saved = localStorage.getItem("theme"); 560 561 if (saved && saved !== "light") { 561 562 document.documentElement.setAttribute("data-theme", saved); 562 563 } 563 564 564 - // Toggle filter chips 565 565 document.querySelectorAll(".filter-chip").forEach((chip) => { 566 566 chip.addEventListener("click", () => chip.classList.toggle("active")); 567 567 }); 568 568 569 - // Toggle expanded log entries 570 569 document.querySelectorAll(".log-entry").forEach((entry) => { 571 570 entry.addEventListener("click", () => entry.classList.toggle("expanded")); 572 571 }); 573 572 574 - // Toggle auto-scroll 575 573 document.querySelector(".autoscroll-indicator").addEventListener("click", function () { 576 574 this.classList.toggle("active"); 577 575 });
+612 -546
docs/designs/messages.html
··· 1 - <!DOCTYPE html> 1 + <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 - <title>Messages - Lazurite</title> 7 - <link rel="preconnect" href="https://fonts.googleapis.com"> 8 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 - <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet"> 11 - <link rel="stylesheet" href="styles.css"> 12 - <style> 13 - .messages-container { 14 - padding-bottom: 88px; 15 - } 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> 6 + <title>Messages - Lazurite</title> 7 + <link rel="preconnect" href="https://fonts.googleapis.com" /> 8 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 9 + <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet" /> 10 + <link 11 + href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" 12 + rel="stylesheet" /> 13 + <link rel="stylesheet" href="styles.css" /> 14 + <style> 15 + .messages-container { 16 + padding-bottom: 88px; 17 + } 16 18 17 - /* Tabs */ 18 - .msg-tabs { 19 - display: flex; 20 - border-bottom: 1px solid var(--border); 21 - background-color: var(--bg); 22 - } 19 + /* Tabs */ 20 + .msg-tabs { 21 + display: flex; 22 + border-bottom: 1px solid var(--border); 23 + background-color: var(--bg); 24 + } 23 25 24 - .msg-tab { 25 - flex: 1; 26 - padding: 14px; 27 - text-align: center; 28 - font-weight: 600; 29 - font-size: 15px; 30 - color: var(--text-secondary); 31 - cursor: pointer; 32 - border-bottom: 2px solid transparent; 33 - transition: all 0.2s ease; 34 - background: none; 35 - border-top: none; 36 - border-left: none; 37 - border-right: none; 38 - } 26 + .msg-tab { 27 + flex: 1; 28 + padding: 14px; 29 + text-align: center; 30 + font-weight: 600; 31 + font-size: 15px; 32 + color: var(--text-secondary); 33 + cursor: pointer; 34 + border-bottom: 2px solid transparent; 35 + transition: all 0.2s ease; 36 + background: none; 37 + border-top: none; 38 + border-left: none; 39 + border-right: none; 40 + } 39 41 40 - .msg-tab:hover { 41 - background-color: var(--surface); 42 - color: var(--text-primary); 43 - } 42 + .msg-tab:hover { 43 + background-color: var(--surface); 44 + color: var(--text-primary); 45 + } 44 46 45 - .msg-tab.active { 46 - color: var(--text-primary); 47 - border-bottom-color: var(--accent-primary); 48 - } 47 + .msg-tab.active { 48 + color: var(--text-primary); 49 + border-bottom-color: var(--accent-primary); 50 + } 49 51 50 - .msg-tab-badge { 51 - display: inline-flex; 52 - align-items: center; 53 - justify-content: center; 54 - min-width: 18px; 55 - height: 18px; 56 - padding: 0 5px; 57 - border-radius: 9px; 58 - background-color: var(--accent-error); 59 - color: white; 60 - font-size: 10px; 61 - font-weight: 700; 62 - margin-left: 6px; 63 - } 52 + .msg-tab-badge { 53 + display: inline-flex; 54 + align-items: center; 55 + justify-content: center; 56 + min-width: 18px; 57 + height: 18px; 58 + padding: 0 5px; 59 + border-radius: 9px; 60 + background-color: var(--accent-error); 61 + color: white; 62 + font-size: 10px; 63 + font-weight: 700; 64 + margin-left: 6px; 65 + } 64 66 65 - /* Conversation Item */ 66 - .convo-item { 67 - display: flex; 68 - align-items: center; 69 - gap: 12px; 70 - padding: 14px 16px; 71 - border-bottom: 1px solid var(--border); 72 - cursor: pointer; 73 - transition: background-color 0.2s ease; 74 - position: relative; 75 - } 67 + /* Conversation Item */ 68 + .convo-item { 69 + display: flex; 70 + align-items: center; 71 + gap: 12px; 72 + padding: 14px 16px; 73 + border-bottom: 1px solid var(--border); 74 + cursor: pointer; 75 + transition: background-color 0.2s ease; 76 + position: relative; 77 + } 76 78 77 - .convo-item:hover { 78 - background-color: var(--surface); 79 - } 79 + .convo-item:hover { 80 + background-color: var(--surface); 81 + } 80 82 81 - .convo-item.unread { 82 - background-color: var(--surface); 83 - } 83 + .convo-item.unread { 84 + background-color: var(--surface); 85 + } 84 86 85 - .convo-avatar { 86 - position: relative; 87 - flex-shrink: 0; 88 - } 87 + .convo-avatar { 88 + position: relative; 89 + flex-shrink: 0; 90 + } 89 91 90 - .convo-avatar .avatar { 91 - width: 48px; 92 - height: 48px; 93 - } 92 + .convo-avatar .avatar { 93 + width: 48px; 94 + height: 48px; 95 + } 94 96 95 - .convo-unread-dot { 96 - position: absolute; 97 - top: 0; 98 - right: 0; 99 - width: 12px; 100 - height: 12px; 101 - border-radius: 50%; 102 - background-color: var(--accent-primary); 103 - border: 2px solid var(--bg); 104 - } 97 + .convo-unread-dot { 98 + position: absolute; 99 + top: 0; 100 + right: 0; 101 + width: 12px; 102 + height: 12px; 103 + border-radius: 50%; 104 + background-color: var(--accent-primary); 105 + border: 2px solid var(--bg); 106 + } 105 107 106 - .convo-info { 107 - flex: 1; 108 - min-width: 0; 109 - } 108 + .convo-info { 109 + flex: 1; 110 + min-width: 0; 111 + } 110 112 111 - .convo-header { 112 - display: flex; 113 - align-items: baseline; 114 - justify-content: space-between; 115 - gap: 8px; 116 - margin-bottom: 2px; 117 - } 113 + .convo-header { 114 + display: flex; 115 + align-items: baseline; 116 + justify-content: space-between; 117 + gap: 8px; 118 + margin-bottom: 2px; 119 + } 118 120 119 - .convo-name { 120 - font-weight: 600; 121 - font-size: 15px; 122 - color: var(--text-primary); 123 - white-space: nowrap; 124 - overflow: hidden; 125 - text-overflow: ellipsis; 126 - } 121 + .convo-name { 122 + font-weight: 600; 123 + font-size: 15px; 124 + color: var(--text-primary); 125 + white-space: nowrap; 126 + overflow: hidden; 127 + text-overflow: ellipsis; 128 + } 127 129 128 - .convo-time { 129 - font-size: 12px; 130 - color: var(--text-muted); 131 - flex-shrink: 0; 132 - } 130 + .convo-time { 131 + font-size: 12px; 132 + color: var(--text-muted); 133 + flex-shrink: 0; 134 + } 133 135 134 - .convo-last-message { 135 - font-size: 14px; 136 - color: var(--text-secondary); 137 - white-space: nowrap; 138 - overflow: hidden; 139 - text-overflow: ellipsis; 140 - } 136 + .convo-last-message { 137 + font-size: 14px; 138 + color: var(--text-secondary); 139 + white-space: nowrap; 140 + overflow: hidden; 141 + text-overflow: ellipsis; 142 + } 141 143 142 - .convo-last-message.unread { 143 - color: var(--text-primary); 144 - font-weight: 500; 145 - } 144 + .convo-last-message.unread { 145 + color: var(--text-primary); 146 + font-weight: 500; 147 + } 146 148 147 - .convo-muted-icon { 148 - width: 14px; 149 - height: 14px; 150 - color: var(--text-muted); 151 - flex-shrink: 0; 152 - } 149 + .convo-muted-icon { 150 + width: 14px; 151 + height: 14px; 152 + color: var(--text-muted); 153 + flex-shrink: 0; 154 + } 153 155 154 - /* New Message FAB */ 155 - .fab-new-message { 156 - position: fixed; 157 - bottom: 100px; 158 - right: calc(50% - 207px + 16px); 159 - width: 56px; 160 - height: 56px; 161 - border-radius: 50%; 162 - background-color: var(--accent-primary); 163 - color: white; 164 - border: none; 165 - cursor: pointer; 166 - display: flex; 167 - align-items: center; 168 - justify-content: center; 169 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 170 - transition: all 0.2s ease; 171 - z-index: 50; 172 - } 156 + /* New Message FAB */ 157 + .fab-new-message { 158 + position: fixed; 159 + bottom: 100px; 160 + right: calc(50% - 207px + 16px); 161 + width: 56px; 162 + height: 56px; 163 + border-radius: 50%; 164 + background-color: var(--accent-primary); 165 + color: white; 166 + border: none; 167 + cursor: pointer; 168 + display: flex; 169 + align-items: center; 170 + justify-content: center; 171 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 172 + transition: all 0.2s ease; 173 + z-index: 50; 174 + } 173 175 174 - .fab-new-message:hover { 175 - transform: scale(1.05); 176 - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); 177 - } 176 + .fab-new-message:hover { 177 + transform: scale(1.05); 178 + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); 179 + } 178 180 179 - .fab-new-message svg { 180 - width: 24px; 181 - height: 24px; 182 - } 181 + .fab-new-message svg { 182 + width: 24px; 183 + height: 24px; 184 + } 183 185 184 - /* Message Thread View */ 185 - .thread-header { 186 - display: flex; 187 - align-items: center; 188 - gap: 12px; 189 - padding: 12px 16px; 190 - border-bottom: 1px solid var(--border); 191 - background-color: var(--bg); 192 - position: sticky; 193 - top: 0; 194 - z-index: 50; 195 - } 186 + /* Message Thread View */ 187 + .thread-header { 188 + display: flex; 189 + align-items: center; 190 + gap: 12px; 191 + padding: 12px 16px; 192 + border-bottom: 1px solid var(--border); 193 + background-color: var(--bg); 194 + position: sticky; 195 + top: 0; 196 + z-index: 50; 197 + } 196 198 197 - .thread-back { 198 - background: none; 199 - border: none; 200 - color: var(--text-secondary); 201 - cursor: pointer; 202 - display: flex; 203 - align-items: center; 204 - } 199 + .thread-back { 200 + background: none; 201 + border: none; 202 + color: var(--text-secondary); 203 + cursor: pointer; 204 + display: flex; 205 + align-items: center; 206 + } 205 207 206 - .thread-back svg { 207 - width: 24px; 208 - height: 24px; 209 - } 208 + .thread-back svg { 209 + width: 24px; 210 + height: 24px; 211 + } 210 212 211 - .thread-info { 212 - flex: 1; 213 - } 213 + .thread-info { 214 + flex: 1; 215 + } 214 216 215 - .thread-name { 216 - font-weight: 600; 217 - font-size: 16px; 218 - color: var(--text-primary); 219 - } 217 + .thread-name { 218 + font-weight: 600; 219 + font-size: 16px; 220 + color: var(--text-primary); 221 + } 220 222 221 - .thread-handle { 222 - font-size: 13px; 223 - color: var(--text-secondary); 224 - } 223 + .thread-handle { 224 + font-size: 13px; 225 + color: var(--text-secondary); 226 + } 225 227 226 - .thread-overflow { 227 - background: none; 228 - border: none; 229 - color: var(--text-secondary); 230 - cursor: pointer; 231 - padding: 8px; 232 - } 228 + .thread-overflow { 229 + background: none; 230 + border: none; 231 + color: var(--text-secondary); 232 + cursor: pointer; 233 + padding: 8px; 234 + } 233 235 234 - .thread-overflow svg { 235 - width: 20px; 236 - height: 20px; 237 - } 236 + .thread-overflow svg { 237 + width: 20px; 238 + height: 20px; 239 + } 238 240 239 - /* Chat Bubbles */ 240 - .chat-area { 241 - padding: 16px; 242 - display: flex; 243 - flex-direction: column; 244 - gap: 8px; 245 - padding-bottom: 80px; 246 - } 241 + /* Chat Bubbles */ 242 + .chat-area { 243 + padding: 16px; 244 + display: flex; 245 + flex-direction: column; 246 + gap: 8px; 247 + padding-bottom: 80px; 248 + } 247 249 248 - .chat-bubble-row { 249 - display: flex; 250 - gap: 8px; 251 - max-width: 80%; 252 - } 253 - 254 - .chat-bubble-row.sent { 255 - align-self: flex-end; 256 - flex-direction: row-reverse; 257 - } 258 - 259 - .chat-bubble-row.received { 260 - align-self: flex-start; 261 - } 262 - 263 - .chat-bubble { 264 - padding: 10px 14px; 265 - border-radius: 18px; 266 - font-size: 15px; 267 - line-height: 1.4; 268 - position: relative; 269 - } 250 + .chat-bubble-row { 251 + display: flex; 252 + gap: 8px; 253 + max-width: 80%; 254 + } 270 255 271 - .chat-bubble.sent { 272 - background-color: var(--accent-primary); 273 - color: white; 274 - border-bottom-right-radius: 4px; 275 - } 256 + .chat-bubble-row.sent { 257 + align-self: flex-end; 258 + flex-direction: row-reverse; 259 + } 276 260 277 - .chat-bubble.received { 278 - background-color: var(--surface); 279 - color: var(--text-primary); 280 - border: 1px solid var(--border); 281 - border-bottom-left-radius: 4px; 282 - } 261 + .chat-bubble-row.received { 262 + align-self: flex-start; 263 + } 283 264 284 - .chat-time { 285 - font-size: 11px; 286 - color: var(--text-muted); 287 - margin-top: 4px; 288 - padding: 0 4px; 289 - } 265 + .chat-bubble { 266 + padding: 10px 14px; 267 + border-radius: 18px; 268 + font-size: 15px; 269 + line-height: 1.4; 270 + position: relative; 271 + } 290 272 291 - .chat-time.sent { 292 - text-align: right; 293 - } 273 + .chat-bubble.sent { 274 + background-color: var(--accent-primary); 275 + color: white; 276 + border-bottom-right-radius: 4px; 277 + } 294 278 295 - .chat-date-divider { 296 - text-align: center; 297 - padding: 12px 0; 298 - font-size: 12px; 299 - color: var(--text-muted); 300 - font-weight: 500; 301 - } 279 + .chat-bubble.received { 280 + background-color: var(--surface); 281 + color: var(--text-primary); 282 + border: 1px solid var(--border); 283 + border-bottom-left-radius: 4px; 284 + } 302 285 303 - /* Message Input */ 304 - .msg-input-bar { 305 - position: fixed; 306 - bottom: 0; 307 - left: 50%; 308 - transform: translateX(-50%); 309 - width: 100%; 310 - max-width: 414px; 311 - display: flex; 312 - align-items: center; 313 - gap: 8px; 314 - padding: 12px 16px; 315 - background-color: var(--bg); 316 - border-top: 1px solid var(--border); 317 - z-index: 100; 318 - } 286 + .chat-time { 287 + font-size: 11px; 288 + color: var(--text-muted); 289 + margin-top: 4px; 290 + padding: 0 4px; 291 + } 319 292 320 - .msg-input { 321 - flex: 1; 322 - padding: 10px 16px; 323 - border: 1px solid var(--border); 324 - border-radius: 9999px; 325 - background-color: var(--surface); 326 - color: var(--text-primary); 327 - font-size: 15px; 328 - font-family: var(--font-body); 329 - outline: none; 330 - transition: border-color 0.2s ease; 331 - } 293 + .chat-time.sent { 294 + text-align: right; 295 + } 332 296 333 - .msg-input:focus { 334 - border-color: var(--accent-primary); 335 - } 297 + .chat-date-divider { 298 + text-align: center; 299 + padding: 12px 0; 300 + font-size: 12px; 301 + color: var(--text-muted); 302 + font-weight: 500; 303 + } 336 304 337 - .msg-input::placeholder { 338 - color: var(--text-muted); 339 - } 305 + /* Message Input */ 306 + .msg-input-bar { 307 + position: fixed; 308 + bottom: 0; 309 + left: 50%; 310 + transform: translateX(-50%); 311 + width: 100%; 312 + max-width: 414px; 313 + display: flex; 314 + align-items: center; 315 + gap: 8px; 316 + padding: 12px 16px; 317 + background-color: var(--bg); 318 + border-top: 1px solid var(--border); 319 + z-index: 100; 320 + } 340 321 341 - .msg-send-btn { 342 - width: 40px; 343 - height: 40px; 344 - border-radius: 50%; 345 - border: none; 346 - background-color: var(--accent-primary); 347 - color: white; 348 - cursor: pointer; 349 - display: flex; 350 - align-items: center; 351 - justify-content: center; 352 - transition: background-color 0.2s ease; 353 - flex-shrink: 0; 354 - } 322 + .msg-input { 323 + flex: 1; 324 + padding: 10px 16px; 325 + border: 1px solid var(--border); 326 + border-radius: 9999px; 327 + background-color: var(--surface); 328 + color: var(--text-primary); 329 + font-size: 15px; 330 + font-family: var(--font-body); 331 + outline: none; 332 + transition: border-color 0.2s ease; 333 + } 355 334 356 - .msg-send-btn:hover { 357 - background-color: var(--accent-primary-hover); 358 - } 335 + .msg-input:focus { 336 + border-color: var(--accent-primary); 337 + } 359 338 360 - .msg-send-btn svg { 361 - width: 18px; 362 - height: 18px; 363 - } 339 + .msg-input::placeholder { 340 + color: var(--text-muted); 341 + } 364 342 365 - /* Nav badge */ 366 - .nav-item { 367 - position: relative; 368 - } 343 + .msg-send-btn { 344 + width: 40px; 345 + height: 40px; 346 + border-radius: 50%; 347 + border: none; 348 + background-color: var(--accent-primary); 349 + color: white; 350 + cursor: pointer; 351 + display: flex; 352 + align-items: center; 353 + justify-content: center; 354 + transition: background-color 0.2s ease; 355 + flex-shrink: 0; 356 + } 369 357 370 - .nav-item-badge { 371 - position: absolute; 372 - top: 2px; 373 - right: 8px; 374 - min-width: 18px; 375 - height: 18px; 376 - padding: 0 5px; 377 - border-radius: 9px; 378 - background-color: var(--accent-error); 379 - color: white; 380 - font-size: 10px; 381 - font-weight: 700; 382 - display: flex; 383 - align-items: center; 384 - justify-content: center; 385 - } 358 + .msg-send-btn:hover { 359 + background-color: var(--accent-primary-hover); 360 + } 386 361 387 - /* View toggle */ 388 - .view-toggle { display: none; } 389 - .view-toggle.active { display: block; } 390 - </style> 391 - </head> 392 - <body> 393 - <div class="mobile-container"> 362 + .msg-send-btn svg { 363 + width: 18px; 364 + height: 18px; 365 + } 394 366 395 - <!-- ==================== --> 396 - <!-- CONVERSATION LIST VIEW --> 397 - <!-- ==================== --> 398 - <div class="view-toggle active" id="list-view"> 367 + /* Nav badge */ 368 + .nav-item { 369 + position: relative; 370 + } 399 371 400 - <!-- Header --> 401 - <header class="header"> 402 - <h1 class="header-title">Messages</h1> 403 - <button class="header-action" onclick="toggleView()">Open Thread</button> 404 - </header> 372 + .nav-item-badge { 373 + position: absolute; 374 + top: 2px; 375 + right: 8px; 376 + min-width: 18px; 377 + height: 18px; 378 + padding: 0 5px; 379 + border-radius: 9px; 380 + background-color: var(--accent-error); 381 + color: white; 382 + font-size: 10px; 383 + font-weight: 700; 384 + display: flex; 385 + align-items: center; 386 + justify-content: center; 387 + } 405 388 406 - <!-- Tabs --> 407 - <div class="msg-tabs"> 408 - <button class="msg-tab active">Primary</button> 409 - <button class="msg-tab">Requests <span class="msg-tab-badge">2</span></button> 410 - </div> 389 + /* View toggle */ 390 + .view-toggle { 391 + display: none; 392 + } 393 + .view-toggle.active { 394 + display: block; 395 + } 396 + </style> 397 + </head> 398 + <body> 399 + <div class="mobile-container"> 400 + <!-- ==================== --> 401 + <!-- CONVERSATION LIST VIEW --> 402 + <!-- ==================== --> 403 + <div class="view-toggle active" id="list-view"> 404 + <!-- Header --> 405 + <header class="header"> 406 + <h1 class="header-title">Messages</h1> 407 + <button class="header-action" onclick="toggleView()">Open Thread</button> 408 + </header> 411 409 412 - <div class="messages-container"> 410 + <!-- Tabs --> 411 + <div class="msg-tabs"> 412 + <button class="msg-tab active">Primary</button> 413 + <button class="msg-tab">Requests <span class="msg-tab-badge">2</span></button> 414 + </div> 413 415 414 - <!-- Unread conversation --> 415 - <div class="convo-item unread"> 416 - <div class="convo-avatar"> 417 - <div class="avatar">AS</div> 418 - <div class="convo-unread-dot"></div> 419 - </div> 420 - <div class="convo-info"> 421 - <div class="convo-header"> 422 - <span class="convo-name">Alice Smith</span> 423 - <span class="convo-time">12m</span> 416 + <div class="messages-container"> 417 + <!-- Unread conversation --> 418 + <div class="convo-item unread"> 419 + <div class="convo-avatar"> 420 + <div class="avatar">AS</div> 421 + <div class="convo-unread-dot"></div> 422 + </div> 423 + <div class="convo-info"> 424 + <div class="convo-header"> 425 + <span class="convo-name">Alice Smith</span> 426 + <span class="convo-time">12m</span> 427 + </div> 428 + <div class="convo-last-message unread"> 429 + Hey! Did you see the new federation update? It's really exciting. 430 + </div> 424 431 </div> 425 - <div class="convo-last-message unread">Hey! Did you see the new federation update? It's really exciting.</div> 426 432 </div> 427 - </div> 428 433 429 - <!-- Unread conversation --> 430 - <div class="convo-item unread"> 431 - <div class="convo-avatar"> 432 - <div class="avatar">BJ</div> 433 - <div class="convo-unread-dot"></div> 434 - </div> 435 - <div class="convo-info"> 436 - <div class="convo-header"> 437 - <span class="convo-name">Bob Johnson</span> 438 - <span class="convo-time">2h</span> 434 + <!-- Unread conversation --> 435 + <div class="convo-item unread"> 436 + <div class="convo-avatar"> 437 + <div class="avatar">BJ</div> 438 + <div class="convo-unread-dot"></div> 439 + </div> 440 + <div class="convo-info"> 441 + <div class="convo-header"> 442 + <span class="convo-name">Bob Johnson</span> 443 + <span class="convo-time">2h</span> 444 + </div> 445 + <div class="convo-last-message unread">Would love to collaborate on the AT Protocol project</div> 439 446 </div> 440 - <div class="convo-last-message unread">Would love to collaborate on the AT Protocol project</div> 441 447 </div> 442 - </div> 443 448 444 - <!-- Read conversation --> 445 - <div class="convo-item"> 446 - <div class="convo-avatar"> 447 - <div class="avatar">CW</div> 448 - </div> 449 - <div class="convo-info"> 450 - <div class="convo-header"> 451 - <span class="convo-name">Carol White</span> 452 - <span class="convo-time">1d</span> 449 + <!-- Read conversation --> 450 + <div class="convo-item"> 451 + <div class="convo-avatar"> 452 + <div class="avatar">CW</div> 453 453 </div> 454 - <div class="convo-last-message">Thanks for sharing that article! I'll check it out this weekend.</div> 454 + <div class="convo-info"> 455 + <div class="convo-header"> 456 + <span class="convo-name">Carol White</span> 457 + <span class="convo-time">1d</span> 458 + </div> 459 + <div class="convo-last-message">Thanks for sharing that article! I'll check it out this weekend.</div> 460 + </div> 455 461 </div> 456 - </div> 457 462 458 - <!-- Muted conversation --> 459 - <div class="convo-item"> 460 - <div class="convo-avatar"> 461 - <div class="avatar">DM</div> 462 - </div> 463 - <div class="convo-info"> 464 - <div class="convo-header"> 465 - <span class="convo-name">David Miller</span> 466 - <span class="convo-time">3d</span> 463 + <!-- Muted conversation --> 464 + <div class="convo-item"> 465 + <div class="convo-avatar"> 466 + <div class="avatar">DM</div> 467 467 </div> 468 - <div class="convo-last-message">Sounds good, let's catch up next week then!</div> 468 + <div class="convo-info"> 469 + <div class="convo-header"> 470 + <span class="convo-name">David Miller</span> 471 + <span class="convo-time">3d</span> 472 + </div> 473 + <div class="convo-last-message">Sounds good, let's catch up next week then!</div> 474 + </div> 475 + <svg 476 + class="convo-muted-icon" 477 + viewBox="0 0 24 24" 478 + fill="none" 479 + stroke="currentColor" 480 + stroke-width="2" 481 + stroke-linecap="round" 482 + stroke-linejoin="round"> 483 + <path d="M11 5L6 9H2v6h4l5 4V5z" /> 484 + <line x1="23" y1="9" x2="17" y2="15" /> 485 + <line x1="17" y1="9" x2="23" y2="15" /> 486 + </svg> 469 487 </div> 470 - <svg class="convo-muted-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 471 - <path d="M11 5L6 9H2v6h4l5 4V5z"/> 472 - <line x1="23" y1="9" x2="17" y2="15"/> 473 - <line x1="17" y1="9" x2="23" y2="15"/> 474 - </svg> 475 - </div> 476 488 477 - <!-- Old conversation --> 478 - <div class="convo-item"> 479 - <div class="convo-avatar"> 480 - <div class="avatar">EL</div> 481 - </div> 482 - <div class="convo-info"> 483 - <div class="convo-header"> 484 - <span class="convo-name">Eva Lee</span> 485 - <span class="convo-time">1w</span> 489 + <!-- Old conversation --> 490 + <div class="convo-item"> 491 + <div class="convo-avatar"> 492 + <div class="avatar">EL</div> 486 493 </div> 487 - <div class="convo-last-message">Great chatting with you at the meetup!</div> 494 + <div class="convo-info"> 495 + <div class="convo-header"> 496 + <span class="convo-name">Eva Lee</span> 497 + <span class="convo-time">1w</span> 498 + </div> 499 + <div class="convo-last-message">Great chatting with you at the meetup!</div> 500 + </div> 488 501 </div> 489 502 </div> 490 503 491 - </div> 492 - 493 - <!-- New Message FAB --> 494 - <button class="fab-new-message" title="New message"> 495 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 496 - <line x1="22" y1="2" x2="11" y2="13"/> 497 - <polygon points="22 2 15 22 11 13 2 9 22 2"/> 498 - </svg> 499 - </button> 500 - 501 - <!-- Bottom Navigation --> 502 - <nav class="nav-bar"> 503 - <a href="home.html" class="nav-item"> 504 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 505 - <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/> 506 - <polyline points="9 22 9 12 15 12 15 22"/> 504 + <!-- New Message FAB --> 505 + <button class="fab-new-message" title="New message"> 506 + <svg 507 + viewBox="0 0 24 24" 508 + fill="none" 509 + stroke="currentColor" 510 + stroke-width="2" 511 + stroke-linecap="round" 512 + stroke-linejoin="round"> 513 + <line x1="22" y1="2" x2="11" y2="13" /> 514 + <polygon points="22 2 15 22 11 13 2 9 22 2" /> 507 515 </svg> 508 - <span>Home</span> 509 - </a> 516 + </button> 510 517 511 - <a href="search.html" class="nav-item"> 512 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 513 - <circle cx="11" cy="11" r="8"/> 514 - <line x1="21" y1="21" x2="16.65" y2="16.65"/> 515 - </svg> 516 - <span>Search</span> 517 - </a> 518 + <!-- Bottom Navigation --> 519 + <nav class="nav-bar"> 520 + <a href="home.html" class="nav-item"> 521 + <svg 522 + viewBox="0 0 24 24" 523 + fill="none" 524 + stroke="currentColor" 525 + stroke-width="2" 526 + stroke-linecap="round" 527 + stroke-linejoin="round"> 528 + <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> 529 + <polyline points="9 22 9 12 15 12 15 22" /> 530 + </svg> 531 + <span>Home</span> 532 + </a> 518 533 519 - <a href="notifications.html" class="nav-item"> 520 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 521 - <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/> 522 - <path d="M13.73 21a2 2 0 0 1-3.46 0"/> 523 - </svg> 524 - <span>Alerts</span> 525 - </a> 534 + <a href="search.html" class="nav-item"> 535 + <svg 536 + viewBox="0 0 24 24" 537 + fill="none" 538 + stroke="currentColor" 539 + stroke-width="2" 540 + stroke-linecap="round" 541 + stroke-linejoin="round"> 542 + <circle cx="11" cy="11" r="8" /> 543 + <line x1="21" y1="21" x2="16.65" y2="16.65" /> 544 + </svg> 545 + <span>Search</span> 546 + </a> 526 547 527 - <a href="messages.html" class="nav-item active"> 528 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 529 - <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/> 530 - </svg> 531 - <span class="nav-item-badge">2</span> 532 - <span>Chat</span> 533 - </a> 548 + <a href="notifications.html" class="nav-item"> 549 + <svg 550 + viewBox="0 0 24 24" 551 + fill="none" 552 + stroke="currentColor" 553 + stroke-width="2" 554 + stroke-linecap="round" 555 + stroke-linejoin="round"> 556 + <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" /> 557 + <path d="M13.73 21a2 2 0 0 1-3.46 0" /> 558 + </svg> 559 + <span>Alerts</span> 560 + </a> 534 561 535 - <a href="profile.html" class="nav-item"> 536 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 537 - <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 538 - <circle cx="12" cy="7" r="4"/> 539 - </svg> 540 - <span>Profile</span> 541 - </a> 542 - </nav> 543 - </div> 562 + <a href="messages.html" class="nav-item active"> 563 + <svg 564 + viewBox="0 0 24 24" 565 + fill="none" 566 + stroke="currentColor" 567 + stroke-width="2" 568 + stroke-linecap="round" 569 + stroke-linejoin="round"> 570 + <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /> 571 + </svg> 572 + <span class="nav-item-badge">2</span> 573 + <span>Chat</span> 574 + </a> 544 575 545 - <!-- ==================== --> 546 - <!-- MESSAGE THREAD VIEW --> 547 - <!-- ==================== --> 548 - <div class="view-toggle" id="thread-view"> 576 + <a href="profile.html" class="nav-item"> 577 + <svg 578 + viewBox="0 0 24 24" 579 + fill="none" 580 + stroke="currentColor" 581 + stroke-width="2" 582 + stroke-linecap="round" 583 + stroke-linejoin="round"> 584 + <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> 585 + <circle cx="12" cy="7" r="4" /> 586 + </svg> 587 + <span>Profile</span> 588 + </a> 589 + </nav> 590 + </div> 549 591 550 - <!-- Thread Header --> 551 - <div class="thread-header"> 552 - <button class="thread-back" onclick="toggleView()"> 553 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 554 - <polyline points="15 18 9 12 15 6"/> 555 - </svg> 556 - </button> 557 - <div class="thread-info"> 558 - <div class="thread-name">Alice Smith</div> 559 - <div class="thread-handle">@alice.bsky.social</div> 592 + <!-- ==================== --> 593 + <!-- MESSAGE THREAD VIEW --> 594 + <!-- ==================== --> 595 + <div class="view-toggle" id="thread-view"> 596 + <!-- Thread Header --> 597 + <div class="thread-header"> 598 + <button class="thread-back" onclick="toggleView()"> 599 + <svg 600 + viewBox="0 0 24 24" 601 + fill="none" 602 + stroke="currentColor" 603 + stroke-width="2" 604 + stroke-linecap="round" 605 + stroke-linejoin="round"> 606 + <polyline points="15 18 9 12 15 6" /> 607 + </svg> 608 + </button> 609 + <div class="thread-info"> 610 + <div class="thread-name">Alice Smith</div> 611 + <div class="thread-handle">@alice.bsky.social</div> 612 + </div> 613 + <button class="thread-overflow"> 614 + <svg 615 + viewBox="0 0 24 24" 616 + fill="none" 617 + stroke="currentColor" 618 + stroke-width="2" 619 + stroke-linecap="round" 620 + stroke-linejoin="round"> 621 + <circle cx="12" cy="12" r="1" /> 622 + <circle cx="19" cy="12" r="1" /> 623 + <circle cx="5" cy="12" r="1" /> 624 + </svg> 625 + </button> 560 626 </div> 561 - <button class="thread-overflow"> 562 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 563 - <circle cx="12" cy="12" r="1"/> 564 - <circle cx="19" cy="12" r="1"/> 565 - <circle cx="5" cy="12" r="1"/> 566 - </svg> 567 - </button> 568 - </div> 569 627 570 - <!-- Chat Area --> 571 - <div class="chat-area"> 628 + <!-- Chat Area --> 629 + <div class="chat-area"> 630 + <div class="chat-date-divider">Today</div> 572 631 573 - <div class="chat-date-divider">Today</div> 632 + <div class="chat-bubble-row received"> 633 + <div class="chat-bubble received">Hey! Did you see the new federation update? It's really exciting.</div> 634 + </div> 635 + <div class="chat-time">10:23 AM</div> 574 636 575 - <div class="chat-bubble-row received"> 576 - <div class="chat-bubble received">Hey! Did you see the new federation update? It's really exciting.</div> 577 - </div> 578 - <div class="chat-time">10:23 AM</div> 637 + <div class="chat-bubble-row sent"> 638 + <div class="chat-bubble sent"> 639 + Yes! I was just reading through the announcement. The self-hosting guide looks much improved. 640 + </div> 641 + </div> 642 + <div class="chat-time sent">10:25 AM</div> 579 643 580 - <div class="chat-bubble-row sent"> 581 - <div class="chat-bubble sent">Yes! I was just reading through the announcement. The self-hosting guide looks much improved.</div> 582 - </div> 583 - <div class="chat-time sent">10:25 AM</div> 644 + <div class="chat-bubble-row received"> 645 + <div class="chat-bubble received"> 646 + Right? I'm thinking of setting up my own PDS this weekend. Want to try it together? 647 + </div> 648 + </div> 649 + <div class="chat-time">10:27 AM</div> 584 650 585 - <div class="chat-bubble-row received"> 586 - <div class="chat-bubble received">Right? I'm thinking of setting up my own PDS this weekend. Want to try it together?</div> 587 - </div> 588 - <div class="chat-time">10:27 AM</div> 651 + <div class="chat-bubble-row sent"> 652 + <div class="chat-bubble sent"> 653 + That sounds great! I've been meaning to do that for a while. Let me know when you're free. 654 + </div> 655 + </div> 656 + <div class="chat-time sent">10:30 AM</div> 589 657 590 - <div class="chat-bubble-row sent"> 591 - <div class="chat-bubble sent">That sounds great! I've been meaning to do that for a while. Let me know when you're free.</div> 658 + <div class="chat-bubble-row received"> 659 + <div class="chat-bubble received"> 660 + Saturday afternoon works for me. We could do a video call and set them up at the same time. 661 + </div> 662 + </div> 663 + <div class="chat-time">10:32 AM</div> 592 664 </div> 593 - <div class="chat-time sent">10:30 AM</div> 594 665 595 - <div class="chat-bubble-row received"> 596 - <div class="chat-bubble received">Saturday afternoon works for me. We could do a video call and set them up at the same time.</div> 666 + <!-- Message Input --> 667 + <div class="msg-input-bar"> 668 + <input class="msg-input" type="text" placeholder="Type a message..." /> 669 + <button class="msg-send-btn"> 670 + <svg 671 + viewBox="0 0 24 24" 672 + fill="none" 673 + stroke="currentColor" 674 + stroke-width="2" 675 + stroke-linecap="round" 676 + stroke-linejoin="round"> 677 + <line x1="22" y1="2" x2="11" y2="13" /> 678 + <polygon points="22 2 15 22 11 13 2 9 22 2" /> 679 + </svg> 680 + </button> 597 681 </div> 598 - <div class="chat-time">10:32 AM</div> 599 - 600 682 </div> 601 - 602 - <!-- Message Input --> 603 - <div class="msg-input-bar"> 604 - <input class="msg-input" type="text" placeholder="Type a message..."> 605 - <button class="msg-send-btn"> 606 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 607 - <line x1="22" y1="2" x2="11" y2="13"/> 608 - <polygon points="22 2 15 22 11 13 2 9 22 2"/> 609 - </svg> 610 - </button> 611 - </div> 612 - 613 683 </div> 614 684 615 - </div> 616 - 617 - <script> 618 - if (localStorage.getItem('theme')) { 619 - const t = localStorage.getItem('theme'); 620 - if (t !== 'light') document.documentElement.setAttribute('data-theme', t); 621 - } 685 + <script> 686 + if (localStorage.getItem("theme")) { 687 + const t = localStorage.getItem("theme"); 688 + if (t !== "light") document.documentElement.setAttribute("data-theme", t); 689 + } 622 690 623 - // Toggle between list and thread views for demo 624 - function toggleView() { 625 - document.getElementById('list-view').classList.toggle('active'); 626 - document.getElementById('thread-view').classList.toggle('active'); 627 - } 691 + function toggleView() { 692 + document.getElementById("list-view").classList.toggle("active"); 693 + document.getElementById("thread-view").classList.toggle("active"); 694 + } 628 695 629 - // Tab switching demo 630 - document.querySelectorAll('.msg-tab').forEach(tab => { 631 - tab.addEventListener('click', () => { 632 - document.querySelectorAll('.msg-tab').forEach(t => t.classList.remove('active')); 633 - tab.classList.add('active'); 696 + document.querySelectorAll(".msg-tab").forEach((tab) => { 697 + tab.addEventListener("click", () => { 698 + document.querySelectorAll(".msg-tab").forEach((t) => t.classList.remove("active")); 699 + tab.classList.add("active"); 700 + }); 634 701 }); 635 - }); 636 - </script> 637 - </body> 702 + </script> 703 + </body> 638 704 </html>
+551 -439
docs/designs/profile.html
··· 1 - <!DOCTYPE html> 1 + <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 - <title>Profile - Lazurite</title> 7 - <link rel="preconnect" href="https://fonts.googleapis.com"> 8 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 - <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet"> 11 - <link rel="stylesheet" href="styles.css"> 12 - <style> 13 - .profile-container { 14 - padding-bottom: 88px; 15 - } 16 - 17 - .profile-header { 18 - background: linear-gradient(180deg, var(--surface) 0%, var(--bg) 100%); 19 - padding: 0 16px 16px; 20 - } 21 - 22 - .profile-cover { 23 - height: 120px; 24 - background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%); 25 - margin: 0 -16px 16px; 26 - border-radius: 0 0 16px 16px; 27 - } 28 - 29 - .profile-info { 30 - position: relative; 31 - } 32 - 33 - .profile-avatar { 34 - position: absolute; 35 - top: -50px; 36 - left: 0; 37 - width: 100px; 38 - height: 100px; 39 - border-radius: 50%; 40 - background-color: var(--surface); 41 - border: 4px solid var(--bg); 42 - display: flex; 43 - align-items: center; 44 - justify-content: center; 45 - font-size: 36px; 46 - font-weight: 700; 47 - color: var(--text-primary); 48 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 49 - } 50 - 51 - .profile-actions { 52 - display: flex; 53 - justify-content: flex-end; 54 - gap: 12px; 55 - margin-bottom: 16px; 56 - padding-top: 8px; 57 - } 58 - 59 - .profile-btn { 60 - padding: 8px 20px; 61 - border-radius: 9999px; 62 - font-size: 14px; 63 - font-weight: 600; 64 - cursor: pointer; 65 - transition: all 0.2s ease; 66 - border: 1.5px solid var(--border); 67 - background-color: var(--surface); 68 - color: var(--text-primary); 69 - } 70 - 71 - .profile-btn:hover { 72 - background-color: var(--surface-variant); 73 - } 74 - 75 - .profile-btn-primary { 76 - background-color: var(--text-primary); 77 - color: var(--bg); 78 - border-color: var(--text-primary); 79 - } 80 - 81 - .profile-btn-primary:hover { 82 - opacity: 0.9; 83 - } 84 - 85 - .profile-name { 86 - font-size: 22px; 87 - font-weight: 700; 88 - color: var(--text-primary); 89 - margin-top: 56px; 90 - margin-bottom: 4px; 91 - } 92 - 93 - .profile-handle { 94 - color: var(--text-secondary); 95 - font-size: 15px; 96 - margin-bottom: 12px; 97 - } 98 - 99 - .profile-bio { 100 - color: var(--text-primary); 101 - font-size: 15px; 102 - line-height: 1.5; 103 - margin-bottom: 16px; 104 - } 105 - 106 - .profile-meta { 107 - display: flex; 108 - flex-wrap: wrap; 109 - gap: 16px; 110 - margin-bottom: 16px; 111 - } 112 - 113 - .profile-meta-item { 114 - display: flex; 115 - align-items: center; 116 - gap: 6px; 117 - color: var(--text-secondary); 118 - font-size: 14px; 119 - } 120 - 121 - .profile-meta-item svg { 122 - width: 16px; 123 - height: 16px; 124 - color: var(--text-muted); 125 - } 126 - 127 - .profile-stats { 128 - display: flex; 129 - gap: 24px; 130 - } 131 - 132 - .profile-stat { 133 - color: var(--text-secondary); 134 - font-size: 14px; 135 - } 136 - 137 - .profile-stat strong { 138 - color: var(--text-primary); 139 - font-weight: 700; 140 - } 141 - 142 - .profile-tabs { 143 - display: flex; 144 - border-bottom: 1px solid var(--border); 145 - background-color: var(--bg); 146 - } 147 - 148 - .profile-tab { 149 - flex: 1; 150 - padding: 16px; 151 - text-align: center; 152 - font-weight: 600; 153 - font-size: 15px; 154 - color: var(--text-secondary); 155 - cursor: pointer; 156 - border-bottom: 2px solid transparent; 157 - transition: all 0.2s ease; 158 - background: none; 159 - border-top: none; 160 - border-left: none; 161 - border-right: none; 162 - } 163 - 164 - .profile-tab:hover { 165 - background-color: var(--surface); 166 - color: var(--text-primary); 167 - } 168 - 169 - .profile-tab.active { 170 - color: var(--text-primary); 171 - border-bottom-color: var(--accent-primary); 172 - } 173 - 174 - .posts-empty { 175 - padding: 48px 24px; 176 - text-align: center; 177 - } 178 - 179 - .posts-empty-icon { 180 - width: 64px; 181 - height: 64px; 182 - color: var(--text-muted); 183 - margin-bottom: 16px; 184 - } 185 - 186 - .posts-empty-title { 187 - font-size: 18px; 188 - font-weight: 600; 189 - color: var(--text-primary); 190 - margin-bottom: 8px; 191 - } 192 - 193 - .posts-empty-text { 194 - color: var(--text-secondary); 195 - font-size: 14px; 196 - } 197 - </style> 198 - </head> 199 - <body> 200 - <div class="mobile-container"> 201 - 202 - <!-- Header --> 203 - <header class="header"> 204 - <button class="header-action">← Back</button> 205 - <h1 class="header-title">John Doe</h1> 206 - <button class="header-action">⋯</button> 207 - </header> 208 - 209 - <div class="profile-container"> 210 - 211 - <!-- Profile Header --> 212 - <div class="profile-header"> 213 - <div class="profile-cover"></div> 214 - 215 - <div class="profile-info"> 216 - <div class="profile-avatar">JD</div> 217 - 218 - <div class="profile-actions"> 219 - <button class="profile-btn">Edit Profile</button> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> 6 + <title>Profile - Lazurite</title> 7 + <link rel="preconnect" href="https://fonts.googleapis.com" /> 8 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 9 + <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet" /> 10 + <link 11 + href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" 12 + rel="stylesheet" /> 13 + <link rel="stylesheet" href="styles.css" /> 14 + <style> 15 + .profile-container { 16 + padding-bottom: 88px; 17 + } 18 + 19 + .profile-header { 20 + background: linear-gradient(180deg, var(--surface) 0%, var(--bg) 100%); 21 + padding: 0 16px 16px; 22 + } 23 + 24 + .profile-cover { 25 + height: 120px; 26 + background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%); 27 + margin: 0 -16px 16px; 28 + border-radius: 0 0 16px 16px; 29 + } 30 + 31 + .profile-info { 32 + position: relative; 33 + } 34 + 35 + .profile-avatar { 36 + position: absolute; 37 + top: -50px; 38 + left: 0; 39 + width: 100px; 40 + height: 100px; 41 + border-radius: 50%; 42 + background-color: var(--surface); 43 + border: 4px solid var(--bg); 44 + display: flex; 45 + align-items: center; 46 + justify-content: center; 47 + font-size: 36px; 48 + font-weight: 700; 49 + color: var(--text-primary); 50 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 51 + } 52 + 53 + .profile-actions { 54 + display: flex; 55 + justify-content: flex-end; 56 + gap: 12px; 57 + margin-bottom: 16px; 58 + padding-top: 8px; 59 + } 60 + 61 + .profile-btn { 62 + padding: 8px 20px; 63 + border-radius: 9999px; 64 + font-size: 14px; 65 + font-weight: 600; 66 + cursor: pointer; 67 + transition: all 0.2s ease; 68 + border: 1.5px solid var(--border); 69 + background-color: var(--surface); 70 + color: var(--text-primary); 71 + } 72 + 73 + .profile-btn:hover { 74 + background-color: var(--surface-variant); 75 + } 76 + 77 + .profile-btn-primary { 78 + background-color: var(--text-primary); 79 + color: var(--bg); 80 + border-color: var(--text-primary); 81 + } 82 + 83 + .profile-btn-primary:hover { 84 + opacity: 0.9; 85 + } 86 + 87 + .profile-name { 88 + font-size: 22px; 89 + font-weight: 700; 90 + color: var(--text-primary); 91 + margin-top: 56px; 92 + margin-bottom: 4px; 93 + } 94 + 95 + .profile-handle { 96 + color: var(--text-secondary); 97 + font-size: 15px; 98 + margin-bottom: 12px; 99 + } 100 + 101 + .profile-bio { 102 + color: var(--text-primary); 103 + font-size: 15px; 104 + line-height: 1.5; 105 + margin-bottom: 16px; 106 + } 107 + 108 + .profile-meta { 109 + display: flex; 110 + flex-wrap: wrap; 111 + gap: 16px; 112 + margin-bottom: 16px; 113 + } 114 + 115 + .profile-meta-item { 116 + display: flex; 117 + align-items: center; 118 + gap: 6px; 119 + color: var(--text-secondary); 120 + font-size: 14px; 121 + } 122 + 123 + .profile-meta-item svg { 124 + width: 16px; 125 + height: 16px; 126 + color: var(--text-muted); 127 + } 128 + 129 + .profile-stats { 130 + display: flex; 131 + gap: 24px; 132 + } 133 + 134 + .profile-stat { 135 + color: var(--text-secondary); 136 + font-size: 14px; 137 + } 138 + 139 + .profile-stat strong { 140 + color: var(--text-primary); 141 + font-weight: 700; 142 + } 143 + 144 + .profile-tabs { 145 + display: flex; 146 + border-bottom: 1px solid var(--border); 147 + background-color: var(--bg); 148 + } 149 + 150 + .profile-tab { 151 + flex: 1; 152 + padding: 16px; 153 + text-align: center; 154 + font-weight: 600; 155 + font-size: 15px; 156 + color: var(--text-secondary); 157 + cursor: pointer; 158 + border-bottom: 2px solid transparent; 159 + transition: all 0.2s ease; 160 + background: none; 161 + border-top: none; 162 + border-left: none; 163 + border-right: none; 164 + } 165 + 166 + .profile-tab:hover { 167 + background-color: var(--surface); 168 + color: var(--text-primary); 169 + } 170 + 171 + .profile-tab.active { 172 + color: var(--text-primary); 173 + border-bottom-color: var(--accent-primary); 174 + } 175 + 176 + .posts-empty { 177 + padding: 48px 24px; 178 + text-align: center; 179 + } 180 + 181 + .posts-empty-icon { 182 + width: 64px; 183 + height: 64px; 184 + color: var(--text-muted); 185 + margin-bottom: 16px; 186 + } 187 + 188 + .posts-empty-title { 189 + font-size: 18px; 190 + font-weight: 600; 191 + color: var(--text-primary); 192 + margin-bottom: 8px; 193 + } 194 + 195 + .posts-empty-text { 196 + color: var(--text-secondary); 197 + font-size: 14px; 198 + } 199 + </style> 200 + </head> 201 + <body> 202 + <div class="mobile-container"> 203 + <!-- Header --> 204 + <header class="header"> 205 + <button class="header-action">← Back</button> 206 + <h1 class="header-title">John Doe</h1> 207 + <button class="header-action">⋯</button> 208 + </header> 209 + 210 + <div class="profile-container"> 211 + <!-- Profile Header --> 212 + <div class="profile-header"> 213 + <div class="profile-cover"></div> 214 + 215 + <div class="profile-info"> 216 + <div class="profile-avatar">JD</div> 217 + 218 + <div class="profile-actions"> 219 + <button class="profile-btn">Edit Profile</button> 220 + </div> 221 + 222 + <h2 class="profile-name">John Doe</h2> 223 + <div class="profile-handle">@johndoe.bsky.social</div> 224 + 225 + <p class="profile-bio"> 226 + Building things on the web 🚀 • Open source enthusiast • Coffee addict ☕ • 227 + <a href="#" class="link">johndoe.dev</a> 228 + </p> 229 + 230 + <div class="profile-meta"> 231 + <div class="profile-meta-item"> 232 + <svg 233 + viewBox="0 0 24 24" 234 + fill="none" 235 + stroke="currentColor" 236 + stroke-width="2" 237 + stroke-linecap="round" 238 + stroke-linejoin="round"> 239 + <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" /> 240 + <circle cx="12" cy="10" r="3" /> 241 + </svg> 242 + San Francisco, CA 243 + </div> 244 + 245 + <div class="profile-meta-item"> 246 + <svg 247 + viewBox="0 0 24 24" 248 + fill="none" 249 + stroke="currentColor" 250 + stroke-width="2" 251 + stroke-linecap="round" 252 + stroke-linejoin="round"> 253 + <rect x="3" y="4" width="18" height="18" rx="2" ry="2" /> 254 + <line x1="16" y1="2" x2="16" y2="6" /> 255 + <line x1="8" y1="2" x2="8" y2="6" /> 256 + <line x1="3" y1="10" x2="21" y2="10" /> 257 + </svg> 258 + Joined March 2023 259 + </div> 260 + </div> 261 + 262 + <div class="profile-stats"> 263 + <div class="profile-stat"><strong>256</strong> Following</div> 264 + <div class="profile-stat"><strong>1.2K</strong> Followers</div> 265 + <div class="profile-stat"><strong>482</strong> Posts</div> 266 + </div> 220 267 </div> 221 - 222 - <h2 class="profile-name">John Doe</h2> 223 - <div class="profile-handle">@johndoe.bsky.social</div> 224 - 225 - <p class="profile-bio"> 226 - Building things on the web 🚀 • Open source enthusiast • Coffee addict ☕ • 227 - <a href="#" class="link">johndoe.dev</a> 228 - </p> 229 - 230 - <div class="profile-meta"> 231 - <div class="profile-meta-item"> 232 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 233 - <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/> 234 - <circle cx="12" cy="10" r="3"/> 235 - </svg> 236 - San Francisco, CA 268 + </div> 269 + 270 + <!-- Profile Tabs --> 271 + <div class="profile-tabs"> 272 + <button class="profile-tab active">Posts</button> 273 + <button class="profile-tab">Replies</button> 274 + <button class="profile-tab">Media</button> 275 + <button class="profile-tab">Likes</button> 276 + </div> 277 + 278 + <!-- User Posts --> 279 + 280 + <!-- Post 1 --> 281 + <article class="post-card"> 282 + <div class="post-header"> 283 + <div class="avatar">JD</div> 284 + <div class="post-author"> 285 + <div class="post-author-name">John Doe</div> 286 + <div class="post-author-handle">@johndoe.bsky.social · <span class="post-timestamp">2h</span></div> 237 287 </div> 238 - 239 - <div class="profile-meta-item"> 240 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 241 - <rect x="3" y="4" width="18" height="18" rx="2" ry="2"/> 242 - <line x1="16" y1="2" x2="16" y2="6"/> 243 - <line x1="8" y1="2" x2="8" y2="6"/> 244 - <line x1="3" y1="10" x2="21" y2="10"/> 288 + </div> 289 + 290 + <div class="post-content"> 291 + Finally shipped the new feature I've been working on! 🎉 Check it out and let me know what you think. Always 292 + open to feedback! <a href="#" class="post-facet-hashtag">#buildinpublic</a> 293 + <a href="#" class="post-facet-hashtag">#indiehacker</a> 294 + </div> 295 + 296 + <div class="post-actions"> 297 + <button class="post-action"> 298 + <svg 299 + viewBox="0 0 24 24" 300 + fill="none" 301 + stroke="currentColor" 302 + stroke-width="2" 303 + stroke-linecap="round" 304 + stroke-linejoin="round"> 305 + <path 306 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 245 307 </svg> 246 - Joined March 2023 308 + 8 309 + </button> 310 + <button class="post-action"> 311 + <svg 312 + viewBox="0 0 24 24" 313 + fill="none" 314 + stroke="currentColor" 315 + stroke-width="2" 316 + stroke-linecap="round" 317 + stroke-linejoin="round"> 318 + <polyline points="17 1 21 5 17 9" /> 319 + <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 320 + <polyline points="7 23 3 19 7 15" /> 321 + <path d="M21 13v2a4 4 0 0 1-4 4H3" /> 322 + </svg> 323 + 3 324 + </button> 325 + <button class="post-action"> 326 + <svg 327 + viewBox="0 0 24 24" 328 + fill="none" 329 + stroke="currentColor" 330 + stroke-width="2" 331 + stroke-linecap="round" 332 + stroke-linejoin="round"> 333 + <path 334 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 335 + </svg> 336 + 42 337 + </button> 338 + <button class="post-action"> 339 + <svg 340 + viewBox="0 0 24 24" 341 + fill="none" 342 + stroke="currentColor" 343 + stroke-width="2" 344 + stroke-linecap="round" 345 + stroke-linejoin="round"> 346 + <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 347 + <polyline points="16 6 12 2 8 6" /> 348 + <line x1="12" y1="2" x2="12" y2="15" /> 349 + </svg> 350 + </button> 351 + </div> 352 + </article> 353 + 354 + <!-- Post 2 --> 355 + <article class="post-card"> 356 + <div class="post-header"> 357 + <div class="avatar">JD</div> 358 + <div class="post-author"> 359 + <div class="post-author-name">John Doe</div> 360 + <div class="post-author-handle">@johndoe.bsky.social · <span class="post-timestamp">1d</span></div> 247 361 </div> 248 362 </div> 249 - 250 - <div class="profile-stats"> 251 - <div class="profile-stat"><strong>256</strong> Following</div> 252 - <div class="profile-stat"><strong>1.2K</strong> Followers</div> 253 - <div class="profile-stat"><strong>482</strong> Posts</div> 363 + 364 + <div class="post-content"> 365 + Really enjoying the <a href="#" class="post-facet-mention">@bluesky</a> community so far. Great 366 + conversations happening here! Looking forward to seeing how the platform evolves. 367 + <a href="#" class="post-facet-hashtag">#bluesky</a> <a href="#" class="post-facet-hashtag">#socialmedia</a> 254 368 </div> 255 - </div> 256 - </div> 257 - 258 - <!-- Profile Tabs --> 259 - <div class="profile-tabs"> 260 - <button class="profile-tab active">Posts</button> 261 - <button class="profile-tab">Replies</button> 262 - <button class="profile-tab">Media</button> 263 - <button class="profile-tab">Likes</button> 264 - </div> 265 - 266 - <!-- User Posts --> 267 - 268 - <!-- Post 1 --> 269 - <article class="post-card"> 270 - <div class="post-header"> 271 - <div class="avatar">JD</div> 272 - <div class="post-author"> 273 - <div class="post-author-name">John Doe</div> 274 - <div class="post-author-handle">@johndoe.bsky.social · <span class="post-timestamp">2h</span></div> 369 + 370 + <div class="post-actions"> 371 + <button class="post-action"> 372 + <svg 373 + viewBox="0 0 24 24" 374 + fill="none" 375 + stroke="currentColor" 376 + stroke-width="2" 377 + stroke-linecap="round" 378 + stroke-linejoin="round"> 379 + <path 380 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 381 + </svg> 382 + 15 383 + </button> 384 + <button class="post-action"> 385 + <svg 386 + viewBox="0 0 24 24" 387 + fill="none" 388 + stroke="currentColor" 389 + stroke-width="2" 390 + stroke-linecap="round" 391 + stroke-linejoin="round"> 392 + <polyline points="17 1 21 5 17 9" /> 393 + <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 394 + <polyline points="7 23 3 19 7 15" /> 395 + <path d="M21 13v2a4 4 0 0 1-4 4H3" /> 396 + </svg> 397 + 7 398 + </button> 399 + <button class="post-action"> 400 + <svg 401 + viewBox="0 0 24 24" 402 + fill="none" 403 + stroke="currentColor" 404 + stroke-width="2" 405 + stroke-linecap="round" 406 + stroke-linejoin="round"> 407 + <path 408 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 409 + </svg> 410 + 89 411 + </button> 412 + <button class="post-action"> 413 + <svg 414 + viewBox="0 0 24 24" 415 + fill="none" 416 + stroke="currentColor" 417 + stroke-width="2" 418 + stroke-linecap="round" 419 + stroke-linejoin="round"> 420 + <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 421 + <polyline points="16 6 12 2 8 6" /> 422 + <line x1="12" y1="2" x2="12" y2="15" /> 423 + </svg> 424 + </button> 275 425 </div> 276 - </div> 277 - 278 - <div class="post-content"> 279 - Finally shipped the new feature I've been working on! 🎉 Check it out and let me know what you think. Always open to feedback! <a href="#" class="post-facet-hashtag">#buildinpublic</a> <a href="#" class="post-facet-hashtag">#indiehacker</a> 280 - </div> 281 - 282 - <div class="post-actions"> 283 - <button class="post-action"> 284 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 285 - <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/> 286 - </svg> 287 - 8 288 - </button> 289 - <button class="post-action"> 290 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 291 - <polyline points="17 1 21 5 17 9"/> 292 - <path d="M3 11V9a4 4 0 0 1 4-4h14"/> 293 - <polyline points="7 23 3 19 7 15"/> 294 - <path d="M21 13v2a4 4 0 0 1-4 4H3"/> 295 - </svg> 296 - 3 297 - </button> 298 - <button class="post-action"> 299 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 300 - <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/> 301 - </svg> 302 - 42 303 - </button> 304 - <button class="post-action"> 305 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 306 - <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/> 307 - <polyline points="16 6 12 2 8 6"/> 308 - <line x1="12" y1="2" x2="12" y2="15"/> 309 - </svg> 310 - </button> 311 - </div> 312 - </article> 313 - 314 - <!-- Post 2 --> 315 - <article class="post-card"> 316 - <div class="post-header"> 317 - <div class="avatar">JD</div> 318 - <div class="post-author"> 319 - <div class="post-author-name">John Doe</div> 320 - <div class="post-author-handle">@johndoe.bsky.social · <span class="post-timestamp">1d</span></div> 426 + </article> 427 + 428 + <!-- Post 3 - With Embedded Link --> 429 + <article class="post-card"> 430 + <div class="post-header"> 431 + <div class="avatar">JD</div> 432 + <div class="post-author"> 433 + <div class="post-author-name">John Doe</div> 434 + <div class="post-author-handle">@johndoe.bsky.social · <span class="post-timestamp">3d</span></div> 435 + </div> 436 + </div> 437 + 438 + <div class="post-content"> 439 + Great read on the future of decentralized social networks by 440 + <a href="#" class="post-facet-mention">@protocol</a> <a href="#" class="post-facet-hashtag">#atprotocol</a> 441 + <a href="#" class="post-facet-hashtag">#web3</a> 321 442 </div> 322 - </div> 323 - 324 - <div class="post-content"> 325 - Really enjoying the <a href="#" class="post-facet-mention">@bluesky</a> community so far. Great conversations happening here! Looking forward to seeing how the platform evolves. <a href="#" class="post-facet-hashtag">#bluesky</a> <a href="#" class="post-facet-hashtag">#socialmedia</a> 326 - </div> 327 - 328 - <div class="post-actions"> 329 - <button class="post-action"> 330 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 331 - <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/> 332 - </svg> 333 - 15 334 - </button> 335 - <button class="post-action"> 336 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 337 - <polyline points="17 1 21 5 17 9"/> 338 - <path d="M3 11V9a4 4 0 0 1 4-4h14"/> 339 - <polyline points="7 23 3 19 7 15"/> 340 - <path d="M21 13v2a4 4 0 0 1-4 4H3"/> 341 - </svg> 342 - 7 343 - </button> 344 - <button class="post-action"> 345 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 346 - <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/> 347 - </svg> 348 - 89 349 - </button> 350 - <button class="post-action"> 351 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 352 - <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/> 353 - <polyline points="16 6 12 2 8 6"/> 354 - <line x1="12" y1="2" x2="12" y2="15"/> 355 - </svg> 356 - </button> 357 - </div> 358 - </article> 359 - 360 - <!-- Post 3 - With Embedded Link --> 361 - <article class="post-card"> 362 - <div class="post-header"> 363 - <div class="avatar">JD</div> 364 - <div class="post-author"> 365 - <div class="post-author-name">John Doe</div> 366 - <div class="post-author-handle">@johndoe.bsky.social · <span class="post-timestamp">3d</span></div> 443 + 444 + <div class="post-embed"> 445 + <div class="post-embed-image">[Article Preview Image]</div> 446 + <div class="post-embed-content"> 447 + <div class="post-embed-title">The Future of Decentralized Social Networks</div> 448 + <div class="post-embed-url">protocol.com/blog/future-decentralized-social</div> 449 + </div> 367 450 </div> 368 - </div> 369 - 370 - <div class="post-content"> 371 - Great read on the future of decentralized social networks by <a href="#" class="post-facet-mention">@protocol</a> <a href="#" class="post-facet-hashtag">#atprotocol</a> <a href="#" class="post-facet-hashtag">#web3</a> 372 - </div> 373 - 374 - <div class="post-embed"> 375 - <div class="post-embed-image">[Article Preview Image]</div> 376 - <div class="post-embed-content"> 377 - <div class="post-embed-title">The Future of Decentralized Social Networks</div> 378 - <div class="post-embed-url">protocol.com/blog/future-decentralized-social</div> 451 + 452 + <div class="post-actions"> 453 + <button class="post-action"> 454 + <svg 455 + viewBox="0 0 24 24" 456 + fill="none" 457 + stroke="currentColor" 458 + stroke-width="2" 459 + stroke-linecap="round" 460 + stroke-linejoin="round"> 461 + <path 462 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 463 + </svg> 464 + 22 465 + </button> 466 + <button class="post-action"> 467 + <svg 468 + viewBox="0 0 24 24" 469 + fill="none" 470 + stroke="currentColor" 471 + stroke-width="2" 472 + stroke-linecap="round" 473 + stroke-linejoin="round"> 474 + <polyline points="17 1 21 5 17 9" /> 475 + <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 476 + <polyline points="7 23 3 19 7 15" /> 477 + <path d="M21 13v2a4 4 0 0 1-4 4H3" /> 478 + </svg> 479 + 14 480 + </button> 481 + <button class="post-action"> 482 + <svg 483 + viewBox="0 0 24 24" 484 + fill="none" 485 + stroke="currentColor" 486 + stroke-width="2" 487 + stroke-linecap="round" 488 + stroke-linejoin="round"> 489 + <path 490 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 491 + </svg> 492 + 156 493 + </button> 494 + <button class="post-action"> 495 + <svg 496 + viewBox="0 0 24 24" 497 + fill="none" 498 + stroke="currentColor" 499 + stroke-width="2" 500 + stroke-linecap="round" 501 + stroke-linejoin="round"> 502 + <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 503 + <polyline points="16 6 12 2 8 6" /> 504 + <line x1="12" y1="2" x2="12" y2="15" /> 505 + </svg> 506 + </button> 379 507 </div> 380 - </div> 381 - 382 - <div class="post-actions"> 383 - <button class="post-action"> 384 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 385 - <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/> 386 - </svg> 387 - 22 388 - </button> 389 - <button class="post-action"> 390 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 391 - <polyline points="17 1 21 5 17 9"/> 392 - <path d="M3 11V9a4 4 0 0 1 4-4h14"/> 393 - <polyline points="7 23 3 19 7 15"/> 394 - <path d="M21 13v2a4 4 0 0 1-4 4H3"/> 395 - </svg> 396 - 14 397 - </button> 398 - <button class="post-action"> 399 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 400 - <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/> 401 - </svg> 402 - 156 403 - </button> 404 - <button class="post-action"> 405 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 406 - <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/> 407 - <polyline points="16 6 12 2 8 6"/> 408 - <line x1="12" y1="2" x2="12" y2="15"/> 409 - </svg> 410 - </button> 411 - </div> 412 - </article> 413 - 508 + </article> 509 + </div> 510 + 511 + <!-- Bottom Navigation --> 512 + <nav class="nav-bar"> 513 + <a href="home.html" class="nav-item"> 514 + <svg 515 + viewBox="0 0 24 24" 516 + fill="none" 517 + stroke="currentColor" 518 + stroke-width="2" 519 + stroke-linecap="round" 520 + stroke-linejoin="round"> 521 + <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> 522 + <polyline points="9 22 9 12 15 12 15 22" /> 523 + </svg> 524 + <span>Home</span> 525 + </a> 526 + 527 + <a href="profile.html" class="nav-item active"> 528 + <svg 529 + viewBox="0 0 24 24" 530 + fill="none" 531 + stroke="currentColor" 532 + stroke-width="2" 533 + stroke-linecap="round" 534 + stroke-linejoin="round"> 535 + <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> 536 + <circle cx="12" cy="7" r="4" /> 537 + </svg> 538 + <span>Profile</span> 539 + </a> 540 + 541 + <a href="settings.html" class="nav-item"> 542 + <svg 543 + viewBox="0 0 24 24" 544 + fill="none" 545 + stroke="currentColor" 546 + stroke-width="2" 547 + stroke-linecap="round" 548 + stroke-linejoin="round"> 549 + <circle cx="12" cy="12" r="3" /> 550 + <path 551 + d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" /> 552 + </svg> 553 + <span>Settings</span> 554 + </a> 555 + </nav> 414 556 </div> 415 - 416 - <!-- Bottom Navigation --> 417 - <nav class="nav-bar"> 418 - <a href="home.html" class="nav-item"> 419 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 420 - <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/> 421 - <polyline points="9 22 9 12 15 12 15 22"/> 422 - </svg> 423 - <span>Home</span> 424 - </a> 425 - 426 - <a href="profile.html" class="nav-item active"> 427 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 428 - <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 429 - <circle cx="12" cy="7" r="4"/> 430 - </svg> 431 - <span>Profile</span> 432 - </a> 433 - 434 - <a href="settings.html" class="nav-item"> 435 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 436 - <circle cx="12" cy="12" r="3"/> 437 - <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/> 438 - </svg> 439 - <span>Settings</span> 440 - </a> 441 - </nav> 442 - 443 - </div> 444 - 445 - <script> 446 - // Theme toggle functionality for demo 447 - if (localStorage.getItem('theme') === 'dark') { 448 - document.documentElement.setAttribute('data-theme', 'dark'); 449 - } 450 - </script> 451 - </body> 557 + 558 + <script> 559 + if (localStorage.getItem("theme") === "dark") { 560 + document.documentElement.setAttribute("data-theme", "dark"); 561 + } 562 + </script> 563 + </body> 452 564 </html>
+39 -91
docs/designs/search.html
··· 2 2 <html lang="en"> 3 3 <head> 4 4 <meta charset="UTF-8" /> 5 - <meta 6 - name="viewport" 7 - content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" 8 - /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> 9 6 <title>Search - Lazurite</title> 10 7 <link rel="preconnect" href="https://fonts.googleapis.com" /> 11 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 12 9 <link 13 10 href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" 14 - rel="stylesheet" 15 - /> 11 + rel="stylesheet" /> 16 12 <link rel="stylesheet" href="styles.css" /> 17 13 <style> 18 14 .search-container { ··· 313 309 stroke="currentColor" 314 310 stroke-width="2" 315 311 stroke-linecap="round" 316 - stroke-linejoin="round" 317 - > 312 + stroke-linejoin="round"> 318 313 <circle cx="11" cy="11" r="8" /> 319 314 <line x1="21" y1="21" x2="16.65" y2="16.65" /> 320 315 </svg> 321 - <input 322 - class="search-input" 323 - type="text" 324 - placeholder="Search posts or people" 325 - value="atproto" 326 - /> 316 + <input class="search-input" type="text" placeholder="Search posts or people" value="atproto" /> 327 317 </div> 328 318 <button class="search-cancel">Cancel</button> 329 319 </div> ··· 350 340 <div class="avatar">PB</div> 351 341 <div class="post-author"> 352 342 <div class="post-author-name">Paul Frazee</div> 353 - <div class="post-author-handle"> 354 - @pfrazee.com · <span class="post-timestamp">3h</span> 355 - </div> 343 + <div class="post-author-handle">@pfrazee.com · <span class="post-timestamp">3h</span></div> 356 344 </div> 357 345 </div> 358 346 <div class="post-content"> 359 347 We just shipped a major update to the 360 - <a href="#" class="post-facet-mention">@atproto</a> federation code. 361 - Self-hosting your own PDS is now easier than ever. 348 + <a href="#" class="post-facet-mention">@atproto</a> federation code. Self-hosting your own PDS is now easier 349 + than ever. 362 350 <a href="#" class="post-facet-hashtag">#atproto</a> 363 351 <a href="#" class="post-facet-hashtag">#decentralized</a> 364 352 </div> ··· 370 358 stroke="currentColor" 371 359 stroke-width="2" 372 360 stroke-linecap="round" 373 - stroke-linejoin="round" 374 - > 361 + stroke-linejoin="round"> 375 362 <path 376 - d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" 377 - /> 363 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 378 364 </svg> 379 365 67 380 366 </button> ··· 385 371 stroke="currentColor" 386 372 stroke-width="2" 387 373 stroke-linecap="round" 388 - stroke-linejoin="round" 389 - > 374 + stroke-linejoin="round"> 390 375 <polyline points="17 1 21 5 17 9" /> 391 376 <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 392 377 <polyline points="7 23 3 19 7 15" /> ··· 401 386 stroke="currentColor" 402 387 stroke-width="2" 403 388 stroke-linecap="round" 404 - stroke-linejoin="round" 405 - > 389 + stroke-linejoin="round"> 406 390 <path 407 - d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" 408 - /> 391 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 409 392 </svg> 410 393 512 411 394 </button> ··· 416 399 stroke="currentColor" 417 400 stroke-width="2" 418 401 stroke-linecap="round" 419 - stroke-linejoin="round" 420 - > 402 + stroke-linejoin="round"> 421 403 <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 422 404 <polyline points="16 6 12 2 8 6" /> 423 405 <line x1="12" y1="2" x2="12" y2="15" /> ··· 431 413 <div class="avatar">JK</div> 432 414 <div class="post-author"> 433 415 <div class="post-author-name">Jake Gold</div> 434 - <div class="post-author-handle"> 435 - @jake.bsky.social · <span class="post-timestamp">6h</span> 436 - </div> 416 + <div class="post-author-handle">@jake.bsky.social · <span class="post-timestamp">6h</span></div> 437 417 </div> 438 418 </div> 439 419 <div class="post-content"> 440 - The <a href="#" class="post-facet-hashtag">#atproto</a> ecosystem is 441 - growing fast. More and more third-party apps popping up every week. 442 - The open protocol approach is really paying off 🌐 420 + The <a href="#" class="post-facet-hashtag">#atproto</a> ecosystem is growing fast. More and more third-party 421 + apps popping up every week. The open protocol approach is really paying off 🌐 443 422 </div> 444 423 <div class="post-actions"> 445 424 <button class="post-action"> ··· 449 428 stroke="currentColor" 450 429 stroke-width="2" 451 430 stroke-linecap="round" 452 - stroke-linejoin="round" 453 - > 431 + stroke-linejoin="round"> 454 432 <path 455 - d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" 456 - /> 433 + d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /> 457 434 </svg> 458 435 23 459 436 </button> ··· 464 441 stroke="currentColor" 465 442 stroke-width="2" 466 443 stroke-linecap="round" 467 - stroke-linejoin="round" 468 - > 444 + stroke-linejoin="round"> 469 445 <polyline points="17 1 21 5 17 9" /> 470 446 <path d="M3 11V9a4 4 0 0 1 4-4h14" /> 471 447 <polyline points="7 23 3 19 7 15" /> ··· 480 456 stroke="currentColor" 481 457 stroke-width="2" 482 458 stroke-linecap="round" 483 - stroke-linejoin="round" 484 - > 459 + stroke-linejoin="round"> 485 460 <path 486 - d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" 487 - /> 461 + d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" /> 488 462 </svg> 489 463 178 490 464 </button> ··· 495 469 stroke="currentColor" 496 470 stroke-width="2" 497 471 stroke-linecap="round" 498 - stroke-linejoin="round" 499 - > 472 + stroke-linejoin="round"> 500 473 <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> 501 474 <polyline points="16 6 12 2 8 6" /> 502 475 <line x1="12" y1="2" x2="12" y2="15" /> ··· 514 487 <div class="actor-result-info"> 515 488 <div class="actor-result-name">AT Protocol</div> 516 489 <div class="actor-result-handle">@atproto.com</div> 517 - <div class="actor-result-bio"> 518 - The AT Protocol – social networking technology created by 519 - Bluesky 520 - </div> 490 + <div class="actor-result-bio">The AT Protocol – social networking technology created by Bluesky</div> 521 491 </div> 522 492 <button class="follow-btn">Follow</button> 523 493 </div> ··· 527 497 <div class="actor-result-info"> 528 498 <div class="actor-result-name">Bluesky</div> 529 499 <div class="actor-result-handle">@bsky.app</div> 530 - <div class="actor-result-bio"> 531 - Building a social internet. Join us at bsky.app 532 - </div> 500 + <div class="actor-result-bio">Building a social internet. Join us at bsky.app</div> 533 501 </div> 534 502 <button class="follow-btn">Follow</button> 535 503 </div> ··· 551 519 stroke="currentColor" 552 520 stroke-width="2" 553 521 stroke-linecap="round" 554 - stroke-linejoin="round" 555 - > 522 + stroke-linejoin="round"> 556 523 <polyline points="12 8 12 12 14 14" /> 557 524 <circle cx="12" cy="12" r="10" /> 558 525 </svg> ··· 568 535 stroke="currentColor" 569 536 stroke-width="2" 570 537 stroke-linecap="round" 571 - stroke-linejoin="round" 572 - > 538 + stroke-linejoin="round"> 573 539 <line x1="18" y1="6" x2="6" y2="18" /> 574 540 <line x1="6" y1="6" x2="18" y2="18" /> 575 541 </svg> ··· 585 551 stroke="currentColor" 586 552 stroke-width="2" 587 553 stroke-linecap="round" 588 - stroke-linejoin="round" 589 - > 554 + stroke-linejoin="round"> 590 555 <polyline points="12 8 12 12 14 14" /> 591 556 <circle cx="12" cy="12" r="10" /> 592 557 </svg> ··· 602 567 stroke="currentColor" 603 568 stroke-width="2" 604 569 stroke-linecap="round" 605 - stroke-linejoin="round" 606 - > 570 + stroke-linejoin="round"> 607 571 <line x1="18" y1="6" x2="6" y2="18" /> 608 572 <line x1="6" y1="6" x2="18" y2="18" /> 609 573 </svg> ··· 619 583 stroke="currentColor" 620 584 stroke-width="2" 621 585 stroke-linecap="round" 622 - stroke-linejoin="round" 623 - > 586 + stroke-linejoin="round"> 624 587 <polyline points="12 8 12 12 14 14" /> 625 588 <circle cx="12" cy="12" r="10" /> 626 589 </svg> ··· 636 599 stroke="currentColor" 637 600 stroke-width="2" 638 601 stroke-linecap="round" 639 - stroke-linejoin="round" 640 - > 602 + stroke-linejoin="round"> 641 603 <line x1="18" y1="6" x2="6" y2="18" /> 642 604 <line x1="6" y1="6" x2="18" y2="18" /> 643 605 </svg> ··· 655 617 stroke="currentColor" 656 618 stroke-width="2" 657 619 stroke-linecap="round" 658 - stroke-linejoin="round" 659 - > 620 + stroke-linejoin="round"> 660 621 <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> 661 622 <polyline points="9 22 9 12 15 12 15 22" /> 662 623 </svg> ··· 670 631 stroke="currentColor" 671 632 stroke-width="2" 672 633 stroke-linecap="round" 673 - stroke-linejoin="round" 674 - > 634 + stroke-linejoin="round"> 675 635 <circle cx="11" cy="11" r="8" /> 676 636 <line x1="21" y1="21" x2="16.65" y2="16.65" /> 677 637 </svg> ··· 685 645 stroke="currentColor" 686 646 stroke-width="2" 687 647 stroke-linecap="round" 688 - stroke-linejoin="round" 689 - > 648 + stroke-linejoin="round"> 690 649 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> 691 650 <circle cx="12" cy="7" r="4" /> 692 651 </svg> ··· 700 659 stroke="currentColor" 701 660 stroke-width="2" 702 661 stroke-linecap="round" 703 - stroke-linejoin="round" 704 - > 662 + stroke-linejoin="round"> 705 663 <circle cx="12" cy="12" r="3" /> 706 664 <path 707 - d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" 708 - /> 665 + d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" /> 709 666 </svg> 710 667 <span>Settings</span> 711 668 </a> ··· 717 674 document.documentElement.setAttribute("data-theme", "dark"); 718 675 } 719 676 720 - // Tab switching demo 721 677 document.querySelectorAll(".search-tab").forEach((tab) => { 722 678 tab.addEventListener("click", () => { 723 - document 724 - .querySelectorAll(".search-tab") 725 - .forEach((t) => t.classList.remove("active")); 679 + document.querySelectorAll(".search-tab").forEach((t) => t.classList.remove("active")); 726 680 tab.classList.add("active"); 727 681 728 682 const isPeople = tab.textContent === "People"; 729 - document 730 - .querySelectorAll(".post-card") 731 - .forEach((el) => (el.style.display = isPeople ? "none" : "block")); 732 - document.querySelector(".search-sort").style.display = isPeople 733 - ? "none" 734 - : "flex"; 735 - document.querySelector(".typeahead-section").style.display = isPeople 736 - ? "block" 737 - : "none"; 683 + document.querySelectorAll(".post-card").forEach((el) => (el.style.display = isPeople ? "none" : "block")); 684 + document.querySelector(".search-sort").style.display = isPeople ? "none" : "flex"; 685 + document.querySelector(".typeahead-section").style.display = isPeople ? "block" : "none"; 738 686 }); 739 687 }); 740 688 </script>
+782 -582
docs/designs/settings.html
··· 1 - <!DOCTYPE html> 1 + <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8"> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 - <title>Settings - Lazurite</title> 7 - <link rel="preconnect" href="https://fonts.googleapis.com"> 8 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 - <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet"> 11 - <link rel="stylesheet" href="styles.css"> 12 - <style> 13 - .settings-container { 14 - padding-bottom: 88px; 15 - } 16 - 17 - .settings-section { 18 - margin-bottom: 24px; 19 - } 20 - 21 - .settings-section-title { 22 - padding: 16px; 23 - font-size: 13px; 24 - font-weight: 600; 25 - color: var(--text-muted); 26 - text-transform: uppercase; 27 - letter-spacing: 0.5px; 28 - } 29 - 30 - .settings-group { 31 - background-color: var(--surface); 32 - border-top: 1px solid var(--border); 33 - border-bottom: 1px solid var(--border); 34 - } 35 - 36 - .theme-selector { 37 - padding: 16px; 38 - display: grid; 39 - grid-template-columns: repeat(4, 1fr); 40 - gap: 8px; 41 - justify-items: center; 42 - } 43 - 44 - .theme-option { 45 - display: flex; 46 - flex-direction: column; 47 - align-items: center; 48 - gap: 8px; 49 - cursor: pointer; 50 - padding: 12px; 51 - border-radius: 12px; 52 - transition: background-color 0.2s ease; 53 - } 54 - 55 - .theme-option:hover { 56 - background-color: var(--surface-variant); 57 - } 58 - 59 - .theme-option.active { 60 - background-color: var(--surface-variant); 61 - } 62 - 63 - .theme-section-label { 64 - grid-column: 1 / -1; 65 - font-size: 12px; 66 - font-weight: 600; 67 - color: var(--text-muted); 68 - text-transform: uppercase; 69 - letter-spacing: 0.5px; 70 - padding: 8px 0 0; 71 - width: 100%; 72 - text-align: left; 73 - } 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> 6 + <title>Settings - Lazurite</title> 7 + <link rel="preconnect" href="https://fonts.googleapis.com" /> 8 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 9 + <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet" /> 10 + <link 11 + href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" 12 + rel="stylesheet" /> 13 + <link rel="stylesheet" href="styles.css" /> 14 + <style> 15 + .settings-container { 16 + padding-bottom: 88px; 17 + } 18 + 19 + .settings-section { 20 + margin-bottom: 24px; 21 + } 22 + 23 + .settings-section-title { 24 + padding: 16px; 25 + font-size: 13px; 26 + font-weight: 600; 27 + color: var(--text-muted); 28 + text-transform: uppercase; 29 + letter-spacing: 0.5px; 30 + } 31 + 32 + .settings-group { 33 + background-color: var(--surface); 34 + border-top: 1px solid var(--border); 35 + border-bottom: 1px solid var(--border); 36 + } 37 + 38 + .theme-selector { 39 + padding: 16px; 40 + display: grid; 41 + grid-template-columns: repeat(4, 1fr); 42 + gap: 8px; 43 + justify-items: center; 44 + } 45 + 46 + .theme-option { 47 + display: flex; 48 + flex-direction: column; 49 + align-items: center; 50 + gap: 8px; 51 + cursor: pointer; 52 + padding: 12px; 53 + border-radius: 12px; 54 + transition: background-color 0.2s ease; 55 + } 56 + 57 + .theme-option:hover { 58 + background-color: var(--surface-variant); 59 + } 60 + 61 + .theme-option.active { 62 + background-color: var(--surface-variant); 63 + } 64 + 65 + .theme-section-label { 66 + grid-column: 1 / -1; 67 + font-size: 12px; 68 + font-weight: 600; 69 + color: var(--text-muted); 70 + text-transform: uppercase; 71 + letter-spacing: 0.5px; 72 + padding: 8px 0 0; 73 + width: 100%; 74 + text-align: left; 75 + } 74 76 75 - .theme-section-label:first-child { 76 - padding-top: 0; 77 - } 77 + .theme-section-label:first-child { 78 + padding-top: 0; 79 + } 78 80 79 - .theme-option-preview { 80 - width: 64px; 81 - height: 64px; 82 - border-radius: 12px; 83 - border: 2px solid var(--border); 84 - overflow: hidden; 85 - position: relative; 86 - transition: all 0.2s ease; 87 - } 88 - 89 - .theme-option:hover .theme-option-preview, 90 - .theme-option.active .theme-option-preview { 91 - border-color: var(--accent-primary); 92 - transform: scale(1.02); 93 - } 94 - 95 - .theme-preview-light { 96 - background: linear-gradient(135deg, #ffffff 50%, #f4f4f4 50%); 97 - } 98 - 99 - .theme-preview-dark { 100 - background: linear-gradient(135deg, #161616 50%, #262626 50%); 101 - } 102 - 103 - .theme-option-check { 104 - position: absolute; 105 - bottom: 4px; 106 - right: 4px; 107 - width: 20px; 108 - height: 20px; 109 - background-color: var(--accent-primary); 110 - border-radius: 50%; 111 - display: flex; 112 - align-items: center; 113 - justify-content: center; 114 - opacity: 0; 115 - transition: opacity 0.2s ease; 116 - } 117 - 118 - .theme-option.active .theme-option-check { 119 - opacity: 1; 120 - } 121 - 122 - .theme-option-check svg { 123 - width: 12px; 124 - height: 12px; 125 - color: white; 126 - } 127 - 128 - .theme-option-label { 129 - font-size: 14px; 130 - font-weight: 500; 131 - color: var(--text-primary); 132 - } 133 - 134 - .auto-theme-toggle { 135 - padding: 16px; 136 - border-top: 1px solid var(--border); 137 - display: flex; 138 - align-items: center; 139 - justify-content: space-between; 140 - } 141 - 142 - .auto-theme-info { 143 - display: flex; 144 - flex-direction: column; 145 - gap: 4px; 146 - } 147 - 148 - .auto-theme-title { 149 - font-size: 15px; 150 - font-weight: 500; 151 - color: var(--text-primary); 152 - } 153 - 154 - .auto-theme-subtitle { 155 - font-size: 13px; 156 - color: var(--text-secondary); 157 - } 158 - 159 - .settings-item-danger { 160 - color: var(--accent-error); 161 - } 162 - 163 - .settings-item-danger .settings-item-title { 164 - color: var(--accent-error); 165 - } 166 - 167 - .app-version { 168 - text-align: center; 169 - padding: 24px; 170 - color: var(--text-muted); 171 - font-size: 13px; 172 - } 173 - 174 - .user-card { 175 - display: flex; 176 - align-items: center; 177 - gap: 12px; 178 - padding: 16px; 179 - background-color: var(--surface); 180 - border-top: 1px solid var(--border); 181 - border-bottom: 1px solid var(--border); 182 - } 183 - 184 - .user-card-avatar { 185 - width: 48px; 186 - height: 48px; 187 - border-radius: 50%; 188 - background-color: var(--surface-variant); 189 - display: flex; 190 - align-items: center; 191 - justify-content: center; 192 - font-size: 18px; 193 - font-weight: 600; 194 - color: var(--text-primary); 195 - } 196 - 197 - .user-card-info { 198 - flex: 1; 199 - } 200 - 201 - .user-card-name { 202 - font-weight: 600; 203 - color: var(--text-primary); 204 - font-size: 15px; 205 - } 206 - 207 - .user-card-handle { 208 - color: var(--text-secondary); 209 - font-size: 14px; 210 - } 211 - 212 - .user-card-arrow { 213 - color: var(--text-muted); 214 - } 215 - 216 - .user-card-arrow svg { 217 - width: 20px; 218 - height: 20px; 219 - } 220 - </style> 221 - </head> 222 - <body> 223 - <div class="mobile-container"> 224 - 225 - <!-- Header --> 226 - <header class="header"> 227 - <h1 class="header-title">Settings</h1> 228 - <button class="header-action" onclick="logout()">Log Out</button> 229 - </header> 230 - 231 - <div class="settings-container"> 232 - 233 - <!-- User Card --> 234 - <a href="profile.html" class="user-card"> 235 - <div class="user-card-avatar">JD</div> 236 - <div class="user-card-info"> 237 - <div class="user-card-name">John Doe</div> 238 - <div class="user-card-handle">@johndoe.bsky.social</div> 239 - </div> 240 - <div class="user-card-arrow"> 241 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 242 - <polyline points="9 18 15 12 9 6"/> 243 - </svg> 244 - </div> 245 - </a> 246 - 247 - <!-- Appearance Section --> 248 - <div class="settings-section"> 249 - <div class="settings-section-title">Appearance</div> 250 - 251 - <div class="settings-group"> 252 - <!-- Theme Selector --> 253 - <div class="theme-selector"> 254 - <!-- Oxocarbon --> 255 - <div class="theme-section-label">Oxocarbon</div> 256 - <div class="theme-option active" data-theme="light" onclick="setTheme('light')"> 257 - <div class="theme-option-preview theme-preview-light"> 258 - <div class="theme-option-check"> 259 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 81 + .theme-option-preview { 82 + width: 64px; 83 + height: 64px; 84 + border-radius: 12px; 85 + border: 2px solid var(--border); 86 + overflow: hidden; 87 + position: relative; 88 + transition: all 0.2s ease; 89 + } 90 + 91 + .theme-option:hover .theme-option-preview, 92 + .theme-option.active .theme-option-preview { 93 + border-color: var(--accent-primary); 94 + transform: scale(1.02); 95 + } 96 + 97 + .theme-preview-light { 98 + background: linear-gradient(135deg, #ffffff 50%, #f4f4f4 50%); 99 + } 100 + 101 + .theme-preview-dark { 102 + background: linear-gradient(135deg, #161616 50%, #262626 50%); 103 + } 104 + 105 + .theme-option-check { 106 + position: absolute; 107 + bottom: 4px; 108 + right: 4px; 109 + width: 20px; 110 + height: 20px; 111 + background-color: var(--accent-primary); 112 + border-radius: 50%; 113 + display: flex; 114 + align-items: center; 115 + justify-content: center; 116 + opacity: 0; 117 + transition: opacity 0.2s ease; 118 + } 119 + 120 + .theme-option.active .theme-option-check { 121 + opacity: 1; 122 + } 123 + 124 + .theme-option-check svg { 125 + width: 12px; 126 + height: 12px; 127 + color: white; 128 + } 129 + 130 + .theme-option-label { 131 + font-size: 14px; 132 + font-weight: 500; 133 + color: var(--text-primary); 134 + } 135 + 136 + .auto-theme-toggle { 137 + padding: 16px; 138 + border-top: 1px solid var(--border); 139 + display: flex; 140 + align-items: center; 141 + justify-content: space-between; 142 + } 143 + 144 + .auto-theme-info { 145 + display: flex; 146 + flex-direction: column; 147 + gap: 4px; 148 + } 149 + 150 + .auto-theme-title { 151 + font-size: 15px; 152 + font-weight: 500; 153 + color: var(--text-primary); 154 + } 155 + 156 + .auto-theme-subtitle { 157 + font-size: 13px; 158 + color: var(--text-secondary); 159 + } 160 + 161 + .settings-item-danger { 162 + color: var(--accent-error); 163 + } 164 + 165 + .settings-item-danger .settings-item-title { 166 + color: var(--accent-error); 167 + } 168 + 169 + .app-version { 170 + text-align: center; 171 + padding: 24px; 172 + color: var(--text-muted); 173 + font-size: 13px; 174 + } 175 + 176 + .user-card { 177 + display: flex; 178 + align-items: center; 179 + gap: 12px; 180 + padding: 16px; 181 + background-color: var(--surface); 182 + border-top: 1px solid var(--border); 183 + border-bottom: 1px solid var(--border); 184 + } 185 + 186 + .user-card-avatar { 187 + width: 48px; 188 + height: 48px; 189 + border-radius: 50%; 190 + background-color: var(--surface-variant); 191 + display: flex; 192 + align-items: center; 193 + justify-content: center; 194 + font-size: 18px; 195 + font-weight: 600; 196 + color: var(--text-primary); 197 + } 198 + 199 + .user-card-info { 200 + flex: 1; 201 + } 202 + 203 + .user-card-name { 204 + font-weight: 600; 205 + color: var(--text-primary); 206 + font-size: 15px; 207 + } 208 + 209 + .user-card-handle { 210 + color: var(--text-secondary); 211 + font-size: 14px; 212 + } 213 + 214 + .user-card-arrow { 215 + color: var(--text-muted); 216 + } 217 + 218 + .user-card-arrow svg { 219 + width: 20px; 220 + height: 20px; 221 + } 222 + </style> 223 + </head> 224 + <body> 225 + <div class="mobile-container"> 226 + <!-- Header --> 227 + <header class="header"> 228 + <h1 class="header-title">Settings</h1> 229 + <button class="header-action" onclick="logout()">Log Out</button> 230 + </header> 231 + 232 + <div class="settings-container"> 233 + <!-- User Card --> 234 + <a href="profile.html" class="user-card"> 235 + <div class="user-card-avatar">JD</div> 236 + <div class="user-card-info"> 237 + <div class="user-card-name">John Doe</div> 238 + <div class="user-card-handle">@johndoe.bsky.social</div> 239 + </div> 240 + <div class="user-card-arrow"> 241 + <svg 242 + viewBox="0 0 24 24" 243 + fill="none" 244 + stroke="currentColor" 245 + stroke-width="2" 246 + stroke-linecap="round" 247 + stroke-linejoin="round"> 248 + <polyline points="9 18 15 12 9 6" /> 249 + </svg> 250 + </div> 251 + </a> 252 + 253 + <!-- Appearance Section --> 254 + <div class="settings-section"> 255 + <div class="settings-section-title">Appearance</div> 256 + 257 + <div class="settings-group"> 258 + <!-- Theme Selector --> 259 + <div class="theme-selector"> 260 + <!-- Oxocarbon --> 261 + <div class="theme-section-label">Oxocarbon</div> 262 + <div class="theme-option active" data-theme="light" onclick="setTheme('light')"> 263 + <div class="theme-option-preview theme-preview-light"> 264 + <div class="theme-option-check"> 265 + <svg 266 + viewBox="0 0 24 24" 267 + fill="none" 268 + stroke="currentColor" 269 + stroke-width="3" 270 + stroke-linecap="round" 271 + stroke-linejoin="round"> 272 + <polyline points="20 6 9 17 4 12" /> 273 + </svg> 274 + </div> 260 275 </div> 276 + <span class="theme-option-label">Light</span> 261 277 </div> 262 - <span class="theme-option-label">Light</span> 263 - </div> 264 - <div class="theme-option" data-theme="dark" onclick="setTheme('dark')"> 265 - <div class="theme-option-preview theme-preview-dark"> 266 - <div class="theme-option-check"> 267 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 278 + <div class="theme-option" data-theme="dark" onclick="setTheme('dark')"> 279 + <div class="theme-option-preview theme-preview-dark"> 280 + <div class="theme-option-check"> 281 + <svg 282 + viewBox="0 0 24 24" 283 + fill="none" 284 + stroke="currentColor" 285 + stroke-width="3" 286 + stroke-linecap="round" 287 + stroke-linejoin="round"> 288 + <polyline points="20 6 9 17 4 12" /> 289 + </svg> 290 + </div> 268 291 </div> 292 + <span class="theme-option-label">Dark</span> 269 293 </div> 270 - <span class="theme-option-label">Dark</span> 271 - </div> 272 - <!-- spacers for 4-col grid --> 273 - <div></div> 274 - <div></div> 294 + <!-- spacers for 4-col grid --> 295 + <div></div> 296 + <div></div> 275 297 276 - <!-- Catppuccin --> 277 - <div class="theme-section-label">Catppuccin</div> 278 - <div class="theme-option" data-theme="catppuccin-latte" onclick="setTheme('catppuccin-latte')"> 279 - <div class="theme-option-preview theme-preview-catppuccin-latte"> 280 - <div class="theme-option-check"> 281 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 298 + <!-- Catppuccin --> 299 + <div class="theme-section-label">Catppuccin</div> 300 + <div class="theme-option" data-theme="catppuccin-latte" onclick="setTheme('catppuccin-latte')"> 301 + <div class="theme-option-preview theme-preview-catppuccin-latte"> 302 + <div class="theme-option-check"> 303 + <svg 304 + viewBox="0 0 24 24" 305 + fill="none" 306 + stroke="currentColor" 307 + stroke-width="3" 308 + stroke-linecap="round" 309 + stroke-linejoin="round"> 310 + <polyline points="20 6 9 17 4 12" /> 311 + </svg> 312 + </div> 282 313 </div> 314 + <span class="theme-option-label">Latte</span> 283 315 </div> 284 - <span class="theme-option-label">Latte</span> 285 - </div> 286 - <div class="theme-option" data-theme="catppuccin-mocha" onclick="setTheme('catppuccin-mocha')"> 287 - <div class="theme-option-preview theme-preview-catppuccin-mocha"> 288 - <div class="theme-option-check"> 289 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 316 + <div class="theme-option" data-theme="catppuccin-mocha" onclick="setTheme('catppuccin-mocha')"> 317 + <div class="theme-option-preview theme-preview-catppuccin-mocha"> 318 + <div class="theme-option-check"> 319 + <svg 320 + viewBox="0 0 24 24" 321 + fill="none" 322 + stroke="currentColor" 323 + stroke-width="3" 324 + stroke-linecap="round" 325 + stroke-linejoin="round"> 326 + <polyline points="20 6 9 17 4 12" /> 327 + </svg> 328 + </div> 290 329 </div> 330 + <span class="theme-option-label">Mocha</span> 291 331 </div> 292 - <span class="theme-option-label">Mocha</span> 293 - </div> 294 - <div></div> 295 - <div></div> 332 + <div></div> 333 + <div></div> 296 334 297 - <!-- Nord --> 298 - <div class="theme-section-label">Nord</div> 299 - <div class="theme-option" data-theme="nord-light" onclick="setTheme('nord-light')"> 300 - <div class="theme-option-preview theme-preview-nord-light"> 301 - <div class="theme-option-check"> 302 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 335 + <!-- Nord --> 336 + <div class="theme-section-label">Nord</div> 337 + <div class="theme-option" data-theme="nord-light" onclick="setTheme('nord-light')"> 338 + <div class="theme-option-preview theme-preview-nord-light"> 339 + <div class="theme-option-check"> 340 + <svg 341 + viewBox="0 0 24 24" 342 + fill="none" 343 + stroke="currentColor" 344 + stroke-width="3" 345 + stroke-linecap="round" 346 + stroke-linejoin="round"> 347 + <polyline points="20 6 9 17 4 12" /> 348 + </svg> 349 + </div> 303 350 </div> 351 + <span class="theme-option-label">Snow</span> 304 352 </div> 305 - <span class="theme-option-label">Snow</span> 306 - </div> 307 - <div class="theme-option" data-theme="nord-dark" onclick="setTheme('nord-dark')"> 308 - <div class="theme-option-preview theme-preview-nord-dark"> 309 - <div class="theme-option-check"> 310 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 353 + <div class="theme-option" data-theme="nord-dark" onclick="setTheme('nord-dark')"> 354 + <div class="theme-option-preview theme-preview-nord-dark"> 355 + <div class="theme-option-check"> 356 + <svg 357 + viewBox="0 0 24 24" 358 + fill="none" 359 + stroke="currentColor" 360 + stroke-width="3" 361 + stroke-linecap="round" 362 + stroke-linejoin="round"> 363 + <polyline points="20 6 9 17 4 12" /> 364 + </svg> 365 + </div> 311 366 </div> 367 + <span class="theme-option-label">Polar</span> 312 368 </div> 313 - <span class="theme-option-label">Polar</span> 314 - </div> 315 - <div></div> 316 - <div></div> 369 + <div></div> 370 + <div></div> 317 371 318 - <!-- Rosé Pine --> 319 - <div class="theme-section-label">Rosé Pine</div> 320 - <div class="theme-option" data-theme="rose-pine-dawn" onclick="setTheme('rose-pine-dawn')"> 321 - <div class="theme-option-preview theme-preview-rose-pine-dawn"> 322 - <div class="theme-option-check"> 323 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 372 + <!-- Rosé Pine --> 373 + <div class="theme-section-label">Rosé Pine</div> 374 + <div class="theme-option" data-theme="rose-pine-dawn" onclick="setTheme('rose-pine-dawn')"> 375 + <div class="theme-option-preview theme-preview-rose-pine-dawn"> 376 + <div class="theme-option-check"> 377 + <svg 378 + viewBox="0 0 24 24" 379 + fill="none" 380 + stroke="currentColor" 381 + stroke-width="3" 382 + stroke-linecap="round" 383 + stroke-linejoin="round"> 384 + <polyline points="20 6 9 17 4 12" /> 385 + </svg> 386 + </div> 324 387 </div> 388 + <span class="theme-option-label">Dawn</span> 325 389 </div> 326 - <span class="theme-option-label">Dawn</span> 327 - </div> 328 - <div class="theme-option" data-theme="rose-pine-moon" onclick="setTheme('rose-pine-moon')"> 329 - <div class="theme-option-preview theme-preview-rose-pine-moon"> 330 - <div class="theme-option-check"> 331 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> 390 + <div class="theme-option" data-theme="rose-pine-moon" onclick="setTheme('rose-pine-moon')"> 391 + <div class="theme-option-preview theme-preview-rose-pine-moon"> 392 + <div class="theme-option-check"> 393 + <svg 394 + viewBox="0 0 24 24" 395 + fill="none" 396 + stroke="currentColor" 397 + stroke-width="3" 398 + stroke-linecap="round" 399 + stroke-linejoin="round"> 400 + <polyline points="20 6 9 17 4 12" /> 401 + </svg> 402 + </div> 332 403 </div> 404 + <span class="theme-option-label">Moon</span> 333 405 </div> 334 - <span class="theme-option-label">Moon</span> 406 + <div></div> 407 + <div></div> 335 408 </div> 336 - <div></div> 337 - <div></div> 338 - </div> 339 - 340 - <!-- Auto Theme Toggle --> 341 - <div class="auto-theme-toggle"> 342 - <div class="auto-theme-info"> 343 - <div class="auto-theme-title">Auto</div> 344 - <div class="auto-theme-subtitle">Follow system theme</div> 345 - </div> 346 - <div class="toggle" id="auto-theme-toggle" onclick="toggleAutoTheme()"> 347 - <div class="toggle-thumb"></div> 409 + 410 + <!-- Auto Theme Toggle --> 411 + <div class="auto-theme-toggle"> 412 + <div class="auto-theme-info"> 413 + <div class="auto-theme-title">Auto</div> 414 + <div class="auto-theme-subtitle">Follow system theme</div> 415 + </div> 416 + <div class="toggle" id="auto-theme-toggle" onclick="toggleAutoTheme()"> 417 + <div class="toggle-thumb"></div> 418 + </div> 348 419 </div> 349 420 </div> 350 421 </div> 351 - </div> 352 - 353 - <!-- Account Section --> 354 - <div class="settings-section"> 355 - <div class="settings-section-title">Account</div> 356 - 357 - <div class="settings-group"> 358 - <div class="settings-item"> 359 - <div class="settings-item-left"> 360 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 361 - <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 362 - <circle cx="12" cy="7" r="4"/> 363 - </svg> 364 - <div class="settings-item-content"> 365 - <div class="settings-item-title">Edit Profile</div> 366 - <div class="settings-item-subtitle">Name, bio, avatar</div> 422 + 423 + <!-- Account Section --> 424 + <div class="settings-section"> 425 + <div class="settings-section-title">Account</div> 426 + 427 + <div class="settings-group"> 428 + <div class="settings-item"> 429 + <div class="settings-item-left"> 430 + <svg 431 + class="settings-item-icon" 432 + viewBox="0 0 24 24" 433 + fill="none" 434 + stroke="currentColor" 435 + stroke-width="2" 436 + stroke-linecap="round" 437 + stroke-linejoin="round"> 438 + <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> 439 + <circle cx="12" cy="7" r="4" /> 440 + </svg> 441 + <div class="settings-item-content"> 442 + <div class="settings-item-title">Edit Profile</div> 443 + <div class="settings-item-subtitle">Name, bio, avatar</div> 444 + </div> 445 + </div> 446 + <div class="settings-item-right"> 447 + <svg 448 + width="20" 449 + height="20" 450 + viewBox="0 0 24 24" 451 + fill="none" 452 + stroke="currentColor" 453 + stroke-width="2" 454 + stroke-linecap="round" 455 + stroke-linejoin="round"> 456 + <polyline points="9 18 15 12 9 6" /> 457 + </svg> 367 458 </div> 368 459 </div> 369 - <div class="settings-item-right"> 370 - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 371 - <polyline points="9 18 15 12 9 6"/> 372 - </svg> 373 - </div> 374 - </div> 375 - 376 - <div class="settings-item"> 377 - <div class="settings-item-left"> 378 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 379 - <rect x="3" y="11" width="18" height="11" rx="2" ry="2"/> 380 - <path d="M7 11V7a5 5 0 0 1 10 0v4"/> 381 - </svg> 382 - <div class="settings-item-content"> 383 - <div class="settings-item-title">Privacy</div> 384 - <div class="settings-item-subtitle">Visibility settings</div> 460 + 461 + <div class="settings-item"> 462 + <div class="settings-item-left"> 463 + <svg 464 + class="settings-item-icon" 465 + viewBox="0 0 24 24" 466 + fill="none" 467 + stroke="currentColor" 468 + stroke-width="2" 469 + stroke-linecap="round" 470 + stroke-linejoin="round"> 471 + <rect x="3" y="11" width="18" height="11" rx="2" ry="2" /> 472 + <path d="M7 11V7a5 5 0 0 1 10 0v4" /> 473 + </svg> 474 + <div class="settings-item-content"> 475 + <div class="settings-item-title">Privacy</div> 476 + <div class="settings-item-subtitle">Visibility settings</div> 477 + </div> 478 + </div> 479 + <div class="settings-item-right"> 480 + <svg 481 + width="20" 482 + height="20" 483 + viewBox="0 0 24 24" 484 + fill="none" 485 + stroke="currentColor" 486 + stroke-width="2" 487 + stroke-linecap="round" 488 + stroke-linejoin="round"> 489 + <polyline points="9 18 15 12 9 6" /> 490 + </svg> 385 491 </div> 386 492 </div> 387 - <div class="settings-item-right"> 388 - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 389 - <polyline points="9 18 15 12 9 6"/> 390 - </svg> 391 - </div> 392 493 </div> 393 494 </div> 394 - </div> 395 - 396 - <!-- Notifications Section --> 397 - <div class="settings-section"> 398 - <div class="settings-section-title">Notifications</div> 399 - 400 - <div class="settings-group"> 401 - <div class="settings-item"> 402 - <div class="settings-item-left"> 403 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 404 - <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/> 405 - <path d="M13.73 21a2 2 0 0 1-3.46 0"/> 406 - </svg> 407 - <div class="settings-item-content"> 408 - <div class="settings-item-title">Push Notifications</div> 495 + 496 + <!-- Notifications Section --> 497 + <div class="settings-section"> 498 + <div class="settings-section-title">Notifications</div> 499 + 500 + <div class="settings-group"> 501 + <div class="settings-item"> 502 + <div class="settings-item-left"> 503 + <svg 504 + class="settings-item-icon" 505 + viewBox="0 0 24 24" 506 + fill="none" 507 + stroke="currentColor" 508 + stroke-width="2" 509 + stroke-linecap="round" 510 + stroke-linejoin="round"> 511 + <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" /> 512 + <path d="M13.73 21a2 2 0 0 1-3.46 0" /> 513 + </svg> 514 + <div class="settings-item-content"> 515 + <div class="settings-item-title">Push Notifications</div> 516 + </div> 409 517 </div> 410 - </div> 411 - <div class="settings-item-right"> 412 - <div class="toggle active"> 413 - <div class="toggle-thumb"></div> 518 + <div class="settings-item-right"> 519 + <div class="toggle active"> 520 + <div class="toggle-thumb"></div> 521 + </div> 414 522 </div> 415 523 </div> 416 - </div> 417 - 418 - <div class="settings-item"> 419 - <div class="settings-item-left"> 420 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 421 - <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/> 422 - <polyline points="22,6 12,13 2,6"/> 423 - </svg> 424 - <div class="settings-item-content"> 425 - <div class="settings-item-title">Email Notifications</div> 524 + 525 + <div class="settings-item"> 526 + <div class="settings-item-left"> 527 + <svg 528 + class="settings-item-icon" 529 + viewBox="0 0 24 24" 530 + fill="none" 531 + stroke="currentColor" 532 + stroke-width="2" 533 + stroke-linecap="round" 534 + stroke-linejoin="round"> 535 + <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" /> 536 + <polyline points="22,6 12,13 2,6" /> 537 + </svg> 538 + <div class="settings-item-content"> 539 + <div class="settings-item-title">Email Notifications</div> 540 + </div> 426 541 </div> 427 - </div> 428 - <div class="settings-item-right"> 429 - <div class="toggle"> 430 - <div class="toggle-thumb"></div> 542 + <div class="settings-item-right"> 543 + <div class="toggle"> 544 + <div class="toggle-thumb"></div> 545 + </div> 431 546 </div> 432 547 </div> 433 548 </div> 434 549 </div> 435 - </div> 436 - 437 - <!-- About Section --> 438 - <div class="settings-section"> 439 - <div class="settings-section-title">About</div> 440 - 441 - <div class="settings-group"> 442 - <a href="devtools.html" class="settings-item" style="text-decoration:none;"> 443 - <div class="settings-item-left"> 444 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 445 - <polyline points="16 18 22 12 16 6"/> 446 - <polyline points="8 6 2 12 8 18"/> 447 - </svg> 448 - <div class="settings-item-content"> 449 - <div class="settings-item-title">Dev Tools</div> 450 - <div class="settings-item-subtitle">PDS Explorer</div> 550 + 551 + <!-- About Section --> 552 + <div class="settings-section"> 553 + <div class="settings-section-title">About</div> 554 + 555 + <div class="settings-group"> 556 + <a href="devtools.html" class="settings-item" style="text-decoration: none"> 557 + <div class="settings-item-left"> 558 + <svg 559 + class="settings-item-icon" 560 + viewBox="0 0 24 24" 561 + fill="none" 562 + stroke="currentColor" 563 + stroke-width="2" 564 + stroke-linecap="round" 565 + stroke-linejoin="round"> 566 + <polyline points="16 18 22 12 16 6" /> 567 + <polyline points="8 6 2 12 8 18" /> 568 + </svg> 569 + <div class="settings-item-content"> 570 + <div class="settings-item-title">Dev Tools</div> 571 + <div class="settings-item-subtitle">PDS Explorer</div> 572 + </div> 451 573 </div> 452 - </div> 453 - <div class="settings-item-right"> 454 - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 455 - <polyline points="9 18 15 12 9 6"/> 456 - </svg> 457 - </div> 458 - </a> 574 + <div class="settings-item-right"> 575 + <svg 576 + width="20" 577 + height="20" 578 + viewBox="0 0 24 24" 579 + fill="none" 580 + stroke="currentColor" 581 + stroke-width="2" 582 + stroke-linecap="round" 583 + stroke-linejoin="round"> 584 + <polyline points="9 18 15 12 9 6" /> 585 + </svg> 586 + </div> 587 + </a> 459 588 460 - <a href="logs.html" class="settings-item" style="text-decoration:none;"> 461 - <div class="settings-item-left"> 462 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 463 - <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> 464 - <polyline points="14 2 14 8 20 8"/> 465 - <line x1="16" y1="13" x2="8" y2="13"/> 466 - <line x1="16" y1="17" x2="8" y2="17"/> 467 - <polyline points="10 9 9 9 8 9"/> 468 - </svg> 469 - <div class="settings-item-content"> 470 - <div class="settings-item-title">Logs</div> 471 - <div class="settings-item-subtitle">View app log files</div> 589 + <a href="logs.html" class="settings-item" style="text-decoration: none"> 590 + <div class="settings-item-left"> 591 + <svg 592 + class="settings-item-icon" 593 + viewBox="0 0 24 24" 594 + fill="none" 595 + stroke="currentColor" 596 + stroke-width="2" 597 + stroke-linecap="round" 598 + stroke-linejoin="round"> 599 + <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> 600 + <polyline points="14 2 14 8 20 8" /> 601 + <line x1="16" y1="13" x2="8" y2="13" /> 602 + <line x1="16" y1="17" x2="8" y2="17" /> 603 + <polyline points="10 9 9 9 8 9" /> 604 + </svg> 605 + <div class="settings-item-content"> 606 + <div class="settings-item-title">Logs</div> 607 + <div class="settings-item-subtitle">View app log files</div> 608 + </div> 472 609 </div> 473 - </div> 474 - <div class="settings-item-right"> 475 - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 476 - <polyline points="9 18 15 12 9 6"/> 477 - </svg> 478 - </div> 479 - </a> 480 - 481 - <div class="settings-item"> 482 - <div class="settings-item-left"> 483 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 484 - <circle cx="12" cy="12" r="10"/> 485 - <line x1="12" y1="16" x2="12" y2="12"/> 486 - <line x1="12" y1="8" x2="12.01" y2="8"/> 487 - </svg> 488 - <div class="settings-item-content"> 489 - <div class="settings-item-title">Help & Support</div> 610 + <div class="settings-item-right"> 611 + <svg 612 + width="20" 613 + height="20" 614 + viewBox="0 0 24 24" 615 + fill="none" 616 + stroke="currentColor" 617 + stroke-width="2" 618 + stroke-linecap="round" 619 + stroke-linejoin="round"> 620 + <polyline points="9 18 15 12 9 6" /> 621 + </svg> 490 622 </div> 491 - </div> 492 - <div class="settings-item-right"> 493 - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 494 - <polyline points="9 18 15 12 9 6"/> 495 - </svg> 496 - </div> 497 - </div> 498 - 499 - <div class="settings-item"> 500 - <div class="settings-item-left"> 501 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 502 - <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/> 503 - </svg> 504 - <div class="settings-item-content"> 505 - <div class="settings-item-title">Privacy Policy</div> 623 + </a> 624 + 625 + <div class="settings-item"> 626 + <div class="settings-item-left"> 627 + <svg 628 + class="settings-item-icon" 629 + viewBox="0 0 24 24" 630 + fill="none" 631 + stroke="currentColor" 632 + stroke-width="2" 633 + stroke-linecap="round" 634 + stroke-linejoin="round"> 635 + <circle cx="12" cy="12" r="10" /> 636 + <line x1="12" y1="16" x2="12" y2="12" /> 637 + <line x1="12" y1="8" x2="12.01" y2="8" /> 638 + </svg> 639 + <div class="settings-item-content"> 640 + <div class="settings-item-title">Help & Support</div> 641 + </div> 642 + </div> 643 + <div class="settings-item-right"> 644 + <svg 645 + width="20" 646 + height="20" 647 + viewBox="0 0 24 24" 648 + fill="none" 649 + stroke="currentColor" 650 + stroke-width="2" 651 + stroke-linecap="round" 652 + stroke-linejoin="round"> 653 + <polyline points="9 18 15 12 9 6" /> 654 + </svg> 506 655 </div> 507 656 </div> 508 - <div class="settings-item-right"> 509 - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 510 - <polyline points="9 18 15 12 9 6"/> 511 - </svg> 657 + 658 + <div class="settings-item"> 659 + <div class="settings-item-left"> 660 + <svg 661 + class="settings-item-icon" 662 + viewBox="0 0 24 24" 663 + fill="none" 664 + stroke="currentColor" 665 + stroke-width="2" 666 + stroke-linecap="round" 667 + stroke-linejoin="round"> 668 + <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" /> 669 + </svg> 670 + <div class="settings-item-content"> 671 + <div class="settings-item-title">Privacy Policy</div> 672 + </div> 673 + </div> 674 + <div class="settings-item-right"> 675 + <svg 676 + width="20" 677 + height="20" 678 + viewBox="0 0 24 24" 679 + fill="none" 680 + stroke="currentColor" 681 + stroke-width="2" 682 + stroke-linecap="round" 683 + stroke-linejoin="round"> 684 + <polyline points="9 18 15 12 9 6" /> 685 + </svg> 686 + </div> 512 687 </div> 513 688 </div> 514 689 </div> 515 - </div> 516 - 517 - <!-- Danger Zone --> 518 - <div class="settings-section"> 519 - <div class="settings-section-title">Danger Zone</div> 520 - 521 - <div class="settings-group"> 522 - <div class="settings-item settings-item-danger" onclick="logout()"> 523 - <div class="settings-item-left"> 524 - <svg class="settings-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: var(--accent-error);"> 525 - <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/> 526 - <polyline points="16 17 21 12 16 7"/> 527 - <line x1="21" y1="12" x2="9" y2="12"/> 528 - </svg> 529 - <div class="settings-item-content"> 530 - <div class="settings-item-title">Log Out</div> 690 + 691 + <!-- Danger Zone --> 692 + <div class="settings-section"> 693 + <div class="settings-section-title">Danger Zone</div> 694 + 695 + <div class="settings-group"> 696 + <div class="settings-item settings-item-danger" onclick="logout()"> 697 + <div class="settings-item-left"> 698 + <svg 699 + class="settings-item-icon" 700 + viewBox="0 0 24 24" 701 + fill="none" 702 + stroke="currentColor" 703 + stroke-width="2" 704 + stroke-linecap="round" 705 + stroke-linejoin="round" 706 + style="color: var(--accent-error)"> 707 + <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /> 708 + <polyline points="16 17 21 12 16 7" /> 709 + <line x1="21" y1="12" x2="9" y2="12" /> 710 + </svg> 711 + <div class="settings-item-content"> 712 + <div class="settings-item-title">Log Out</div> 713 + </div> 531 714 </div> 532 715 </div> 533 716 </div> 534 717 </div> 718 + 719 + <!-- App Version --> 720 + <div class="app-version">Lazurite v1.0.0 (Phase 1)</div> 535 721 </div> 536 - 537 - <!-- App Version --> 538 - <div class="app-version"> 539 - Lazurite v1.0.0 (Phase 1) 540 - </div> 541 - 722 + 723 + <!-- Bottom Navigation --> 724 + <nav class="nav-bar"> 725 + <a href="home.html" class="nav-item"> 726 + <svg 727 + viewBox="0 0 24 24" 728 + fill="none" 729 + stroke="currentColor" 730 + stroke-width="2" 731 + stroke-linecap="round" 732 + stroke-linejoin="round"> 733 + <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> 734 + <polyline points="9 22 9 12 15 12 15 22" /> 735 + </svg> 736 + <span>Home</span> 737 + </a> 738 + 739 + <a href="profile.html" class="nav-item"> 740 + <svg 741 + viewBox="0 0 24 24" 742 + fill="none" 743 + stroke="currentColor" 744 + stroke-width="2" 745 + stroke-linecap="round" 746 + stroke-linejoin="round"> 747 + <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> 748 + <circle cx="12" cy="7" r="4" /> 749 + </svg> 750 + <span>Profile</span> 751 + </a> 752 + 753 + <a href="settings.html" class="nav-item active"> 754 + <svg 755 + viewBox="0 0 24 24" 756 + fill="none" 757 + stroke="currentColor" 758 + stroke-width="2" 759 + stroke-linecap="round" 760 + stroke-linejoin="round"> 761 + <circle cx="12" cy="12" r="3" /> 762 + <path 763 + d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" /> 764 + </svg> 765 + <span>Settings</span> 766 + </a> 767 + </nav> 542 768 </div> 543 - 544 - <!-- Bottom Navigation --> 545 - <nav class="nav-bar"> 546 - <a href="home.html" class="nav-item"> 547 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 548 - <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/> 549 - <polyline points="9 22 9 12 15 12 15 22"/> 550 - </svg> 551 - <span>Home</span> 552 - </a> 553 - 554 - <a href="profile.html" class="nav-item"> 555 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 556 - <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/> 557 - <circle cx="12" cy="7" r="4"/> 558 - </svg> 559 - <span>Profile</span> 560 - </a> 561 - 562 - <a href="settings.html" class="nav-item active"> 563 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 564 - <circle cx="12" cy="12" r="3"/> 565 - <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/> 566 - </svg> 567 - <span>Settings</span> 568 - </a> 569 - </nav> 570 - 571 - </div> 572 - 573 - <script> 574 - // Theme management 575 - const ALL_THEMES = ['light', 'dark', 'catppuccin-latte', 'catppuccin-mocha', 'nord-light', 'nord-dark', 'rose-pine-dawn', 'rose-pine-moon']; 576 769 577 - function setTheme(theme) { 578 - // Update UI 579 - document.querySelectorAll('.theme-option').forEach(el => { 580 - el.classList.remove('active'); 581 - }); 582 - const target = document.querySelector(`.theme-option[data-theme="${theme}"]`); 583 - if (target) target.classList.add('active'); 770 + <script> 771 + const ALL_THEMES = [ 772 + "light", 773 + "dark", 774 + "catppuccin-latte", 775 + "catppuccin-mocha", 776 + "nord-light", 777 + "nord-dark", 778 + "rose-pine-dawn", 779 + "rose-pine-moon", 780 + ]; 584 781 585 - // Apply theme — 'light' is the default (no data-theme attr), everything else sets the attr 586 - if (theme === 'light') { 587 - document.documentElement.removeAttribute('data-theme'); 588 - } else { 589 - document.documentElement.setAttribute('data-theme', theme); 590 - } 591 - localStorage.setItem('theme', theme); 782 + function setTheme(theme) { 783 + document.querySelectorAll(".theme-option").forEach((el) => { 784 + el.classList.remove("active"); 785 + }); 786 + const target = document.querySelector(`.theme-option[data-theme="${theme}"]`); 787 + if (target) target.classList.add("active"); 592 788 593 - // Disable auto theme 594 - document.getElementById('auto-theme-toggle').classList.remove('active'); 595 - localStorage.removeItem('auto-theme'); 596 - } 597 - 598 - function toggleAutoTheme() { 599 - const toggle = document.getElementById('auto-theme-toggle'); 600 - toggle.classList.toggle('active'); 601 - 602 - if (toggle.classList.contains('active')) { 603 - localStorage.setItem('auto-theme', 'true'); 604 - // Check system preference 605 - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { 606 - document.documentElement.setAttribute('data-theme', 'dark'); 607 - localStorage.setItem('theme', 'dark'); 789 + if (theme === "light") { 790 + document.documentElement.removeAttribute("data-theme"); 608 791 } else { 609 - document.documentElement.removeAttribute('data-theme'); 610 - localStorage.setItem('theme', 'light'); 792 + document.documentElement.setAttribute("data-theme", theme); 611 793 } 612 - } else { 613 - localStorage.removeItem('auto-theme'); 794 + localStorage.setItem("theme", theme); 795 + 796 + document.getElementById("auto-theme-toggle").classList.remove("active"); 797 + localStorage.removeItem("auto-theme"); 614 798 } 615 - } 616 - 617 - function logout() { 618 - if (confirm('Are you sure you want to log out?')) { 619 - localStorage.removeItem('theme'); 620 - localStorage.removeItem('auto-theme'); 621 - window.location.href = 'login.html'; 799 + 800 + function toggleAutoTheme() { 801 + const toggle = document.getElementById("auto-theme-toggle"); 802 + toggle.classList.toggle("active"); 803 + 804 + if (toggle.classList.contains("active")) { 805 + localStorage.setItem("auto-theme", "true"); 806 + 807 + if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { 808 + document.documentElement.setAttribute("data-theme", "dark"); 809 + localStorage.setItem("theme", "dark"); 810 + } else { 811 + document.documentElement.removeAttribute("data-theme"); 812 + localStorage.setItem("theme", "light"); 813 + } 814 + } else { 815 + localStorage.removeItem("auto-theme"); 816 + } 622 817 } 623 - } 624 - 625 - // Initialize theme on load 626 - (function() { 627 - const savedTheme = localStorage.getItem('theme') || 'light'; 628 - const autoTheme = localStorage.getItem('auto-theme'); 629 818 630 - if (autoTheme === 'true') { 631 - document.getElementById('auto-theme-toggle').classList.add('active'); 819 + function logout() { 820 + if (confirm("Are you sure you want to log out?")) { 821 + localStorage.removeItem("theme"); 822 + localStorage.removeItem("auto-theme"); 823 + window.location.href = "login.html"; 824 + } 632 825 } 633 826 634 - // Apply saved theme 635 - document.querySelectorAll('.theme-option').forEach(el => { 636 - el.classList.remove('active'); 637 - }); 827 + (function () { 828 + const savedTheme = localStorage.getItem("theme") || "light"; 829 + const autoTheme = localStorage.getItem("auto-theme"); 638 830 639 - if (savedTheme === 'light') { 640 - document.documentElement.removeAttribute('data-theme'); 641 - } else { 642 - document.documentElement.setAttribute('data-theme', savedTheme); 643 - } 831 + if (autoTheme === "true") { 832 + document.getElementById("auto-theme-toggle").classList.add("active"); 833 + } 834 + 835 + document.querySelectorAll(".theme-option").forEach((el) => { 836 + el.classList.remove("active"); 837 + }); 838 + 839 + if (savedTheme === "light") { 840 + document.documentElement.removeAttribute("data-theme"); 841 + } else { 842 + document.documentElement.setAttribute("data-theme", savedTheme); 843 + } 644 844 645 - const active = document.querySelector(`.theme-option[data-theme="${savedTheme}"]`); 646 - if (active) active.classList.add('active'); 647 - })(); 648 - </script> 649 - </body> 845 + const active = document.querySelector(`.theme-option[data-theme="${savedTheme}"]`); 846 + if (active) active.classList.add("active"); 847 + })(); 848 + </script> 849 + </body> 650 850 </html>
+328 -925
lib/core/database/app_database.g.dart
··· 26 26 type: DriftSqlType.string, 27 27 requiredDuringInsert: true, 28 28 ); 29 - static const VerificationMeta _displayNameMeta = const VerificationMeta( 30 - 'displayName', 31 - ); 29 + static const VerificationMeta _displayNameMeta = const VerificationMeta('displayName'); 32 30 @override 33 31 late final GeneratedColumn<String> displayName = GeneratedColumn<String>( 34 32 'display_name', ··· 37 35 type: DriftSqlType.string, 38 36 requiredDuringInsert: false, 39 37 ); 40 - static const VerificationMeta _serviceMeta = const VerificationMeta( 41 - 'service', 42 - ); 38 + static const VerificationMeta _serviceMeta = const VerificationMeta('service'); 43 39 @override 44 40 late final GeneratedColumn<String> service = GeneratedColumn<String>( 45 41 'service', ··· 48 44 type: DriftSqlType.string, 49 45 requiredDuringInsert: false, 50 46 ); 51 - static const VerificationMeta _accessTokenMeta = const VerificationMeta( 52 - 'accessToken', 53 - ); 47 + static const VerificationMeta _accessTokenMeta = const VerificationMeta('accessToken'); 54 48 @override 55 49 late final GeneratedColumn<String> accessToken = GeneratedColumn<String>( 56 50 'access_token', ··· 59 53 type: DriftSqlType.string, 60 54 requiredDuringInsert: true, 61 55 ); 62 - static const VerificationMeta _refreshTokenMeta = const VerificationMeta( 63 - 'refreshToken', 64 - ); 56 + static const VerificationMeta _refreshTokenMeta = const VerificationMeta('refreshToken'); 65 57 @override 66 58 late final GeneratedColumn<String> refreshToken = GeneratedColumn<String>( 67 59 'refresh_token', ··· 70 62 type: DriftSqlType.string, 71 63 requiredDuringInsert: false, 72 64 ); 73 - static const VerificationMeta _dpopPublicKeyMeta = const VerificationMeta( 74 - 'dpopPublicKey', 75 - ); 65 + static const VerificationMeta _dpopPublicKeyMeta = const VerificationMeta('dpopPublicKey'); 76 66 @override 77 67 late final GeneratedColumn<String> dpopPublicKey = GeneratedColumn<String>( 78 68 'dpop_public_key', ··· 81 71 type: DriftSqlType.string, 82 72 requiredDuringInsert: false, 83 73 ); 84 - static const VerificationMeta _dpopPrivateKeyMeta = const VerificationMeta( 85 - 'dpopPrivateKey', 86 - ); 74 + static const VerificationMeta _dpopPrivateKeyMeta = const VerificationMeta('dpopPrivateKey'); 87 75 @override 88 76 late final GeneratedColumn<String> dpopPrivateKey = GeneratedColumn<String>( 89 77 'dpop_private_key', ··· 92 80 type: DriftSqlType.string, 93 81 requiredDuringInsert: false, 94 82 ); 95 - static const VerificationMeta _dpopNonceMeta = const VerificationMeta( 96 - 'dpopNonce', 97 - ); 83 + static const VerificationMeta _dpopNonceMeta = const VerificationMeta('dpopNonce'); 98 84 @override 99 85 late final GeneratedColumn<String> dpopNonce = GeneratedColumn<String>( 100 86 'dpop_nonce', ··· 103 89 type: DriftSqlType.string, 104 90 requiredDuringInsert: false, 105 91 ); 106 - static const VerificationMeta _expiresAtMeta = const VerificationMeta( 107 - 'expiresAt', 108 - ); 92 + static const VerificationMeta _expiresAtMeta = const VerificationMeta('expiresAt'); 109 93 @override 110 94 late final GeneratedColumn<DateTime> expiresAt = GeneratedColumn<DateTime>( 111 95 'expires_at', ··· 114 98 type: DriftSqlType.dateTime, 115 99 requiredDuringInsert: false, 116 100 ); 117 - static const VerificationMeta _createdAtMeta = const VerificationMeta( 118 - 'createdAt', 119 - ); 101 + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); 120 102 @override 121 103 late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( 122 104 'created_at', ··· 126 108 requiredDuringInsert: false, 127 109 defaultValue: currentDateAndTime, 128 110 ); 129 - static const VerificationMeta _updatedAtMeta = const VerificationMeta( 130 - 'updatedAt', 131 - ); 111 + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); 132 112 @override 133 113 late final GeneratedColumn<DateTime> updatedAt = GeneratedColumn<DateTime>( 134 114 'updated_at', ··· 159 139 String get actualTableName => $name; 160 140 static const String $name = 'accounts'; 161 141 @override 162 - VerificationContext validateIntegrity( 163 - Insertable<Account> instance, { 164 - bool isInserting = false, 165 - }) { 142 + VerificationContext validateIntegrity(Insertable<Account> instance, {bool isInserting = false}) { 166 143 final context = VerificationContext(); 167 144 final data = instance.toColumns(true); 168 145 if (data.containsKey('did')) { 169 - context.handle( 170 - _didMeta, 171 - did.isAcceptableOrUnknown(data['did']!, _didMeta), 172 - ); 146 + context.handle(_didMeta, did.isAcceptableOrUnknown(data['did']!, _didMeta)); 173 147 } else if (isInserting) { 174 148 context.missing(_didMeta); 175 149 } 176 150 if (data.containsKey('handle')) { 177 - context.handle( 178 - _handleMeta, 179 - handle.isAcceptableOrUnknown(data['handle']!, _handleMeta), 180 - ); 151 + context.handle(_handleMeta, handle.isAcceptableOrUnknown(data['handle']!, _handleMeta)); 181 152 } else if (isInserting) { 182 153 context.missing(_handleMeta); 183 154 } 184 155 if (data.containsKey('display_name')) { 185 - context.handle( 186 - _displayNameMeta, 187 - displayName.isAcceptableOrUnknown( 188 - data['display_name']!, 189 - _displayNameMeta, 190 - ), 191 - ); 156 + context.handle(_displayNameMeta, displayName.isAcceptableOrUnknown(data['display_name']!, _displayNameMeta)); 192 157 } 193 158 if (data.containsKey('service')) { 194 - context.handle( 195 - _serviceMeta, 196 - service.isAcceptableOrUnknown(data['service']!, _serviceMeta), 197 - ); 159 + context.handle(_serviceMeta, service.isAcceptableOrUnknown(data['service']!, _serviceMeta)); 198 160 } 199 161 if (data.containsKey('access_token')) { 200 - context.handle( 201 - _accessTokenMeta, 202 - accessToken.isAcceptableOrUnknown( 203 - data['access_token']!, 204 - _accessTokenMeta, 205 - ), 206 - ); 162 + context.handle(_accessTokenMeta, accessToken.isAcceptableOrUnknown(data['access_token']!, _accessTokenMeta)); 207 163 } else if (isInserting) { 208 164 context.missing(_accessTokenMeta); 209 165 } 210 166 if (data.containsKey('refresh_token')) { 211 - context.handle( 212 - _refreshTokenMeta, 213 - refreshToken.isAcceptableOrUnknown( 214 - data['refresh_token']!, 215 - _refreshTokenMeta, 216 - ), 217 - ); 167 + context.handle(_refreshTokenMeta, refreshToken.isAcceptableOrUnknown(data['refresh_token']!, _refreshTokenMeta)); 218 168 } 219 169 if (data.containsKey('dpop_public_key')) { 220 170 context.handle( 221 171 _dpopPublicKeyMeta, 222 - dpopPublicKey.isAcceptableOrUnknown( 223 - data['dpop_public_key']!, 224 - _dpopPublicKeyMeta, 225 - ), 172 + dpopPublicKey.isAcceptableOrUnknown(data['dpop_public_key']!, _dpopPublicKeyMeta), 226 173 ); 227 174 } 228 175 if (data.containsKey('dpop_private_key')) { 229 176 context.handle( 230 177 _dpopPrivateKeyMeta, 231 - dpopPrivateKey.isAcceptableOrUnknown( 232 - data['dpop_private_key']!, 233 - _dpopPrivateKeyMeta, 234 - ), 178 + dpopPrivateKey.isAcceptableOrUnknown(data['dpop_private_key']!, _dpopPrivateKeyMeta), 235 179 ); 236 180 } 237 181 if (data.containsKey('dpop_nonce')) { 238 - context.handle( 239 - _dpopNonceMeta, 240 - dpopNonce.isAcceptableOrUnknown(data['dpop_nonce']!, _dpopNonceMeta), 241 - ); 182 + context.handle(_dpopNonceMeta, dpopNonce.isAcceptableOrUnknown(data['dpop_nonce']!, _dpopNonceMeta)); 242 183 } 243 184 if (data.containsKey('expires_at')) { 244 - context.handle( 245 - _expiresAtMeta, 246 - expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta), 247 - ); 185 + context.handle(_expiresAtMeta, expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta)); 248 186 } 249 187 if (data.containsKey('created_at')) { 250 - context.handle( 251 - _createdAtMeta, 252 - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), 253 - ); 188 + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); 254 189 } 255 190 if (data.containsKey('updated_at')) { 256 - context.handle( 257 - _updatedAtMeta, 258 - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), 259 - ); 191 + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); 260 192 } 261 193 return context; 262 194 } ··· 267 199 Account map(Map<String, dynamic> data, {String? tablePrefix}) { 268 200 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 269 201 return Account( 270 - did: attachedDatabase.typeMapping.read( 271 - DriftSqlType.string, 272 - data['${effectivePrefix}did'], 273 - )!, 274 - handle: attachedDatabase.typeMapping.read( 275 - DriftSqlType.string, 276 - data['${effectivePrefix}handle'], 277 - )!, 278 - displayName: attachedDatabase.typeMapping.read( 279 - DriftSqlType.string, 280 - data['${effectivePrefix}display_name'], 281 - ), 282 - service: attachedDatabase.typeMapping.read( 283 - DriftSqlType.string, 284 - data['${effectivePrefix}service'], 285 - ), 286 - accessToken: attachedDatabase.typeMapping.read( 287 - DriftSqlType.string, 288 - data['${effectivePrefix}access_token'], 289 - )!, 290 - refreshToken: attachedDatabase.typeMapping.read( 291 - DriftSqlType.string, 292 - data['${effectivePrefix}refresh_token'], 293 - ), 294 - dpopPublicKey: attachedDatabase.typeMapping.read( 295 - DriftSqlType.string, 296 - data['${effectivePrefix}dpop_public_key'], 297 - ), 202 + did: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}did'])!, 203 + handle: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}handle'])!, 204 + displayName: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}display_name']), 205 + service: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}service']), 206 + accessToken: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}access_token'])!, 207 + refreshToken: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}refresh_token']), 208 + dpopPublicKey: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}dpop_public_key']), 298 209 dpopPrivateKey: attachedDatabase.typeMapping.read( 299 210 DriftSqlType.string, 300 211 data['${effectivePrefix}dpop_private_key'], 301 212 ), 302 - dpopNonce: attachedDatabase.typeMapping.read( 303 - DriftSqlType.string, 304 - data['${effectivePrefix}dpop_nonce'], 305 - ), 306 - expiresAt: attachedDatabase.typeMapping.read( 307 - DriftSqlType.dateTime, 308 - data['${effectivePrefix}expires_at'], 309 - ), 310 - createdAt: attachedDatabase.typeMapping.read( 311 - DriftSqlType.dateTime, 312 - data['${effectivePrefix}created_at'], 313 - )!, 314 - updatedAt: attachedDatabase.typeMapping.read( 315 - DriftSqlType.dateTime, 316 - data['${effectivePrefix}updated_at'], 317 - )!, 213 + dpopNonce: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}dpop_nonce']), 214 + expiresAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}expires_at']), 215 + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, 216 + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, 318 217 ); 319 218 } 320 219 ··· 387 286 return AccountsCompanion( 388 287 did: Value(did), 389 288 handle: Value(handle), 390 - displayName: displayName == null && nullToAbsent 391 - ? const Value.absent() 392 - : Value(displayName), 393 - service: service == null && nullToAbsent 394 - ? const Value.absent() 395 - : Value(service), 289 + displayName: displayName == null && nullToAbsent ? const Value.absent() : Value(displayName), 290 + service: service == null && nullToAbsent ? const Value.absent() : Value(service), 396 291 accessToken: Value(accessToken), 397 - refreshToken: refreshToken == null && nullToAbsent 398 - ? const Value.absent() 399 - : Value(refreshToken), 400 - dpopPublicKey: dpopPublicKey == null && nullToAbsent 401 - ? const Value.absent() 402 - : Value(dpopPublicKey), 403 - dpopPrivateKey: dpopPrivateKey == null && nullToAbsent 404 - ? const Value.absent() 405 - : Value(dpopPrivateKey), 406 - dpopNonce: dpopNonce == null && nullToAbsent 407 - ? const Value.absent() 408 - : Value(dpopNonce), 409 - expiresAt: expiresAt == null && nullToAbsent 410 - ? const Value.absent() 411 - : Value(expiresAt), 292 + refreshToken: refreshToken == null && nullToAbsent ? const Value.absent() : Value(refreshToken), 293 + dpopPublicKey: dpopPublicKey == null && nullToAbsent ? const Value.absent() : Value(dpopPublicKey), 294 + dpopPrivateKey: dpopPrivateKey == null && nullToAbsent ? const Value.absent() : Value(dpopPrivateKey), 295 + dpopNonce: dpopNonce == null && nullToAbsent ? const Value.absent() : Value(dpopNonce), 296 + expiresAt: expiresAt == null && nullToAbsent ? const Value.absent() : Value(expiresAt), 412 297 createdAt: Value(createdAt), 413 298 updatedAt: Value(updatedAt), 414 299 ); 415 300 } 416 301 417 - factory Account.fromJson( 418 - Map<String, dynamic> json, { 419 - ValueSerializer? serializer, 420 - }) { 302 + factory Account.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 421 303 serializer ??= driftRuntimeOptions.defaultSerializer; 422 304 return Account( 423 305 did: serializer.fromJson<String>(json['did']), ··· 473 355 service: service.present ? service.value : this.service, 474 356 accessToken: accessToken ?? this.accessToken, 475 357 refreshToken: refreshToken.present ? refreshToken.value : this.refreshToken, 476 - dpopPublicKey: dpopPublicKey.present 477 - ? dpopPublicKey.value 478 - : this.dpopPublicKey, 479 - dpopPrivateKey: dpopPrivateKey.present 480 - ? dpopPrivateKey.value 481 - : this.dpopPrivateKey, 358 + dpopPublicKey: dpopPublicKey.present ? dpopPublicKey.value : this.dpopPublicKey, 359 + dpopPrivateKey: dpopPrivateKey.present ? dpopPrivateKey.value : this.dpopPrivateKey, 482 360 dpopNonce: dpopNonce.present ? dpopNonce.value : this.dpopNonce, 483 361 expiresAt: expiresAt.present ? expiresAt.value : this.expiresAt, 484 362 createdAt: createdAt ?? this.createdAt, ··· 488 366 return Account( 489 367 did: data.did.present ? data.did.value : this.did, 490 368 handle: data.handle.present ? data.handle.value : this.handle, 491 - displayName: data.displayName.present 492 - ? data.displayName.value 493 - : this.displayName, 369 + displayName: data.displayName.present ? data.displayName.value : this.displayName, 494 370 service: data.service.present ? data.service.value : this.service, 495 - accessToken: data.accessToken.present 496 - ? data.accessToken.value 497 - : this.accessToken, 498 - refreshToken: data.refreshToken.present 499 - ? data.refreshToken.value 500 - : this.refreshToken, 501 - dpopPublicKey: data.dpopPublicKey.present 502 - ? data.dpopPublicKey.value 503 - : this.dpopPublicKey, 504 - dpopPrivateKey: data.dpopPrivateKey.present 505 - ? data.dpopPrivateKey.value 506 - : this.dpopPrivateKey, 371 + accessToken: data.accessToken.present ? data.accessToken.value : this.accessToken, 372 + refreshToken: data.refreshToken.present ? data.refreshToken.value : this.refreshToken, 373 + dpopPublicKey: data.dpopPublicKey.present ? data.dpopPublicKey.value : this.dpopPublicKey, 374 + dpopPrivateKey: data.dpopPrivateKey.present ? data.dpopPrivateKey.value : this.dpopPrivateKey, 507 375 dpopNonce: data.dpopNonce.present ? data.dpopNonce.value : this.dpopNonce, 508 376 expiresAt: data.expiresAt.present ? data.expiresAt.value : this.expiresAt, 509 377 createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ··· 739 607 } 740 608 } 741 609 742 - class $CachedProfilesTable extends CachedProfiles 743 - with TableInfo<$CachedProfilesTable, CachedProfile> { 610 + class $CachedProfilesTable extends CachedProfiles with TableInfo<$CachedProfilesTable, CachedProfile> { 744 611 @override 745 612 final GeneratedDatabase attachedDatabase; 746 613 final String? _alias; ··· 763 630 type: DriftSqlType.string, 764 631 requiredDuringInsert: true, 765 632 ); 766 - static const VerificationMeta _payloadMeta = const VerificationMeta( 767 - 'payload', 768 - ); 633 + static const VerificationMeta _payloadMeta = const VerificationMeta('payload'); 769 634 @override 770 635 late final GeneratedColumn<String> payload = GeneratedColumn<String>( 771 636 'payload', ··· 774 639 type: DriftSqlType.string, 775 640 requiredDuringInsert: true, 776 641 ); 777 - static const VerificationMeta _fetchedAtMeta = const VerificationMeta( 778 - 'fetchedAt', 779 - ); 642 + static const VerificationMeta _fetchedAtMeta = const VerificationMeta('fetchedAt'); 780 643 @override 781 644 late final GeneratedColumn<DateTime> fetchedAt = GeneratedColumn<DateTime>( 782 645 'fetched_at', ··· 794 657 String get actualTableName => $name; 795 658 static const String $name = 'cached_profiles'; 796 659 @override 797 - VerificationContext validateIntegrity( 798 - Insertable<CachedProfile> instance, { 799 - bool isInserting = false, 800 - }) { 660 + VerificationContext validateIntegrity(Insertable<CachedProfile> instance, {bool isInserting = false}) { 801 661 final context = VerificationContext(); 802 662 final data = instance.toColumns(true); 803 663 if (data.containsKey('did')) { 804 - context.handle( 805 - _didMeta, 806 - did.isAcceptableOrUnknown(data['did']!, _didMeta), 807 - ); 664 + context.handle(_didMeta, did.isAcceptableOrUnknown(data['did']!, _didMeta)); 808 665 } else if (isInserting) { 809 666 context.missing(_didMeta); 810 667 } 811 668 if (data.containsKey('handle')) { 812 - context.handle( 813 - _handleMeta, 814 - handle.isAcceptableOrUnknown(data['handle']!, _handleMeta), 815 - ); 669 + context.handle(_handleMeta, handle.isAcceptableOrUnknown(data['handle']!, _handleMeta)); 816 670 } else if (isInserting) { 817 671 context.missing(_handleMeta); 818 672 } 819 673 if (data.containsKey('payload')) { 820 - context.handle( 821 - _payloadMeta, 822 - payload.isAcceptableOrUnknown(data['payload']!, _payloadMeta), 823 - ); 674 + context.handle(_payloadMeta, payload.isAcceptableOrUnknown(data['payload']!, _payloadMeta)); 824 675 } else if (isInserting) { 825 676 context.missing(_payloadMeta); 826 677 } 827 678 if (data.containsKey('fetched_at')) { 828 - context.handle( 829 - _fetchedAtMeta, 830 - fetchedAt.isAcceptableOrUnknown(data['fetched_at']!, _fetchedAtMeta), 831 - ); 679 + context.handle(_fetchedAtMeta, fetchedAt.isAcceptableOrUnknown(data['fetched_at']!, _fetchedAtMeta)); 832 680 } 833 681 return context; 834 682 } ··· 839 687 CachedProfile map(Map<String, dynamic> data, {String? tablePrefix}) { 840 688 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 841 689 return CachedProfile( 842 - did: attachedDatabase.typeMapping.read( 843 - DriftSqlType.string, 844 - data['${effectivePrefix}did'], 845 - )!, 846 - handle: attachedDatabase.typeMapping.read( 847 - DriftSqlType.string, 848 - data['${effectivePrefix}handle'], 849 - )!, 850 - payload: attachedDatabase.typeMapping.read( 851 - DriftSqlType.string, 852 - data['${effectivePrefix}payload'], 853 - )!, 854 - fetchedAt: attachedDatabase.typeMapping.read( 855 - DriftSqlType.dateTime, 856 - data['${effectivePrefix}fetched_at'], 857 - )!, 690 + did: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}did'])!, 691 + handle: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}handle'])!, 692 + payload: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}payload'])!, 693 + fetchedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}fetched_at'])!, 858 694 ); 859 695 } 860 696 ··· 869 705 final String handle; 870 706 final String payload; 871 707 final DateTime fetchedAt; 872 - const CachedProfile({ 873 - required this.did, 874 - required this.handle, 875 - required this.payload, 876 - required this.fetchedAt, 877 - }); 708 + const CachedProfile({required this.did, required this.handle, required this.payload, required this.fetchedAt}); 878 709 @override 879 710 Map<String, Expression> toColumns(bool nullToAbsent) { 880 711 final map = <String, Expression>{}; ··· 894 725 ); 895 726 } 896 727 897 - factory CachedProfile.fromJson( 898 - Map<String, dynamic> json, { 899 - ValueSerializer? serializer, 900 - }) { 728 + factory CachedProfile.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 901 729 serializer ??= driftRuntimeOptions.defaultSerializer; 902 730 return CachedProfile( 903 731 did: serializer.fromJson<String>(json['did']), ··· 917 745 }; 918 746 } 919 747 920 - CachedProfile copyWith({ 921 - String? did, 922 - String? handle, 923 - String? payload, 924 - DateTime? fetchedAt, 925 - }) => CachedProfile( 748 + CachedProfile copyWith({String? did, String? handle, String? payload, DateTime? fetchedAt}) => CachedProfile( 926 749 did: did ?? this.did, 927 750 handle: handle ?? this.handle, 928 751 payload: payload ?? this.payload, ··· 1048 871 } 1049 872 } 1050 873 1051 - class $CachedPostsTable extends CachedPosts 1052 - with TableInfo<$CachedPostsTable, CachedPost> { 874 + class $CachedPostsTable extends CachedPosts with TableInfo<$CachedPostsTable, CachedPost> { 1053 875 @override 1054 876 final GeneratedDatabase attachedDatabase; 1055 877 final String? _alias; ··· 1063 885 type: DriftSqlType.string, 1064 886 requiredDuringInsert: true, 1065 887 ); 1066 - static const VerificationMeta _authorDidMeta = const VerificationMeta( 1067 - 'authorDid', 1068 - ); 888 + static const VerificationMeta _authorDidMeta = const VerificationMeta('authorDid'); 1069 889 @override 1070 890 late final GeneratedColumn<String> authorDid = GeneratedColumn<String>( 1071 891 'author_did', ··· 1074 894 type: DriftSqlType.string, 1075 895 requiredDuringInsert: true, 1076 896 ); 1077 - static const VerificationMeta _payloadMeta = const VerificationMeta( 1078 - 'payload', 1079 - ); 897 + static const VerificationMeta _payloadMeta = const VerificationMeta('payload'); 1080 898 @override 1081 899 late final GeneratedColumn<String> payload = GeneratedColumn<String>( 1082 900 'payload', ··· 1085 903 type: DriftSqlType.string, 1086 904 requiredDuringInsert: true, 1087 905 ); 1088 - static const VerificationMeta _createdAtMeta = const VerificationMeta( 1089 - 'createdAt', 1090 - ); 906 + static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); 1091 907 @override 1092 908 late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( 1093 909 'created_at', ··· 1096 912 type: DriftSqlType.dateTime, 1097 913 requiredDuringInsert: false, 1098 914 ); 1099 - static const VerificationMeta _fetchedAtMeta = const VerificationMeta( 1100 - 'fetchedAt', 1101 - ); 915 + static const VerificationMeta _fetchedAtMeta = const VerificationMeta('fetchedAt'); 1102 916 @override 1103 917 late final GeneratedColumn<DateTime> fetchedAt = GeneratedColumn<DateTime>( 1104 918 'fetched_at', ··· 1109 923 defaultValue: currentDateAndTime, 1110 924 ); 1111 925 @override 1112 - List<GeneratedColumn> get $columns => [ 1113 - uri, 1114 - authorDid, 1115 - payload, 1116 - createdAt, 1117 - fetchedAt, 1118 - ]; 926 + List<GeneratedColumn> get $columns => [uri, authorDid, payload, createdAt, fetchedAt]; 1119 927 @override 1120 928 String get aliasedName => _alias ?? actualTableName; 1121 929 @override 1122 930 String get actualTableName => $name; 1123 931 static const String $name = 'cached_posts'; 1124 932 @override 1125 - VerificationContext validateIntegrity( 1126 - Insertable<CachedPost> instance, { 1127 - bool isInserting = false, 1128 - }) { 933 + VerificationContext validateIntegrity(Insertable<CachedPost> instance, {bool isInserting = false}) { 1129 934 final context = VerificationContext(); 1130 935 final data = instance.toColumns(true); 1131 936 if (data.containsKey('uri')) { 1132 - context.handle( 1133 - _uriMeta, 1134 - uri.isAcceptableOrUnknown(data['uri']!, _uriMeta), 1135 - ); 937 + context.handle(_uriMeta, uri.isAcceptableOrUnknown(data['uri']!, _uriMeta)); 1136 938 } else if (isInserting) { 1137 939 context.missing(_uriMeta); 1138 940 } 1139 941 if (data.containsKey('author_did')) { 1140 - context.handle( 1141 - _authorDidMeta, 1142 - authorDid.isAcceptableOrUnknown(data['author_did']!, _authorDidMeta), 1143 - ); 942 + context.handle(_authorDidMeta, authorDid.isAcceptableOrUnknown(data['author_did']!, _authorDidMeta)); 1144 943 } else if (isInserting) { 1145 944 context.missing(_authorDidMeta); 1146 945 } 1147 946 if (data.containsKey('payload')) { 1148 - context.handle( 1149 - _payloadMeta, 1150 - payload.isAcceptableOrUnknown(data['payload']!, _payloadMeta), 1151 - ); 947 + context.handle(_payloadMeta, payload.isAcceptableOrUnknown(data['payload']!, _payloadMeta)); 1152 948 } else if (isInserting) { 1153 949 context.missing(_payloadMeta); 1154 950 } 1155 951 if (data.containsKey('created_at')) { 1156 - context.handle( 1157 - _createdAtMeta, 1158 - createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), 1159 - ); 952 + context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); 1160 953 } 1161 954 if (data.containsKey('fetched_at')) { 1162 - context.handle( 1163 - _fetchedAtMeta, 1164 - fetchedAt.isAcceptableOrUnknown(data['fetched_at']!, _fetchedAtMeta), 1165 - ); 955 + context.handle(_fetchedAtMeta, fetchedAt.isAcceptableOrUnknown(data['fetched_at']!, _fetchedAtMeta)); 1166 956 } 1167 957 return context; 1168 958 } ··· 1173 963 CachedPost map(Map<String, dynamic> data, {String? tablePrefix}) { 1174 964 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 1175 965 return CachedPost( 1176 - uri: attachedDatabase.typeMapping.read( 1177 - DriftSqlType.string, 1178 - data['${effectivePrefix}uri'], 1179 - )!, 1180 - authorDid: attachedDatabase.typeMapping.read( 1181 - DriftSqlType.string, 1182 - data['${effectivePrefix}author_did'], 1183 - )!, 1184 - payload: attachedDatabase.typeMapping.read( 1185 - DriftSqlType.string, 1186 - data['${effectivePrefix}payload'], 1187 - )!, 1188 - createdAt: attachedDatabase.typeMapping.read( 1189 - DriftSqlType.dateTime, 1190 - data['${effectivePrefix}created_at'], 1191 - ), 1192 - fetchedAt: attachedDatabase.typeMapping.read( 1193 - DriftSqlType.dateTime, 1194 - data['${effectivePrefix}fetched_at'], 1195 - )!, 966 + uri: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}uri'])!, 967 + authorDid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}author_did'])!, 968 + payload: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}payload'])!, 969 + createdAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']), 970 + fetchedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}fetched_at'])!, 1196 971 ); 1197 972 } 1198 973 ··· 1233 1008 uri: Value(uri), 1234 1009 authorDid: Value(authorDid), 1235 1010 payload: Value(payload), 1236 - createdAt: createdAt == null && nullToAbsent 1237 - ? const Value.absent() 1238 - : Value(createdAt), 1011 + createdAt: createdAt == null && nullToAbsent ? const Value.absent() : Value(createdAt), 1239 1012 fetchedAt: Value(fetchedAt), 1240 1013 ); 1241 1014 } 1242 1015 1243 - factory CachedPost.fromJson( 1244 - Map<String, dynamic> json, { 1245 - ValueSerializer? serializer, 1246 - }) { 1016 + factory CachedPost.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 1247 1017 serializer ??= driftRuntimeOptions.defaultSerializer; 1248 1018 return CachedPost( 1249 1019 uri: serializer.fromJson<String>(json['uri']), ··· 1301 1071 } 1302 1072 1303 1073 @override 1304 - int get hashCode => 1305 - Object.hash(uri, authorDid, payload, createdAt, fetchedAt); 1074 + int get hashCode => Object.hash(uri, authorDid, payload, createdAt, fetchedAt); 1306 1075 @override 1307 1076 bool operator ==(Object other) => 1308 1077 identical(this, other) || ··· 1413 1182 } 1414 1183 } 1415 1184 1416 - class $SettingsTable extends Settings 1417 - with TableInfo<$SettingsTable, SettingsEntry> { 1185 + class $SettingsTable extends Settings with TableInfo<$SettingsTable, SettingsEntry> { 1418 1186 @override 1419 1187 final GeneratedDatabase attachedDatabase; 1420 1188 final String? _alias; ··· 1437 1205 type: DriftSqlType.string, 1438 1206 requiredDuringInsert: true, 1439 1207 ); 1440 - static const VerificationMeta _updatedAtMeta = const VerificationMeta( 1441 - 'updatedAt', 1442 - ); 1208 + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); 1443 1209 @override 1444 1210 late final GeneratedColumn<DateTime> updatedAt = GeneratedColumn<DateTime>( 1445 1211 'updated_at', ··· 1457 1223 String get actualTableName => $name; 1458 1224 static const String $name = 'settings'; 1459 1225 @override 1460 - VerificationContext validateIntegrity( 1461 - Insertable<SettingsEntry> instance, { 1462 - bool isInserting = false, 1463 - }) { 1226 + VerificationContext validateIntegrity(Insertable<SettingsEntry> instance, {bool isInserting = false}) { 1464 1227 final context = VerificationContext(); 1465 1228 final data = instance.toColumns(true); 1466 1229 if (data.containsKey('key')) { 1467 - context.handle( 1468 - _keyMeta, 1469 - key.isAcceptableOrUnknown(data['key']!, _keyMeta), 1470 - ); 1230 + context.handle(_keyMeta, key.isAcceptableOrUnknown(data['key']!, _keyMeta)); 1471 1231 } else if (isInserting) { 1472 1232 context.missing(_keyMeta); 1473 1233 } 1474 1234 if (data.containsKey('value')) { 1475 - context.handle( 1476 - _valueMeta, 1477 - value.isAcceptableOrUnknown(data['value']!, _valueMeta), 1478 - ); 1235 + context.handle(_valueMeta, value.isAcceptableOrUnknown(data['value']!, _valueMeta)); 1479 1236 } else if (isInserting) { 1480 1237 context.missing(_valueMeta); 1481 1238 } 1482 1239 if (data.containsKey('updated_at')) { 1483 - context.handle( 1484 - _updatedAtMeta, 1485 - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), 1486 - ); 1240 + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); 1487 1241 } 1488 1242 return context; 1489 1243 } ··· 1494 1248 SettingsEntry map(Map<String, dynamic> data, {String? tablePrefix}) { 1495 1249 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 1496 1250 return SettingsEntry( 1497 - key: attachedDatabase.typeMapping.read( 1498 - DriftSqlType.string, 1499 - data['${effectivePrefix}key'], 1500 - )!, 1501 - value: attachedDatabase.typeMapping.read( 1502 - DriftSqlType.string, 1503 - data['${effectivePrefix}value'], 1504 - )!, 1505 - updatedAt: attachedDatabase.typeMapping.read( 1506 - DriftSqlType.dateTime, 1507 - data['${effectivePrefix}updated_at'], 1508 - )!, 1251 + key: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}key'])!, 1252 + value: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}value'])!, 1253 + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, 1509 1254 ); 1510 1255 } 1511 1256 ··· 1519 1264 final String key; 1520 1265 final String value; 1521 1266 final DateTime updatedAt; 1522 - const SettingsEntry({ 1523 - required this.key, 1524 - required this.value, 1525 - required this.updatedAt, 1526 - }); 1267 + const SettingsEntry({required this.key, required this.value, required this.updatedAt}); 1527 1268 @override 1528 1269 Map<String, Expression> toColumns(bool nullToAbsent) { 1529 1270 final map = <String, Expression>{}; ··· 1534 1275 } 1535 1276 1536 1277 SettingsCompanion toCompanion(bool nullToAbsent) { 1537 - return SettingsCompanion( 1538 - key: Value(key), 1539 - value: Value(value), 1540 - updatedAt: Value(updatedAt), 1541 - ); 1278 + return SettingsCompanion(key: Value(key), value: Value(value), updatedAt: Value(updatedAt)); 1542 1279 } 1543 1280 1544 - factory SettingsEntry.fromJson( 1545 - Map<String, dynamic> json, { 1546 - ValueSerializer? serializer, 1547 - }) { 1281 + factory SettingsEntry.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 1548 1282 serializer ??= driftRuntimeOptions.defaultSerializer; 1549 1283 return SettingsEntry( 1550 1284 key: serializer.fromJson<String>(json['key']), ··· 1563 1297 } 1564 1298 1565 1299 SettingsEntry copyWith({String? key, String? value, DateTime? updatedAt}) => 1566 - SettingsEntry( 1567 - key: key ?? this.key, 1568 - value: value ?? this.value, 1569 - updatedAt: updatedAt ?? this.updatedAt, 1570 - ); 1300 + SettingsEntry(key: key ?? this.key, value: value ?? this.value, updatedAt: updatedAt ?? this.updatedAt); 1571 1301 SettingsEntry copyWithCompanion(SettingsCompanion data) { 1572 1302 return SettingsEntry( 1573 1303 key: data.key.present ? data.key.value : this.key, ··· 1673 1403 } 1674 1404 } 1675 1405 1676 - class $SavedFeedsTable extends SavedFeeds 1677 - with TableInfo<$SavedFeedsTable, SavedFeedEntry> { 1406 + class $SavedFeedsTable extends SavedFeeds with TableInfo<$SavedFeedsTable, SavedFeedEntry> { 1678 1407 @override 1679 1408 final GeneratedDatabase attachedDatabase; 1680 1409 final String? _alias; ··· 1688 1417 type: DriftSqlType.string, 1689 1418 requiredDuringInsert: true, 1690 1419 ); 1691 - static const VerificationMeta _accountDidMeta = const VerificationMeta( 1692 - 'accountDid', 1693 - ); 1420 + static const VerificationMeta _accountDidMeta = const VerificationMeta('accountDid'); 1694 1421 @override 1695 1422 late final GeneratedColumn<String> accountDid = GeneratedColumn<String>( 1696 1423 'account_did', ··· 1725 1452 false, 1726 1453 type: DriftSqlType.bool, 1727 1454 requiredDuringInsert: false, 1728 - defaultConstraints: GeneratedColumn.constraintIsAlways( 1729 - 'CHECK ("pinned" IN (0, 1))', 1730 - ), 1455 + defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'), 1731 1456 defaultValue: const Constant(false), 1732 1457 ); 1733 - static const VerificationMeta _sortOrderMeta = const VerificationMeta( 1734 - 'sortOrder', 1735 - ); 1458 + static const VerificationMeta _sortOrderMeta = const VerificationMeta('sortOrder'); 1736 1459 @override 1737 1460 late final GeneratedColumn<int> sortOrder = GeneratedColumn<int>( 1738 1461 'sort_order', ··· 1742 1465 requiredDuringInsert: false, 1743 1466 defaultValue: const Constant(0), 1744 1467 ); 1745 - static const VerificationMeta _updatedAtMeta = const VerificationMeta( 1746 - 'updatedAt', 1747 - ); 1468 + static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); 1748 1469 @override 1749 1470 late final GeneratedColumn<DateTime> updatedAt = GeneratedColumn<DateTime>( 1750 1471 'updated_at', ··· 1755 1476 defaultValue: currentDateAndTime, 1756 1477 ); 1757 1478 @override 1758 - List<GeneratedColumn> get $columns => [ 1759 - id, 1760 - accountDid, 1761 - type, 1762 - value, 1763 - pinned, 1764 - sortOrder, 1765 - updatedAt, 1766 - ]; 1479 + List<GeneratedColumn> get $columns => [id, accountDid, type, value, pinned, sortOrder, updatedAt]; 1767 1480 @override 1768 1481 String get aliasedName => _alias ?? actualTableName; 1769 1482 @override 1770 1483 String get actualTableName => $name; 1771 1484 static const String $name = 'saved_feeds'; 1772 1485 @override 1773 - VerificationContext validateIntegrity( 1774 - Insertable<SavedFeedEntry> instance, { 1775 - bool isInserting = false, 1776 - }) { 1486 + VerificationContext validateIntegrity(Insertable<SavedFeedEntry> instance, {bool isInserting = false}) { 1777 1487 final context = VerificationContext(); 1778 1488 final data = instance.toColumns(true); 1779 1489 if (data.containsKey('id')) { ··· 1782 1492 context.missing(_idMeta); 1783 1493 } 1784 1494 if (data.containsKey('account_did')) { 1785 - context.handle( 1786 - _accountDidMeta, 1787 - accountDid.isAcceptableOrUnknown(data['account_did']!, _accountDidMeta), 1788 - ); 1495 + context.handle(_accountDidMeta, accountDid.isAcceptableOrUnknown(data['account_did']!, _accountDidMeta)); 1789 1496 } else if (isInserting) { 1790 1497 context.missing(_accountDidMeta); 1791 1498 } 1792 1499 if (data.containsKey('type')) { 1793 - context.handle( 1794 - _typeMeta, 1795 - type.isAcceptableOrUnknown(data['type']!, _typeMeta), 1796 - ); 1500 + context.handle(_typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); 1797 1501 } else if (isInserting) { 1798 1502 context.missing(_typeMeta); 1799 1503 } 1800 1504 if (data.containsKey('value')) { 1801 - context.handle( 1802 - _valueMeta, 1803 - value.isAcceptableOrUnknown(data['value']!, _valueMeta), 1804 - ); 1505 + context.handle(_valueMeta, value.isAcceptableOrUnknown(data['value']!, _valueMeta)); 1805 1506 } else if (isInserting) { 1806 1507 context.missing(_valueMeta); 1807 1508 } 1808 1509 if (data.containsKey('pinned')) { 1809 - context.handle( 1810 - _pinnedMeta, 1811 - pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta), 1812 - ); 1510 + context.handle(_pinnedMeta, pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta)); 1813 1511 } 1814 1512 if (data.containsKey('sort_order')) { 1815 - context.handle( 1816 - _sortOrderMeta, 1817 - sortOrder.isAcceptableOrUnknown(data['sort_order']!, _sortOrderMeta), 1818 - ); 1513 + context.handle(_sortOrderMeta, sortOrder.isAcceptableOrUnknown(data['sort_order']!, _sortOrderMeta)); 1819 1514 } 1820 1515 if (data.containsKey('updated_at')) { 1821 - context.handle( 1822 - _updatedAtMeta, 1823 - updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), 1824 - ); 1516 + context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); 1825 1517 } 1826 1518 return context; 1827 1519 } ··· 1832 1524 SavedFeedEntry map(Map<String, dynamic> data, {String? tablePrefix}) { 1833 1525 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 1834 1526 return SavedFeedEntry( 1835 - id: attachedDatabase.typeMapping.read( 1836 - DriftSqlType.string, 1837 - data['${effectivePrefix}id'], 1838 - )!, 1839 - accountDid: attachedDatabase.typeMapping.read( 1840 - DriftSqlType.string, 1841 - data['${effectivePrefix}account_did'], 1842 - )!, 1843 - type: attachedDatabase.typeMapping.read( 1844 - DriftSqlType.string, 1845 - data['${effectivePrefix}type'], 1846 - )!, 1847 - value: attachedDatabase.typeMapping.read( 1848 - DriftSqlType.string, 1849 - data['${effectivePrefix}value'], 1850 - )!, 1851 - pinned: attachedDatabase.typeMapping.read( 1852 - DriftSqlType.bool, 1853 - data['${effectivePrefix}pinned'], 1854 - )!, 1855 - sortOrder: attachedDatabase.typeMapping.read( 1856 - DriftSqlType.int, 1857 - data['${effectivePrefix}sort_order'], 1858 - )!, 1859 - updatedAt: attachedDatabase.typeMapping.read( 1860 - DriftSqlType.dateTime, 1861 - data['${effectivePrefix}updated_at'], 1862 - )!, 1527 + id: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}id'])!, 1528 + accountDid: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}account_did'])!, 1529 + type: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}type'])!, 1530 + value: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}value'])!, 1531 + pinned: attachedDatabase.typeMapping.read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!, 1532 + sortOrder: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}sort_order'])!, 1533 + updatedAt: attachedDatabase.typeMapping.read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, 1863 1534 ); 1864 1535 } 1865 1536 ··· 1911 1582 ); 1912 1583 } 1913 1584 1914 - factory SavedFeedEntry.fromJson( 1915 - Map<String, dynamic> json, { 1916 - ValueSerializer? serializer, 1917 - }) { 1585 + factory SavedFeedEntry.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 1918 1586 serializer ??= driftRuntimeOptions.defaultSerializer; 1919 1587 return SavedFeedEntry( 1920 1588 id: serializer.fromJson<String>(json['id']), ··· 1960 1628 SavedFeedEntry copyWithCompanion(SavedFeedsCompanion data) { 1961 1629 return SavedFeedEntry( 1962 1630 id: data.id.present ? data.id.value : this.id, 1963 - accountDid: data.accountDid.present 1964 - ? data.accountDid.value 1965 - : this.accountDid, 1631 + accountDid: data.accountDid.present ? data.accountDid.value : this.accountDid, 1966 1632 type: data.type.present ? data.type.value : this.type, 1967 1633 value: data.value.present ? data.value.value : this.value, 1968 1634 pinned: data.pinned.present ? data.pinned.value : this.pinned, ··· 1986 1652 } 1987 1653 1988 1654 @override 1989 - int get hashCode => 1990 - Object.hash(id, accountDid, type, value, pinned, sortOrder, updatedAt); 1655 + int get hashCode => Object.hash(id, accountDid, type, value, pinned, sortOrder, updatedAt); 1991 1656 @override 1992 1657 bool operator ==(Object other) => 1993 1658 identical(this, other) || ··· 2132 1797 late final $SettingsTable settings = $SettingsTable(this); 2133 1798 late final $SavedFeedsTable savedFeeds = $SavedFeedsTable(this); 2134 1799 @override 2135 - Iterable<TableInfo<Table, Object?>> get allTables => 2136 - allSchemaEntities.whereType<TableInfo<Table, Object?>>(); 1800 + Iterable<TableInfo<Table, Object?>> get allTables => allSchemaEntities.whereType<TableInfo<Table, Object?>>(); 2137 1801 @override 2138 - List<DatabaseSchemaEntity> get allSchemaEntities => [ 2139 - accounts, 2140 - cachedProfiles, 2141 - cachedPosts, 2142 - settings, 2143 - savedFeeds, 2144 - ]; 1802 + List<DatabaseSchemaEntity> get allSchemaEntities => [accounts, cachedProfiles, cachedPosts, settings, savedFeeds]; 2145 1803 } 2146 1804 2147 1805 typedef $$AccountsTableCreateCompanionBuilder = ··· 2177 1835 Value<int> rowid, 2178 1836 }); 2179 1837 2180 - class $$AccountsTableFilterComposer 2181 - extends Composer<_$AppDatabase, $AccountsTable> { 1838 + class $$AccountsTableFilterComposer extends Composer<_$AppDatabase, $AccountsTable> { 2182 1839 $$AccountsTableFilterComposer({ 2183 1840 required super.$db, 2184 1841 required super.$table, ··· 2186 1843 super.$addJoinBuilderToRootComposer, 2187 1844 super.$removeJoinBuilderFromRootComposer, 2188 1845 }); 2189 - ColumnFilters<String> get did => $composableBuilder( 2190 - column: $table.did, 2191 - builder: (column) => ColumnFilters(column), 2192 - ); 1846 + ColumnFilters<String> get did => $composableBuilder(column: $table.did, builder: (column) => ColumnFilters(column)); 2193 1847 2194 - ColumnFilters<String> get handle => $composableBuilder( 2195 - column: $table.handle, 2196 - builder: (column) => ColumnFilters(column), 2197 - ); 1848 + ColumnFilters<String> get handle => 1849 + $composableBuilder(column: $table.handle, builder: (column) => ColumnFilters(column)); 2198 1850 2199 - ColumnFilters<String> get displayName => $composableBuilder( 2200 - column: $table.displayName, 2201 - builder: (column) => ColumnFilters(column), 2202 - ); 1851 + ColumnFilters<String> get displayName => 1852 + $composableBuilder(column: $table.displayName, builder: (column) => ColumnFilters(column)); 2203 1853 2204 - ColumnFilters<String> get service => $composableBuilder( 2205 - column: $table.service, 2206 - builder: (column) => ColumnFilters(column), 2207 - ); 1854 + ColumnFilters<String> get service => 1855 + $composableBuilder(column: $table.service, builder: (column) => ColumnFilters(column)); 2208 1856 2209 - ColumnFilters<String> get accessToken => $composableBuilder( 2210 - column: $table.accessToken, 2211 - builder: (column) => ColumnFilters(column), 2212 - ); 1857 + ColumnFilters<String> get accessToken => 1858 + $composableBuilder(column: $table.accessToken, builder: (column) => ColumnFilters(column)); 2213 1859 2214 - ColumnFilters<String> get refreshToken => $composableBuilder( 2215 - column: $table.refreshToken, 2216 - builder: (column) => ColumnFilters(column), 2217 - ); 1860 + ColumnFilters<String> get refreshToken => 1861 + $composableBuilder(column: $table.refreshToken, builder: (column) => ColumnFilters(column)); 2218 1862 2219 - ColumnFilters<String> get dpopPublicKey => $composableBuilder( 2220 - column: $table.dpopPublicKey, 2221 - builder: (column) => ColumnFilters(column), 2222 - ); 1863 + ColumnFilters<String> get dpopPublicKey => 1864 + $composableBuilder(column: $table.dpopPublicKey, builder: (column) => ColumnFilters(column)); 2223 1865 2224 - ColumnFilters<String> get dpopPrivateKey => $composableBuilder( 2225 - column: $table.dpopPrivateKey, 2226 - builder: (column) => ColumnFilters(column), 2227 - ); 1866 + ColumnFilters<String> get dpopPrivateKey => 1867 + $composableBuilder(column: $table.dpopPrivateKey, builder: (column) => ColumnFilters(column)); 2228 1868 2229 - ColumnFilters<String> get dpopNonce => $composableBuilder( 2230 - column: $table.dpopNonce, 2231 - builder: (column) => ColumnFilters(column), 2232 - ); 1869 + ColumnFilters<String> get dpopNonce => 1870 + $composableBuilder(column: $table.dpopNonce, builder: (column) => ColumnFilters(column)); 2233 1871 2234 - ColumnFilters<DateTime> get expiresAt => $composableBuilder( 2235 - column: $table.expiresAt, 2236 - builder: (column) => ColumnFilters(column), 2237 - ); 1872 + ColumnFilters<DateTime> get expiresAt => 1873 + $composableBuilder(column: $table.expiresAt, builder: (column) => ColumnFilters(column)); 2238 1874 2239 - ColumnFilters<DateTime> get createdAt => $composableBuilder( 2240 - column: $table.createdAt, 2241 - builder: (column) => ColumnFilters(column), 2242 - ); 1875 + ColumnFilters<DateTime> get createdAt => 1876 + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); 2243 1877 2244 - ColumnFilters<DateTime> get updatedAt => $composableBuilder( 2245 - column: $table.updatedAt, 2246 - builder: (column) => ColumnFilters(column), 2247 - ); 1878 + ColumnFilters<DateTime> get updatedAt => 1879 + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); 2248 1880 } 2249 1881 2250 - class $$AccountsTableOrderingComposer 2251 - extends Composer<_$AppDatabase, $AccountsTable> { 1882 + class $$AccountsTableOrderingComposer extends Composer<_$AppDatabase, $AccountsTable> { 2252 1883 $$AccountsTableOrderingComposer({ 2253 1884 required super.$db, 2254 1885 required super.$table, ··· 2256 1887 super.$addJoinBuilderToRootComposer, 2257 1888 super.$removeJoinBuilderFromRootComposer, 2258 1889 }); 2259 - ColumnOrderings<String> get did => $composableBuilder( 2260 - column: $table.did, 2261 - builder: (column) => ColumnOrderings(column), 2262 - ); 1890 + ColumnOrderings<String> get did => 1891 + $composableBuilder(column: $table.did, builder: (column) => ColumnOrderings(column)); 2263 1892 2264 - ColumnOrderings<String> get handle => $composableBuilder( 2265 - column: $table.handle, 2266 - builder: (column) => ColumnOrderings(column), 2267 - ); 1893 + ColumnOrderings<String> get handle => 1894 + $composableBuilder(column: $table.handle, builder: (column) => ColumnOrderings(column)); 2268 1895 2269 - ColumnOrderings<String> get displayName => $composableBuilder( 2270 - column: $table.displayName, 2271 - builder: (column) => ColumnOrderings(column), 2272 - ); 1896 + ColumnOrderings<String> get displayName => 1897 + $composableBuilder(column: $table.displayName, builder: (column) => ColumnOrderings(column)); 2273 1898 2274 - ColumnOrderings<String> get service => $composableBuilder( 2275 - column: $table.service, 2276 - builder: (column) => ColumnOrderings(column), 2277 - ); 1899 + ColumnOrderings<String> get service => 1900 + $composableBuilder(column: $table.service, builder: (column) => ColumnOrderings(column)); 2278 1901 2279 - ColumnOrderings<String> get accessToken => $composableBuilder( 2280 - column: $table.accessToken, 2281 - builder: (column) => ColumnOrderings(column), 2282 - ); 1902 + ColumnOrderings<String> get accessToken => 1903 + $composableBuilder(column: $table.accessToken, builder: (column) => ColumnOrderings(column)); 2283 1904 2284 - ColumnOrderings<String> get refreshToken => $composableBuilder( 2285 - column: $table.refreshToken, 2286 - builder: (column) => ColumnOrderings(column), 2287 - ); 1905 + ColumnOrderings<String> get refreshToken => 1906 + $composableBuilder(column: $table.refreshToken, builder: (column) => ColumnOrderings(column)); 2288 1907 2289 - ColumnOrderings<String> get dpopPublicKey => $composableBuilder( 2290 - column: $table.dpopPublicKey, 2291 - builder: (column) => ColumnOrderings(column), 2292 - ); 1908 + ColumnOrderings<String> get dpopPublicKey => 1909 + $composableBuilder(column: $table.dpopPublicKey, builder: (column) => ColumnOrderings(column)); 2293 1910 2294 - ColumnOrderings<String> get dpopPrivateKey => $composableBuilder( 2295 - column: $table.dpopPrivateKey, 2296 - builder: (column) => ColumnOrderings(column), 2297 - ); 1911 + ColumnOrderings<String> get dpopPrivateKey => 1912 + $composableBuilder(column: $table.dpopPrivateKey, builder: (column) => ColumnOrderings(column)); 2298 1913 2299 - ColumnOrderings<String> get dpopNonce => $composableBuilder( 2300 - column: $table.dpopNonce, 2301 - builder: (column) => ColumnOrderings(column), 2302 - ); 1914 + ColumnOrderings<String> get dpopNonce => 1915 + $composableBuilder(column: $table.dpopNonce, builder: (column) => ColumnOrderings(column)); 2303 1916 2304 - ColumnOrderings<DateTime> get expiresAt => $composableBuilder( 2305 - column: $table.expiresAt, 2306 - builder: (column) => ColumnOrderings(column), 2307 - ); 1917 + ColumnOrderings<DateTime> get expiresAt => 1918 + $composableBuilder(column: $table.expiresAt, builder: (column) => ColumnOrderings(column)); 2308 1919 2309 - ColumnOrderings<DateTime> get createdAt => $composableBuilder( 2310 - column: $table.createdAt, 2311 - builder: (column) => ColumnOrderings(column), 2312 - ); 1920 + ColumnOrderings<DateTime> get createdAt => 1921 + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); 2313 1922 2314 - ColumnOrderings<DateTime> get updatedAt => $composableBuilder( 2315 - column: $table.updatedAt, 2316 - builder: (column) => ColumnOrderings(column), 2317 - ); 1923 + ColumnOrderings<DateTime> get updatedAt => 1924 + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); 2318 1925 } 2319 1926 2320 - class $$AccountsTableAnnotationComposer 2321 - extends Composer<_$AppDatabase, $AccountsTable> { 1927 + class $$AccountsTableAnnotationComposer extends Composer<_$AppDatabase, $AccountsTable> { 2322 1928 $$AccountsTableAnnotationComposer({ 2323 1929 required super.$db, 2324 1930 required super.$table, ··· 2326 1932 super.$addJoinBuilderToRootComposer, 2327 1933 super.$removeJoinBuilderFromRootComposer, 2328 1934 }); 2329 - GeneratedColumn<String> get did => 2330 - $composableBuilder(column: $table.did, builder: (column) => column); 1935 + GeneratedColumn<String> get did => $composableBuilder(column: $table.did, builder: (column) => column); 2331 1936 2332 - GeneratedColumn<String> get handle => 2333 - $composableBuilder(column: $table.handle, builder: (column) => column); 1937 + GeneratedColumn<String> get handle => $composableBuilder(column: $table.handle, builder: (column) => column); 2334 1938 2335 - GeneratedColumn<String> get displayName => $composableBuilder( 2336 - column: $table.displayName, 2337 - builder: (column) => column, 2338 - ); 1939 + GeneratedColumn<String> get displayName => 1940 + $composableBuilder(column: $table.displayName, builder: (column) => column); 2339 1941 2340 - GeneratedColumn<String> get service => 2341 - $composableBuilder(column: $table.service, builder: (column) => column); 1942 + GeneratedColumn<String> get service => $composableBuilder(column: $table.service, builder: (column) => column); 2342 1943 2343 - GeneratedColumn<String> get accessToken => $composableBuilder( 2344 - column: $table.accessToken, 2345 - builder: (column) => column, 2346 - ); 1944 + GeneratedColumn<String> get accessToken => 1945 + $composableBuilder(column: $table.accessToken, builder: (column) => column); 2347 1946 2348 - GeneratedColumn<String> get refreshToken => $composableBuilder( 2349 - column: $table.refreshToken, 2350 - builder: (column) => column, 2351 - ); 1947 + GeneratedColumn<String> get refreshToken => 1948 + $composableBuilder(column: $table.refreshToken, builder: (column) => column); 2352 1949 2353 - GeneratedColumn<String> get dpopPublicKey => $composableBuilder( 2354 - column: $table.dpopPublicKey, 2355 - builder: (column) => column, 2356 - ); 1950 + GeneratedColumn<String> get dpopPublicKey => 1951 + $composableBuilder(column: $table.dpopPublicKey, builder: (column) => column); 2357 1952 2358 - GeneratedColumn<String> get dpopPrivateKey => $composableBuilder( 2359 - column: $table.dpopPrivateKey, 2360 - builder: (column) => column, 2361 - ); 1953 + GeneratedColumn<String> get dpopPrivateKey => 1954 + $composableBuilder(column: $table.dpopPrivateKey, builder: (column) => column); 2362 1955 2363 - GeneratedColumn<String> get dpopNonce => 2364 - $composableBuilder(column: $table.dpopNonce, builder: (column) => column); 1956 + GeneratedColumn<String> get dpopNonce => $composableBuilder(column: $table.dpopNonce, builder: (column) => column); 2365 1957 2366 - GeneratedColumn<DateTime> get expiresAt => 2367 - $composableBuilder(column: $table.expiresAt, builder: (column) => column); 1958 + GeneratedColumn<DateTime> get expiresAt => $composableBuilder(column: $table.expiresAt, builder: (column) => column); 2368 1959 2369 - GeneratedColumn<DateTime> get createdAt => 2370 - $composableBuilder(column: $table.createdAt, builder: (column) => column); 1960 + GeneratedColumn<DateTime> get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); 2371 1961 2372 - GeneratedColumn<DateTime> get updatedAt => 2373 - $composableBuilder(column: $table.updatedAt, builder: (column) => column); 1962 + GeneratedColumn<DateTime> get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); 2374 1963 } 2375 1964 2376 1965 class $$AccountsTableTableManager ··· 2393 1982 TableManagerState( 2394 1983 db: db, 2395 1984 table: table, 2396 - createFilteringComposer: () => 2397 - $$AccountsTableFilterComposer($db: db, $table: table), 2398 - createOrderingComposer: () => 2399 - $$AccountsTableOrderingComposer($db: db, $table: table), 2400 - createComputedFieldComposer: () => 2401 - $$AccountsTableAnnotationComposer($db: db, $table: table), 1985 + createFilteringComposer: () => $$AccountsTableFilterComposer($db: db, $table: table), 1986 + createOrderingComposer: () => $$AccountsTableOrderingComposer($db: db, $table: table), 1987 + createComputedFieldComposer: () => $$AccountsTableAnnotationComposer($db: db, $table: table), 2402 1988 updateCompanionCallback: 2403 1989 ({ 2404 1990 Value<String> did = const Value.absent(), ··· 2459 2045 updatedAt: updatedAt, 2460 2046 rowid: rowid, 2461 2047 ), 2462 - withReferenceMapper: (p0) => p0 2463 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 2464 - .toList(), 2048 + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 2465 2049 prefetchHooksCallback: null, 2466 2050 ), 2467 2051 ); ··· 2498 2082 Value<int> rowid, 2499 2083 }); 2500 2084 2501 - class $$CachedProfilesTableFilterComposer 2502 - extends Composer<_$AppDatabase, $CachedProfilesTable> { 2085 + class $$CachedProfilesTableFilterComposer extends Composer<_$AppDatabase, $CachedProfilesTable> { 2503 2086 $$CachedProfilesTableFilterComposer({ 2504 2087 required super.$db, 2505 2088 required super.$table, ··· 2507 2090 super.$addJoinBuilderToRootComposer, 2508 2091 super.$removeJoinBuilderFromRootComposer, 2509 2092 }); 2510 - ColumnFilters<String> get did => $composableBuilder( 2511 - column: $table.did, 2512 - builder: (column) => ColumnFilters(column), 2513 - ); 2093 + ColumnFilters<String> get did => $composableBuilder(column: $table.did, builder: (column) => ColumnFilters(column)); 2514 2094 2515 - ColumnFilters<String> get handle => $composableBuilder( 2516 - column: $table.handle, 2517 - builder: (column) => ColumnFilters(column), 2518 - ); 2095 + ColumnFilters<String> get handle => 2096 + $composableBuilder(column: $table.handle, builder: (column) => ColumnFilters(column)); 2519 2097 2520 - ColumnFilters<String> get payload => $composableBuilder( 2521 - column: $table.payload, 2522 - builder: (column) => ColumnFilters(column), 2523 - ); 2098 + ColumnFilters<String> get payload => 2099 + $composableBuilder(column: $table.payload, builder: (column) => ColumnFilters(column)); 2524 2100 2525 - ColumnFilters<DateTime> get fetchedAt => $composableBuilder( 2526 - column: $table.fetchedAt, 2527 - builder: (column) => ColumnFilters(column), 2528 - ); 2101 + ColumnFilters<DateTime> get fetchedAt => 2102 + $composableBuilder(column: $table.fetchedAt, builder: (column) => ColumnFilters(column)); 2529 2103 } 2530 2104 2531 - class $$CachedProfilesTableOrderingComposer 2532 - extends Composer<_$AppDatabase, $CachedProfilesTable> { 2105 + class $$CachedProfilesTableOrderingComposer extends Composer<_$AppDatabase, $CachedProfilesTable> { 2533 2106 $$CachedProfilesTableOrderingComposer({ 2534 2107 required super.$db, 2535 2108 required super.$table, ··· 2537 2110 super.$addJoinBuilderToRootComposer, 2538 2111 super.$removeJoinBuilderFromRootComposer, 2539 2112 }); 2540 - ColumnOrderings<String> get did => $composableBuilder( 2541 - column: $table.did, 2542 - builder: (column) => ColumnOrderings(column), 2543 - ); 2113 + ColumnOrderings<String> get did => 2114 + $composableBuilder(column: $table.did, builder: (column) => ColumnOrderings(column)); 2544 2115 2545 - ColumnOrderings<String> get handle => $composableBuilder( 2546 - column: $table.handle, 2547 - builder: (column) => ColumnOrderings(column), 2548 - ); 2116 + ColumnOrderings<String> get handle => 2117 + $composableBuilder(column: $table.handle, builder: (column) => ColumnOrderings(column)); 2549 2118 2550 - ColumnOrderings<String> get payload => $composableBuilder( 2551 - column: $table.payload, 2552 - builder: (column) => ColumnOrderings(column), 2553 - ); 2119 + ColumnOrderings<String> get payload => 2120 + $composableBuilder(column: $table.payload, builder: (column) => ColumnOrderings(column)); 2554 2121 2555 - ColumnOrderings<DateTime> get fetchedAt => $composableBuilder( 2556 - column: $table.fetchedAt, 2557 - builder: (column) => ColumnOrderings(column), 2558 - ); 2122 + ColumnOrderings<DateTime> get fetchedAt => 2123 + $composableBuilder(column: $table.fetchedAt, builder: (column) => ColumnOrderings(column)); 2559 2124 } 2560 2125 2561 - class $$CachedProfilesTableAnnotationComposer 2562 - extends Composer<_$AppDatabase, $CachedProfilesTable> { 2126 + class $$CachedProfilesTableAnnotationComposer extends Composer<_$AppDatabase, $CachedProfilesTable> { 2563 2127 $$CachedProfilesTableAnnotationComposer({ 2564 2128 required super.$db, 2565 2129 required super.$table, ··· 2567 2131 super.$addJoinBuilderToRootComposer, 2568 2132 super.$removeJoinBuilderFromRootComposer, 2569 2133 }); 2570 - GeneratedColumn<String> get did => 2571 - $composableBuilder(column: $table.did, builder: (column) => column); 2134 + GeneratedColumn<String> get did => $composableBuilder(column: $table.did, builder: (column) => column); 2572 2135 2573 - GeneratedColumn<String> get handle => 2574 - $composableBuilder(column: $table.handle, builder: (column) => column); 2136 + GeneratedColumn<String> get handle => $composableBuilder(column: $table.handle, builder: (column) => column); 2575 2137 2576 - GeneratedColumn<String> get payload => 2577 - $composableBuilder(column: $table.payload, builder: (column) => column); 2138 + GeneratedColumn<String> get payload => $composableBuilder(column: $table.payload, builder: (column) => column); 2578 2139 2579 - GeneratedColumn<DateTime> get fetchedAt => 2580 - $composableBuilder(column: $table.fetchedAt, builder: (column) => column); 2140 + GeneratedColumn<DateTime> get fetchedAt => $composableBuilder(column: $table.fetchedAt, builder: (column) => column); 2581 2141 } 2582 2142 2583 2143 class $$CachedProfilesTableTableManager ··· 2591 2151 $$CachedProfilesTableAnnotationComposer, 2592 2152 $$CachedProfilesTableCreateCompanionBuilder, 2593 2153 $$CachedProfilesTableUpdateCompanionBuilder, 2594 - ( 2595 - CachedProfile, 2596 - BaseReferences<_$AppDatabase, $CachedProfilesTable, CachedProfile>, 2597 - ), 2154 + (CachedProfile, BaseReferences<_$AppDatabase, $CachedProfilesTable, CachedProfile>), 2598 2155 CachedProfile, 2599 2156 PrefetchHooks Function() 2600 2157 > { 2601 - $$CachedProfilesTableTableManager( 2602 - _$AppDatabase db, 2603 - $CachedProfilesTable table, 2604 - ) : super( 2158 + $$CachedProfilesTableTableManager(_$AppDatabase db, $CachedProfilesTable table) 2159 + : super( 2605 2160 TableManagerState( 2606 2161 db: db, 2607 2162 table: table, 2608 - createFilteringComposer: () => 2609 - $$CachedProfilesTableFilterComposer($db: db, $table: table), 2610 - createOrderingComposer: () => 2611 - $$CachedProfilesTableOrderingComposer($db: db, $table: table), 2612 - createComputedFieldComposer: () => 2613 - $$CachedProfilesTableAnnotationComposer($db: db, $table: table), 2163 + createFilteringComposer: () => $$CachedProfilesTableFilterComposer($db: db, $table: table), 2164 + createOrderingComposer: () => $$CachedProfilesTableOrderingComposer($db: db, $table: table), 2165 + createComputedFieldComposer: () => $$CachedProfilesTableAnnotationComposer($db: db, $table: table), 2614 2166 updateCompanionCallback: 2615 2167 ({ 2616 2168 Value<String> did = const Value.absent(), ··· 2639 2191 fetchedAt: fetchedAt, 2640 2192 rowid: rowid, 2641 2193 ), 2642 - withReferenceMapper: (p0) => p0 2643 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 2644 - .toList(), 2194 + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 2645 2195 prefetchHooksCallback: null, 2646 2196 ), 2647 2197 ); ··· 2657 2207 $$CachedProfilesTableAnnotationComposer, 2658 2208 $$CachedProfilesTableCreateCompanionBuilder, 2659 2209 $$CachedProfilesTableUpdateCompanionBuilder, 2660 - ( 2661 - CachedProfile, 2662 - BaseReferences<_$AppDatabase, $CachedProfilesTable, CachedProfile>, 2663 - ), 2210 + (CachedProfile, BaseReferences<_$AppDatabase, $CachedProfilesTable, CachedProfile>), 2664 2211 CachedProfile, 2665 2212 PrefetchHooks Function() 2666 2213 >; ··· 2683 2230 Value<int> rowid, 2684 2231 }); 2685 2232 2686 - class $$CachedPostsTableFilterComposer 2687 - extends Composer<_$AppDatabase, $CachedPostsTable> { 2233 + class $$CachedPostsTableFilterComposer extends Composer<_$AppDatabase, $CachedPostsTable> { 2688 2234 $$CachedPostsTableFilterComposer({ 2689 2235 required super.$db, 2690 2236 required super.$table, ··· 2692 2238 super.$addJoinBuilderToRootComposer, 2693 2239 super.$removeJoinBuilderFromRootComposer, 2694 2240 }); 2695 - ColumnFilters<String> get uri => $composableBuilder( 2696 - column: $table.uri, 2697 - builder: (column) => ColumnFilters(column), 2698 - ); 2241 + ColumnFilters<String> get uri => $composableBuilder(column: $table.uri, builder: (column) => ColumnFilters(column)); 2699 2242 2700 - ColumnFilters<String> get authorDid => $composableBuilder( 2701 - column: $table.authorDid, 2702 - builder: (column) => ColumnFilters(column), 2703 - ); 2243 + ColumnFilters<String> get authorDid => 2244 + $composableBuilder(column: $table.authorDid, builder: (column) => ColumnFilters(column)); 2704 2245 2705 - ColumnFilters<String> get payload => $composableBuilder( 2706 - column: $table.payload, 2707 - builder: (column) => ColumnFilters(column), 2708 - ); 2246 + ColumnFilters<String> get payload => 2247 + $composableBuilder(column: $table.payload, builder: (column) => ColumnFilters(column)); 2709 2248 2710 - ColumnFilters<DateTime> get createdAt => $composableBuilder( 2711 - column: $table.createdAt, 2712 - builder: (column) => ColumnFilters(column), 2713 - ); 2249 + ColumnFilters<DateTime> get createdAt => 2250 + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); 2714 2251 2715 - ColumnFilters<DateTime> get fetchedAt => $composableBuilder( 2716 - column: $table.fetchedAt, 2717 - builder: (column) => ColumnFilters(column), 2718 - ); 2252 + ColumnFilters<DateTime> get fetchedAt => 2253 + $composableBuilder(column: $table.fetchedAt, builder: (column) => ColumnFilters(column)); 2719 2254 } 2720 2255 2721 - class $$CachedPostsTableOrderingComposer 2722 - extends Composer<_$AppDatabase, $CachedPostsTable> { 2256 + class $$CachedPostsTableOrderingComposer extends Composer<_$AppDatabase, $CachedPostsTable> { 2723 2257 $$CachedPostsTableOrderingComposer({ 2724 2258 required super.$db, 2725 2259 required super.$table, ··· 2727 2261 super.$addJoinBuilderToRootComposer, 2728 2262 super.$removeJoinBuilderFromRootComposer, 2729 2263 }); 2730 - ColumnOrderings<String> get uri => $composableBuilder( 2731 - column: $table.uri, 2732 - builder: (column) => ColumnOrderings(column), 2733 - ); 2264 + ColumnOrderings<String> get uri => 2265 + $composableBuilder(column: $table.uri, builder: (column) => ColumnOrderings(column)); 2734 2266 2735 - ColumnOrderings<String> get authorDid => $composableBuilder( 2736 - column: $table.authorDid, 2737 - builder: (column) => ColumnOrderings(column), 2738 - ); 2267 + ColumnOrderings<String> get authorDid => 2268 + $composableBuilder(column: $table.authorDid, builder: (column) => ColumnOrderings(column)); 2739 2269 2740 - ColumnOrderings<String> get payload => $composableBuilder( 2741 - column: $table.payload, 2742 - builder: (column) => ColumnOrderings(column), 2743 - ); 2270 + ColumnOrderings<String> get payload => 2271 + $composableBuilder(column: $table.payload, builder: (column) => ColumnOrderings(column)); 2744 2272 2745 - ColumnOrderings<DateTime> get createdAt => $composableBuilder( 2746 - column: $table.createdAt, 2747 - builder: (column) => ColumnOrderings(column), 2748 - ); 2273 + ColumnOrderings<DateTime> get createdAt => 2274 + $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); 2749 2275 2750 - ColumnOrderings<DateTime> get fetchedAt => $composableBuilder( 2751 - column: $table.fetchedAt, 2752 - builder: (column) => ColumnOrderings(column), 2753 - ); 2276 + ColumnOrderings<DateTime> get fetchedAt => 2277 + $composableBuilder(column: $table.fetchedAt, builder: (column) => ColumnOrderings(column)); 2754 2278 } 2755 2279 2756 - class $$CachedPostsTableAnnotationComposer 2757 - extends Composer<_$AppDatabase, $CachedPostsTable> { 2280 + class $$CachedPostsTableAnnotationComposer extends Composer<_$AppDatabase, $CachedPostsTable> { 2758 2281 $$CachedPostsTableAnnotationComposer({ 2759 2282 required super.$db, 2760 2283 required super.$table, ··· 2762 2285 super.$addJoinBuilderToRootComposer, 2763 2286 super.$removeJoinBuilderFromRootComposer, 2764 2287 }); 2765 - GeneratedColumn<String> get uri => 2766 - $composableBuilder(column: $table.uri, builder: (column) => column); 2288 + GeneratedColumn<String> get uri => $composableBuilder(column: $table.uri, builder: (column) => column); 2767 2289 2768 - GeneratedColumn<String> get authorDid => 2769 - $composableBuilder(column: $table.authorDid, builder: (column) => column); 2290 + GeneratedColumn<String> get authorDid => $composableBuilder(column: $table.authorDid, builder: (column) => column); 2770 2291 2771 - GeneratedColumn<String> get payload => 2772 - $composableBuilder(column: $table.payload, builder: (column) => column); 2292 + GeneratedColumn<String> get payload => $composableBuilder(column: $table.payload, builder: (column) => column); 2773 2293 2774 - GeneratedColumn<DateTime> get createdAt => 2775 - $composableBuilder(column: $table.createdAt, builder: (column) => column); 2294 + GeneratedColumn<DateTime> get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); 2776 2295 2777 - GeneratedColumn<DateTime> get fetchedAt => 2778 - $composableBuilder(column: $table.fetchedAt, builder: (column) => column); 2296 + GeneratedColumn<DateTime> get fetchedAt => $composableBuilder(column: $table.fetchedAt, builder: (column) => column); 2779 2297 } 2780 2298 2781 2299 class $$CachedPostsTableTableManager ··· 2789 2307 $$CachedPostsTableAnnotationComposer, 2790 2308 $$CachedPostsTableCreateCompanionBuilder, 2791 2309 $$CachedPostsTableUpdateCompanionBuilder, 2792 - ( 2793 - CachedPost, 2794 - BaseReferences<_$AppDatabase, $CachedPostsTable, CachedPost>, 2795 - ), 2310 + (CachedPost, BaseReferences<_$AppDatabase, $CachedPostsTable, CachedPost>), 2796 2311 CachedPost, 2797 2312 PrefetchHooks Function() 2798 2313 > { ··· 2801 2316 TableManagerState( 2802 2317 db: db, 2803 2318 table: table, 2804 - createFilteringComposer: () => 2805 - $$CachedPostsTableFilterComposer($db: db, $table: table), 2806 - createOrderingComposer: () => 2807 - $$CachedPostsTableOrderingComposer($db: db, $table: table), 2808 - createComputedFieldComposer: () => 2809 - $$CachedPostsTableAnnotationComposer($db: db, $table: table), 2319 + createFilteringComposer: () => $$CachedPostsTableFilterComposer($db: db, $table: table), 2320 + createOrderingComposer: () => $$CachedPostsTableOrderingComposer($db: db, $table: table), 2321 + createComputedFieldComposer: () => $$CachedPostsTableAnnotationComposer($db: db, $table: table), 2810 2322 updateCompanionCallback: 2811 2323 ({ 2812 2324 Value<String> uri = const Value.absent(), ··· 2839 2351 fetchedAt: fetchedAt, 2840 2352 rowid: rowid, 2841 2353 ), 2842 - withReferenceMapper: (p0) => p0 2843 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 2844 - .toList(), 2354 + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 2845 2355 prefetchHooksCallback: null, 2846 2356 ), 2847 2357 ); ··· 2857 2367 $$CachedPostsTableAnnotationComposer, 2858 2368 $$CachedPostsTableCreateCompanionBuilder, 2859 2369 $$CachedPostsTableUpdateCompanionBuilder, 2860 - ( 2861 - CachedPost, 2862 - BaseReferences<_$AppDatabase, $CachedPostsTable, CachedPost>, 2863 - ), 2370 + (CachedPost, BaseReferences<_$AppDatabase, $CachedPostsTable, CachedPost>), 2864 2371 CachedPost, 2865 2372 PrefetchHooks Function() 2866 2373 >; ··· 2872 2379 Value<int> rowid, 2873 2380 }); 2874 2381 typedef $$SettingsTableUpdateCompanionBuilder = 2875 - SettingsCompanion Function({ 2876 - Value<String> key, 2877 - Value<String> value, 2878 - Value<DateTime> updatedAt, 2879 - Value<int> rowid, 2880 - }); 2382 + SettingsCompanion Function({Value<String> key, Value<String> value, Value<DateTime> updatedAt, Value<int> rowid}); 2881 2383 2882 - class $$SettingsTableFilterComposer 2883 - extends Composer<_$AppDatabase, $SettingsTable> { 2384 + class $$SettingsTableFilterComposer extends Composer<_$AppDatabase, $SettingsTable> { 2884 2385 $$SettingsTableFilterComposer({ 2885 2386 required super.$db, 2886 2387 required super.$table, ··· 2888 2389 super.$addJoinBuilderToRootComposer, 2889 2390 super.$removeJoinBuilderFromRootComposer, 2890 2391 }); 2891 - ColumnFilters<String> get key => $composableBuilder( 2892 - column: $table.key, 2893 - builder: (column) => ColumnFilters(column), 2894 - ); 2392 + ColumnFilters<String> get key => $composableBuilder(column: $table.key, builder: (column) => ColumnFilters(column)); 2895 2393 2896 - ColumnFilters<String> get value => $composableBuilder( 2897 - column: $table.value, 2898 - builder: (column) => ColumnFilters(column), 2899 - ); 2394 + ColumnFilters<String> get value => 2395 + $composableBuilder(column: $table.value, builder: (column) => ColumnFilters(column)); 2900 2396 2901 - ColumnFilters<DateTime> get updatedAt => $composableBuilder( 2902 - column: $table.updatedAt, 2903 - builder: (column) => ColumnFilters(column), 2904 - ); 2397 + ColumnFilters<DateTime> get updatedAt => 2398 + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); 2905 2399 } 2906 2400 2907 - class $$SettingsTableOrderingComposer 2908 - extends Composer<_$AppDatabase, $SettingsTable> { 2401 + class $$SettingsTableOrderingComposer extends Composer<_$AppDatabase, $SettingsTable> { 2909 2402 $$SettingsTableOrderingComposer({ 2910 2403 required super.$db, 2911 2404 required super.$table, ··· 2913 2406 super.$addJoinBuilderToRootComposer, 2914 2407 super.$removeJoinBuilderFromRootComposer, 2915 2408 }); 2916 - ColumnOrderings<String> get key => $composableBuilder( 2917 - column: $table.key, 2918 - builder: (column) => ColumnOrderings(column), 2919 - ); 2409 + ColumnOrderings<String> get key => 2410 + $composableBuilder(column: $table.key, builder: (column) => ColumnOrderings(column)); 2920 2411 2921 - ColumnOrderings<String> get value => $composableBuilder( 2922 - column: $table.value, 2923 - builder: (column) => ColumnOrderings(column), 2924 - ); 2412 + ColumnOrderings<String> get value => 2413 + $composableBuilder(column: $table.value, builder: (column) => ColumnOrderings(column)); 2925 2414 2926 - ColumnOrderings<DateTime> get updatedAt => $composableBuilder( 2927 - column: $table.updatedAt, 2928 - builder: (column) => ColumnOrderings(column), 2929 - ); 2415 + ColumnOrderings<DateTime> get updatedAt => 2416 + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); 2930 2417 } 2931 2418 2932 - class $$SettingsTableAnnotationComposer 2933 - extends Composer<_$AppDatabase, $SettingsTable> { 2419 + class $$SettingsTableAnnotationComposer extends Composer<_$AppDatabase, $SettingsTable> { 2934 2420 $$SettingsTableAnnotationComposer({ 2935 2421 required super.$db, 2936 2422 required super.$table, ··· 2938 2424 super.$addJoinBuilderToRootComposer, 2939 2425 super.$removeJoinBuilderFromRootComposer, 2940 2426 }); 2941 - GeneratedColumn<String> get key => 2942 - $composableBuilder(column: $table.key, builder: (column) => column); 2427 + GeneratedColumn<String> get key => $composableBuilder(column: $table.key, builder: (column) => column); 2943 2428 2944 - GeneratedColumn<String> get value => 2945 - $composableBuilder(column: $table.value, builder: (column) => column); 2429 + GeneratedColumn<String> get value => $composableBuilder(column: $table.value, builder: (column) => column); 2946 2430 2947 - GeneratedColumn<DateTime> get updatedAt => 2948 - $composableBuilder(column: $table.updatedAt, builder: (column) => column); 2431 + GeneratedColumn<DateTime> get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); 2949 2432 } 2950 2433 2951 2434 class $$SettingsTableTableManager ··· 2959 2442 $$SettingsTableAnnotationComposer, 2960 2443 $$SettingsTableCreateCompanionBuilder, 2961 2444 $$SettingsTableUpdateCompanionBuilder, 2962 - ( 2963 - SettingsEntry, 2964 - BaseReferences<_$AppDatabase, $SettingsTable, SettingsEntry>, 2965 - ), 2445 + (SettingsEntry, BaseReferences<_$AppDatabase, $SettingsTable, SettingsEntry>), 2966 2446 SettingsEntry, 2967 2447 PrefetchHooks Function() 2968 2448 > { ··· 2971 2451 TableManagerState( 2972 2452 db: db, 2973 2453 table: table, 2974 - createFilteringComposer: () => 2975 - $$SettingsTableFilterComposer($db: db, $table: table), 2976 - createOrderingComposer: () => 2977 - $$SettingsTableOrderingComposer($db: db, $table: table), 2978 - createComputedFieldComposer: () => 2979 - $$SettingsTableAnnotationComposer($db: db, $table: table), 2454 + createFilteringComposer: () => $$SettingsTableFilterComposer($db: db, $table: table), 2455 + createOrderingComposer: () => $$SettingsTableOrderingComposer($db: db, $table: table), 2456 + createComputedFieldComposer: () => $$SettingsTableAnnotationComposer($db: db, $table: table), 2980 2457 updateCompanionCallback: 2981 2458 ({ 2982 2459 Value<String> key = const Value.absent(), 2983 2460 Value<String> value = const Value.absent(), 2984 2461 Value<DateTime> updatedAt = const Value.absent(), 2985 2462 Value<int> rowid = const Value.absent(), 2986 - }) => SettingsCompanion( 2987 - key: key, 2988 - value: value, 2989 - updatedAt: updatedAt, 2990 - rowid: rowid, 2991 - ), 2463 + }) => SettingsCompanion(key: key, value: value, updatedAt: updatedAt, rowid: rowid), 2992 2464 createCompanionCallback: 2993 2465 ({ 2994 2466 required String key, 2995 2467 required String value, 2996 2468 Value<DateTime> updatedAt = const Value.absent(), 2997 2469 Value<int> rowid = const Value.absent(), 2998 - }) => SettingsCompanion.insert( 2999 - key: key, 3000 - value: value, 3001 - updatedAt: updatedAt, 3002 - rowid: rowid, 3003 - ), 3004 - withReferenceMapper: (p0) => p0 3005 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 3006 - .toList(), 2470 + }) => SettingsCompanion.insert(key: key, value: value, updatedAt: updatedAt, rowid: rowid), 2471 + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 3007 2472 prefetchHooksCallback: null, 3008 2473 ), 3009 2474 ); ··· 3019 2484 $$SettingsTableAnnotationComposer, 3020 2485 $$SettingsTableCreateCompanionBuilder, 3021 2486 $$SettingsTableUpdateCompanionBuilder, 3022 - ( 3023 - SettingsEntry, 3024 - BaseReferences<_$AppDatabase, $SettingsTable, SettingsEntry>, 3025 - ), 2487 + (SettingsEntry, BaseReferences<_$AppDatabase, $SettingsTable, SettingsEntry>), 3026 2488 SettingsEntry, 3027 2489 PrefetchHooks Function() 3028 2490 >; ··· 3049 2511 Value<int> rowid, 3050 2512 }); 3051 2513 3052 - class $$SavedFeedsTableFilterComposer 3053 - extends Composer<_$AppDatabase, $SavedFeedsTable> { 2514 + class $$SavedFeedsTableFilterComposer extends Composer<_$AppDatabase, $SavedFeedsTable> { 3054 2515 $$SavedFeedsTableFilterComposer({ 3055 2516 required super.$db, 3056 2517 required super.$table, ··· 3058 2519 super.$addJoinBuilderToRootComposer, 3059 2520 super.$removeJoinBuilderFromRootComposer, 3060 2521 }); 3061 - ColumnFilters<String> get id => $composableBuilder( 3062 - column: $table.id, 3063 - builder: (column) => ColumnFilters(column), 3064 - ); 2522 + ColumnFilters<String> get id => $composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column)); 3065 2523 3066 - ColumnFilters<String> get accountDid => $composableBuilder( 3067 - column: $table.accountDid, 3068 - builder: (column) => ColumnFilters(column), 3069 - ); 2524 + ColumnFilters<String> get accountDid => 2525 + $composableBuilder(column: $table.accountDid, builder: (column) => ColumnFilters(column)); 3070 2526 3071 - ColumnFilters<String> get type => $composableBuilder( 3072 - column: $table.type, 3073 - builder: (column) => ColumnFilters(column), 3074 - ); 2527 + ColumnFilters<String> get type => $composableBuilder(column: $table.type, builder: (column) => ColumnFilters(column)); 3075 2528 3076 - ColumnFilters<String> get value => $composableBuilder( 3077 - column: $table.value, 3078 - builder: (column) => ColumnFilters(column), 3079 - ); 2529 + ColumnFilters<String> get value => 2530 + $composableBuilder(column: $table.value, builder: (column) => ColumnFilters(column)); 3080 2531 3081 - ColumnFilters<bool> get pinned => $composableBuilder( 3082 - column: $table.pinned, 3083 - builder: (column) => ColumnFilters(column), 3084 - ); 2532 + ColumnFilters<bool> get pinned => 2533 + $composableBuilder(column: $table.pinned, builder: (column) => ColumnFilters(column)); 3085 2534 3086 - ColumnFilters<int> get sortOrder => $composableBuilder( 3087 - column: $table.sortOrder, 3088 - builder: (column) => ColumnFilters(column), 3089 - ); 2535 + ColumnFilters<int> get sortOrder => 2536 + $composableBuilder(column: $table.sortOrder, builder: (column) => ColumnFilters(column)); 3090 2537 3091 - ColumnFilters<DateTime> get updatedAt => $composableBuilder( 3092 - column: $table.updatedAt, 3093 - builder: (column) => ColumnFilters(column), 3094 - ); 2538 + ColumnFilters<DateTime> get updatedAt => 2539 + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); 3095 2540 } 3096 2541 3097 - class $$SavedFeedsTableOrderingComposer 3098 - extends Composer<_$AppDatabase, $SavedFeedsTable> { 2542 + class $$SavedFeedsTableOrderingComposer extends Composer<_$AppDatabase, $SavedFeedsTable> { 3099 2543 $$SavedFeedsTableOrderingComposer({ 3100 2544 required super.$db, 3101 2545 required super.$table, ··· 3103 2547 super.$addJoinBuilderToRootComposer, 3104 2548 super.$removeJoinBuilderFromRootComposer, 3105 2549 }); 3106 - ColumnOrderings<String> get id => $composableBuilder( 3107 - column: $table.id, 3108 - builder: (column) => ColumnOrderings(column), 3109 - ); 2550 + ColumnOrderings<String> get id => $composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column)); 3110 2551 3111 - ColumnOrderings<String> get accountDid => $composableBuilder( 3112 - column: $table.accountDid, 3113 - builder: (column) => ColumnOrderings(column), 3114 - ); 2552 + ColumnOrderings<String> get accountDid => 2553 + $composableBuilder(column: $table.accountDid, builder: (column) => ColumnOrderings(column)); 3115 2554 3116 - ColumnOrderings<String> get type => $composableBuilder( 3117 - column: $table.type, 3118 - builder: (column) => ColumnOrderings(column), 3119 - ); 2555 + ColumnOrderings<String> get type => 2556 + $composableBuilder(column: $table.type, builder: (column) => ColumnOrderings(column)); 3120 2557 3121 - ColumnOrderings<String> get value => $composableBuilder( 3122 - column: $table.value, 3123 - builder: (column) => ColumnOrderings(column), 3124 - ); 2558 + ColumnOrderings<String> get value => 2559 + $composableBuilder(column: $table.value, builder: (column) => ColumnOrderings(column)); 3125 2560 3126 - ColumnOrderings<bool> get pinned => $composableBuilder( 3127 - column: $table.pinned, 3128 - builder: (column) => ColumnOrderings(column), 3129 - ); 2561 + ColumnOrderings<bool> get pinned => 2562 + $composableBuilder(column: $table.pinned, builder: (column) => ColumnOrderings(column)); 3130 2563 3131 - ColumnOrderings<int> get sortOrder => $composableBuilder( 3132 - column: $table.sortOrder, 3133 - builder: (column) => ColumnOrderings(column), 3134 - ); 2564 + ColumnOrderings<int> get sortOrder => 2565 + $composableBuilder(column: $table.sortOrder, builder: (column) => ColumnOrderings(column)); 3135 2566 3136 - ColumnOrderings<DateTime> get updatedAt => $composableBuilder( 3137 - column: $table.updatedAt, 3138 - builder: (column) => ColumnOrderings(column), 3139 - ); 2567 + ColumnOrderings<DateTime> get updatedAt => 2568 + $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); 3140 2569 } 3141 2570 3142 - class $$SavedFeedsTableAnnotationComposer 3143 - extends Composer<_$AppDatabase, $SavedFeedsTable> { 2571 + class $$SavedFeedsTableAnnotationComposer extends Composer<_$AppDatabase, $SavedFeedsTable> { 3144 2572 $$SavedFeedsTableAnnotationComposer({ 3145 2573 required super.$db, 3146 2574 required super.$table, ··· 3148 2576 super.$addJoinBuilderToRootComposer, 3149 2577 super.$removeJoinBuilderFromRootComposer, 3150 2578 }); 3151 - GeneratedColumn<String> get id => 3152 - $composableBuilder(column: $table.id, builder: (column) => column); 2579 + GeneratedColumn<String> get id => $composableBuilder(column: $table.id, builder: (column) => column); 3153 2580 3154 - GeneratedColumn<String> get accountDid => $composableBuilder( 3155 - column: $table.accountDid, 3156 - builder: (column) => column, 3157 - ); 2581 + GeneratedColumn<String> get accountDid => $composableBuilder(column: $table.accountDid, builder: (column) => column); 3158 2582 3159 - GeneratedColumn<String> get type => 3160 - $composableBuilder(column: $table.type, builder: (column) => column); 2583 + GeneratedColumn<String> get type => $composableBuilder(column: $table.type, builder: (column) => column); 3161 2584 3162 - GeneratedColumn<String> get value => 3163 - $composableBuilder(column: $table.value, builder: (column) => column); 2585 + GeneratedColumn<String> get value => $composableBuilder(column: $table.value, builder: (column) => column); 3164 2586 3165 - GeneratedColumn<bool> get pinned => 3166 - $composableBuilder(column: $table.pinned, builder: (column) => column); 2587 + GeneratedColumn<bool> get pinned => $composableBuilder(column: $table.pinned, builder: (column) => column); 3167 2588 3168 - GeneratedColumn<int> get sortOrder => 3169 - $composableBuilder(column: $table.sortOrder, builder: (column) => column); 2589 + GeneratedColumn<int> get sortOrder => $composableBuilder(column: $table.sortOrder, builder: (column) => column); 3170 2590 3171 - GeneratedColumn<DateTime> get updatedAt => 3172 - $composableBuilder(column: $table.updatedAt, builder: (column) => column); 2591 + GeneratedColumn<DateTime> get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); 3173 2592 } 3174 2593 3175 2594 class $$SavedFeedsTableTableManager ··· 3183 2602 $$SavedFeedsTableAnnotationComposer, 3184 2603 $$SavedFeedsTableCreateCompanionBuilder, 3185 2604 $$SavedFeedsTableUpdateCompanionBuilder, 3186 - ( 3187 - SavedFeedEntry, 3188 - BaseReferences<_$AppDatabase, $SavedFeedsTable, SavedFeedEntry>, 3189 - ), 2605 + (SavedFeedEntry, BaseReferences<_$AppDatabase, $SavedFeedsTable, SavedFeedEntry>), 3190 2606 SavedFeedEntry, 3191 2607 PrefetchHooks Function() 3192 2608 > { ··· 3195 2611 TableManagerState( 3196 2612 db: db, 3197 2613 table: table, 3198 - createFilteringComposer: () => 3199 - $$SavedFeedsTableFilterComposer($db: db, $table: table), 3200 - createOrderingComposer: () => 3201 - $$SavedFeedsTableOrderingComposer($db: db, $table: table), 3202 - createComputedFieldComposer: () => 3203 - $$SavedFeedsTableAnnotationComposer($db: db, $table: table), 2614 + createFilteringComposer: () => $$SavedFeedsTableFilterComposer($db: db, $table: table), 2615 + createOrderingComposer: () => $$SavedFeedsTableOrderingComposer($db: db, $table: table), 2616 + createComputedFieldComposer: () => $$SavedFeedsTableAnnotationComposer($db: db, $table: table), 3204 2617 updateCompanionCallback: 3205 2618 ({ 3206 2619 Value<String> id = const Value.absent(), ··· 3241 2654 updatedAt: updatedAt, 3242 2655 rowid: rowid, 3243 2656 ), 3244 - withReferenceMapper: (p0) => p0 3245 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 3246 - .toList(), 2657 + withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 3247 2658 prefetchHooksCallback: null, 3248 2659 ), 3249 2660 ); ··· 3259 2670 $$SavedFeedsTableAnnotationComposer, 3260 2671 $$SavedFeedsTableCreateCompanionBuilder, 3261 2672 $$SavedFeedsTableUpdateCompanionBuilder, 3262 - ( 3263 - SavedFeedEntry, 3264 - BaseReferences<_$AppDatabase, $SavedFeedsTable, SavedFeedEntry>, 3265 - ), 2673 + (SavedFeedEntry, BaseReferences<_$AppDatabase, $SavedFeedsTable, SavedFeedEntry>), 3266 2674 SavedFeedEntry, 3267 2675 PrefetchHooks Function() 3268 2676 >; ··· 3270 2678 class $AppDatabaseManager { 3271 2679 final _$AppDatabase _db; 3272 2680 $AppDatabaseManager(this._db); 3273 - $$AccountsTableTableManager get accounts => 3274 - $$AccountsTableTableManager(_db, _db.accounts); 3275 - $$CachedProfilesTableTableManager get cachedProfiles => 3276 - $$CachedProfilesTableTableManager(_db, _db.cachedProfiles); 3277 - $$CachedPostsTableTableManager get cachedPosts => 3278 - $$CachedPostsTableTableManager(_db, _db.cachedPosts); 3279 - $$SettingsTableTableManager get settings => 3280 - $$SettingsTableTableManager(_db, _db.settings); 3281 - $$SavedFeedsTableTableManager get savedFeeds => 3282 - $$SavedFeedsTableTableManager(_db, _db.savedFeeds); 2681 + $$AccountsTableTableManager get accounts => $$AccountsTableTableManager(_db, _db.accounts); 2682 + $$CachedProfilesTableTableManager get cachedProfiles => $$CachedProfilesTableTableManager(_db, _db.cachedProfiles); 2683 + $$CachedPostsTableTableManager get cachedPosts => $$CachedPostsTableTableManager(_db, _db.cachedPosts); 2684 + $$SettingsTableTableManager get settings => $$SettingsTableTableManager(_db, _db.settings); 2685 + $$SavedFeedsTableTableManager get savedFeeds => $$SavedFeedsTableTableManager(_db, _db.savedFeeds); 3283 2686 }
+46 -7
lib/core/router/app_router.dart
··· 3 3 import 'package:flutter/material.dart'; 4 4 import 'package:go_router/go_router.dart'; 5 5 import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; 6 + import 'package:lazurite/core/router/app_shell.dart'; 6 7 import 'package:lazurite/features/auth/presentation/login_screen.dart'; 7 8 import 'package:lazurite/features/feed/presentation/feed_management_screen.dart'; 8 9 import 'package:lazurite/features/feed/presentation/home_feed_screen.dart'; ··· 14 15 AppRouter({required this.authBloc, this.navigatorObserver}); 15 16 final AuthBloc authBloc; 16 17 final NavigatorObserver? navigatorObserver; 18 + final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root'); 19 + final GlobalKey<NavigatorState> _homeNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'home'); 20 + final GlobalKey<NavigatorState> _profileNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'profile'); 21 + final GlobalKey<NavigatorState> _settingsNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'settings'); 17 22 18 23 GoRouter get router => GoRouter( 24 + navigatorKey: _rootNavigatorKey, 19 25 refreshListenable: GoRouterRefreshStream(authBloc.stream), 20 26 observers: navigatorObserver != null ? [navigatorObserver!] : null, 21 27 redirect: (context, state) { ··· 33 39 return null; 34 40 }, 35 41 routes: [ 36 - GoRoute(path: '/', builder: (context, state) => const HomeFeedScreen()), 37 42 GoRoute(path: '/login', builder: (context, state) => const LoginScreen()), 38 - GoRoute( 39 - path: '/profile', 40 - builder: (context, state) => ProfileScreen(actor: state.uri.queryParameters['actor']), 43 + StatefulShellRoute.indexedStack( 44 + builder: (context, state, navigationShell) => AppShell(navigationShell: navigationShell), 45 + branches: [ 46 + StatefulShellBranch( 47 + navigatorKey: _homeNavigatorKey, 48 + routes: [ 49 + GoRoute( 50 + path: '/', 51 + builder: (context, state) => const HomeFeedScreen(), 52 + routes: [GoRoute(path: 'feeds', builder: (context, state) => const FeedManagementScreen())], 53 + ), 54 + ], 55 + ), 56 + StatefulShellBranch( 57 + navigatorKey: _profileNavigatorKey, 58 + routes: [ 59 + GoRoute( 60 + path: '/profile', 61 + builder: (context, state) => const ProfileScreen(), 62 + routes: [ 63 + GoRoute( 64 + path: 'view', 65 + builder: (context, state) => 66 + ProfileScreen(actor: state.uri.queryParameters['actor'], showBackButton: true), 67 + ), 68 + ], 69 + ), 70 + ], 71 + ), 72 + StatefulShellBranch( 73 + navigatorKey: _settingsNavigatorKey, 74 + routes: [ 75 + GoRoute( 76 + path: '/settings', 77 + builder: (context, state) => const SettingsScreen(), 78 + routes: [GoRoute(path: 'logs', builder: (context, state) => const LogsScreen())], 79 + ), 80 + ], 81 + ), 82 + ], 41 83 ), 42 - GoRoute(path: '/settings', builder: (context, state) => const SettingsScreen()), 43 - GoRoute(path: '/logs', builder: (context, state) => const LogsScreen()), 44 - GoRoute(path: '/feeds', builder: (context, state) => const FeedManagementScreen()), 45 84 ], 46 85 ); 47 86 }
+30
lib/core/router/app_shell.dart
··· 1 + import 'package:flutter/material.dart'; 2 + import 'package:go_router/go_router.dart'; 3 + 4 + class AppShell extends StatelessWidget { 5 + const AppShell({super.key, required this.navigationShell}); 6 + 7 + final StatefulNavigationShell navigationShell; 8 + 9 + @override 10 + Widget build(BuildContext context) { 11 + return Scaffold( 12 + body: navigationShell, 13 + bottomNavigationBar: NavigationBar( 14 + selectedIndex: navigationShell.currentIndex, 15 + onDestinationSelected: (index) { 16 + navigationShell.goBranch(index, initialLocation: index == navigationShell.currentIndex); 17 + }, 18 + destinations: const [ 19 + NavigationDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: 'Home'), 20 + NavigationDestination(icon: Icon(Icons.person_outline), selectedIcon: Icon(Icons.person), label: 'Profile'), 21 + NavigationDestination( 22 + icon: Icon(Icons.settings_outlined), 23 + selectedIcon: Icon(Icons.settings), 24 + label: 'Settings', 25 + ), 26 + ], 27 + ), 28 + ); 29 + } 30 + }
+172 -16
lib/features/feed/cubit/feed_preferences_cubit.dart
··· 1 + import 'package:atproto_core/atproto_core.dart'; 1 2 import 'package:bluesky/app_bsky_actor_defs.dart'; 3 + import 'package:bluesky/app_bsky_feed_defs.dart'; 2 4 import 'package:drift/drift.dart'; 3 5 import 'package:equatable/equatable.dart'; 4 6 import 'package:flutter_bloc/flutter_bloc.dart'; 5 7 import 'package:lazurite/core/database/app_database.dart'; 8 + import 'package:lazurite/core/logging/app_logger.dart'; 6 9 import 'package:lazurite/features/feed/data/feed_repository.dart'; 7 10 import 'package:uuid/uuid.dart'; 8 11 ··· 21 24 final String _accountDid; 22 25 23 26 Future<void> loadPreferences() async { 27 + log.d('FeedPreferencesCubit: Loading feed preferences for $_accountDid'); 24 28 emit(state.copyWith(status: FeedPreferencesStatus.loading)); 25 29 26 30 try { ··· 28 32 29 33 if (cachedFeeds.isNotEmpty) { 30 34 final feeds = cachedFeeds.map(_mapFromCached).toList(); 31 - emit(FeedPreferencesState.loaded(feeds: feeds)); 35 + log.d('FeedPreferencesCubit: Loaded ${feeds.length} cached feeds for $_accountDid'); 36 + _emitLoaded(feeds); 32 37 } 33 38 34 39 final result = await _feedRepository.getPreferences(); 35 40 final savedFeedsPref = result.preferences.whereType<UPreferencesSavedFeedsPrefV2>().firstOrNull; 36 41 37 42 if (savedFeedsPref != null) { 38 - final feeds = savedFeedsPref.data.items; 43 + final feeds = _ensureDefaultFeeds(savedFeedsPref.data.items); 39 44 await _cacheFeeds(feeds); 40 - emit(FeedPreferencesState.loaded(feeds: feeds)); 45 + log.i('FeedPreferencesCubit: Loaded ${feeds.length} feed preferences from server for $_accountDid'); 46 + _emitLoaded(feeds); 47 + await _hydrateGeneratorViews(feeds); 41 48 } else { 42 49 final defaultFeeds = [_createDefaultTimelineFeed()]; 43 50 await _cacheFeeds(defaultFeeds); 44 - emit(FeedPreferencesState.loaded(feeds: defaultFeeds)); 51 + log.i('FeedPreferencesCubit: No server feed preferences found, creating default timeline for $_accountDid'); 52 + _emitLoaded(defaultFeeds); 45 53 } 46 - } catch (e) { 54 + } catch (e, stackTrace) { 47 55 final cachedFeeds = await _database.getSavedFeeds(_accountDid); 48 56 if (cachedFeeds.isNotEmpty) { 49 57 final feeds = cachedFeeds.map(_mapFromCached).toList(); 50 - emit(FeedPreferencesState.loaded(feeds: feeds)); 58 + log.w( 59 + 'FeedPreferencesCubit: Falling back to ${feeds.length} cached feeds for $_accountDid after load failure', 60 + error: e, 61 + stackTrace: stackTrace, 62 + ); 63 + _emitLoaded(feeds); 64 + await _hydrateGeneratorViews(feeds); 51 65 } else { 66 + log.e( 67 + 'FeedPreferencesCubit: Failed to load feed preferences for $_accountDid with no cached fallback', 68 + error: e, 69 + stackTrace: stackTrace, 70 + ); 52 71 emit(FeedPreferencesState.error(message: e.toString())); 53 72 } 54 73 } ··· 109 128 } 110 129 111 130 Future<void> addFeed({required SavedFeedType type, required String value, bool pinned = false}) async { 131 + if (state.feeds.any((feed) => feed.value == value)) { 132 + log.d('FeedPreferencesCubit: Ignoring duplicate feed $value for $_accountDid'); 133 + return; 134 + } 135 + 112 136 final newFeed = SavedFeed(id: _generateId(), type: type, value: value, pinned: pinned); 113 137 final feeds = [...state.feeds, newFeed]; 114 138 await _savePreferences(feeds); ··· 116 140 117 141 Future<bool> _savePreferences(List<SavedFeed> feeds) async { 118 142 final previousState = state; 143 + log.d('FeedPreferencesCubit: Saving ${feeds.length} feed preferences for $_accountDid'); 119 144 emit(state.copyWith(status: FeedPreferencesStatus.saving)); 120 145 121 146 try { 122 - await _cacheFeeds(feeds); 123 - 124 147 final result = await _feedRepository.getPreferences(); 125 148 final preferences = List<UPreferences>.from(result.preferences); 126 149 ··· 128 151 preferences.add(UPreferences.savedFeedsPrefV2(data: SavedFeedsPrefV2(items: feeds))); 129 152 130 153 await _feedRepository.putPreferences(preferences: preferences); 154 + await _cacheFeeds(feeds); 131 155 132 - emit(FeedPreferencesState.loaded(feeds: feeds)); 156 + log.i('FeedPreferencesCubit: Saved ${feeds.length} feed preferences for $_accountDid'); 157 + _emitLoaded(feeds); 158 + await _hydrateGeneratorViews(feeds); 133 159 return true; 134 - } catch (e) { 135 - emit(FeedPreferencesState.saveError(feeds: feeds, message: e.toString(), previousState: previousState)); 160 + } catch (e, stackTrace) { 161 + log.e('FeedPreferencesCubit: Failed to save feed preferences for $_accountDid', error: e, stackTrace: stackTrace); 162 + emit( 163 + FeedPreferencesState.saveError( 164 + feeds: previousState.feeds, 165 + generatorViews: previousState.generatorViews, 166 + message: e.toString(), 167 + previousState: previousState, 168 + ), 169 + ); 136 170 return false; 137 171 } 138 172 } ··· 172 206 173 207 String _generateId() => const Uuid().v4(); 174 208 209 + void _emitLoaded(List<SavedFeed> feeds) { 210 + emit(FeedPreferencesState.loaded(feeds: feeds, generatorViews: _retainGeneratorViews(feeds))); 211 + } 212 + 213 + List<SavedFeed> _ensureDefaultFeeds(List<SavedFeed> feeds) { 214 + if (feeds.isNotEmpty) { 215 + return feeds; 216 + } 217 + 218 + return [_createDefaultTimelineFeed()]; 219 + } 220 + 221 + List<GeneratorView> _retainGeneratorViews(List<SavedFeed> feeds) { 222 + final values = feeds.map((feed) => feed.value).toSet(); 223 + return state.generatorViews.where((view) => values.contains(view.uri.toString())).toList(growable: false); 224 + } 225 + 226 + Future<void> _hydrateGeneratorViews(List<SavedFeed> feeds) async { 227 + final feedUris = <AtUri>[]; 228 + final seenUris = <String>{}; 229 + 230 + for (final feed in feeds) { 231 + if (!_isGeneratorFeed(feed)) { 232 + continue; 233 + } 234 + 235 + if (!seenUris.add(feed.value)) { 236 + continue; 237 + } 238 + 239 + try { 240 + feedUris.add(AtUri.parse(feed.value)); 241 + } catch (_) { 242 + continue; 243 + } 244 + } 245 + 246 + if (feedUris.isEmpty) { 247 + if (state.generatorViews.isNotEmpty) { 248 + emit(state.copyWith(generatorViews: const [], status: FeedPreferencesStatus.loaded)); 249 + } 250 + return; 251 + } 252 + 253 + try { 254 + final generatorViews = await _feedRepository.getFeedGenerators(feedUris); 255 + log.d('FeedPreferencesCubit: Hydrated ${generatorViews.length} generator views for $_accountDid'); 256 + emit(state.copyWith(generatorViews: generatorViews, status: FeedPreferencesStatus.loaded, feeds: feeds)); 257 + } catch (e, stackTrace) { 258 + log.w( 259 + 'FeedPreferencesCubit: Failed to hydrate ${feedUris.length} generator views for $_accountDid', 260 + error: e, 261 + stackTrace: stackTrace, 262 + ); 263 + } 264 + } 265 + 266 + bool _isGeneratorFeed(SavedFeed feed) { 267 + final feedType = feed.type; 268 + return feedType is SavedFeedTypeKnownValue && feedType.data == KnownSavedFeedType.feed; 269 + } 270 + 175 271 SavedFeed _createDefaultTimelineFeed() { 176 272 return SavedFeed( 177 273 id: _generateId(), ··· 185 281 enum FeedPreferencesStatus { initial, loading, loaded, saving, saveError, error } 186 282 187 283 class FeedPreferencesState extends Equatable { 188 - const FeedPreferencesState._({required this.status, this.feeds = const [], this.message, this.previousState}); 284 + const FeedPreferencesState._({ 285 + required this.status, 286 + this.feeds = const [], 287 + this.generatorViews = const [], 288 + this.message, 289 + this.previousState, 290 + }); 189 291 190 292 const FeedPreferencesState.initial() : this._(status: FeedPreferencesStatus.initial); 191 293 192 - const FeedPreferencesState.loaded({required List<SavedFeed> feeds}) 193 - : this._(status: FeedPreferencesStatus.loaded, feeds: feeds); 294 + const FeedPreferencesState.loaded({required List<SavedFeed> feeds, List<GeneratorView> generatorViews = const []}) 295 + : this._(status: FeedPreferencesStatus.loaded, feeds: feeds, generatorViews: generatorViews); 194 296 195 297 const FeedPreferencesState.error({required String message}) 196 298 : this._(status: FeedPreferencesStatus.error, message: message); 197 299 198 300 const FeedPreferencesState.saveError({ 199 301 required List<SavedFeed> feeds, 302 + required List<GeneratorView> generatorViews, 200 303 required String message, 201 304 required FeedPreferencesState previousState, 202 - }) : this._(status: FeedPreferencesStatus.saveError, feeds: feeds, message: message, previousState: previousState); 305 + }) : this._( 306 + status: FeedPreferencesStatus.saveError, 307 + feeds: feeds, 308 + generatorViews: generatorViews, 309 + message: message, 310 + previousState: previousState, 311 + ); 203 312 204 313 final FeedPreferencesStatus status; 205 314 final List<SavedFeed> feeds; 315 + final List<GeneratorView> generatorViews; 206 316 final String? message; 207 317 final FeedPreferencesState? previousState; 208 318 ··· 210 320 211 321 List<SavedFeed> get unpinnedFeeds => feeds.where((f) => !f.pinned).toList(); 212 322 323 + bool isTimeline(SavedFeed feed) { 324 + final feedType = feed.type; 325 + return feedType is SavedFeedTypeKnownValue && feedType.data == KnownSavedFeedType.timeline; 326 + } 327 + 328 + GeneratorView? generatorFor(SavedFeed feed) { 329 + for (final view in generatorViews) { 330 + if (view.uri.toString() == feed.value) { 331 + return view; 332 + } 333 + } 334 + 335 + return null; 336 + } 337 + 338 + String displayNameFor(SavedFeed feed) { 339 + if (isTimeline(feed)) { 340 + return 'Following'; 341 + } 342 + 343 + final generator = generatorFor(feed); 344 + if (generator != null) { 345 + return generator.displayName; 346 + } 347 + 348 + final segments = feed.value.split('/'); 349 + return segments.isEmpty ? 'Feed' : segments.last; 350 + } 351 + 352 + String subtitleFor(SavedFeed feed) { 353 + if (isTimeline(feed)) { 354 + return 'Timeline'; 355 + } 356 + 357 + final generator = generatorFor(feed); 358 + if (generator != null) { 359 + return 'by ${generator.creator.displayName ?? generator.creator.handle}'; 360 + } 361 + 362 + return 'Custom Feed'; 363 + } 364 + 365 + String? descriptionFor(SavedFeed feed) => generatorFor(feed)?.description; 366 + 213 367 FeedPreferencesState copyWith({ 214 368 FeedPreferencesStatus? status, 215 369 List<SavedFeed>? feeds, 370 + List<GeneratorView>? generatorViews, 216 371 String? message, 217 372 FeedPreferencesState? previousState, 218 373 }) { 219 374 return FeedPreferencesState._( 220 375 status: status ?? this.status, 221 376 feeds: feeds ?? this.feeds, 377 + generatorViews: generatorViews ?? this.generatorViews, 222 378 message: message ?? this.message, 223 379 previousState: previousState ?? this.previousState, 224 380 ); 225 381 } 226 382 227 383 @override 228 - List<Object?> get props => [status, feeds, message, previousState]; 384 + List<Object?> get props => [status, feeds, generatorViews, message, previousState]; 229 385 }
+11 -22
lib/features/feed/presentation/feed_management_screen.dart
··· 1 - import 'package:atproto_core/atproto_core.dart'; 2 1 import 'package:bluesky/app_bsky_actor_defs.dart'; 3 2 import 'package:bluesky/app_bsky_feed_defs.dart'; 4 3 import 'package:flutter/material.dart'; ··· 116 115 }, 117 116 itemBuilder: (context, index) { 118 117 final feed = pinnedFeeds[index]; 119 - final isTimeline = 120 - feed.type is SavedFeedTypeKnownValue && 121 - (feed.type as SavedFeedTypeKnownValue).data == KnownSavedFeedType.timeline; 118 + final isTimeline = state.isTimeline(feed); 122 119 123 120 return ListTile( 124 121 key: ValueKey(feed.id), 125 122 leading: isTimeline ? _buildTimelineIcon(context) : _buildFeedIcon(context, feed.value), 126 - title: Text(isTimeline ? 'Following' : _getFeedDisplayName(feed.value)), 127 - subtitle: Text(isTimeline ? 'Timeline' : 'Custom Feed'), 123 + title: Text(state.displayNameFor(feed)), 124 + subtitle: Text(state.subtitleFor(feed)), 128 125 trailing: ReorderableDragStartListener(index: index, child: const Icon(Icons.drag_handle)), 129 126 ); 130 127 }, ··· 156 153 } 157 154 158 155 Widget _buildPinnedFeedItem(BuildContext context, SavedFeed feed, FeedPreferencesState state) { 159 - final isTimeline = 160 - feed.type is SavedFeedTypeKnownValue && 161 - (feed.type as SavedFeedTypeKnownValue).data == KnownSavedFeedType.timeline; 156 + final isTimeline = state.isTimeline(feed); 162 157 163 158 return ListTile( 164 159 leading: isTimeline ? _buildTimelineIcon(context) : _buildFeedIcon(context, feed.value), 165 - title: Text(isTimeline ? 'Following' : _getFeedDisplayName(feed.value)), 166 - subtitle: Text(isTimeline ? 'Timeline' : 'Custom Feed'), 160 + title: Text(state.displayNameFor(feed)), 161 + subtitle: Text(state.subtitleFor(feed)), 167 162 trailing: IconButton( 168 163 icon: const Icon(Icons.check_circle), 169 164 color: Theme.of(context).colorScheme.primary, ··· 173 168 } 174 169 175 170 Widget _buildSavedFeedItem(BuildContext context, SavedFeed feed) { 171 + final state = context.watch<FeedPreferencesCubit>().state; 172 + final description = state.descriptionFor(feed); 173 + 176 174 return ListTile( 177 175 leading: _buildFeedIcon(context, feed.value), 178 - title: Text(_getFeedDisplayName(feed.value)), 179 - subtitle: const Text('Custom Feed'), 176 + title: Text(state.displayNameFor(feed)), 177 + subtitle: Text(description ?? state.subtitleFor(feed)), 180 178 trailing: Row( 181 179 mainAxisSize: MainAxisSize.min, 182 180 children: [ ··· 331 329 ) 332 330 : const Icon(Icons.rss_feed, color: Colors.white), 333 331 ); 334 - } 335 - 336 - String _getFeedDisplayName(String feedUri) { 337 - try { 338 - final uri = AtUri.parse(feedUri); 339 - return uri.rkey; 340 - } catch (_) { 341 - return feedUri.split('/').lastOrNull ?? feedUri; 342 - } 343 332 } 344 333 345 334 void _confirmRemoveFeed(BuildContext context, String feedId) {
+91 -19
lib/features/feed/presentation/home_feed_screen.dart
··· 17 17 18 18 class _HomeFeedScreenState extends State<HomeFeedScreen> { 19 19 late final PageController _pageController; 20 - int _currentTabIndex = 0; 20 + String? _selectedFeedId; 21 21 22 22 @override 23 23 void initState() { ··· 35 35 Widget build(BuildContext context) { 36 36 return BlocBuilder<FeedPreferencesCubit, FeedPreferencesState>( 37 37 builder: (context, prefsState) { 38 + if (prefsState.status == FeedPreferencesStatus.initial || prefsState.status == FeedPreferencesStatus.loading) { 39 + return const Scaffold(body: Center(child: CircularProgressIndicator())); 40 + } 41 + 42 + if (prefsState.status == FeedPreferencesStatus.error) { 43 + return Scaffold( 44 + appBar: AppBar(title: const Text('Home')), 45 + body: Center( 46 + child: Column( 47 + mainAxisAlignment: MainAxisAlignment.center, 48 + children: [ 49 + Text('Failed to load feeds', style: Theme.of(context).textTheme.titleMedium), 50 + const SizedBox(height: 8), 51 + Text(prefsState.message ?? 'Unknown error', textAlign: TextAlign.center), 52 + const SizedBox(height: 16), 53 + FilledButton( 54 + onPressed: () => context.read<FeedPreferencesCubit>().loadPreferences(), 55 + child: const Text('Retry'), 56 + ), 57 + ], 58 + ), 59 + ), 60 + ); 61 + } 62 + 38 63 final pinnedFeeds = prefsState.pinnedFeeds; 39 64 40 65 if (pinnedFeeds.isEmpty) { 41 66 return Scaffold( 42 67 appBar: AppBar(title: const Text('Home')), 43 - body: const Center(child: Text('No feeds pinned. Add feeds from settings.')), 68 + body: Center( 69 + child: Padding( 70 + padding: const EdgeInsets.all(24), 71 + child: Column( 72 + mainAxisAlignment: MainAxisAlignment.center, 73 + children: [ 74 + Text('No feeds pinned', style: Theme.of(context).textTheme.titleMedium), 75 + const SizedBox(height: 8), 76 + Text( 77 + 'Pin a timeline or custom feed to build your home tabs.', 78 + style: Theme.of(context).textTheme.bodyMedium, 79 + textAlign: TextAlign.center, 80 + ), 81 + const SizedBox(height: 16), 82 + FilledButton(onPressed: () => context.push('/feeds'), child: const Text('Manage Feeds')), 83 + ], 84 + ), 85 + ), 86 + ), 44 87 ); 45 88 } 46 89 90 + final currentTabIndex = _selectedIndexFor(pinnedFeeds); 91 + _syncSelectedFeed(pinnedFeeds, currentTabIndex); 92 + 47 93 return Scaffold( 48 94 appBar: AppBar( 49 95 title: const Text('Home'), 50 - actions: [IconButton(icon: const Icon(Icons.settings_outlined), onPressed: () => context.push('/feeds'))], 96 + actions: [ 97 + IconButton(icon: const Icon(Icons.dynamic_feed_outlined), onPressed: () => context.push('/feeds')), 98 + ], 51 99 ), 52 100 body: Column( 53 101 children: [ 54 - _buildTabBar(context, pinnedFeeds), 102 + _buildTabBar(context, pinnedFeeds, prefsState, currentTabIndex), 55 103 Expanded( 56 104 child: PageView.builder( 57 105 controller: _pageController, 58 - onPageChanged: (index) => setState(() => _currentTabIndex = index), 106 + onPageChanged: (index) => setState(() => _selectedFeedId = pinnedFeeds[index].id), 59 107 itemCount: pinnedFeeds.length, 60 108 itemBuilder: (context, index) => 61 109 _FeedListView(feed: pinnedFeeds[index], key: ValueKey(pinnedFeeds[index].id)), ··· 68 116 ); 69 117 } 70 118 71 - Widget _buildTabBar(BuildContext context, List<SavedFeed> feeds) { 119 + void _syncSelectedFeed(List<SavedFeed> feeds, int currentTabIndex) { 120 + final selectedFeedId = feeds[currentTabIndex].id; 121 + if (_selectedFeedId != selectedFeedId) { 122 + _selectedFeedId = selectedFeedId; 123 + } 124 + 125 + WidgetsBinding.instance.addPostFrameCallback((_) { 126 + if (!mounted || !_pageController.hasClients) { 127 + return; 128 + } 129 + 130 + final roundedPage = _pageController.page?.round(); 131 + if (roundedPage == currentTabIndex) { 132 + return; 133 + } 134 + 135 + _pageController.jumpToPage(currentTabIndex); 136 + }); 137 + } 138 + 139 + int _selectedIndexFor(List<SavedFeed> feeds) { 140 + if (feeds.isEmpty) { 141 + return 0; 142 + } 143 + 144 + final index = feeds.indexWhere((feed) => feed.id == _selectedFeedId); 145 + return index >= 0 ? index : 0; 146 + } 147 + 148 + Widget _buildTabBar( 149 + BuildContext context, 150 + List<SavedFeed> feeds, 151 + FeedPreferencesState prefsState, 152 + int currentTabIndex, 153 + ) { 72 154 return Container( 73 155 decoration: BoxDecoration( 74 156 border: Border(bottom: BorderSide(color: Theme.of(context).dividerColor)), ··· 79 161 children: feeds.asMap().entries.map((entry) { 80 162 final index = entry.key; 81 163 final feed = entry.value; 82 - final isSelected = _currentTabIndex == index; 164 + final isSelected = currentTabIndex == index; 83 165 84 166 return GestureDetector( 85 167 onTap: () { ··· 88 170 duration: const Duration(milliseconds: 300), 89 171 curve: Curves.easeInOut, 90 172 ); 91 - setState(() => _currentTabIndex = index); 173 + setState(() => _selectedFeedId = feed.id); 92 174 }, 93 175 child: Container( 94 176 padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), ··· 101 183 ), 102 184 ), 103 185 child: Text( 104 - _getFeedName(feed), 186 + prefsState.displayNameFor(feed), 105 187 style: Theme.of(context).textTheme.bodyLarge?.copyWith( 106 188 fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, 107 189 color: isSelected ··· 115 197 ), 116 198 ), 117 199 ); 118 - } 119 - 120 - String _getFeedName(SavedFeed feed) { 121 - if (feed.type is SavedFeedTypeKnownValue) { 122 - final knownType = (feed.type as SavedFeedTypeKnownValue).data; 123 - if (knownType == KnownSavedFeedType.timeline) { 124 - return 'Following'; 125 - } 126 - } 127 - return feed.value.split('/').lastOrNull ?? 'Feed'; 128 200 } 129 201 } 130 202
+1 -1
lib/features/feed/presentation/widgets/facet_text.dart
··· 200 200 return; 201 201 } 202 202 203 - router.push('/profile?actor=${Uri.encodeQueryComponent(actor)}'); 203 + router.push('/profile/view?actor=${Uri.encodeQueryComponent(actor)}'); 204 204 } 205 205 206 206 Uri _searchUri(String tag) => Uri.https('bsky.app', '/search', {'q': '#$tag'});
+1 -1
lib/features/feed/presentation/widgets/post_card.dart
··· 341 341 onTap: () { 342 342 final router = GoRouter.maybeOf(context); 343 343 if (router != null) { 344 - router.push('/profile?actor=${Uri.encodeQueryComponent(quoted.author.did)}'); 344 + router.push('/profile/view?actor=${Uri.encodeQueryComponent(quoted.author.did)}'); 345 345 } 346 346 }, 347 347 borderRadius: BorderRadius.circular(16),
+9 -9
lib/features/profile/presentation/profile_screen.dart
··· 10 10 import 'package:url_launcher/url_launcher.dart'; 11 11 12 12 class ProfileScreen extends StatefulWidget { 13 - const ProfileScreen({super.key, this.actor}); 13 + const ProfileScreen({super.key, this.actor, this.showBackButton = false}); 14 14 15 15 final String? actor; 16 + final bool showBackButton; 16 17 17 18 @override 18 19 State<ProfileScreen> createState() => _ProfileScreenState(); ··· 97 98 stretch: true, 98 99 title: innerBoxIsScrolled ? Text(profile?.displayName ?? profile?.handle ?? 'Profile') : null, 99 100 flexibleSpace: FlexibleSpaceBar(background: _buildBanner(context, profile)), 100 - leading: IconButton( 101 - icon: const Icon(Icons.arrow_back), 102 - onPressed: () => context.canPop() ? context.pop() : context.go('/'), 103 - ), 101 + leading: widget.showBackButton 102 + ? IconButton( 103 + icon: const Icon(Icons.arrow_back), 104 + onPressed: () => context.canPop() ? context.pop() : context.go('/profile'), 105 + ) 106 + : null, 104 107 actions: [ 105 - IconButton( 106 - icon: const Icon(Icons.settings_outlined), 107 - onPressed: () => context.push('/settings'), 108 - ), 108 + IconButton(icon: const Icon(Icons.settings_outlined), onPressed: () => context.go('/settings')), 109 109 ], 110 110 ), 111 111 SliverToBoxAdapter(
+9 -3
lib/features/settings/presentation/settings_screen.dart
··· 38 38 title: Text(tokens.displayName ?? tokens.handle), 39 39 subtitle: Text('@${tokens.handle}'), 40 40 trailing: const Icon(Icons.chevron_right), 41 - onTap: () => context.push('/profile'), 41 + onTap: () => context.go('/profile'), 42 42 ); 43 43 }, 44 44 ), ··· 47 47 _buildThemeSelector(context), 48 48 const SizedBox(height: 24), 49 49 _buildSectionHeader(context, 'Account'), 50 + _SettingsTile( 51 + icon: Icons.dynamic_feed_outlined, 52 + title: 'Feeds', 53 + subtitle: 'Manage pinned and saved feeds', 54 + onTap: () => context.push('/feeds'), 55 + ), 50 56 _SettingsTile(icon: Icons.person_outline, title: 'Edit Profile', subtitle: 'Name, bio, avatar', onTap: () {}), 51 57 _SettingsTile(icon: Icons.lock_outline, title: 'Privacy', subtitle: 'Visibility settings', onTap: () {}), 52 58 const SizedBox(height: 24), ··· 68 74 icon: Icons.description_outlined, 69 75 title: 'Logs', 70 76 subtitle: 'View app log files', 71 - onTap: () => context.push('/logs'), 77 + onTap: () => context.push('/settings/logs'), 72 78 ), 73 79 _SettingsTile(icon: Icons.help_outline, title: 'Help & Support', onTap: () {}), 74 80 _SettingsTile(icon: Icons.security_outlined, title: 'Privacy Policy', onTap: () {}), ··· 84 90 }, 85 91 ), 86 92 const SizedBox(height: 24), 87 - Center(child: Text('Lazurite v1.0.0 (Phase 1)', style: Theme.of(context).textTheme.bodySmall)), 93 + Center(child: Text('Lazurite v1.0.0 (Phase 2)', style: Theme.of(context).textTheme.bodySmall)), 88 94 const SizedBox(height: 24), 89 95 ], 90 96 ),
+134
test/core/router/app_router_test.dart
··· 1 + import 'package:bloc_test/bloc_test.dart'; 2 + import 'package:bluesky/app_bsky_actor_defs.dart'; 3 + import 'package:flutter/material.dart'; 4 + import 'package:flutter_bloc/flutter_bloc.dart'; 5 + import 'package:flutter_test/flutter_test.dart'; 6 + import 'package:lazurite/core/router/app_router.dart'; 7 + import 'package:lazurite/core/theme/app_theme.dart'; 8 + import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; 9 + import 'package:lazurite/features/auth/data/models/auth_models.dart'; 10 + import 'package:lazurite/features/feed/bloc/feed_bloc.dart'; 11 + import 'package:lazurite/features/feed/cubit/feed_preferences_cubit.dart'; 12 + import 'package:lazurite/features/profile/bloc/profile_bloc.dart'; 13 + import 'package:lazurite/features/settings/bloc/settings_cubit.dart'; 14 + import 'package:lazurite/features/settings/bloc/settings_state.dart'; 15 + import 'package:mocktail/mocktail.dart'; 16 + 17 + class MockAuthBloc extends MockBloc<AuthEvent, AuthState> implements AuthBloc {} 18 + 19 + class MockFeedPreferencesCubit extends MockCubit<FeedPreferencesState> implements FeedPreferencesCubit {} 20 + 21 + class MockProfileBloc extends MockBloc<ProfileEvent, ProfileState> implements ProfileBloc {} 22 + 23 + class MockFeedBloc extends MockBloc<FeedEvent, FeedState> implements FeedBloc {} 24 + 25 + class MockSettingsCubit extends MockCubit<SettingsState> implements SettingsCubit {} 26 + 27 + void main() { 28 + late MockAuthBloc authBloc; 29 + late MockFeedPreferencesCubit feedPreferencesCubit; 30 + late MockProfileBloc profileBloc; 31 + late MockFeedBloc feedBloc; 32 + late MockSettingsCubit settingsCubit; 33 + 34 + const tokens = AuthTokens( 35 + accessToken: 'access', 36 + refreshToken: 'refresh', 37 + did: 'did:plc:me', 38 + handle: 'me.bsky.social', 39 + displayName: 'River Tam', 40 + ); 41 + 42 + final profile = ProfileViewDetailed( 43 + did: 'did:plc:me', 44 + handle: 'me.bsky.social', 45 + displayName: 'River Tam', 46 + followersCount: 12, 47 + followsCount: 8, 48 + postsCount: 3, 49 + createdAt: DateTime.utc(2024, 3, 1), 50 + ); 51 + 52 + setUp(() { 53 + authBloc = MockAuthBloc(); 54 + feedPreferencesCubit = MockFeedPreferencesCubit(); 55 + profileBloc = MockProfileBloc(); 56 + feedBloc = MockFeedBloc(); 57 + settingsCubit = MockSettingsCubit(); 58 + 59 + when(() => authBloc.state).thenReturn(const AuthState.authenticated(tokens)); 60 + when(() => feedPreferencesCubit.state).thenReturn(const FeedPreferencesState.loaded(feeds: [])); 61 + when(() => profileBloc.state).thenReturn(ProfileState.loaded(profile: profile)); 62 + when(() => feedBloc.state).thenReturn( 63 + const FeedState.loaded(actor: 'did:plc:me', posts: [], filter: FeedFilter.postsNoReplies, hasMore: false), 64 + ); 65 + when(() => settingsCubit.state).thenReturn( 66 + const SettingsState( 67 + themePalette: AppThemePalette.oxocarbon, 68 + themeVariant: AppThemeVariant.dark, 69 + useSystemTheme: false, 70 + ), 71 + ); 72 + 73 + whenListen(authBloc, const Stream<AuthState>.empty(), initialState: const AuthState.authenticated(tokens)); 74 + whenListen( 75 + feedPreferencesCubit, 76 + const Stream<FeedPreferencesState>.empty(), 77 + initialState: const FeedPreferencesState.loaded(feeds: []), 78 + ); 79 + whenListen(profileBloc, const Stream<ProfileState>.empty(), initialState: ProfileState.loaded(profile: profile)); 80 + whenListen( 81 + feedBloc, 82 + const Stream<FeedState>.empty(), 83 + initialState: const FeedState.loaded( 84 + actor: 'did:plc:me', 85 + posts: [], 86 + filter: FeedFilter.postsNoReplies, 87 + hasMore: false, 88 + ), 89 + ); 90 + whenListen( 91 + settingsCubit, 92 + const Stream<SettingsState>.empty(), 93 + initialState: const SettingsState( 94 + themePalette: AppThemePalette.oxocarbon, 95 + themeVariant: AppThemeVariant.dark, 96 + useSystemTheme: false, 97 + ), 98 + ); 99 + }); 100 + 101 + Widget buildSubject() { 102 + return MultiBlocProvider( 103 + providers: [ 104 + BlocProvider<AuthBloc>.value(value: authBloc), 105 + BlocProvider<FeedPreferencesCubit>.value(value: feedPreferencesCubit), 106 + BlocProvider<ProfileBloc>.value(value: profileBloc), 107 + BlocProvider<FeedBloc>.value(value: feedBloc), 108 + BlocProvider<SettingsCubit>.value(value: settingsCubit), 109 + ], 110 + child: MaterialApp.router(routerConfig: AppRouter(authBloc: authBloc).router), 111 + ); 112 + } 113 + 114 + testWidgets('renders bottom navigation and switches authenticated branches', (tester) async { 115 + await tester.pumpWidget(buildSubject()); 116 + await tester.pumpAndSettle(); 117 + 118 + expect(find.text('Home'), findsAtLeastNWidgets(1)); 119 + expect(find.text('Profile'), findsAtLeastNWidgets(1)); 120 + expect(find.text('Settings'), findsAtLeastNWidgets(1)); 121 + expect(find.text('No feeds pinned'), findsOneWidget); 122 + 123 + await tester.tap(find.text('Profile').last); 124 + await tester.pumpAndSettle(); 125 + 126 + expect(find.text('River Tam'), findsOneWidget); 127 + 128 + await tester.tap(find.text('Settings').last); 129 + await tester.pumpAndSettle(); 130 + 131 + expect(find.text('Settings'), findsAtLeastNWidgets(1)); 132 + expect(find.text('APPEARANCE'), findsOneWidget); 133 + }); 134 + }
+50 -1
test/features/feed/cubit/feed_preferences_cubit_test.dart
··· 1 + import 'package:atproto_core/atproto_core.dart'; 1 2 import 'package:bloc_test/bloc_test.dart'; 2 3 import 'package:bluesky/app_bsky_actor_defs.dart'; 4 + import 'package:bluesky/app_bsky_feed_defs.dart'; 3 5 import 'package:drift/drift.dart' hide isNotNull; 4 6 import 'package:drift/native.dart'; 5 7 import 'package:flutter_test/flutter_test.dart'; ··· 258 260 build: () => 259 261 FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 260 262 seed: () => FeedPreferencesState.loaded(feeds: [createTestFeed(id: 'feed-1', pinned: true)]), 261 - setUp: () { 263 + setUp: () async { 264 + await database.replaceSavedFeeds('did:plc:test', [ 265 + SavedFeedsCompanion( 266 + id: const Value('feed-1'), 267 + accountDid: const Value('did:plc:test'), 268 + type: const Value('{"\$type":"app.bsky.actor.defs#savedFeedTypeKnownValue","data":"feed"}'), 269 + value: const Value('at://did:plc:test/app.bsky.feed.generator/test'), 270 + pinned: const Value(false), 271 + sortOrder: const Value(0), 272 + updatedAt: Value(DateTime.now()), 273 + ), 274 + ]); 262 275 when(() => mockFeedRepository.getPreferences()).thenThrow(Exception('API error')); 263 276 }, 264 277 act: (cubit) => cubit.pinFeed('feed-1'), ··· 266 279 isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.saving), 267 280 isA<FeedPreferencesState>() 268 281 .having((s) => s.status, 'status', FeedPreferencesStatus.saveError) 282 + .having((s) => s.feeds.first.pinned, 'pinned', true) 269 283 .having((s) => s.message, 'message', isNotNull), 270 284 ], 285 + verify: (_) async { 286 + final cached = await database.getSavedFeeds('did:plc:test'); 287 + expect(cached.single.pinned, isFalse); 288 + }, 289 + ); 290 + 291 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 292 + 'addFeed ignores duplicate feed values', 293 + build: () => 294 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 295 + seed: () => FeedPreferencesState.loaded(feeds: [createTestFeed(id: 'feed-1', pinned: true)]), 296 + act: (cubit) => cubit.addFeed( 297 + type: const SavedFeedType.knownValue(data: KnownSavedFeedType.feed), 298 + value: 'at://did:plc:test/app.bsky.feed.generator/test', 299 + ), 300 + expect: () => [], 271 301 ); 272 302 273 303 blocTest<FeedPreferencesCubit, FeedPreferencesState>( ··· 276 306 FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 277 307 seed: () => FeedPreferencesState.saveError( 278 308 feeds: [createTestFeed(id: 'feed-1', pinned: true)], 309 + generatorViews: const [], 279 310 message: 'Error', 280 311 previousState: FeedPreferencesState.loaded(feeds: [createTestFeed(id: 'feed-1', pinned: false)]), 281 312 ), ··· 322 353 323 354 expect(copied.status, FeedPreferencesStatus.saving); 324 355 expect(copied.feeds.length, 1); 356 + }); 357 + 358 + test('displayNameFor prefers hydrated generator metadata', () { 359 + final state = FeedPreferencesState.loaded( 360 + feeds: [createTestFeed(id: '1')], 361 + generatorViews: [ 362 + GeneratorView( 363 + uri: const AtUri('at://did:plc:test/app.bsky.feed.generator/test'), 364 + cid: 'cid-1', 365 + creator: const ProfileView(did: 'did:plc:creator', handle: 'creator.bsky.social'), 366 + did: 'did:plc:test', 367 + displayName: 'What\'s Hot', 368 + indexedAt: DateTime.utc(2026, 3, 16), 369 + ), 370 + ], 371 + ); 372 + 373 + expect(state.displayNameFor(state.feeds.single), 'What\'s Hot'); 325 374 }); 326 375 }); 327 376 }