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: add feed management screen

* update home to show feeds

+3484 -732
+492 -442
docs/designs/compose.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>Compose - 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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css"> 11 - <link rel="stylesheet" href="styles.css"> 12 - <style> 13 - .compose-header { 14 - display: flex; 15 - align-items: center; 16 - justify-content: space-between; 17 - padding: 12px 16px; 18 - border-bottom: 1px solid var(--border); 19 - background-color: var(--bg); 20 - position: sticky; 21 - top: 0; 22 - z-index: 50; 23 - } 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>Compose - 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 + .compose-header { 16 + display: flex; 17 + align-items: center; 18 + justify-content: space-between; 19 + padding: 12px 16px; 20 + border-bottom: 1px solid var(--border); 21 + background-color: var(--bg); 22 + position: sticky; 23 + top: 0; 24 + z-index: 50; 25 + } 24 26 25 - .compose-header-cancel { 26 - background: none; 27 - border: none; 28 - color: var(--text-secondary); 29 - font-size: 15px; 30 - font-weight: 500; 31 - cursor: pointer; 32 - } 27 + .compose-header-cancel { 28 + background: none; 29 + border: none; 30 + color: var(--text-secondary); 31 + font-size: 15px; 32 + font-weight: 500; 33 + cursor: pointer; 34 + } 33 35 34 - .compose-header-title { 35 - font-size: 18px; 36 - font-weight: 600; 37 - color: var(--text-primary); 38 - font-family: var(--font-heading); 39 - } 36 + .compose-header-title { 37 + font-size: 18px; 38 + font-weight: 600; 39 + color: var(--text-primary); 40 + font-family: var(--font-heading); 41 + } 40 42 41 - .compose-header-post { 42 - padding: 8px 20px; 43 - border-radius: 9999px; 44 - border: none; 45 - background-color: var(--accent-primary); 46 - color: white; 47 - font-weight: 600; 48 - font-size: 14px; 49 - cursor: pointer; 50 - transition: background-color 0.2s ease; 51 - } 43 + .compose-header-post { 44 + padding: 8px 20px; 45 + border-radius: 9999px; 46 + border: none; 47 + background-color: var(--accent-primary); 48 + color: white; 49 + font-weight: 600; 50 + font-size: 14px; 51 + cursor: pointer; 52 + transition: background-color 0.2s ease; 53 + } 52 54 53 - .compose-header-post:hover { 54 - background-color: var(--accent-primary-hover); 55 - } 55 + .compose-header-post:hover { 56 + background-color: var(--accent-primary-hover); 57 + } 56 58 57 - .compose-header-post:disabled { 58 - opacity: 0.5; 59 - cursor: not-allowed; 60 - } 59 + .compose-header-post:disabled { 60 + opacity: 0.5; 61 + cursor: not-allowed; 62 + } 61 63 62 - .compose-body { 63 - padding: 16px; 64 - display: flex; 65 - gap: 12px; 66 - min-height: 200px; 67 - } 64 + .compose-body { 65 + padding: 16px; 66 + display: flex; 67 + gap: 12px; 68 + min-height: 200px; 69 + } 68 70 69 - .compose-textarea { 70 - flex: 1; 71 - border: none; 72 - background: transparent; 73 - color: var(--text-primary); 74 - font-size: 16px; 75 - font-family: var(--font-body); 76 - resize: none; 77 - outline: none; 78 - min-height: 160px; 79 - line-height: 1.5; 80 - } 71 + .compose-textarea { 72 + flex: 1; 73 + border: none; 74 + background: transparent; 75 + color: var(--text-primary); 76 + font-size: 16px; 77 + font-family: var(--font-body); 78 + resize: none; 79 + outline: none; 80 + min-height: 160px; 81 + line-height: 1.5; 82 + } 81 83 82 - .compose-textarea::placeholder { 83 - color: var(--text-muted); 84 - } 84 + .compose-textarea::placeholder { 85 + color: var(--text-muted); 86 + } 85 87 86 - /* Reply Context */ 87 - .reply-context { 88 - padding: 12px 16px; 89 - border-bottom: 1px solid var(--border); 90 - display: flex; 91 - align-items: center; 92 - gap: 8px; 93 - background-color: var(--surface); 94 - } 88 + /* Reply Context */ 89 + .reply-context { 90 + padding: 12px 16px; 91 + border-bottom: 1px solid var(--border); 92 + display: flex; 93 + align-items: center; 94 + gap: 8px; 95 + background-color: var(--surface); 96 + } 95 97 96 - .reply-context-icon { 97 - width: 16px; 98 - height: 16px; 99 - color: var(--text-muted); 100 - } 98 + .reply-context-icon { 99 + width: 16px; 100 + height: 16px; 101 + color: var(--text-muted); 102 + } 101 103 102 - .reply-context-text { 103 - font-size: 13px; 104 - color: var(--text-secondary); 105 - } 104 + .reply-context-text { 105 + font-size: 13px; 106 + color: var(--text-secondary); 107 + } 106 108 107 - .reply-context-handle { 108 - color: var(--accent-primary); 109 - font-weight: 500; 110 - } 109 + .reply-context-handle { 110 + color: var(--accent-primary); 111 + font-weight: 500; 112 + } 111 113 112 - /* Media Attachments */ 113 - .compose-media { 114 - padding: 0 16px 16px; 115 - padding-left: 68px; 116 - } 114 + /* Media Attachments */ 115 + .compose-media { 116 + padding: 0 16px 16px; 117 + padding-left: 68px; 118 + } 117 119 118 - .media-grid { 119 - display: grid; 120 - grid-template-columns: repeat(2, 1fr); 121 - gap: 8px; 122 - } 120 + .media-grid { 121 + display: grid; 122 + grid-template-columns: repeat(2, 1fr); 123 + gap: 8px; 124 + } 123 125 124 - .media-item { 125 - position: relative; 126 - border-radius: 12px; 127 - overflow: hidden; 128 - background: linear-gradient(135deg, var(--surface) 0%, var(--surface-variant) 100%); 129 - aspect-ratio: 4 / 3; 130 - display: flex; 131 - align-items: center; 132 - justify-content: center; 133 - color: var(--text-muted); 134 - font-size: 13px; 135 - } 126 + .media-item { 127 + position: relative; 128 + border-radius: 12px; 129 + overflow: hidden; 130 + background: linear-gradient(135deg, var(--surface) 0%, var(--surface-variant) 100%); 131 + aspect-ratio: 4 / 3; 132 + display: flex; 133 + align-items: center; 134 + justify-content: center; 135 + color: var(--text-muted); 136 + font-size: 13px; 137 + } 136 138 137 - .media-item-remove { 138 - position: absolute; 139 - top: 6px; 140 - right: 6px; 141 - width: 24px; 142 - height: 24px; 143 - border-radius: 50%; 144 - background-color: rgba(0, 0, 0, 0.6); 145 - border: none; 146 - color: white; 147 - cursor: pointer; 148 - display: flex; 149 - align-items: center; 150 - justify-content: center; 151 - } 139 + .media-item-remove { 140 + position: absolute; 141 + top: 6px; 142 + right: 6px; 143 + width: 24px; 144 + height: 24px; 145 + border-radius: 50%; 146 + background-color: rgba(0, 0, 0, 0.6); 147 + border: none; 148 + color: white; 149 + cursor: pointer; 150 + display: flex; 151 + align-items: center; 152 + justify-content: center; 153 + } 152 154 153 - .media-item-remove svg { 154 - width: 14px; 155 - height: 14px; 156 - } 155 + .media-item-remove svg { 156 + width: 14px; 157 + height: 14px; 158 + } 157 159 158 - .media-item-alt { 159 - position: absolute; 160 - bottom: 6px; 161 - left: 6px; 162 - padding: 2px 8px; 163 - border-radius: 4px; 164 - background-color: rgba(0, 0, 0, 0.6); 165 - color: white; 166 - font-size: 11px; 167 - font-weight: 600; 168 - cursor: pointer; 169 - } 160 + .media-item-alt { 161 + position: absolute; 162 + bottom: 6px; 163 + left: 6px; 164 + padding: 2px 8px; 165 + border-radius: 4px; 166 + background-color: rgba(0, 0, 0, 0.6); 167 + color: white; 168 + font-size: 11px; 169 + font-weight: 600; 170 + cursor: pointer; 171 + } 170 172 171 - .media-item-alt.has-alt { 172 - background-color: var(--accent-primary); 173 - } 173 + .media-item-alt.has-alt { 174 + background-color: var(--accent-primary); 175 + } 174 176 175 - /* Toolbar */ 176 - .compose-toolbar { 177 - display: flex; 178 - align-items: center; 179 - justify-content: space-between; 180 - padding: 12px 16px; 181 - border-top: 1px solid var(--border); 182 - position: sticky; 183 - bottom: 0; 184 - background-color: var(--bg); 185 - } 177 + /* Toolbar */ 178 + .compose-toolbar { 179 + display: flex; 180 + align-items: center; 181 + justify-content: space-between; 182 + padding: 12px 16px; 183 + border-top: 1px solid var(--border); 184 + position: sticky; 185 + bottom: 0; 186 + background-color: var(--bg); 187 + } 186 188 187 - .toolbar-actions { 188 - display: flex; 189 - gap: 4px; 190 - } 189 + .toolbar-actions { 190 + display: flex; 191 + gap: 4px; 192 + } 191 193 192 - .toolbar-btn { 193 - width: 40px; 194 - height: 40px; 195 - border-radius: 50%; 196 - border: none; 197 - background: transparent; 198 - color: var(--accent-primary); 199 - cursor: pointer; 200 - display: flex; 201 - align-items: center; 202 - justify-content: center; 203 - transition: background-color 0.2s ease; 204 - } 194 + .toolbar-btn { 195 + width: 40px; 196 + height: 40px; 197 + border-radius: 50%; 198 + border: none; 199 + background: transparent; 200 + color: var(--accent-primary); 201 + cursor: pointer; 202 + display: flex; 203 + align-items: center; 204 + justify-content: center; 205 + transition: background-color 0.2s ease; 206 + } 205 207 206 - .toolbar-btn:hover { 207 - background-color: var(--surface); 208 - } 208 + .toolbar-btn:hover { 209 + background-color: var(--surface); 210 + } 209 211 210 - .toolbar-btn svg { 211 - width: 22px; 212 - height: 22px; 213 - } 212 + .toolbar-btn svg { 213 + width: 22px; 214 + height: 22px; 215 + } 214 216 215 - .toolbar-btn.disabled { 216 - color: var(--text-muted); 217 - cursor: not-allowed; 218 - } 217 + .toolbar-btn.disabled { 218 + color: var(--text-muted); 219 + cursor: not-allowed; 220 + } 219 221 220 - /* Character Counter */ 221 - .char-counter { 222 - display: flex; 223 - align-items: center; 224 - gap: 8px; 225 - } 222 + /* Character Counter */ 223 + .char-counter { 224 + display: flex; 225 + align-items: center; 226 + gap: 8px; 227 + } 226 228 227 - .char-counter-ring { 228 - width: 28px; 229 - height: 28px; 230 - position: relative; 231 - } 229 + .char-counter-ring { 230 + width: 28px; 231 + height: 28px; 232 + position: relative; 233 + } 232 234 233 - .char-counter-ring svg { 234 - width: 28px; 235 - height: 28px; 236 - transform: rotate(-90deg); 237 - } 235 + .char-counter-ring svg { 236 + width: 28px; 237 + height: 28px; 238 + transform: rotate(-90deg); 239 + } 238 240 239 - .char-counter-ring circle { 240 - fill: none; 241 - stroke-width: 2.5; 242 - } 241 + .char-counter-ring circle { 242 + fill: none; 243 + stroke-width: 2.5; 244 + } 243 245 244 - .char-counter-bg { 245 - stroke: var(--surface-variant); 246 - } 246 + .char-counter-bg { 247 + stroke: var(--surface-variant); 248 + } 247 249 248 - .char-counter-fill { 249 - stroke: var(--accent-primary); 250 - stroke-dasharray: 75.4; 251 - stroke-dashoffset: 30; 252 - stroke-linecap: round; 253 - transition: stroke-dashoffset 0.2s ease, stroke 0.2s ease; 254 - } 250 + .char-counter-fill { 251 + stroke: var(--accent-primary); 252 + stroke-dasharray: 75.4; 253 + stroke-dashoffset: 30; 254 + stroke-linecap: round; 255 + transition: 256 + stroke-dashoffset 0.2s ease, 257 + stroke 0.2s ease; 258 + } 255 259 256 - .char-counter-fill.warning { 257 - stroke: var(--accent-warning); 258 - } 260 + .char-counter-fill.warning { 261 + stroke: var(--accent-warning); 262 + } 259 263 260 - .char-counter-fill.danger { 261 - stroke: var(--accent-error); 262 - } 264 + .char-counter-fill.danger { 265 + stroke: var(--accent-error); 266 + } 263 267 264 - .char-counter-text { 265 - font-size: 13px; 266 - color: var(--text-muted); 267 - font-variant-numeric: tabular-nums; 268 - } 268 + .char-counter-text { 269 + font-size: 13px; 270 + color: var(--text-muted); 271 + font-variant-numeric: tabular-nums; 272 + } 269 273 270 - /* Drafts */ 271 - .drafts-divider { 272 - height: 8px; 273 - background-color: var(--surface); 274 - border-top: 1px solid var(--border); 275 - border-bottom: 1px solid var(--border); 276 - } 274 + /* Drafts */ 275 + .drafts-divider { 276 + height: 8px; 277 + background-color: var(--surface); 278 + border-top: 1px solid var(--border); 279 + border-bottom: 1px solid var(--border); 280 + } 277 281 278 - .drafts-header { 279 - display: flex; 280 - align-items: center; 281 - justify-content: space-between; 282 - padding: 16px; 283 - } 282 + .drafts-header { 283 + display: flex; 284 + align-items: center; 285 + justify-content: space-between; 286 + padding: 16px; 287 + } 284 288 285 - .drafts-title { 286 - font-size: 16px; 287 - font-weight: 600; 288 - color: var(--text-primary); 289 - } 289 + .drafts-title { 290 + font-size: 16px; 291 + font-weight: 600; 292 + color: var(--text-primary); 293 + } 290 294 291 - .drafts-count { 292 - font-size: 13px; 293 - color: var(--text-muted); 294 - } 295 + .drafts-count { 296 + font-size: 13px; 297 + color: var(--text-muted); 298 + } 295 299 296 - .draft-item { 297 - padding: 12px 16px; 298 - border-bottom: 1px solid var(--border); 299 - cursor: pointer; 300 - transition: background-color 0.2s ease; 301 - } 300 + .draft-item { 301 + padding: 12px 16px; 302 + border-bottom: 1px solid var(--border); 303 + cursor: pointer; 304 + transition: background-color 0.2s ease; 305 + } 302 306 303 - .draft-item:hover { 304 - background-color: var(--surface); 305 - } 307 + .draft-item:hover { 308 + background-color: var(--surface); 309 + } 306 310 307 - .draft-item-text { 308 - font-size: 15px; 309 - color: var(--text-primary); 310 - line-height: 1.4; 311 - display: -webkit-box; 312 - -webkit-line-clamp: 2; 313 - -webkit-box-orient: vertical; 314 - overflow: hidden; 315 - } 311 + .draft-item-text { 312 + font-size: 15px; 313 + color: var(--text-primary); 314 + line-height: 1.4; 315 + display: -webkit-box; 316 + line-clamp: 2; 317 + -webkit-line-clamp: 2; 318 + -webkit-box-orient: vertical; 319 + overflow: hidden; 320 + } 316 321 317 - .draft-item-meta { 318 - display: flex; 319 - align-items: center; 320 - gap: 8px; 321 - margin-top: 6px; 322 - font-size: 12px; 323 - color: var(--text-muted); 324 - } 322 + .draft-item-meta { 323 + display: flex; 324 + align-items: center; 325 + gap: 8px; 326 + margin-top: 6px; 327 + font-size: 12px; 328 + color: var(--text-muted); 329 + } 325 330 326 - .draft-item-badge { 327 - padding: 2px 6px; 328 - border-radius: 4px; 329 - background-color: var(--surface-variant); 330 - font-size: 11px; 331 - font-weight: 600; 332 - color: var(--text-secondary); 333 - } 331 + .draft-item-badge { 332 + padding: 2px 6px; 333 + border-radius: 4px; 334 + background-color: var(--surface-variant); 335 + font-size: 11px; 336 + font-weight: 600; 337 + color: var(--text-secondary); 338 + } 334 339 335 - .draft-item-badge.scheduled { 336 - background-color: var(--accent-primary); 337 - color: white; 338 - } 340 + .draft-item-badge.scheduled { 341 + background-color: var(--accent-primary); 342 + color: white; 343 + } 339 344 340 - /* Schedule Pill */ 341 - .schedule-pill { 342 - display: inline-flex; 343 - align-items: center; 344 - gap: 6px; 345 - padding: 6px 12px; 346 - border-radius: 9999px; 347 - background-color: var(--surface); 348 - border: 1px solid var(--border); 349 - font-size: 13px; 350 - color: var(--text-secondary); 351 - margin-left: 68px; 352 - margin-bottom: 12px; 353 - cursor: pointer; 354 - transition: all 0.2s ease; 355 - } 345 + /* Schedule Pill */ 346 + .schedule-pill { 347 + display: inline-flex; 348 + align-items: center; 349 + gap: 6px; 350 + padding: 6px 12px; 351 + border-radius: 9999px; 352 + background-color: var(--surface); 353 + border: 1px solid var(--border); 354 + font-size: 13px; 355 + color: var(--text-secondary); 356 + margin-left: 68px; 357 + margin-bottom: 12px; 358 + cursor: pointer; 359 + transition: all 0.2s ease; 360 + } 356 361 357 - .schedule-pill:hover { 358 - border-color: var(--accent-primary); 359 - color: var(--accent-primary); 360 - } 362 + .schedule-pill:hover { 363 + border-color: var(--accent-primary); 364 + color: var(--accent-primary); 365 + } 361 366 362 - .schedule-pill svg { 363 - width: 14px; 364 - height: 14px; 365 - } 367 + .schedule-pill svg { 368 + width: 14px; 369 + height: 14px; 370 + } 366 371 367 - .schedule-pill.active { 368 - background-color: var(--accent-primary); 369 - border-color: var(--accent-primary); 370 - color: white; 371 - } 372 - </style> 373 - </head> 374 - <body> 375 - <div class="mobile-container"> 372 + .schedule-pill.active { 373 + background-color: var(--accent-primary); 374 + border-color: var(--accent-primary); 375 + color: white; 376 + } 377 + </style> 378 + </head> 379 + <body> 380 + <div class="mobile-container"> 381 + <!-- Compose Header --> 382 + <div class="compose-header"> 383 + <button class="compose-header-cancel">Cancel</button> 384 + <span class="compose-header-title">New Post</span> 385 + <button class="compose-header-post">Post</button> 386 + </div> 376 387 377 - <!-- Compose Header --> 378 - <div class="compose-header"> 379 - <button class="compose-header-cancel">Cancel</button> 380 - <span class="compose-header-title">New Post</span> 381 - <button class="compose-header-post">Post</button> 382 - </div> 388 + <!-- Reply Context (shown when replying) --> 389 + <div class="reply-context"> 390 + <svg 391 + class="reply-context-icon" 392 + viewBox="0 0 24 24" 393 + fill="none" 394 + stroke="currentColor" 395 + stroke-width="2" 396 + stroke-linecap="round" 397 + stroke-linejoin="round"> 398 + <polyline points="9 14 4 9 9 4" /> 399 + <path d="M20 20v-7a4 4 0 0 0-4-4H4" /> 400 + </svg> 401 + <span class="reply-context-text">Replying to <span class="reply-context-handle">@alice.bsky.social</span></span> 402 + </div> 383 403 384 - <!-- Reply Context (shown when replying) --> 385 - <div class="reply-context"> 386 - <svg class="reply-context-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 387 - <polyline points="9 14 4 9 9 4"/> 388 - <path d="M20 20v-7a4 4 0 0 0-4-4H4"/> 389 - </svg> 390 - <span class="reply-context-text">Replying to <span class="reply-context-handle">@alice.bsky.social</span></span> 391 - </div> 404 + <!-- Compose Body --> 405 + <div class="compose-body"> 406 + <div class="avatar avatar-sm">JD</div> 407 + <textarea class="compose-textarea" placeholder="What's on your mind?" rows="6"> 408 + Excited to share my latest project built with the AT Protocol! Check it out and let me know what you think.</textarea 409 + > 410 + </div> 392 411 393 - <!-- Compose Body --> 394 - <div class="compose-body"> 395 - <div class="avatar avatar-sm">JD</div> 396 - <textarea class="compose-textarea" placeholder="What's on your mind?" rows="6">Excited to share my latest project built with the AT Protocol! Check it out and let me know what you think.</textarea> 397 - </div> 412 + <!-- Schedule Pill --> 413 + <div class="schedule-pill"> 414 + <svg 415 + viewBox="0 0 24 24" 416 + fill="none" 417 + stroke="currentColor" 418 + stroke-width="2" 419 + stroke-linecap="round" 420 + stroke-linejoin="round"> 421 + <circle cx="12" cy="12" r="10" /> 422 + <polyline points="12 6 12 12 16 14" /> 423 + </svg> 424 + Schedule for later 425 + </div> 398 426 399 - <!-- Schedule Pill --> 400 - <div class="schedule-pill"> 401 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 402 - <circle cx="12" cy="12" r="10"/> 403 - <polyline points="12 6 12 12 16 14"/> 404 - </svg> 405 - Schedule for later 406 - </div> 427 + <!-- Media Attachments --> 428 + <div class="compose-media"> 429 + <div class="media-grid"> 430 + <div class="media-item"> 431 + [Photo 1] 432 + <button class="media-item-remove"> 433 + <svg 434 + viewBox="0 0 24 24" 435 + fill="none" 436 + stroke="currentColor" 437 + stroke-width="2" 438 + stroke-linecap="round" 439 + stroke-linejoin="round"> 440 + <line x1="18" y1="6" x2="6" y2="18" /> 441 + <line x1="6" y1="6" x2="18" y2="18" /> 442 + </svg> 443 + </button> 444 + <span class="media-item-alt has-alt">ALT</span> 445 + </div> 446 + <div class="media-item"> 447 + [Photo 2] 448 + <button class="media-item-remove"> 449 + <svg 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 + <line x1="18" y1="6" x2="6" y2="18" /> 457 + <line x1="6" y1="6" x2="18" y2="18" /> 458 + </svg> 459 + </button> 460 + <span class="media-item-alt">ALT</span> 461 + </div> 462 + </div> 463 + </div> 407 464 408 - <!-- Media Attachments --> 409 - <div class="compose-media"> 410 - <div class="media-grid"> 411 - <div class="media-item"> 412 - [Photo 1] 413 - <button class="media-item-remove"> 414 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 415 - <line x1="18" y1="6" x2="6" y2="18"/> 416 - <line x1="6" y1="6" x2="18" y2="18"/> 465 + <!-- Toolbar --> 466 + <div class="compose-toolbar"> 467 + <div class="toolbar-actions"> 468 + <!-- Image --> 469 + <button class="toolbar-btn" title="Add image"> 470 + <svg 471 + viewBox="0 0 24 24" 472 + fill="none" 473 + stroke="currentColor" 474 + stroke-width="2" 475 + stroke-linecap="round" 476 + stroke-linejoin="round"> 477 + <rect x="3" y="3" width="18" height="18" rx="2" ry="2" /> 478 + <circle cx="8.5" cy="8.5" r="1.5" /> 479 + <polyline points="21 15 16 10 5 21" /> 417 480 </svg> 418 481 </button> 419 - <span class="media-item-alt has-alt">ALT</span> 420 - </div> 421 - <div class="media-item"> 422 - [Photo 2] 423 - <button class="media-item-remove"> 424 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 425 - <line x1="18" y1="6" x2="6" y2="18"/> 426 - <line x1="6" y1="6" x2="18" y2="18"/> 482 + <!-- Drafts --> 483 + <button class="toolbar-btn" title="Drafts"> 484 + <svg 485 + viewBox="0 0 24 24" 486 + fill="none" 487 + stroke="currentColor" 488 + stroke-width="2" 489 + stroke-linecap="round" 490 + stroke-linejoin="round"> 491 + <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> 492 + <polyline points="14 2 14 8 20 8" /> 493 + <line x1="16" y1="13" x2="8" y2="13" /> 494 + <line x1="16" y1="17" x2="8" y2="17" /> 495 + </svg> 496 + </button> 497 + <!-- Schedule --> 498 + <button class="toolbar-btn" title="Schedule"> 499 + <svg 500 + viewBox="0 0 24 24" 501 + fill="none" 502 + stroke="currentColor" 503 + stroke-width="2" 504 + stroke-linecap="round" 505 + stroke-linejoin="round"> 506 + <circle cx="12" cy="12" r="10" /> 507 + <polyline points="12 6 12 12 16 14" /> 427 508 </svg> 428 509 </button> 429 - <span class="media-item-alt">ALT</span> 430 510 </div> 431 - </div> 432 - </div> 433 511 434 - <!-- Toolbar --> 435 - <div class="compose-toolbar"> 436 - <div class="toolbar-actions"> 437 - <!-- Image --> 438 - <button class="toolbar-btn" title="Add image"> 439 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 440 - <rect x="3" y="3" width="18" height="18" rx="2" ry="2"/> 441 - <circle cx="8.5" cy="8.5" r="1.5"/> 442 - <polyline points="21 15 16 10 5 21"/> 443 - </svg> 444 - </button> 445 - <!-- Drafts --> 446 - <button class="toolbar-btn" title="Drafts"> 447 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 448 - <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/> 449 - <polyline points="14 2 14 8 20 8"/> 450 - <line x1="16" y1="13" x2="8" y2="13"/> 451 - <line x1="16" y1="17" x2="8" y2="17"/> 452 - </svg> 453 - </button> 454 - <!-- Schedule --> 455 - <button class="toolbar-btn" title="Schedule"> 456 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> 457 - <circle cx="12" cy="12" r="10"/> 458 - <polyline points="12 6 12 12 16 14"/> 459 - </svg> 460 - </button> 461 - </div> 462 - 463 - <div class="char-counter"> 464 - <span class="char-counter-text">192</span> 465 - <div class="char-counter-ring"> 466 - <svg viewBox="0 0 28 28"> 467 - <circle class="char-counter-bg" cx="14" cy="14" r="12"/> 468 - <circle class="char-counter-fill" cx="14" cy="14" r="12"/> 469 - </svg> 512 + <div class="char-counter"> 513 + <span class="char-counter-text">192</span> 514 + <div class="char-counter-ring"> 515 + <svg viewBox="0 0 28 28"> 516 + <circle class="char-counter-bg" cx="14" cy="14" r="12" /> 517 + <circle class="char-counter-fill" cx="14" cy="14" r="12" /> 518 + </svg> 519 + </div> 470 520 </div> 471 521 </div> 472 - </div> 473 522 474 - <!-- Drafts Section (shown when drafts toolbar button is tapped) --> 475 - <div class="drafts-divider"></div> 523 + <!-- Drafts Section (shown when drafts toolbar button is tapped) --> 524 + <div class="drafts-divider"></div> 476 525 477 - <div class="drafts-header"> 478 - <span class="drafts-title">Drafts</span> 479 - <span class="drafts-count">3 drafts</span> 480 - </div> 526 + <div class="drafts-header"> 527 + <span class="drafts-title">Drafts</span> 528 + <span class="drafts-count">3 drafts</span> 529 + </div> 481 530 482 - <div class="draft-item"> 483 - <div class="draft-item-text">Working on a thread about decentralised identity and why it matters for the open web...</div> 484 - <div class="draft-item-meta"> 485 - <span>2 hours ago</span> 486 - <span class="draft-item-badge">Draft</span> 531 + <div class="draft-item"> 532 + <div class="draft-item-text"> 533 + Working on a thread about decentralised identity and why it matters for the open web... 534 + </div> 535 + <div class="draft-item-meta"> 536 + <span>2 hours ago</span> 537 + <span class="draft-item-badge">Draft</span> 538 + </div> 487 539 </div> 488 - </div> 489 540 490 - <div class="draft-item"> 491 - <div class="draft-item-text">Hot take: the best developer experience is the one you don't notice</div> 492 - <div class="draft-item-meta"> 493 - <span>Yesterday</span> 494 - <span class="draft-item-badge scheduled">Mar 18, 9:00 AM</span> 541 + <div class="draft-item"> 542 + <div class="draft-item-text">Hot take: the best developer experience is the one you don't notice</div> 543 + <div class="draft-item-meta"> 544 + <span>Yesterday</span> 545 + <span class="draft-item-badge scheduled">Mar 18, 9:00 AM</span> 546 + </div> 495 547 </div> 496 - </div> 497 548 498 - <div class="draft-item"> 499 - <div class="draft-item-text">Quick review of the new AT Protocol SDK features that shipped this week</div> 500 - <div class="draft-item-meta"> 501 - <span>3 days ago</span> 502 - <span class="draft-item-badge">Draft</span> 549 + <div class="draft-item"> 550 + <div class="draft-item-text">Quick review of the new AT Protocol SDK features that shipped this week</div> 551 + <div class="draft-item-meta"> 552 + <span>3 days ago</span> 553 + <span class="draft-item-badge">Draft</span> 554 + </div> 503 555 </div> 504 556 </div> 505 557 506 - </div> 507 - 508 - <script> 509 - if (localStorage.getItem('theme')) { 510 - const t = localStorage.getItem('theme'); 511 - if (t !== 'light') document.documentElement.setAttribute('data-theme', t); 512 - } 513 - </script> 514 - </body> 558 + <script> 559 + if (localStorage.getItem("theme")) { 560 + const t = localStorage.getItem("theme"); 561 + if (t !== "light") document.documentElement.setAttribute("data-theme", t); 562 + } 563 + </script> 564 + </body> 515 565 </html>
+1 -1
docs/designs/devtools.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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css" /> 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"> 13 13 <link rel="stylesheet" href="styles.css" /> 14 14 <style> 15 15 .devtools-container {
+1 -1
docs/designs/feeds.html
··· 7 7 <link rel="preconnect" href="https://fonts.googleapis.com" /> 8 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 9 9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet" /> 10 - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css" /> 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 11 <link rel="stylesheet" href="styles.css" /> 12 12 <style> 13 13 .feeds-container {
+1 -1
docs/designs/home.html
··· 7 7 <link rel="preconnect" href="https://fonts.googleapis.com"> 8 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css"> 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 11 <link rel="stylesheet" href="styles.css"> 12 12 <style> 13 13 .feed-container {
+1 -1
docs/designs/login.html
··· 7 7 <link rel="preconnect" href="https://fonts.googleapis.com"> 8 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css"> 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 11 <link rel="stylesheet" href="styles.css"> 12 12 <style> 13 13 .login-screen {
+1 -1
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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css" /> 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" /> 13 13 <link rel="stylesheet" href="styles.css" /> 14 14 <style> 15 15 .logs-container {
+1 -1
docs/designs/messages.html
··· 7 7 <link rel="preconnect" href="https://fonts.googleapis.com"> 8 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css"> 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 11 <link rel="stylesheet" href="styles.css"> 12 12 <style> 13 13 .messages-container {
+1 -1
docs/designs/notifications.html
··· 7 7 <link rel="preconnect" href="https://fonts.googleapis.com" /> 8 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 9 9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet" /> 10 - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css" /> 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 11 <link rel="stylesheet" href="styles.css" /> 12 12 <style> 13 13 .notif-container {
+1 -1
docs/designs/profile.html
··· 7 7 <link rel="preconnect" href="https://fonts.googleapis.com"> 8 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css"> 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 11 <link rel="stylesheet" href="styles.css"> 12 12 <style> 13 13 .profile-container {
+1 -1
docs/designs/search.html
··· 10 10 <link rel="preconnect" href="https://fonts.googleapis.com" /> 11 11 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 12 12 <link 13 - href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Condensed:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" 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 14 rel="stylesheet" 15 15 /> 16 16 <link rel="stylesheet" href="styles.css" />
+1 -1
docs/designs/settings.html
··· 7 7 <link rel="preconnect" href="https://fonts.googleapis.com"> 8 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet"> 10 - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/geist@1.2.2/dist/fonts/geist-sans/style.css"> 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 11 <link rel="stylesheet" href="styles.css"> 12 12 <style> 13 13 .settings-container {
+1 -1
docs/designs/styles.css
··· 228 228 /* Font Variables */ 229 229 :root { 230 230 --font-heading: 'Lora', Georgia, 'Times New Roman', serif; 231 - --font-body: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 231 + --font-body: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 232 232 --font-mono: 'JetBrains Mono', 'IBM Plex Mono', 'Fira Code', monospace; 233 233 } 234 234
+7 -7
docs/tasks/phase-2.md
··· 17 17 18 18 ## M5 — Feeds 19 19 20 - - [ ] Build home screen with horizontally-swipable tab bar (one tab per pinned feed) 21 - - [ ] Implement timeline feed via `getTimeline` with cursor pagination 22 - - [ ] Implement feed generator rendering via `getFeed` (AT-URI + pagination) 23 - - [ ] `FeedPreferencesCubit` — read/write `savedFeedsPrefV2` via `getPreferences` / `putPreferences` 24 - - [ ] Cache feed preferences in Drift for offline access 25 - - [ ] Feed discovery screen via `getSuggestedFeeds` — browse and add generators 26 - - [ ] Feed management UI — pin/unpin, drag-to-reorder, remove saved feeds 20 + - [x] Build home screen with horizontally-swipable tab bar (one tab per pinned feed) 21 + - [x] Implement timeline feed via `getTimeline` with cursor pagination 22 + - [x] Implement feed generator rendering via `getFeed` (AT-URI + pagination) 23 + - [x] `FeedPreferencesCubit` — read/write `savedFeedsPrefV2` via `getPreferences` / `putPreferences` 24 + - [x] Cache feed preferences in Drift for offline access 25 + - [x] Feed discovery screen via `getSuggestedFeeds` — browse and add generators 26 + - [x] Feed management UI — pin/unpin, drag-to-reorder, remove saved feeds 27 27 28 28 ## M6 — Search 29 29
+1 -1
justfile
··· 20 20 flutter test {{ paths }} --fail-fast --timeout=120s 21 21 22 22 generate: 23 - dart run build_runner build --delete-conflicting-outputs 23 + flutter pub run build_runner build --delete-conflicting-outputs 24 24 25 25 # Run code gen 26 26 gen: generate format
+28 -2
lib/core/database/app_database.dart
··· 6 6 7 7 part 'app_database.g.dart'; 8 8 9 - @DriftDatabase(tables: [Accounts, CachedProfiles, CachedPosts, Settings]) 9 + @DriftDatabase(tables: [Accounts, CachedProfiles, CachedPosts, Settings, SavedFeeds]) 10 10 class AppDatabase extends _$AppDatabase { 11 11 AppDatabase({QueryExecutor? executor}) : super(executor ?? _openConnection()); 12 12 13 13 @override 14 - int get schemaVersion => 2; 14 + int get schemaVersion => 3; 15 15 16 16 @override 17 17 MigrationStrategy get migration => MigrationStrategy( ··· 25 25 await migrator.addColumn(accounts, accounts.dpopNonce); 26 26 await migrator.createTable(cachedProfiles); 27 27 await migrator.createTable(cachedPosts); 28 + } 29 + if (from < 3) { 30 + await migrator.createTable(savedFeeds); 28 31 } 29 32 }, 30 33 ); ··· 114 117 ); 115 118 116 119 Future<int> deleteSetting(String key) => (delete(settings)..where((s) => s.key.equals(key))).go(); 120 + 121 + Future<List<SavedFeedEntry>> getSavedFeeds(String accountDid) => 122 + (select(savedFeeds) 123 + ..where((f) => f.accountDid.equals(accountDid)) 124 + ..orderBy([(f) => OrderingTerm.asc(f.sortOrder)])) 125 + .get(); 126 + 127 + Future<int> insertSavedFeed(SavedFeedsCompanion feed) => into(savedFeeds).insert(feed, mode: InsertMode.replace); 128 + 129 + Future<int> deleteSavedFeed(String id, String accountDid) => 130 + (delete(savedFeeds)..where((f) => f.id.equals(id) & f.accountDid.equals(accountDid))).go(); 131 + 132 + Future<int> deleteAllSavedFeeds(String accountDid) => 133 + (delete(savedFeeds)..where((f) => f.accountDid.equals(accountDid))).go(); 134 + 135 + Future<void> replaceSavedFeeds(String accountDid, List<SavedFeedsCompanion> feeds) async { 136 + await transaction(() async { 137 + await deleteAllSavedFeeds(accountDid); 138 + for (final feed in feeds) { 139 + await insertSavedFeed(feed); 140 + } 141 + }); 142 + } 117 143 }
+1433 -263
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('displayName'); 29 + static const VerificationMeta _displayNameMeta = const VerificationMeta( 30 + 'displayName', 31 + ); 30 32 @override 31 33 late final GeneratedColumn<String> displayName = GeneratedColumn<String>( 32 34 'display_name', ··· 35 37 type: DriftSqlType.string, 36 38 requiredDuringInsert: false, 37 39 ); 38 - static const VerificationMeta _serviceMeta = const VerificationMeta('service'); 40 + static const VerificationMeta _serviceMeta = const VerificationMeta( 41 + 'service', 42 + ); 39 43 @override 40 44 late final GeneratedColumn<String> service = GeneratedColumn<String>( 41 45 'service', ··· 44 48 type: DriftSqlType.string, 45 49 requiredDuringInsert: false, 46 50 ); 47 - static const VerificationMeta _accessTokenMeta = const VerificationMeta('accessToken'); 51 + static const VerificationMeta _accessTokenMeta = const VerificationMeta( 52 + 'accessToken', 53 + ); 48 54 @override 49 55 late final GeneratedColumn<String> accessToken = GeneratedColumn<String>( 50 56 'access_token', ··· 53 59 type: DriftSqlType.string, 54 60 requiredDuringInsert: true, 55 61 ); 56 - static const VerificationMeta _refreshTokenMeta = const VerificationMeta('refreshToken'); 62 + static const VerificationMeta _refreshTokenMeta = const VerificationMeta( 63 + 'refreshToken', 64 + ); 57 65 @override 58 66 late final GeneratedColumn<String> refreshToken = GeneratedColumn<String>( 59 67 'refresh_token', ··· 62 70 type: DriftSqlType.string, 63 71 requiredDuringInsert: false, 64 72 ); 65 - static const VerificationMeta _dpopPublicKeyMeta = const VerificationMeta('dpopPublicKey'); 73 + static const VerificationMeta _dpopPublicKeyMeta = const VerificationMeta( 74 + 'dpopPublicKey', 75 + ); 66 76 @override 67 77 late final GeneratedColumn<String> dpopPublicKey = GeneratedColumn<String>( 68 78 'dpop_public_key', ··· 71 81 type: DriftSqlType.string, 72 82 requiredDuringInsert: false, 73 83 ); 74 - static const VerificationMeta _dpopPrivateKeyMeta = const VerificationMeta('dpopPrivateKey'); 84 + static const VerificationMeta _dpopPrivateKeyMeta = const VerificationMeta( 85 + 'dpopPrivateKey', 86 + ); 75 87 @override 76 88 late final GeneratedColumn<String> dpopPrivateKey = GeneratedColumn<String>( 77 89 'dpop_private_key', ··· 80 92 type: DriftSqlType.string, 81 93 requiredDuringInsert: false, 82 94 ); 83 - static const VerificationMeta _dpopNonceMeta = const VerificationMeta('dpopNonce'); 95 + static const VerificationMeta _dpopNonceMeta = const VerificationMeta( 96 + 'dpopNonce', 97 + ); 84 98 @override 85 99 late final GeneratedColumn<String> dpopNonce = GeneratedColumn<String>( 86 100 'dpop_nonce', ··· 89 103 type: DriftSqlType.string, 90 104 requiredDuringInsert: false, 91 105 ); 92 - static const VerificationMeta _expiresAtMeta = const VerificationMeta('expiresAt'); 106 + static const VerificationMeta _expiresAtMeta = const VerificationMeta( 107 + 'expiresAt', 108 + ); 93 109 @override 94 110 late final GeneratedColumn<DateTime> expiresAt = GeneratedColumn<DateTime>( 95 111 'expires_at', ··· 98 114 type: DriftSqlType.dateTime, 99 115 requiredDuringInsert: false, 100 116 ); 101 - static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); 117 + static const VerificationMeta _createdAtMeta = const VerificationMeta( 118 + 'createdAt', 119 + ); 102 120 @override 103 121 late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( 104 122 'created_at', ··· 108 126 requiredDuringInsert: false, 109 127 defaultValue: currentDateAndTime, 110 128 ); 111 - static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); 129 + static const VerificationMeta _updatedAtMeta = const VerificationMeta( 130 + 'updatedAt', 131 + ); 112 132 @override 113 133 late final GeneratedColumn<DateTime> updatedAt = GeneratedColumn<DateTime>( 114 134 'updated_at', ··· 139 159 String get actualTableName => $name; 140 160 static const String $name = 'accounts'; 141 161 @override 142 - VerificationContext validateIntegrity(Insertable<Account> instance, {bool isInserting = false}) { 162 + VerificationContext validateIntegrity( 163 + Insertable<Account> instance, { 164 + bool isInserting = false, 165 + }) { 143 166 final context = VerificationContext(); 144 167 final data = instance.toColumns(true); 145 168 if (data.containsKey('did')) { 146 - context.handle(_didMeta, did.isAcceptableOrUnknown(data['did']!, _didMeta)); 169 + context.handle( 170 + _didMeta, 171 + did.isAcceptableOrUnknown(data['did']!, _didMeta), 172 + ); 147 173 } else if (isInserting) { 148 174 context.missing(_didMeta); 149 175 } 150 176 if (data.containsKey('handle')) { 151 - context.handle(_handleMeta, handle.isAcceptableOrUnknown(data['handle']!, _handleMeta)); 177 + context.handle( 178 + _handleMeta, 179 + handle.isAcceptableOrUnknown(data['handle']!, _handleMeta), 180 + ); 152 181 } else if (isInserting) { 153 182 context.missing(_handleMeta); 154 183 } 155 184 if (data.containsKey('display_name')) { 156 - context.handle(_displayNameMeta, displayName.isAcceptableOrUnknown(data['display_name']!, _displayNameMeta)); 185 + context.handle( 186 + _displayNameMeta, 187 + displayName.isAcceptableOrUnknown( 188 + data['display_name']!, 189 + _displayNameMeta, 190 + ), 191 + ); 157 192 } 158 193 if (data.containsKey('service')) { 159 - context.handle(_serviceMeta, service.isAcceptableOrUnknown(data['service']!, _serviceMeta)); 194 + context.handle( 195 + _serviceMeta, 196 + service.isAcceptableOrUnknown(data['service']!, _serviceMeta), 197 + ); 160 198 } 161 199 if (data.containsKey('access_token')) { 162 - context.handle(_accessTokenMeta, accessToken.isAcceptableOrUnknown(data['access_token']!, _accessTokenMeta)); 200 + context.handle( 201 + _accessTokenMeta, 202 + accessToken.isAcceptableOrUnknown( 203 + data['access_token']!, 204 + _accessTokenMeta, 205 + ), 206 + ); 163 207 } else if (isInserting) { 164 208 context.missing(_accessTokenMeta); 165 209 } 166 210 if (data.containsKey('refresh_token')) { 167 - context.handle(_refreshTokenMeta, refreshToken.isAcceptableOrUnknown(data['refresh_token']!, _refreshTokenMeta)); 211 + context.handle( 212 + _refreshTokenMeta, 213 + refreshToken.isAcceptableOrUnknown( 214 + data['refresh_token']!, 215 + _refreshTokenMeta, 216 + ), 217 + ); 168 218 } 169 219 if (data.containsKey('dpop_public_key')) { 170 220 context.handle( 171 221 _dpopPublicKeyMeta, 172 - dpopPublicKey.isAcceptableOrUnknown(data['dpop_public_key']!, _dpopPublicKeyMeta), 222 + dpopPublicKey.isAcceptableOrUnknown( 223 + data['dpop_public_key']!, 224 + _dpopPublicKeyMeta, 225 + ), 173 226 ); 174 227 } 175 228 if (data.containsKey('dpop_private_key')) { 176 229 context.handle( 177 230 _dpopPrivateKeyMeta, 178 - dpopPrivateKey.isAcceptableOrUnknown(data['dpop_private_key']!, _dpopPrivateKeyMeta), 231 + dpopPrivateKey.isAcceptableOrUnknown( 232 + data['dpop_private_key']!, 233 + _dpopPrivateKeyMeta, 234 + ), 179 235 ); 180 236 } 181 237 if (data.containsKey('dpop_nonce')) { 182 - context.handle(_dpopNonceMeta, dpopNonce.isAcceptableOrUnknown(data['dpop_nonce']!, _dpopNonceMeta)); 238 + context.handle( 239 + _dpopNonceMeta, 240 + dpopNonce.isAcceptableOrUnknown(data['dpop_nonce']!, _dpopNonceMeta), 241 + ); 183 242 } 184 243 if (data.containsKey('expires_at')) { 185 - context.handle(_expiresAtMeta, expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta)); 244 + context.handle( 245 + _expiresAtMeta, 246 + expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta), 247 + ); 186 248 } 187 249 if (data.containsKey('created_at')) { 188 - context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); 250 + context.handle( 251 + _createdAtMeta, 252 + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), 253 + ); 189 254 } 190 255 if (data.containsKey('updated_at')) { 191 - context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); 256 + context.handle( 257 + _updatedAtMeta, 258 + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), 259 + ); 192 260 } 193 261 return context; 194 262 } ··· 199 267 Account map(Map<String, dynamic> data, {String? tablePrefix}) { 200 268 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 201 269 return Account( 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']), 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 + ), 209 298 dpopPrivateKey: attachedDatabase.typeMapping.read( 210 299 DriftSqlType.string, 211 300 data['${effectivePrefix}dpop_private_key'], 212 301 ), 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'])!, 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 + )!, 217 318 ); 218 319 } 219 320 ··· 286 387 return AccountsCompanion( 287 388 did: Value(did), 288 389 handle: Value(handle), 289 - displayName: displayName == null && nullToAbsent ? const Value.absent() : Value(displayName), 290 - service: service == null && nullToAbsent ? const Value.absent() : Value(service), 390 + displayName: displayName == null && nullToAbsent 391 + ? const Value.absent() 392 + : Value(displayName), 393 + service: service == null && nullToAbsent 394 + ? const Value.absent() 395 + : Value(service), 291 396 accessToken: Value(accessToken), 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), 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), 297 412 createdAt: Value(createdAt), 298 413 updatedAt: Value(updatedAt), 299 414 ); 300 415 } 301 416 302 - factory Account.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 417 + factory Account.fromJson( 418 + Map<String, dynamic> json, { 419 + ValueSerializer? serializer, 420 + }) { 303 421 serializer ??= driftRuntimeOptions.defaultSerializer; 304 422 return Account( 305 423 did: serializer.fromJson<String>(json['did']), ··· 355 473 service: service.present ? service.value : this.service, 356 474 accessToken: accessToken ?? this.accessToken, 357 475 refreshToken: refreshToken.present ? refreshToken.value : this.refreshToken, 358 - dpopPublicKey: dpopPublicKey.present ? dpopPublicKey.value : this.dpopPublicKey, 359 - dpopPrivateKey: dpopPrivateKey.present ? dpopPrivateKey.value : this.dpopPrivateKey, 476 + dpopPublicKey: dpopPublicKey.present 477 + ? dpopPublicKey.value 478 + : this.dpopPublicKey, 479 + dpopPrivateKey: dpopPrivateKey.present 480 + ? dpopPrivateKey.value 481 + : this.dpopPrivateKey, 360 482 dpopNonce: dpopNonce.present ? dpopNonce.value : this.dpopNonce, 361 483 expiresAt: expiresAt.present ? expiresAt.value : this.expiresAt, 362 484 createdAt: createdAt ?? this.createdAt, ··· 366 488 return Account( 367 489 did: data.did.present ? data.did.value : this.did, 368 490 handle: data.handle.present ? data.handle.value : this.handle, 369 - displayName: data.displayName.present ? data.displayName.value : this.displayName, 491 + displayName: data.displayName.present 492 + ? data.displayName.value 493 + : this.displayName, 370 494 service: data.service.present ? data.service.value : this.service, 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, 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, 375 507 dpopNonce: data.dpopNonce.present ? data.dpopNonce.value : this.dpopNonce, 376 508 expiresAt: data.expiresAt.present ? data.expiresAt.value : this.expiresAt, 377 509 createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ··· 607 739 } 608 740 } 609 741 610 - class $CachedProfilesTable extends CachedProfiles with TableInfo<$CachedProfilesTable, CachedProfile> { 742 + class $CachedProfilesTable extends CachedProfiles 743 + with TableInfo<$CachedProfilesTable, CachedProfile> { 611 744 @override 612 745 final GeneratedDatabase attachedDatabase; 613 746 final String? _alias; ··· 630 763 type: DriftSqlType.string, 631 764 requiredDuringInsert: true, 632 765 ); 633 - static const VerificationMeta _payloadMeta = const VerificationMeta('payload'); 766 + static const VerificationMeta _payloadMeta = const VerificationMeta( 767 + 'payload', 768 + ); 634 769 @override 635 770 late final GeneratedColumn<String> payload = GeneratedColumn<String>( 636 771 'payload', ··· 639 774 type: DriftSqlType.string, 640 775 requiredDuringInsert: true, 641 776 ); 642 - static const VerificationMeta _fetchedAtMeta = const VerificationMeta('fetchedAt'); 777 + static const VerificationMeta _fetchedAtMeta = const VerificationMeta( 778 + 'fetchedAt', 779 + ); 643 780 @override 644 781 late final GeneratedColumn<DateTime> fetchedAt = GeneratedColumn<DateTime>( 645 782 'fetched_at', ··· 657 794 String get actualTableName => $name; 658 795 static const String $name = 'cached_profiles'; 659 796 @override 660 - VerificationContext validateIntegrity(Insertable<CachedProfile> instance, {bool isInserting = false}) { 797 + VerificationContext validateIntegrity( 798 + Insertable<CachedProfile> instance, { 799 + bool isInserting = false, 800 + }) { 661 801 final context = VerificationContext(); 662 802 final data = instance.toColumns(true); 663 803 if (data.containsKey('did')) { 664 - context.handle(_didMeta, did.isAcceptableOrUnknown(data['did']!, _didMeta)); 804 + context.handle( 805 + _didMeta, 806 + did.isAcceptableOrUnknown(data['did']!, _didMeta), 807 + ); 665 808 } else if (isInserting) { 666 809 context.missing(_didMeta); 667 810 } 668 811 if (data.containsKey('handle')) { 669 - context.handle(_handleMeta, handle.isAcceptableOrUnknown(data['handle']!, _handleMeta)); 812 + context.handle( 813 + _handleMeta, 814 + handle.isAcceptableOrUnknown(data['handle']!, _handleMeta), 815 + ); 670 816 } else if (isInserting) { 671 817 context.missing(_handleMeta); 672 818 } 673 819 if (data.containsKey('payload')) { 674 - context.handle(_payloadMeta, payload.isAcceptableOrUnknown(data['payload']!, _payloadMeta)); 820 + context.handle( 821 + _payloadMeta, 822 + payload.isAcceptableOrUnknown(data['payload']!, _payloadMeta), 823 + ); 675 824 } else if (isInserting) { 676 825 context.missing(_payloadMeta); 677 826 } 678 827 if (data.containsKey('fetched_at')) { 679 - context.handle(_fetchedAtMeta, fetchedAt.isAcceptableOrUnknown(data['fetched_at']!, _fetchedAtMeta)); 828 + context.handle( 829 + _fetchedAtMeta, 830 + fetchedAt.isAcceptableOrUnknown(data['fetched_at']!, _fetchedAtMeta), 831 + ); 680 832 } 681 833 return context; 682 834 } ··· 687 839 CachedProfile map(Map<String, dynamic> data, {String? tablePrefix}) { 688 840 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 689 841 return CachedProfile( 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'])!, 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 + )!, 694 858 ); 695 859 } 696 860 ··· 705 869 final String handle; 706 870 final String payload; 707 871 final DateTime fetchedAt; 708 - const CachedProfile({required this.did, required this.handle, required this.payload, required this.fetchedAt}); 872 + const CachedProfile({ 873 + required this.did, 874 + required this.handle, 875 + required this.payload, 876 + required this.fetchedAt, 877 + }); 709 878 @override 710 879 Map<String, Expression> toColumns(bool nullToAbsent) { 711 880 final map = <String, Expression>{}; ··· 725 894 ); 726 895 } 727 896 728 - factory CachedProfile.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 897 + factory CachedProfile.fromJson( 898 + Map<String, dynamic> json, { 899 + ValueSerializer? serializer, 900 + }) { 729 901 serializer ??= driftRuntimeOptions.defaultSerializer; 730 902 return CachedProfile( 731 903 did: serializer.fromJson<String>(json['did']), ··· 745 917 }; 746 918 } 747 919 748 - CachedProfile copyWith({String? did, String? handle, String? payload, DateTime? fetchedAt}) => CachedProfile( 920 + CachedProfile copyWith({ 921 + String? did, 922 + String? handle, 923 + String? payload, 924 + DateTime? fetchedAt, 925 + }) => CachedProfile( 749 926 did: did ?? this.did, 750 927 handle: handle ?? this.handle, 751 928 payload: payload ?? this.payload, ··· 871 1048 } 872 1049 } 873 1050 874 - class $CachedPostsTable extends CachedPosts with TableInfo<$CachedPostsTable, CachedPost> { 1051 + class $CachedPostsTable extends CachedPosts 1052 + with TableInfo<$CachedPostsTable, CachedPost> { 875 1053 @override 876 1054 final GeneratedDatabase attachedDatabase; 877 1055 final String? _alias; ··· 885 1063 type: DriftSqlType.string, 886 1064 requiredDuringInsert: true, 887 1065 ); 888 - static const VerificationMeta _authorDidMeta = const VerificationMeta('authorDid'); 1066 + static const VerificationMeta _authorDidMeta = const VerificationMeta( 1067 + 'authorDid', 1068 + ); 889 1069 @override 890 1070 late final GeneratedColumn<String> authorDid = GeneratedColumn<String>( 891 1071 'author_did', ··· 894 1074 type: DriftSqlType.string, 895 1075 requiredDuringInsert: true, 896 1076 ); 897 - static const VerificationMeta _payloadMeta = const VerificationMeta('payload'); 1077 + static const VerificationMeta _payloadMeta = const VerificationMeta( 1078 + 'payload', 1079 + ); 898 1080 @override 899 1081 late final GeneratedColumn<String> payload = GeneratedColumn<String>( 900 1082 'payload', ··· 903 1085 type: DriftSqlType.string, 904 1086 requiredDuringInsert: true, 905 1087 ); 906 - static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); 1088 + static const VerificationMeta _createdAtMeta = const VerificationMeta( 1089 + 'createdAt', 1090 + ); 907 1091 @override 908 1092 late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>( 909 1093 'created_at', ··· 912 1096 type: DriftSqlType.dateTime, 913 1097 requiredDuringInsert: false, 914 1098 ); 915 - static const VerificationMeta _fetchedAtMeta = const VerificationMeta('fetchedAt'); 1099 + static const VerificationMeta _fetchedAtMeta = const VerificationMeta( 1100 + 'fetchedAt', 1101 + ); 916 1102 @override 917 1103 late final GeneratedColumn<DateTime> fetchedAt = GeneratedColumn<DateTime>( 918 1104 'fetched_at', ··· 923 1109 defaultValue: currentDateAndTime, 924 1110 ); 925 1111 @override 926 - List<GeneratedColumn> get $columns => [uri, authorDid, payload, createdAt, fetchedAt]; 1112 + List<GeneratedColumn> get $columns => [ 1113 + uri, 1114 + authorDid, 1115 + payload, 1116 + createdAt, 1117 + fetchedAt, 1118 + ]; 927 1119 @override 928 1120 String get aliasedName => _alias ?? actualTableName; 929 1121 @override 930 1122 String get actualTableName => $name; 931 1123 static const String $name = 'cached_posts'; 932 1124 @override 933 - VerificationContext validateIntegrity(Insertable<CachedPost> instance, {bool isInserting = false}) { 1125 + VerificationContext validateIntegrity( 1126 + Insertable<CachedPost> instance, { 1127 + bool isInserting = false, 1128 + }) { 934 1129 final context = VerificationContext(); 935 1130 final data = instance.toColumns(true); 936 1131 if (data.containsKey('uri')) { 937 - context.handle(_uriMeta, uri.isAcceptableOrUnknown(data['uri']!, _uriMeta)); 1132 + context.handle( 1133 + _uriMeta, 1134 + uri.isAcceptableOrUnknown(data['uri']!, _uriMeta), 1135 + ); 938 1136 } else if (isInserting) { 939 1137 context.missing(_uriMeta); 940 1138 } 941 1139 if (data.containsKey('author_did')) { 942 - context.handle(_authorDidMeta, authorDid.isAcceptableOrUnknown(data['author_did']!, _authorDidMeta)); 1140 + context.handle( 1141 + _authorDidMeta, 1142 + authorDid.isAcceptableOrUnknown(data['author_did']!, _authorDidMeta), 1143 + ); 943 1144 } else if (isInserting) { 944 1145 context.missing(_authorDidMeta); 945 1146 } 946 1147 if (data.containsKey('payload')) { 947 - context.handle(_payloadMeta, payload.isAcceptableOrUnknown(data['payload']!, _payloadMeta)); 1148 + context.handle( 1149 + _payloadMeta, 1150 + payload.isAcceptableOrUnknown(data['payload']!, _payloadMeta), 1151 + ); 948 1152 } else if (isInserting) { 949 1153 context.missing(_payloadMeta); 950 1154 } 951 1155 if (data.containsKey('created_at')) { 952 - context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); 1156 + context.handle( 1157 + _createdAtMeta, 1158 + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), 1159 + ); 953 1160 } 954 1161 if (data.containsKey('fetched_at')) { 955 - context.handle(_fetchedAtMeta, fetchedAt.isAcceptableOrUnknown(data['fetched_at']!, _fetchedAtMeta)); 1162 + context.handle( 1163 + _fetchedAtMeta, 1164 + fetchedAt.isAcceptableOrUnknown(data['fetched_at']!, _fetchedAtMeta), 1165 + ); 956 1166 } 957 1167 return context; 958 1168 } ··· 963 1173 CachedPost map(Map<String, dynamic> data, {String? tablePrefix}) { 964 1174 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 965 1175 return CachedPost( 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'])!, 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 + )!, 971 1196 ); 972 1197 } 973 1198 ··· 1008 1233 uri: Value(uri), 1009 1234 authorDid: Value(authorDid), 1010 1235 payload: Value(payload), 1011 - createdAt: createdAt == null && nullToAbsent ? const Value.absent() : Value(createdAt), 1236 + createdAt: createdAt == null && nullToAbsent 1237 + ? const Value.absent() 1238 + : Value(createdAt), 1012 1239 fetchedAt: Value(fetchedAt), 1013 1240 ); 1014 1241 } 1015 1242 1016 - factory CachedPost.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 1243 + factory CachedPost.fromJson( 1244 + Map<String, dynamic> json, { 1245 + ValueSerializer? serializer, 1246 + }) { 1017 1247 serializer ??= driftRuntimeOptions.defaultSerializer; 1018 1248 return CachedPost( 1019 1249 uri: serializer.fromJson<String>(json['uri']), ··· 1071 1301 } 1072 1302 1073 1303 @override 1074 - int get hashCode => Object.hash(uri, authorDid, payload, createdAt, fetchedAt); 1304 + int get hashCode => 1305 + Object.hash(uri, authorDid, payload, createdAt, fetchedAt); 1075 1306 @override 1076 1307 bool operator ==(Object other) => 1077 1308 identical(this, other) || ··· 1182 1413 } 1183 1414 } 1184 1415 1185 - class $SettingsTable extends Settings with TableInfo<$SettingsTable, SettingsEntry> { 1416 + class $SettingsTable extends Settings 1417 + with TableInfo<$SettingsTable, SettingsEntry> { 1186 1418 @override 1187 1419 final GeneratedDatabase attachedDatabase; 1188 1420 final String? _alias; ··· 1205 1437 type: DriftSqlType.string, 1206 1438 requiredDuringInsert: true, 1207 1439 ); 1208 - static const VerificationMeta _updatedAtMeta = const VerificationMeta('updatedAt'); 1440 + static const VerificationMeta _updatedAtMeta = const VerificationMeta( 1441 + 'updatedAt', 1442 + ); 1209 1443 @override 1210 1444 late final GeneratedColumn<DateTime> updatedAt = GeneratedColumn<DateTime>( 1211 1445 'updated_at', ··· 1223 1457 String get actualTableName => $name; 1224 1458 static const String $name = 'settings'; 1225 1459 @override 1226 - VerificationContext validateIntegrity(Insertable<SettingsEntry> instance, {bool isInserting = false}) { 1460 + VerificationContext validateIntegrity( 1461 + Insertable<SettingsEntry> instance, { 1462 + bool isInserting = false, 1463 + }) { 1227 1464 final context = VerificationContext(); 1228 1465 final data = instance.toColumns(true); 1229 1466 if (data.containsKey('key')) { 1230 - context.handle(_keyMeta, key.isAcceptableOrUnknown(data['key']!, _keyMeta)); 1467 + context.handle( 1468 + _keyMeta, 1469 + key.isAcceptableOrUnknown(data['key']!, _keyMeta), 1470 + ); 1231 1471 } else if (isInserting) { 1232 1472 context.missing(_keyMeta); 1233 1473 } 1234 1474 if (data.containsKey('value')) { 1235 - context.handle(_valueMeta, value.isAcceptableOrUnknown(data['value']!, _valueMeta)); 1475 + context.handle( 1476 + _valueMeta, 1477 + value.isAcceptableOrUnknown(data['value']!, _valueMeta), 1478 + ); 1236 1479 } else if (isInserting) { 1237 1480 context.missing(_valueMeta); 1238 1481 } 1239 1482 if (data.containsKey('updated_at')) { 1240 - context.handle(_updatedAtMeta, updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); 1483 + context.handle( 1484 + _updatedAtMeta, 1485 + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), 1486 + ); 1241 1487 } 1242 1488 return context; 1243 1489 } ··· 1248 1494 SettingsEntry map(Map<String, dynamic> data, {String? tablePrefix}) { 1249 1495 final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 1250 1496 return SettingsEntry( 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'])!, 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 + )!, 1254 1509 ); 1255 1510 } 1256 1511 ··· 1264 1519 final String key; 1265 1520 final String value; 1266 1521 final DateTime updatedAt; 1267 - const SettingsEntry({required this.key, required this.value, required this.updatedAt}); 1522 + const SettingsEntry({ 1523 + required this.key, 1524 + required this.value, 1525 + required this.updatedAt, 1526 + }); 1268 1527 @override 1269 1528 Map<String, Expression> toColumns(bool nullToAbsent) { 1270 1529 final map = <String, Expression>{}; ··· 1275 1534 } 1276 1535 1277 1536 SettingsCompanion toCompanion(bool nullToAbsent) { 1278 - return SettingsCompanion(key: Value(key), value: Value(value), updatedAt: Value(updatedAt)); 1537 + return SettingsCompanion( 1538 + key: Value(key), 1539 + value: Value(value), 1540 + updatedAt: Value(updatedAt), 1541 + ); 1279 1542 } 1280 1543 1281 - factory SettingsEntry.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) { 1544 + factory SettingsEntry.fromJson( 1545 + Map<String, dynamic> json, { 1546 + ValueSerializer? serializer, 1547 + }) { 1282 1548 serializer ??= driftRuntimeOptions.defaultSerializer; 1283 1549 return SettingsEntry( 1284 1550 key: serializer.fromJson<String>(json['key']), ··· 1297 1563 } 1298 1564 1299 1565 SettingsEntry copyWith({String? key, String? value, DateTime? updatedAt}) => 1300 - SettingsEntry(key: key ?? this.key, value: value ?? this.value, updatedAt: updatedAt ?? this.updatedAt); 1566 + SettingsEntry( 1567 + key: key ?? this.key, 1568 + value: value ?? this.value, 1569 + updatedAt: updatedAt ?? this.updatedAt, 1570 + ); 1301 1571 SettingsEntry copyWithCompanion(SettingsCompanion data) { 1302 1572 return SettingsEntry( 1303 1573 key: data.key.present ? data.key.value : this.key, ··· 1403 1673 } 1404 1674 } 1405 1675 1676 + class $SavedFeedsTable extends SavedFeeds 1677 + with TableInfo<$SavedFeedsTable, SavedFeedEntry> { 1678 + @override 1679 + final GeneratedDatabase attachedDatabase; 1680 + final String? _alias; 1681 + $SavedFeedsTable(this.attachedDatabase, [this._alias]); 1682 + static const VerificationMeta _idMeta = const VerificationMeta('id'); 1683 + @override 1684 + late final GeneratedColumn<String> id = GeneratedColumn<String>( 1685 + 'id', 1686 + aliasedName, 1687 + false, 1688 + type: DriftSqlType.string, 1689 + requiredDuringInsert: true, 1690 + ); 1691 + static const VerificationMeta _accountDidMeta = const VerificationMeta( 1692 + 'accountDid', 1693 + ); 1694 + @override 1695 + late final GeneratedColumn<String> accountDid = GeneratedColumn<String>( 1696 + 'account_did', 1697 + aliasedName, 1698 + false, 1699 + type: DriftSqlType.string, 1700 + requiredDuringInsert: true, 1701 + ); 1702 + static const VerificationMeta _typeMeta = const VerificationMeta('type'); 1703 + @override 1704 + late final GeneratedColumn<String> type = GeneratedColumn<String>( 1705 + 'type', 1706 + aliasedName, 1707 + false, 1708 + type: DriftSqlType.string, 1709 + requiredDuringInsert: true, 1710 + ); 1711 + static const VerificationMeta _valueMeta = const VerificationMeta('value'); 1712 + @override 1713 + late final GeneratedColumn<String> value = GeneratedColumn<String>( 1714 + 'value', 1715 + aliasedName, 1716 + false, 1717 + type: DriftSqlType.string, 1718 + requiredDuringInsert: true, 1719 + ); 1720 + static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); 1721 + @override 1722 + late final GeneratedColumn<bool> pinned = GeneratedColumn<bool>( 1723 + 'pinned', 1724 + aliasedName, 1725 + false, 1726 + type: DriftSqlType.bool, 1727 + requiredDuringInsert: false, 1728 + defaultConstraints: GeneratedColumn.constraintIsAlways( 1729 + 'CHECK ("pinned" IN (0, 1))', 1730 + ), 1731 + defaultValue: const Constant(false), 1732 + ); 1733 + static const VerificationMeta _sortOrderMeta = const VerificationMeta( 1734 + 'sortOrder', 1735 + ); 1736 + @override 1737 + late final GeneratedColumn<int> sortOrder = GeneratedColumn<int>( 1738 + 'sort_order', 1739 + aliasedName, 1740 + false, 1741 + type: DriftSqlType.int, 1742 + requiredDuringInsert: false, 1743 + defaultValue: const Constant(0), 1744 + ); 1745 + static const VerificationMeta _updatedAtMeta = const VerificationMeta( 1746 + 'updatedAt', 1747 + ); 1748 + @override 1749 + late final GeneratedColumn<DateTime> updatedAt = GeneratedColumn<DateTime>( 1750 + 'updated_at', 1751 + aliasedName, 1752 + false, 1753 + type: DriftSqlType.dateTime, 1754 + requiredDuringInsert: false, 1755 + defaultValue: currentDateAndTime, 1756 + ); 1757 + @override 1758 + List<GeneratedColumn> get $columns => [ 1759 + id, 1760 + accountDid, 1761 + type, 1762 + value, 1763 + pinned, 1764 + sortOrder, 1765 + updatedAt, 1766 + ]; 1767 + @override 1768 + String get aliasedName => _alias ?? actualTableName; 1769 + @override 1770 + String get actualTableName => $name; 1771 + static const String $name = 'saved_feeds'; 1772 + @override 1773 + VerificationContext validateIntegrity( 1774 + Insertable<SavedFeedEntry> instance, { 1775 + bool isInserting = false, 1776 + }) { 1777 + final context = VerificationContext(); 1778 + final data = instance.toColumns(true); 1779 + if (data.containsKey('id')) { 1780 + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); 1781 + } else if (isInserting) { 1782 + context.missing(_idMeta); 1783 + } 1784 + if (data.containsKey('account_did')) { 1785 + context.handle( 1786 + _accountDidMeta, 1787 + accountDid.isAcceptableOrUnknown(data['account_did']!, _accountDidMeta), 1788 + ); 1789 + } else if (isInserting) { 1790 + context.missing(_accountDidMeta); 1791 + } 1792 + if (data.containsKey('type')) { 1793 + context.handle( 1794 + _typeMeta, 1795 + type.isAcceptableOrUnknown(data['type']!, _typeMeta), 1796 + ); 1797 + } else if (isInserting) { 1798 + context.missing(_typeMeta); 1799 + } 1800 + if (data.containsKey('value')) { 1801 + context.handle( 1802 + _valueMeta, 1803 + value.isAcceptableOrUnknown(data['value']!, _valueMeta), 1804 + ); 1805 + } else if (isInserting) { 1806 + context.missing(_valueMeta); 1807 + } 1808 + if (data.containsKey('pinned')) { 1809 + context.handle( 1810 + _pinnedMeta, 1811 + pinned.isAcceptableOrUnknown(data['pinned']!, _pinnedMeta), 1812 + ); 1813 + } 1814 + if (data.containsKey('sort_order')) { 1815 + context.handle( 1816 + _sortOrderMeta, 1817 + sortOrder.isAcceptableOrUnknown(data['sort_order']!, _sortOrderMeta), 1818 + ); 1819 + } 1820 + if (data.containsKey('updated_at')) { 1821 + context.handle( 1822 + _updatedAtMeta, 1823 + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), 1824 + ); 1825 + } 1826 + return context; 1827 + } 1828 + 1829 + @override 1830 + Set<GeneratedColumn> get $primaryKey => {id, accountDid}; 1831 + @override 1832 + SavedFeedEntry map(Map<String, dynamic> data, {String? tablePrefix}) { 1833 + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; 1834 + 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 + )!, 1863 + ); 1864 + } 1865 + 1866 + @override 1867 + $SavedFeedsTable createAlias(String alias) { 1868 + return $SavedFeedsTable(attachedDatabase, alias); 1869 + } 1870 + } 1871 + 1872 + class SavedFeedEntry extends DataClass implements Insertable<SavedFeedEntry> { 1873 + final String id; 1874 + final String accountDid; 1875 + final String type; 1876 + final String value; 1877 + final bool pinned; 1878 + final int sortOrder; 1879 + final DateTime updatedAt; 1880 + const SavedFeedEntry({ 1881 + required this.id, 1882 + required this.accountDid, 1883 + required this.type, 1884 + required this.value, 1885 + required this.pinned, 1886 + required this.sortOrder, 1887 + required this.updatedAt, 1888 + }); 1889 + @override 1890 + Map<String, Expression> toColumns(bool nullToAbsent) { 1891 + final map = <String, Expression>{}; 1892 + map['id'] = Variable<String>(id); 1893 + map['account_did'] = Variable<String>(accountDid); 1894 + map['type'] = Variable<String>(type); 1895 + map['value'] = Variable<String>(value); 1896 + map['pinned'] = Variable<bool>(pinned); 1897 + map['sort_order'] = Variable<int>(sortOrder); 1898 + map['updated_at'] = Variable<DateTime>(updatedAt); 1899 + return map; 1900 + } 1901 + 1902 + SavedFeedsCompanion toCompanion(bool nullToAbsent) { 1903 + return SavedFeedsCompanion( 1904 + id: Value(id), 1905 + accountDid: Value(accountDid), 1906 + type: Value(type), 1907 + value: Value(value), 1908 + pinned: Value(pinned), 1909 + sortOrder: Value(sortOrder), 1910 + updatedAt: Value(updatedAt), 1911 + ); 1912 + } 1913 + 1914 + factory SavedFeedEntry.fromJson( 1915 + Map<String, dynamic> json, { 1916 + ValueSerializer? serializer, 1917 + }) { 1918 + serializer ??= driftRuntimeOptions.defaultSerializer; 1919 + return SavedFeedEntry( 1920 + id: serializer.fromJson<String>(json['id']), 1921 + accountDid: serializer.fromJson<String>(json['accountDid']), 1922 + type: serializer.fromJson<String>(json['type']), 1923 + value: serializer.fromJson<String>(json['value']), 1924 + pinned: serializer.fromJson<bool>(json['pinned']), 1925 + sortOrder: serializer.fromJson<int>(json['sortOrder']), 1926 + updatedAt: serializer.fromJson<DateTime>(json['updatedAt']), 1927 + ); 1928 + } 1929 + @override 1930 + Map<String, dynamic> toJson({ValueSerializer? serializer}) { 1931 + serializer ??= driftRuntimeOptions.defaultSerializer; 1932 + return <String, dynamic>{ 1933 + 'id': serializer.toJson<String>(id), 1934 + 'accountDid': serializer.toJson<String>(accountDid), 1935 + 'type': serializer.toJson<String>(type), 1936 + 'value': serializer.toJson<String>(value), 1937 + 'pinned': serializer.toJson<bool>(pinned), 1938 + 'sortOrder': serializer.toJson<int>(sortOrder), 1939 + 'updatedAt': serializer.toJson<DateTime>(updatedAt), 1940 + }; 1941 + } 1942 + 1943 + SavedFeedEntry copyWith({ 1944 + String? id, 1945 + String? accountDid, 1946 + String? type, 1947 + String? value, 1948 + bool? pinned, 1949 + int? sortOrder, 1950 + DateTime? updatedAt, 1951 + }) => SavedFeedEntry( 1952 + id: id ?? this.id, 1953 + accountDid: accountDid ?? this.accountDid, 1954 + type: type ?? this.type, 1955 + value: value ?? this.value, 1956 + pinned: pinned ?? this.pinned, 1957 + sortOrder: sortOrder ?? this.sortOrder, 1958 + updatedAt: updatedAt ?? this.updatedAt, 1959 + ); 1960 + SavedFeedEntry copyWithCompanion(SavedFeedsCompanion data) { 1961 + return SavedFeedEntry( 1962 + id: data.id.present ? data.id.value : this.id, 1963 + accountDid: data.accountDid.present 1964 + ? data.accountDid.value 1965 + : this.accountDid, 1966 + type: data.type.present ? data.type.value : this.type, 1967 + value: data.value.present ? data.value.value : this.value, 1968 + pinned: data.pinned.present ? data.pinned.value : this.pinned, 1969 + sortOrder: data.sortOrder.present ? data.sortOrder.value : this.sortOrder, 1970 + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, 1971 + ); 1972 + } 1973 + 1974 + @override 1975 + String toString() { 1976 + return (StringBuffer('SavedFeedEntry(') 1977 + ..write('id: $id, ') 1978 + ..write('accountDid: $accountDid, ') 1979 + ..write('type: $type, ') 1980 + ..write('value: $value, ') 1981 + ..write('pinned: $pinned, ') 1982 + ..write('sortOrder: $sortOrder, ') 1983 + ..write('updatedAt: $updatedAt') 1984 + ..write(')')) 1985 + .toString(); 1986 + } 1987 + 1988 + @override 1989 + int get hashCode => 1990 + Object.hash(id, accountDid, type, value, pinned, sortOrder, updatedAt); 1991 + @override 1992 + bool operator ==(Object other) => 1993 + identical(this, other) || 1994 + (other is SavedFeedEntry && 1995 + other.id == this.id && 1996 + other.accountDid == this.accountDid && 1997 + other.type == this.type && 1998 + other.value == this.value && 1999 + other.pinned == this.pinned && 2000 + other.sortOrder == this.sortOrder && 2001 + other.updatedAt == this.updatedAt); 2002 + } 2003 + 2004 + class SavedFeedsCompanion extends UpdateCompanion<SavedFeedEntry> { 2005 + final Value<String> id; 2006 + final Value<String> accountDid; 2007 + final Value<String> type; 2008 + final Value<String> value; 2009 + final Value<bool> pinned; 2010 + final Value<int> sortOrder; 2011 + final Value<DateTime> updatedAt; 2012 + final Value<int> rowid; 2013 + const SavedFeedsCompanion({ 2014 + this.id = const Value.absent(), 2015 + this.accountDid = const Value.absent(), 2016 + this.type = const Value.absent(), 2017 + this.value = const Value.absent(), 2018 + this.pinned = const Value.absent(), 2019 + this.sortOrder = const Value.absent(), 2020 + this.updatedAt = const Value.absent(), 2021 + this.rowid = const Value.absent(), 2022 + }); 2023 + SavedFeedsCompanion.insert({ 2024 + required String id, 2025 + required String accountDid, 2026 + required String type, 2027 + required String value, 2028 + this.pinned = const Value.absent(), 2029 + this.sortOrder = const Value.absent(), 2030 + this.updatedAt = const Value.absent(), 2031 + this.rowid = const Value.absent(), 2032 + }) : id = Value(id), 2033 + accountDid = Value(accountDid), 2034 + type = Value(type), 2035 + value = Value(value); 2036 + static Insertable<SavedFeedEntry> custom({ 2037 + Expression<String>? id, 2038 + Expression<String>? accountDid, 2039 + Expression<String>? type, 2040 + Expression<String>? value, 2041 + Expression<bool>? pinned, 2042 + Expression<int>? sortOrder, 2043 + Expression<DateTime>? updatedAt, 2044 + Expression<int>? rowid, 2045 + }) { 2046 + return RawValuesInsertable({ 2047 + if (id != null) 'id': id, 2048 + if (accountDid != null) 'account_did': accountDid, 2049 + if (type != null) 'type': type, 2050 + if (value != null) 'value': value, 2051 + if (pinned != null) 'pinned': pinned, 2052 + if (sortOrder != null) 'sort_order': sortOrder, 2053 + if (updatedAt != null) 'updated_at': updatedAt, 2054 + if (rowid != null) 'rowid': rowid, 2055 + }); 2056 + } 2057 + 2058 + SavedFeedsCompanion copyWith({ 2059 + Value<String>? id, 2060 + Value<String>? accountDid, 2061 + Value<String>? type, 2062 + Value<String>? value, 2063 + Value<bool>? pinned, 2064 + Value<int>? sortOrder, 2065 + Value<DateTime>? updatedAt, 2066 + Value<int>? rowid, 2067 + }) { 2068 + return SavedFeedsCompanion( 2069 + id: id ?? this.id, 2070 + accountDid: accountDid ?? this.accountDid, 2071 + type: type ?? this.type, 2072 + value: value ?? this.value, 2073 + pinned: pinned ?? this.pinned, 2074 + sortOrder: sortOrder ?? this.sortOrder, 2075 + updatedAt: updatedAt ?? this.updatedAt, 2076 + rowid: rowid ?? this.rowid, 2077 + ); 2078 + } 2079 + 2080 + @override 2081 + Map<String, Expression> toColumns(bool nullToAbsent) { 2082 + final map = <String, Expression>{}; 2083 + if (id.present) { 2084 + map['id'] = Variable<String>(id.value); 2085 + } 2086 + if (accountDid.present) { 2087 + map['account_did'] = Variable<String>(accountDid.value); 2088 + } 2089 + if (type.present) { 2090 + map['type'] = Variable<String>(type.value); 2091 + } 2092 + if (value.present) { 2093 + map['value'] = Variable<String>(value.value); 2094 + } 2095 + if (pinned.present) { 2096 + map['pinned'] = Variable<bool>(pinned.value); 2097 + } 2098 + if (sortOrder.present) { 2099 + map['sort_order'] = Variable<int>(sortOrder.value); 2100 + } 2101 + if (updatedAt.present) { 2102 + map['updated_at'] = Variable<DateTime>(updatedAt.value); 2103 + } 2104 + if (rowid.present) { 2105 + map['rowid'] = Variable<int>(rowid.value); 2106 + } 2107 + return map; 2108 + } 2109 + 2110 + @override 2111 + String toString() { 2112 + return (StringBuffer('SavedFeedsCompanion(') 2113 + ..write('id: $id, ') 2114 + ..write('accountDid: $accountDid, ') 2115 + ..write('type: $type, ') 2116 + ..write('value: $value, ') 2117 + ..write('pinned: $pinned, ') 2118 + ..write('sortOrder: $sortOrder, ') 2119 + ..write('updatedAt: $updatedAt, ') 2120 + ..write('rowid: $rowid') 2121 + ..write(')')) 2122 + .toString(); 2123 + } 2124 + } 2125 + 1406 2126 abstract class _$AppDatabase extends GeneratedDatabase { 1407 2127 _$AppDatabase(QueryExecutor e) : super(e); 1408 2128 $AppDatabaseManager get managers => $AppDatabaseManager(this); ··· 1410 2130 late final $CachedProfilesTable cachedProfiles = $CachedProfilesTable(this); 1411 2131 late final $CachedPostsTable cachedPosts = $CachedPostsTable(this); 1412 2132 late final $SettingsTable settings = $SettingsTable(this); 2133 + late final $SavedFeedsTable savedFeeds = $SavedFeedsTable(this); 1413 2134 @override 1414 - Iterable<TableInfo<Table, Object?>> get allTables => allSchemaEntities.whereType<TableInfo<Table, Object?>>(); 2135 + Iterable<TableInfo<Table, Object?>> get allTables => 2136 + allSchemaEntities.whereType<TableInfo<Table, Object?>>(); 1415 2137 @override 1416 - List<DatabaseSchemaEntity> get allSchemaEntities => [accounts, cachedProfiles, cachedPosts, settings]; 2138 + List<DatabaseSchemaEntity> get allSchemaEntities => [ 2139 + accounts, 2140 + cachedProfiles, 2141 + cachedPosts, 2142 + settings, 2143 + savedFeeds, 2144 + ]; 1417 2145 } 1418 2146 1419 2147 typedef $$AccountsTableCreateCompanionBuilder = ··· 1449 2177 Value<int> rowid, 1450 2178 }); 1451 2179 1452 - class $$AccountsTableFilterComposer extends Composer<_$AppDatabase, $AccountsTable> { 2180 + class $$AccountsTableFilterComposer 2181 + extends Composer<_$AppDatabase, $AccountsTable> { 1453 2182 $$AccountsTableFilterComposer({ 1454 2183 required super.$db, 1455 2184 required super.$table, ··· 1457 2186 super.$addJoinBuilderToRootComposer, 1458 2187 super.$removeJoinBuilderFromRootComposer, 1459 2188 }); 1460 - ColumnFilters<String> get did => $composableBuilder(column: $table.did, builder: (column) => ColumnFilters(column)); 2189 + ColumnFilters<String> get did => $composableBuilder( 2190 + column: $table.did, 2191 + builder: (column) => ColumnFilters(column), 2192 + ); 1461 2193 1462 - ColumnFilters<String> get handle => 1463 - $composableBuilder(column: $table.handle, builder: (column) => ColumnFilters(column)); 2194 + ColumnFilters<String> get handle => $composableBuilder( 2195 + column: $table.handle, 2196 + builder: (column) => ColumnFilters(column), 2197 + ); 1464 2198 1465 - ColumnFilters<String> get displayName => 1466 - $composableBuilder(column: $table.displayName, builder: (column) => ColumnFilters(column)); 2199 + ColumnFilters<String> get displayName => $composableBuilder( 2200 + column: $table.displayName, 2201 + builder: (column) => ColumnFilters(column), 2202 + ); 1467 2203 1468 - ColumnFilters<String> get service => 1469 - $composableBuilder(column: $table.service, builder: (column) => ColumnFilters(column)); 2204 + ColumnFilters<String> get service => $composableBuilder( 2205 + column: $table.service, 2206 + builder: (column) => ColumnFilters(column), 2207 + ); 1470 2208 1471 - ColumnFilters<String> get accessToken => 1472 - $composableBuilder(column: $table.accessToken, builder: (column) => ColumnFilters(column)); 2209 + ColumnFilters<String> get accessToken => $composableBuilder( 2210 + column: $table.accessToken, 2211 + builder: (column) => ColumnFilters(column), 2212 + ); 1473 2213 1474 - ColumnFilters<String> get refreshToken => 1475 - $composableBuilder(column: $table.refreshToken, builder: (column) => ColumnFilters(column)); 2214 + ColumnFilters<String> get refreshToken => $composableBuilder( 2215 + column: $table.refreshToken, 2216 + builder: (column) => ColumnFilters(column), 2217 + ); 1476 2218 1477 - ColumnFilters<String> get dpopPublicKey => 1478 - $composableBuilder(column: $table.dpopPublicKey, builder: (column) => ColumnFilters(column)); 2219 + ColumnFilters<String> get dpopPublicKey => $composableBuilder( 2220 + column: $table.dpopPublicKey, 2221 + builder: (column) => ColumnFilters(column), 2222 + ); 1479 2223 1480 - ColumnFilters<String> get dpopPrivateKey => 1481 - $composableBuilder(column: $table.dpopPrivateKey, builder: (column) => ColumnFilters(column)); 2224 + ColumnFilters<String> get dpopPrivateKey => $composableBuilder( 2225 + column: $table.dpopPrivateKey, 2226 + builder: (column) => ColumnFilters(column), 2227 + ); 1482 2228 1483 - ColumnFilters<String> get dpopNonce => 1484 - $composableBuilder(column: $table.dpopNonce, builder: (column) => ColumnFilters(column)); 2229 + ColumnFilters<String> get dpopNonce => $composableBuilder( 2230 + column: $table.dpopNonce, 2231 + builder: (column) => ColumnFilters(column), 2232 + ); 1485 2233 1486 - ColumnFilters<DateTime> get expiresAt => 1487 - $composableBuilder(column: $table.expiresAt, builder: (column) => ColumnFilters(column)); 2234 + ColumnFilters<DateTime> get expiresAt => $composableBuilder( 2235 + column: $table.expiresAt, 2236 + builder: (column) => ColumnFilters(column), 2237 + ); 1488 2238 1489 - ColumnFilters<DateTime> get createdAt => 1490 - $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); 2239 + ColumnFilters<DateTime> get createdAt => $composableBuilder( 2240 + column: $table.createdAt, 2241 + builder: (column) => ColumnFilters(column), 2242 + ); 1491 2243 1492 - ColumnFilters<DateTime> get updatedAt => 1493 - $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); 2244 + ColumnFilters<DateTime> get updatedAt => $composableBuilder( 2245 + column: $table.updatedAt, 2246 + builder: (column) => ColumnFilters(column), 2247 + ); 1494 2248 } 1495 2249 1496 - class $$AccountsTableOrderingComposer extends Composer<_$AppDatabase, $AccountsTable> { 2250 + class $$AccountsTableOrderingComposer 2251 + extends Composer<_$AppDatabase, $AccountsTable> { 1497 2252 $$AccountsTableOrderingComposer({ 1498 2253 required super.$db, 1499 2254 required super.$table, ··· 1501 2256 super.$addJoinBuilderToRootComposer, 1502 2257 super.$removeJoinBuilderFromRootComposer, 1503 2258 }); 1504 - ColumnOrderings<String> get did => 1505 - $composableBuilder(column: $table.did, builder: (column) => ColumnOrderings(column)); 2259 + ColumnOrderings<String> get did => $composableBuilder( 2260 + column: $table.did, 2261 + builder: (column) => ColumnOrderings(column), 2262 + ); 1506 2263 1507 - ColumnOrderings<String> get handle => 1508 - $composableBuilder(column: $table.handle, builder: (column) => ColumnOrderings(column)); 2264 + ColumnOrderings<String> get handle => $composableBuilder( 2265 + column: $table.handle, 2266 + builder: (column) => ColumnOrderings(column), 2267 + ); 1509 2268 1510 - ColumnOrderings<String> get displayName => 1511 - $composableBuilder(column: $table.displayName, builder: (column) => ColumnOrderings(column)); 2269 + ColumnOrderings<String> get displayName => $composableBuilder( 2270 + column: $table.displayName, 2271 + builder: (column) => ColumnOrderings(column), 2272 + ); 1512 2273 1513 - ColumnOrderings<String> get service => 1514 - $composableBuilder(column: $table.service, builder: (column) => ColumnOrderings(column)); 2274 + ColumnOrderings<String> get service => $composableBuilder( 2275 + column: $table.service, 2276 + builder: (column) => ColumnOrderings(column), 2277 + ); 1515 2278 1516 - ColumnOrderings<String> get accessToken => 1517 - $composableBuilder(column: $table.accessToken, builder: (column) => ColumnOrderings(column)); 2279 + ColumnOrderings<String> get accessToken => $composableBuilder( 2280 + column: $table.accessToken, 2281 + builder: (column) => ColumnOrderings(column), 2282 + ); 1518 2283 1519 - ColumnOrderings<String> get refreshToken => 1520 - $composableBuilder(column: $table.refreshToken, builder: (column) => ColumnOrderings(column)); 2284 + ColumnOrderings<String> get refreshToken => $composableBuilder( 2285 + column: $table.refreshToken, 2286 + builder: (column) => ColumnOrderings(column), 2287 + ); 1521 2288 1522 - ColumnOrderings<String> get dpopPublicKey => 1523 - $composableBuilder(column: $table.dpopPublicKey, builder: (column) => ColumnOrderings(column)); 2289 + ColumnOrderings<String> get dpopPublicKey => $composableBuilder( 2290 + column: $table.dpopPublicKey, 2291 + builder: (column) => ColumnOrderings(column), 2292 + ); 1524 2293 1525 - ColumnOrderings<String> get dpopPrivateKey => 1526 - $composableBuilder(column: $table.dpopPrivateKey, builder: (column) => ColumnOrderings(column)); 2294 + ColumnOrderings<String> get dpopPrivateKey => $composableBuilder( 2295 + column: $table.dpopPrivateKey, 2296 + builder: (column) => ColumnOrderings(column), 2297 + ); 1527 2298 1528 - ColumnOrderings<String> get dpopNonce => 1529 - $composableBuilder(column: $table.dpopNonce, builder: (column) => ColumnOrderings(column)); 2299 + ColumnOrderings<String> get dpopNonce => $composableBuilder( 2300 + column: $table.dpopNonce, 2301 + builder: (column) => ColumnOrderings(column), 2302 + ); 1530 2303 1531 - ColumnOrderings<DateTime> get expiresAt => 1532 - $composableBuilder(column: $table.expiresAt, builder: (column) => ColumnOrderings(column)); 2304 + ColumnOrderings<DateTime> get expiresAt => $composableBuilder( 2305 + column: $table.expiresAt, 2306 + builder: (column) => ColumnOrderings(column), 2307 + ); 1533 2308 1534 - ColumnOrderings<DateTime> get createdAt => 1535 - $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); 2309 + ColumnOrderings<DateTime> get createdAt => $composableBuilder( 2310 + column: $table.createdAt, 2311 + builder: (column) => ColumnOrderings(column), 2312 + ); 1536 2313 1537 - ColumnOrderings<DateTime> get updatedAt => 1538 - $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); 2314 + ColumnOrderings<DateTime> get updatedAt => $composableBuilder( 2315 + column: $table.updatedAt, 2316 + builder: (column) => ColumnOrderings(column), 2317 + ); 1539 2318 } 1540 2319 1541 - class $$AccountsTableAnnotationComposer extends Composer<_$AppDatabase, $AccountsTable> { 2320 + class $$AccountsTableAnnotationComposer 2321 + extends Composer<_$AppDatabase, $AccountsTable> { 1542 2322 $$AccountsTableAnnotationComposer({ 1543 2323 required super.$db, 1544 2324 required super.$table, ··· 1546 2326 super.$addJoinBuilderToRootComposer, 1547 2327 super.$removeJoinBuilderFromRootComposer, 1548 2328 }); 1549 - GeneratedColumn<String> get did => $composableBuilder(column: $table.did, builder: (column) => column); 2329 + GeneratedColumn<String> get did => 2330 + $composableBuilder(column: $table.did, builder: (column) => column); 1550 2331 1551 - GeneratedColumn<String> get handle => $composableBuilder(column: $table.handle, builder: (column) => column); 2332 + GeneratedColumn<String> get handle => 2333 + $composableBuilder(column: $table.handle, builder: (column) => column); 1552 2334 1553 - GeneratedColumn<String> get displayName => 1554 - $composableBuilder(column: $table.displayName, builder: (column) => column); 2335 + GeneratedColumn<String> get displayName => $composableBuilder( 2336 + column: $table.displayName, 2337 + builder: (column) => column, 2338 + ); 1555 2339 1556 - GeneratedColumn<String> get service => $composableBuilder(column: $table.service, builder: (column) => column); 2340 + GeneratedColumn<String> get service => 2341 + $composableBuilder(column: $table.service, builder: (column) => column); 1557 2342 1558 - GeneratedColumn<String> get accessToken => 1559 - $composableBuilder(column: $table.accessToken, builder: (column) => column); 2343 + GeneratedColumn<String> get accessToken => $composableBuilder( 2344 + column: $table.accessToken, 2345 + builder: (column) => column, 2346 + ); 1560 2347 1561 - GeneratedColumn<String> get refreshToken => 1562 - $composableBuilder(column: $table.refreshToken, builder: (column) => column); 2348 + GeneratedColumn<String> get refreshToken => $composableBuilder( 2349 + column: $table.refreshToken, 2350 + builder: (column) => column, 2351 + ); 1563 2352 1564 - GeneratedColumn<String> get dpopPublicKey => 1565 - $composableBuilder(column: $table.dpopPublicKey, builder: (column) => column); 2353 + GeneratedColumn<String> get dpopPublicKey => $composableBuilder( 2354 + column: $table.dpopPublicKey, 2355 + builder: (column) => column, 2356 + ); 1566 2357 1567 - GeneratedColumn<String> get dpopPrivateKey => 1568 - $composableBuilder(column: $table.dpopPrivateKey, builder: (column) => column); 2358 + GeneratedColumn<String> get dpopPrivateKey => $composableBuilder( 2359 + column: $table.dpopPrivateKey, 2360 + builder: (column) => column, 2361 + ); 1569 2362 1570 - GeneratedColumn<String> get dpopNonce => $composableBuilder(column: $table.dpopNonce, builder: (column) => column); 2363 + GeneratedColumn<String> get dpopNonce => 2364 + $composableBuilder(column: $table.dpopNonce, builder: (column) => column); 1571 2365 1572 - GeneratedColumn<DateTime> get expiresAt => $composableBuilder(column: $table.expiresAt, builder: (column) => column); 2366 + GeneratedColumn<DateTime> get expiresAt => 2367 + $composableBuilder(column: $table.expiresAt, builder: (column) => column); 1573 2368 1574 - GeneratedColumn<DateTime> get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); 2369 + GeneratedColumn<DateTime> get createdAt => 2370 + $composableBuilder(column: $table.createdAt, builder: (column) => column); 1575 2371 1576 - GeneratedColumn<DateTime> get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); 2372 + GeneratedColumn<DateTime> get updatedAt => 2373 + $composableBuilder(column: $table.updatedAt, builder: (column) => column); 1577 2374 } 1578 2375 1579 2376 class $$AccountsTableTableManager ··· 1596 2393 TableManagerState( 1597 2394 db: db, 1598 2395 table: table, 1599 - createFilteringComposer: () => $$AccountsTableFilterComposer($db: db, $table: table), 1600 - createOrderingComposer: () => $$AccountsTableOrderingComposer($db: db, $table: table), 1601 - createComputedFieldComposer: () => $$AccountsTableAnnotationComposer($db: db, $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), 1602 2402 updateCompanionCallback: 1603 2403 ({ 1604 2404 Value<String> did = const Value.absent(), ··· 1659 2459 updatedAt: updatedAt, 1660 2460 rowid: rowid, 1661 2461 ), 1662 - withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 2462 + withReferenceMapper: (p0) => p0 2463 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 2464 + .toList(), 1663 2465 prefetchHooksCallback: null, 1664 2466 ), 1665 2467 ); ··· 1696 2498 Value<int> rowid, 1697 2499 }); 1698 2500 1699 - class $$CachedProfilesTableFilterComposer extends Composer<_$AppDatabase, $CachedProfilesTable> { 2501 + class $$CachedProfilesTableFilterComposer 2502 + extends Composer<_$AppDatabase, $CachedProfilesTable> { 1700 2503 $$CachedProfilesTableFilterComposer({ 1701 2504 required super.$db, 1702 2505 required super.$table, ··· 1704 2507 super.$addJoinBuilderToRootComposer, 1705 2508 super.$removeJoinBuilderFromRootComposer, 1706 2509 }); 1707 - ColumnFilters<String> get did => $composableBuilder(column: $table.did, builder: (column) => ColumnFilters(column)); 2510 + ColumnFilters<String> get did => $composableBuilder( 2511 + column: $table.did, 2512 + builder: (column) => ColumnFilters(column), 2513 + ); 1708 2514 1709 - ColumnFilters<String> get handle => 1710 - $composableBuilder(column: $table.handle, builder: (column) => ColumnFilters(column)); 2515 + ColumnFilters<String> get handle => $composableBuilder( 2516 + column: $table.handle, 2517 + builder: (column) => ColumnFilters(column), 2518 + ); 1711 2519 1712 - ColumnFilters<String> get payload => 1713 - $composableBuilder(column: $table.payload, builder: (column) => ColumnFilters(column)); 2520 + ColumnFilters<String> get payload => $composableBuilder( 2521 + column: $table.payload, 2522 + builder: (column) => ColumnFilters(column), 2523 + ); 1714 2524 1715 - ColumnFilters<DateTime> get fetchedAt => 1716 - $composableBuilder(column: $table.fetchedAt, builder: (column) => ColumnFilters(column)); 2525 + ColumnFilters<DateTime> get fetchedAt => $composableBuilder( 2526 + column: $table.fetchedAt, 2527 + builder: (column) => ColumnFilters(column), 2528 + ); 1717 2529 } 1718 2530 1719 - class $$CachedProfilesTableOrderingComposer extends Composer<_$AppDatabase, $CachedProfilesTable> { 2531 + class $$CachedProfilesTableOrderingComposer 2532 + extends Composer<_$AppDatabase, $CachedProfilesTable> { 1720 2533 $$CachedProfilesTableOrderingComposer({ 1721 2534 required super.$db, 1722 2535 required super.$table, ··· 1724 2537 super.$addJoinBuilderToRootComposer, 1725 2538 super.$removeJoinBuilderFromRootComposer, 1726 2539 }); 1727 - ColumnOrderings<String> get did => 1728 - $composableBuilder(column: $table.did, builder: (column) => ColumnOrderings(column)); 2540 + ColumnOrderings<String> get did => $composableBuilder( 2541 + column: $table.did, 2542 + builder: (column) => ColumnOrderings(column), 2543 + ); 1729 2544 1730 - ColumnOrderings<String> get handle => 1731 - $composableBuilder(column: $table.handle, builder: (column) => ColumnOrderings(column)); 2545 + ColumnOrderings<String> get handle => $composableBuilder( 2546 + column: $table.handle, 2547 + builder: (column) => ColumnOrderings(column), 2548 + ); 1732 2549 1733 - ColumnOrderings<String> get payload => 1734 - $composableBuilder(column: $table.payload, builder: (column) => ColumnOrderings(column)); 2550 + ColumnOrderings<String> get payload => $composableBuilder( 2551 + column: $table.payload, 2552 + builder: (column) => ColumnOrderings(column), 2553 + ); 1735 2554 1736 - ColumnOrderings<DateTime> get fetchedAt => 1737 - $composableBuilder(column: $table.fetchedAt, builder: (column) => ColumnOrderings(column)); 2555 + ColumnOrderings<DateTime> get fetchedAt => $composableBuilder( 2556 + column: $table.fetchedAt, 2557 + builder: (column) => ColumnOrderings(column), 2558 + ); 1738 2559 } 1739 2560 1740 - class $$CachedProfilesTableAnnotationComposer extends Composer<_$AppDatabase, $CachedProfilesTable> { 2561 + class $$CachedProfilesTableAnnotationComposer 2562 + extends Composer<_$AppDatabase, $CachedProfilesTable> { 1741 2563 $$CachedProfilesTableAnnotationComposer({ 1742 2564 required super.$db, 1743 2565 required super.$table, ··· 1745 2567 super.$addJoinBuilderToRootComposer, 1746 2568 super.$removeJoinBuilderFromRootComposer, 1747 2569 }); 1748 - GeneratedColumn<String> get did => $composableBuilder(column: $table.did, builder: (column) => column); 2570 + GeneratedColumn<String> get did => 2571 + $composableBuilder(column: $table.did, builder: (column) => column); 1749 2572 1750 - GeneratedColumn<String> get handle => $composableBuilder(column: $table.handle, builder: (column) => column); 2573 + GeneratedColumn<String> get handle => 2574 + $composableBuilder(column: $table.handle, builder: (column) => column); 1751 2575 1752 - GeneratedColumn<String> get payload => $composableBuilder(column: $table.payload, builder: (column) => column); 2576 + GeneratedColumn<String> get payload => 2577 + $composableBuilder(column: $table.payload, builder: (column) => column); 1753 2578 1754 - GeneratedColumn<DateTime> get fetchedAt => $composableBuilder(column: $table.fetchedAt, builder: (column) => column); 2579 + GeneratedColumn<DateTime> get fetchedAt => 2580 + $composableBuilder(column: $table.fetchedAt, builder: (column) => column); 1755 2581 } 1756 2582 1757 2583 class $$CachedProfilesTableTableManager ··· 1765 2591 $$CachedProfilesTableAnnotationComposer, 1766 2592 $$CachedProfilesTableCreateCompanionBuilder, 1767 2593 $$CachedProfilesTableUpdateCompanionBuilder, 1768 - (CachedProfile, BaseReferences<_$AppDatabase, $CachedProfilesTable, CachedProfile>), 2594 + ( 2595 + CachedProfile, 2596 + BaseReferences<_$AppDatabase, $CachedProfilesTable, CachedProfile>, 2597 + ), 1769 2598 CachedProfile, 1770 2599 PrefetchHooks Function() 1771 2600 > { 1772 - $$CachedProfilesTableTableManager(_$AppDatabase db, $CachedProfilesTable table) 1773 - : super( 2601 + $$CachedProfilesTableTableManager( 2602 + _$AppDatabase db, 2603 + $CachedProfilesTable table, 2604 + ) : super( 1774 2605 TableManagerState( 1775 2606 db: db, 1776 2607 table: table, 1777 - createFilteringComposer: () => $$CachedProfilesTableFilterComposer($db: db, $table: table), 1778 - createOrderingComposer: () => $$CachedProfilesTableOrderingComposer($db: db, $table: table), 1779 - createComputedFieldComposer: () => $$CachedProfilesTableAnnotationComposer($db: db, $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), 1780 2614 updateCompanionCallback: 1781 2615 ({ 1782 2616 Value<String> did = const Value.absent(), ··· 1805 2639 fetchedAt: fetchedAt, 1806 2640 rowid: rowid, 1807 2641 ), 1808 - withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 2642 + withReferenceMapper: (p0) => p0 2643 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 2644 + .toList(), 1809 2645 prefetchHooksCallback: null, 1810 2646 ), 1811 2647 ); ··· 1821 2657 $$CachedProfilesTableAnnotationComposer, 1822 2658 $$CachedProfilesTableCreateCompanionBuilder, 1823 2659 $$CachedProfilesTableUpdateCompanionBuilder, 1824 - (CachedProfile, BaseReferences<_$AppDatabase, $CachedProfilesTable, CachedProfile>), 2660 + ( 2661 + CachedProfile, 2662 + BaseReferences<_$AppDatabase, $CachedProfilesTable, CachedProfile>, 2663 + ), 1825 2664 CachedProfile, 1826 2665 PrefetchHooks Function() 1827 2666 >; ··· 1844 2683 Value<int> rowid, 1845 2684 }); 1846 2685 1847 - class $$CachedPostsTableFilterComposer extends Composer<_$AppDatabase, $CachedPostsTable> { 2686 + class $$CachedPostsTableFilterComposer 2687 + extends Composer<_$AppDatabase, $CachedPostsTable> { 1848 2688 $$CachedPostsTableFilterComposer({ 1849 2689 required super.$db, 1850 2690 required super.$table, ··· 1852 2692 super.$addJoinBuilderToRootComposer, 1853 2693 super.$removeJoinBuilderFromRootComposer, 1854 2694 }); 1855 - ColumnFilters<String> get uri => $composableBuilder(column: $table.uri, builder: (column) => ColumnFilters(column)); 2695 + ColumnFilters<String> get uri => $composableBuilder( 2696 + column: $table.uri, 2697 + builder: (column) => ColumnFilters(column), 2698 + ); 1856 2699 1857 - ColumnFilters<String> get authorDid => 1858 - $composableBuilder(column: $table.authorDid, builder: (column) => ColumnFilters(column)); 2700 + ColumnFilters<String> get authorDid => $composableBuilder( 2701 + column: $table.authorDid, 2702 + builder: (column) => ColumnFilters(column), 2703 + ); 1859 2704 1860 - ColumnFilters<String> get payload => 1861 - $composableBuilder(column: $table.payload, builder: (column) => ColumnFilters(column)); 2705 + ColumnFilters<String> get payload => $composableBuilder( 2706 + column: $table.payload, 2707 + builder: (column) => ColumnFilters(column), 2708 + ); 1862 2709 1863 - ColumnFilters<DateTime> get createdAt => 1864 - $composableBuilder(column: $table.createdAt, builder: (column) => ColumnFilters(column)); 2710 + ColumnFilters<DateTime> get createdAt => $composableBuilder( 2711 + column: $table.createdAt, 2712 + builder: (column) => ColumnFilters(column), 2713 + ); 1865 2714 1866 - ColumnFilters<DateTime> get fetchedAt => 1867 - $composableBuilder(column: $table.fetchedAt, builder: (column) => ColumnFilters(column)); 2715 + ColumnFilters<DateTime> get fetchedAt => $composableBuilder( 2716 + column: $table.fetchedAt, 2717 + builder: (column) => ColumnFilters(column), 2718 + ); 1868 2719 } 1869 2720 1870 - class $$CachedPostsTableOrderingComposer extends Composer<_$AppDatabase, $CachedPostsTable> { 2721 + class $$CachedPostsTableOrderingComposer 2722 + extends Composer<_$AppDatabase, $CachedPostsTable> { 1871 2723 $$CachedPostsTableOrderingComposer({ 1872 2724 required super.$db, 1873 2725 required super.$table, ··· 1875 2727 super.$addJoinBuilderToRootComposer, 1876 2728 super.$removeJoinBuilderFromRootComposer, 1877 2729 }); 1878 - ColumnOrderings<String> get uri => 1879 - $composableBuilder(column: $table.uri, builder: (column) => ColumnOrderings(column)); 2730 + ColumnOrderings<String> get uri => $composableBuilder( 2731 + column: $table.uri, 2732 + builder: (column) => ColumnOrderings(column), 2733 + ); 1880 2734 1881 - ColumnOrderings<String> get authorDid => 1882 - $composableBuilder(column: $table.authorDid, builder: (column) => ColumnOrderings(column)); 2735 + ColumnOrderings<String> get authorDid => $composableBuilder( 2736 + column: $table.authorDid, 2737 + builder: (column) => ColumnOrderings(column), 2738 + ); 1883 2739 1884 - ColumnOrderings<String> get payload => 1885 - $composableBuilder(column: $table.payload, builder: (column) => ColumnOrderings(column)); 2740 + ColumnOrderings<String> get payload => $composableBuilder( 2741 + column: $table.payload, 2742 + builder: (column) => ColumnOrderings(column), 2743 + ); 1886 2744 1887 - ColumnOrderings<DateTime> get createdAt => 1888 - $composableBuilder(column: $table.createdAt, builder: (column) => ColumnOrderings(column)); 2745 + ColumnOrderings<DateTime> get createdAt => $composableBuilder( 2746 + column: $table.createdAt, 2747 + builder: (column) => ColumnOrderings(column), 2748 + ); 1889 2749 1890 - ColumnOrderings<DateTime> get fetchedAt => 1891 - $composableBuilder(column: $table.fetchedAt, builder: (column) => ColumnOrderings(column)); 2750 + ColumnOrderings<DateTime> get fetchedAt => $composableBuilder( 2751 + column: $table.fetchedAt, 2752 + builder: (column) => ColumnOrderings(column), 2753 + ); 1892 2754 } 1893 2755 1894 - class $$CachedPostsTableAnnotationComposer extends Composer<_$AppDatabase, $CachedPostsTable> { 2756 + class $$CachedPostsTableAnnotationComposer 2757 + extends Composer<_$AppDatabase, $CachedPostsTable> { 1895 2758 $$CachedPostsTableAnnotationComposer({ 1896 2759 required super.$db, 1897 2760 required super.$table, ··· 1899 2762 super.$addJoinBuilderToRootComposer, 1900 2763 super.$removeJoinBuilderFromRootComposer, 1901 2764 }); 1902 - GeneratedColumn<String> get uri => $composableBuilder(column: $table.uri, builder: (column) => column); 2765 + GeneratedColumn<String> get uri => 2766 + $composableBuilder(column: $table.uri, builder: (column) => column); 1903 2767 1904 - GeneratedColumn<String> get authorDid => $composableBuilder(column: $table.authorDid, builder: (column) => column); 2768 + GeneratedColumn<String> get authorDid => 2769 + $composableBuilder(column: $table.authorDid, builder: (column) => column); 1905 2770 1906 - GeneratedColumn<String> get payload => $composableBuilder(column: $table.payload, builder: (column) => column); 2771 + GeneratedColumn<String> get payload => 2772 + $composableBuilder(column: $table.payload, builder: (column) => column); 1907 2773 1908 - GeneratedColumn<DateTime> get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); 2774 + GeneratedColumn<DateTime> get createdAt => 2775 + $composableBuilder(column: $table.createdAt, builder: (column) => column); 1909 2776 1910 - GeneratedColumn<DateTime> get fetchedAt => $composableBuilder(column: $table.fetchedAt, builder: (column) => column); 2777 + GeneratedColumn<DateTime> get fetchedAt => 2778 + $composableBuilder(column: $table.fetchedAt, builder: (column) => column); 1911 2779 } 1912 2780 1913 2781 class $$CachedPostsTableTableManager ··· 1921 2789 $$CachedPostsTableAnnotationComposer, 1922 2790 $$CachedPostsTableCreateCompanionBuilder, 1923 2791 $$CachedPostsTableUpdateCompanionBuilder, 1924 - (CachedPost, BaseReferences<_$AppDatabase, $CachedPostsTable, CachedPost>), 2792 + ( 2793 + CachedPost, 2794 + BaseReferences<_$AppDatabase, $CachedPostsTable, CachedPost>, 2795 + ), 1925 2796 CachedPost, 1926 2797 PrefetchHooks Function() 1927 2798 > { ··· 1930 2801 TableManagerState( 1931 2802 db: db, 1932 2803 table: table, 1933 - createFilteringComposer: () => $$CachedPostsTableFilterComposer($db: db, $table: table), 1934 - createOrderingComposer: () => $$CachedPostsTableOrderingComposer($db: db, $table: table), 1935 - createComputedFieldComposer: () => $$CachedPostsTableAnnotationComposer($db: db, $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), 1936 2810 updateCompanionCallback: 1937 2811 ({ 1938 2812 Value<String> uri = const Value.absent(), ··· 1965 2839 fetchedAt: fetchedAt, 1966 2840 rowid: rowid, 1967 2841 ), 1968 - withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 2842 + withReferenceMapper: (p0) => p0 2843 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 2844 + .toList(), 1969 2845 prefetchHooksCallback: null, 1970 2846 ), 1971 2847 ); ··· 1981 2857 $$CachedPostsTableAnnotationComposer, 1982 2858 $$CachedPostsTableCreateCompanionBuilder, 1983 2859 $$CachedPostsTableUpdateCompanionBuilder, 1984 - (CachedPost, BaseReferences<_$AppDatabase, $CachedPostsTable, CachedPost>), 2860 + ( 2861 + CachedPost, 2862 + BaseReferences<_$AppDatabase, $CachedPostsTable, CachedPost>, 2863 + ), 1985 2864 CachedPost, 1986 2865 PrefetchHooks Function() 1987 2866 >; ··· 1993 2872 Value<int> rowid, 1994 2873 }); 1995 2874 typedef $$SettingsTableUpdateCompanionBuilder = 1996 - SettingsCompanion Function({Value<String> key, Value<String> value, Value<DateTime> updatedAt, Value<int> rowid}); 2875 + SettingsCompanion Function({ 2876 + Value<String> key, 2877 + Value<String> value, 2878 + Value<DateTime> updatedAt, 2879 + Value<int> rowid, 2880 + }); 1997 2881 1998 - class $$SettingsTableFilterComposer extends Composer<_$AppDatabase, $SettingsTable> { 2882 + class $$SettingsTableFilterComposer 2883 + extends Composer<_$AppDatabase, $SettingsTable> { 1999 2884 $$SettingsTableFilterComposer({ 2000 2885 required super.$db, 2001 2886 required super.$table, ··· 2003 2888 super.$addJoinBuilderToRootComposer, 2004 2889 super.$removeJoinBuilderFromRootComposer, 2005 2890 }); 2006 - ColumnFilters<String> get key => $composableBuilder(column: $table.key, builder: (column) => ColumnFilters(column)); 2891 + ColumnFilters<String> get key => $composableBuilder( 2892 + column: $table.key, 2893 + builder: (column) => ColumnFilters(column), 2894 + ); 2007 2895 2008 - ColumnFilters<String> get value => 2009 - $composableBuilder(column: $table.value, builder: (column) => ColumnFilters(column)); 2896 + ColumnFilters<String> get value => $composableBuilder( 2897 + column: $table.value, 2898 + builder: (column) => ColumnFilters(column), 2899 + ); 2010 2900 2011 - ColumnFilters<DateTime> get updatedAt => 2012 - $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnFilters(column)); 2901 + ColumnFilters<DateTime> get updatedAt => $composableBuilder( 2902 + column: $table.updatedAt, 2903 + builder: (column) => ColumnFilters(column), 2904 + ); 2013 2905 } 2014 2906 2015 - class $$SettingsTableOrderingComposer extends Composer<_$AppDatabase, $SettingsTable> { 2907 + class $$SettingsTableOrderingComposer 2908 + extends Composer<_$AppDatabase, $SettingsTable> { 2016 2909 $$SettingsTableOrderingComposer({ 2017 2910 required super.$db, 2018 2911 required super.$table, ··· 2020 2913 super.$addJoinBuilderToRootComposer, 2021 2914 super.$removeJoinBuilderFromRootComposer, 2022 2915 }); 2023 - ColumnOrderings<String> get key => 2024 - $composableBuilder(column: $table.key, builder: (column) => ColumnOrderings(column)); 2916 + ColumnOrderings<String> get key => $composableBuilder( 2917 + column: $table.key, 2918 + builder: (column) => ColumnOrderings(column), 2919 + ); 2025 2920 2026 - ColumnOrderings<String> get value => 2027 - $composableBuilder(column: $table.value, builder: (column) => ColumnOrderings(column)); 2921 + ColumnOrderings<String> get value => $composableBuilder( 2922 + column: $table.value, 2923 + builder: (column) => ColumnOrderings(column), 2924 + ); 2028 2925 2029 - ColumnOrderings<DateTime> get updatedAt => 2030 - $composableBuilder(column: $table.updatedAt, builder: (column) => ColumnOrderings(column)); 2926 + ColumnOrderings<DateTime> get updatedAt => $composableBuilder( 2927 + column: $table.updatedAt, 2928 + builder: (column) => ColumnOrderings(column), 2929 + ); 2031 2930 } 2032 2931 2033 - class $$SettingsTableAnnotationComposer extends Composer<_$AppDatabase, $SettingsTable> { 2932 + class $$SettingsTableAnnotationComposer 2933 + extends Composer<_$AppDatabase, $SettingsTable> { 2034 2934 $$SettingsTableAnnotationComposer({ 2035 2935 required super.$db, 2036 2936 required super.$table, ··· 2038 2938 super.$addJoinBuilderToRootComposer, 2039 2939 super.$removeJoinBuilderFromRootComposer, 2040 2940 }); 2041 - GeneratedColumn<String> get key => $composableBuilder(column: $table.key, builder: (column) => column); 2941 + GeneratedColumn<String> get key => 2942 + $composableBuilder(column: $table.key, builder: (column) => column); 2042 2943 2043 - GeneratedColumn<String> get value => $composableBuilder(column: $table.value, builder: (column) => column); 2944 + GeneratedColumn<String> get value => 2945 + $composableBuilder(column: $table.value, builder: (column) => column); 2044 2946 2045 - GeneratedColumn<DateTime> get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); 2947 + GeneratedColumn<DateTime> get updatedAt => 2948 + $composableBuilder(column: $table.updatedAt, builder: (column) => column); 2046 2949 } 2047 2950 2048 2951 class $$SettingsTableTableManager ··· 2056 2959 $$SettingsTableAnnotationComposer, 2057 2960 $$SettingsTableCreateCompanionBuilder, 2058 2961 $$SettingsTableUpdateCompanionBuilder, 2059 - (SettingsEntry, BaseReferences<_$AppDatabase, $SettingsTable, SettingsEntry>), 2962 + ( 2963 + SettingsEntry, 2964 + BaseReferences<_$AppDatabase, $SettingsTable, SettingsEntry>, 2965 + ), 2060 2966 SettingsEntry, 2061 2967 PrefetchHooks Function() 2062 2968 > { ··· 2065 2971 TableManagerState( 2066 2972 db: db, 2067 2973 table: table, 2068 - createFilteringComposer: () => $$SettingsTableFilterComposer($db: db, $table: table), 2069 - createOrderingComposer: () => $$SettingsTableOrderingComposer($db: db, $table: table), 2070 - createComputedFieldComposer: () => $$SettingsTableAnnotationComposer($db: db, $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), 2071 2980 updateCompanionCallback: 2072 2981 ({ 2073 2982 Value<String> key = const Value.absent(), 2074 2983 Value<String> value = const Value.absent(), 2075 2984 Value<DateTime> updatedAt = const Value.absent(), 2076 2985 Value<int> rowid = const Value.absent(), 2077 - }) => SettingsCompanion(key: key, value: value, updatedAt: updatedAt, rowid: rowid), 2986 + }) => SettingsCompanion( 2987 + key: key, 2988 + value: value, 2989 + updatedAt: updatedAt, 2990 + rowid: rowid, 2991 + ), 2078 2992 createCompanionCallback: 2079 2993 ({ 2080 2994 required String key, 2081 2995 required String value, 2082 2996 Value<DateTime> updatedAt = const Value.absent(), 2083 2997 Value<int> rowid = const Value.absent(), 2084 - }) => SettingsCompanion.insert(key: key, value: value, updatedAt: updatedAt, rowid: rowid), 2085 - withReferenceMapper: (p0) => p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(), 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(), 2086 3007 prefetchHooksCallback: null, 2087 3008 ), 2088 3009 ); ··· 2098 3019 $$SettingsTableAnnotationComposer, 2099 3020 $$SettingsTableCreateCompanionBuilder, 2100 3021 $$SettingsTableUpdateCompanionBuilder, 2101 - (SettingsEntry, BaseReferences<_$AppDatabase, $SettingsTable, SettingsEntry>), 3022 + ( 3023 + SettingsEntry, 3024 + BaseReferences<_$AppDatabase, $SettingsTable, SettingsEntry>, 3025 + ), 2102 3026 SettingsEntry, 2103 3027 PrefetchHooks Function() 2104 3028 >; 3029 + typedef $$SavedFeedsTableCreateCompanionBuilder = 3030 + SavedFeedsCompanion Function({ 3031 + required String id, 3032 + required String accountDid, 3033 + required String type, 3034 + required String value, 3035 + Value<bool> pinned, 3036 + Value<int> sortOrder, 3037 + Value<DateTime> updatedAt, 3038 + Value<int> rowid, 3039 + }); 3040 + typedef $$SavedFeedsTableUpdateCompanionBuilder = 3041 + SavedFeedsCompanion Function({ 3042 + Value<String> id, 3043 + Value<String> accountDid, 3044 + Value<String> type, 3045 + Value<String> value, 3046 + Value<bool> pinned, 3047 + Value<int> sortOrder, 3048 + Value<DateTime> updatedAt, 3049 + Value<int> rowid, 3050 + }); 3051 + 3052 + class $$SavedFeedsTableFilterComposer 3053 + extends Composer<_$AppDatabase, $SavedFeedsTable> { 3054 + $$SavedFeedsTableFilterComposer({ 3055 + required super.$db, 3056 + required super.$table, 3057 + super.joinBuilder, 3058 + super.$addJoinBuilderToRootComposer, 3059 + super.$removeJoinBuilderFromRootComposer, 3060 + }); 3061 + ColumnFilters<String> get id => $composableBuilder( 3062 + column: $table.id, 3063 + builder: (column) => ColumnFilters(column), 3064 + ); 3065 + 3066 + ColumnFilters<String> get accountDid => $composableBuilder( 3067 + column: $table.accountDid, 3068 + builder: (column) => ColumnFilters(column), 3069 + ); 3070 + 3071 + ColumnFilters<String> get type => $composableBuilder( 3072 + column: $table.type, 3073 + builder: (column) => ColumnFilters(column), 3074 + ); 3075 + 3076 + ColumnFilters<String> get value => $composableBuilder( 3077 + column: $table.value, 3078 + builder: (column) => ColumnFilters(column), 3079 + ); 3080 + 3081 + ColumnFilters<bool> get pinned => $composableBuilder( 3082 + column: $table.pinned, 3083 + builder: (column) => ColumnFilters(column), 3084 + ); 3085 + 3086 + ColumnFilters<int> get sortOrder => $composableBuilder( 3087 + column: $table.sortOrder, 3088 + builder: (column) => ColumnFilters(column), 3089 + ); 3090 + 3091 + ColumnFilters<DateTime> get updatedAt => $composableBuilder( 3092 + column: $table.updatedAt, 3093 + builder: (column) => ColumnFilters(column), 3094 + ); 3095 + } 3096 + 3097 + class $$SavedFeedsTableOrderingComposer 3098 + extends Composer<_$AppDatabase, $SavedFeedsTable> { 3099 + $$SavedFeedsTableOrderingComposer({ 3100 + required super.$db, 3101 + required super.$table, 3102 + super.joinBuilder, 3103 + super.$addJoinBuilderToRootComposer, 3104 + super.$removeJoinBuilderFromRootComposer, 3105 + }); 3106 + ColumnOrderings<String> get id => $composableBuilder( 3107 + column: $table.id, 3108 + builder: (column) => ColumnOrderings(column), 3109 + ); 3110 + 3111 + ColumnOrderings<String> get accountDid => $composableBuilder( 3112 + column: $table.accountDid, 3113 + builder: (column) => ColumnOrderings(column), 3114 + ); 3115 + 3116 + ColumnOrderings<String> get type => $composableBuilder( 3117 + column: $table.type, 3118 + builder: (column) => ColumnOrderings(column), 3119 + ); 3120 + 3121 + ColumnOrderings<String> get value => $composableBuilder( 3122 + column: $table.value, 3123 + builder: (column) => ColumnOrderings(column), 3124 + ); 3125 + 3126 + ColumnOrderings<bool> get pinned => $composableBuilder( 3127 + column: $table.pinned, 3128 + builder: (column) => ColumnOrderings(column), 3129 + ); 3130 + 3131 + ColumnOrderings<int> get sortOrder => $composableBuilder( 3132 + column: $table.sortOrder, 3133 + builder: (column) => ColumnOrderings(column), 3134 + ); 3135 + 3136 + ColumnOrderings<DateTime> get updatedAt => $composableBuilder( 3137 + column: $table.updatedAt, 3138 + builder: (column) => ColumnOrderings(column), 3139 + ); 3140 + } 3141 + 3142 + class $$SavedFeedsTableAnnotationComposer 3143 + extends Composer<_$AppDatabase, $SavedFeedsTable> { 3144 + $$SavedFeedsTableAnnotationComposer({ 3145 + required super.$db, 3146 + required super.$table, 3147 + super.joinBuilder, 3148 + super.$addJoinBuilderToRootComposer, 3149 + super.$removeJoinBuilderFromRootComposer, 3150 + }); 3151 + GeneratedColumn<String> get id => 3152 + $composableBuilder(column: $table.id, builder: (column) => column); 3153 + 3154 + GeneratedColumn<String> get accountDid => $composableBuilder( 3155 + column: $table.accountDid, 3156 + builder: (column) => column, 3157 + ); 3158 + 3159 + GeneratedColumn<String> get type => 3160 + $composableBuilder(column: $table.type, builder: (column) => column); 3161 + 3162 + GeneratedColumn<String> get value => 3163 + $composableBuilder(column: $table.value, builder: (column) => column); 3164 + 3165 + GeneratedColumn<bool> get pinned => 3166 + $composableBuilder(column: $table.pinned, builder: (column) => column); 3167 + 3168 + GeneratedColumn<int> get sortOrder => 3169 + $composableBuilder(column: $table.sortOrder, builder: (column) => column); 3170 + 3171 + GeneratedColumn<DateTime> get updatedAt => 3172 + $composableBuilder(column: $table.updatedAt, builder: (column) => column); 3173 + } 3174 + 3175 + class $$SavedFeedsTableTableManager 3176 + extends 3177 + RootTableManager< 3178 + _$AppDatabase, 3179 + $SavedFeedsTable, 3180 + SavedFeedEntry, 3181 + $$SavedFeedsTableFilterComposer, 3182 + $$SavedFeedsTableOrderingComposer, 3183 + $$SavedFeedsTableAnnotationComposer, 3184 + $$SavedFeedsTableCreateCompanionBuilder, 3185 + $$SavedFeedsTableUpdateCompanionBuilder, 3186 + ( 3187 + SavedFeedEntry, 3188 + BaseReferences<_$AppDatabase, $SavedFeedsTable, SavedFeedEntry>, 3189 + ), 3190 + SavedFeedEntry, 3191 + PrefetchHooks Function() 3192 + > { 3193 + $$SavedFeedsTableTableManager(_$AppDatabase db, $SavedFeedsTable table) 3194 + : super( 3195 + TableManagerState( 3196 + db: db, 3197 + 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), 3204 + updateCompanionCallback: 3205 + ({ 3206 + Value<String> id = const Value.absent(), 3207 + Value<String> accountDid = const Value.absent(), 3208 + Value<String> type = const Value.absent(), 3209 + Value<String> value = const Value.absent(), 3210 + Value<bool> pinned = const Value.absent(), 3211 + Value<int> sortOrder = const Value.absent(), 3212 + Value<DateTime> updatedAt = const Value.absent(), 3213 + Value<int> rowid = const Value.absent(), 3214 + }) => SavedFeedsCompanion( 3215 + id: id, 3216 + accountDid: accountDid, 3217 + type: type, 3218 + value: value, 3219 + pinned: pinned, 3220 + sortOrder: sortOrder, 3221 + updatedAt: updatedAt, 3222 + rowid: rowid, 3223 + ), 3224 + createCompanionCallback: 3225 + ({ 3226 + required String id, 3227 + required String accountDid, 3228 + required String type, 3229 + required String value, 3230 + Value<bool> pinned = const Value.absent(), 3231 + Value<int> sortOrder = const Value.absent(), 3232 + Value<DateTime> updatedAt = const Value.absent(), 3233 + Value<int> rowid = const Value.absent(), 3234 + }) => SavedFeedsCompanion.insert( 3235 + id: id, 3236 + accountDid: accountDid, 3237 + type: type, 3238 + value: value, 3239 + pinned: pinned, 3240 + sortOrder: sortOrder, 3241 + updatedAt: updatedAt, 3242 + rowid: rowid, 3243 + ), 3244 + withReferenceMapper: (p0) => p0 3245 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) 3246 + .toList(), 3247 + prefetchHooksCallback: null, 3248 + ), 3249 + ); 3250 + } 3251 + 3252 + typedef $$SavedFeedsTableProcessedTableManager = 3253 + ProcessedTableManager< 3254 + _$AppDatabase, 3255 + $SavedFeedsTable, 3256 + SavedFeedEntry, 3257 + $$SavedFeedsTableFilterComposer, 3258 + $$SavedFeedsTableOrderingComposer, 3259 + $$SavedFeedsTableAnnotationComposer, 3260 + $$SavedFeedsTableCreateCompanionBuilder, 3261 + $$SavedFeedsTableUpdateCompanionBuilder, 3262 + ( 3263 + SavedFeedEntry, 3264 + BaseReferences<_$AppDatabase, $SavedFeedsTable, SavedFeedEntry>, 3265 + ), 3266 + SavedFeedEntry, 3267 + PrefetchHooks Function() 3268 + >; 2105 3269 2106 3270 class $AppDatabaseManager { 2107 3271 final _$AppDatabase _db; 2108 3272 $AppDatabaseManager(this._db); 2109 - $$AccountsTableTableManager get accounts => $$AccountsTableTableManager(_db, _db.accounts); 2110 - $$CachedProfilesTableTableManager get cachedProfiles => $$CachedProfilesTableTableManager(_db, _db.cachedProfiles); 2111 - $$CachedPostsTableTableManager get cachedPosts => $$CachedPostsTableTableManager(_db, _db.cachedPosts); 2112 - $$SettingsTableTableManager get settings => $$SettingsTableTableManager(_db, _db.settings); 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); 2113 3283 }
+14
lib/core/database/tables.dart
··· 51 51 @override 52 52 Set<Column> get primaryKey => {key}; 53 53 } 54 + 55 + @DataClassName('SavedFeedEntry') 56 + class SavedFeeds extends Table { 57 + TextColumn get id => text()(); 58 + TextColumn get accountDid => text()(); 59 + TextColumn get type => text()(); 60 + TextColumn get value => text()(); 61 + BoolColumn get pinned => boolean().withDefault(const Constant(false))(); 62 + IntColumn get sortOrder => integer().withDefault(const Constant(0))(); 63 + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); 64 + 65 + @override 66 + Set<Column> get primaryKey => {id, accountDid}; 67 + }
+4 -2
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/features/auth/presentation/home_screen.dart'; 7 6 import 'package:lazurite/features/auth/presentation/login_screen.dart'; 7 + import 'package:lazurite/features/feed/presentation/feed_management_screen.dart'; 8 + import 'package:lazurite/features/feed/presentation/home_feed_screen.dart'; 8 9 import 'package:lazurite/features/logs/presentation/logs_screen.dart'; 9 10 import 'package:lazurite/features/profile/presentation/profile_screen.dart'; 10 11 import 'package:lazurite/features/settings/presentation/settings_screen.dart'; ··· 32 33 return null; 33 34 }, 34 35 routes: [ 35 - GoRoute(path: '/', builder: (context, state) => const HomeScreen()), 36 + GoRoute(path: '/', builder: (context, state) => const HomeFeedScreen()), 36 37 GoRoute(path: '/login', builder: (context, state) => const LoginScreen()), 37 38 GoRoute( 38 39 path: '/profile', ··· 40 41 ), 41 42 GoRoute(path: '/settings', builder: (context, state) => const SettingsScreen()), 42 43 GoRoute(path: '/logs', builder: (context, state) => const LogsScreen()), 44 + GoRoute(path: '/feeds', builder: (context, state) => const FeedManagementScreen()), 43 45 ], 44 46 ); 45 47 }
+229
lib/features/feed/cubit/feed_preferences_cubit.dart
··· 1 + import 'package:bluesky/app_bsky_actor_defs.dart'; 2 + import 'package:drift/drift.dart'; 3 + import 'package:equatable/equatable.dart'; 4 + import 'package:flutter_bloc/flutter_bloc.dart'; 5 + import 'package:lazurite/core/database/app_database.dart'; 6 + import 'package:lazurite/features/feed/data/feed_repository.dart'; 7 + import 'package:uuid/uuid.dart'; 8 + 9 + class FeedPreferencesCubit extends Cubit<FeedPreferencesState> { 10 + FeedPreferencesCubit({ 11 + required FeedRepository feedRepository, 12 + required AppDatabase database, 13 + required String accountDid, 14 + }) : _feedRepository = feedRepository, 15 + _database = database, 16 + _accountDid = accountDid, 17 + super(const FeedPreferencesState.initial()); 18 + 19 + final FeedRepository _feedRepository; 20 + final AppDatabase _database; 21 + final String _accountDid; 22 + 23 + Future<void> loadPreferences() async { 24 + emit(state.copyWith(status: FeedPreferencesStatus.loading)); 25 + 26 + try { 27 + final cachedFeeds = await _database.getSavedFeeds(_accountDid); 28 + 29 + if (cachedFeeds.isNotEmpty) { 30 + final feeds = cachedFeeds.map(_mapFromCached).toList(); 31 + emit(FeedPreferencesState.loaded(feeds: feeds)); 32 + } 33 + 34 + final result = await _feedRepository.getPreferences(); 35 + final savedFeedsPref = result.preferences.whereType<UPreferencesSavedFeedsPrefV2>().firstOrNull; 36 + 37 + if (savedFeedsPref != null) { 38 + final feeds = savedFeedsPref.data.items; 39 + await _cacheFeeds(feeds); 40 + emit(FeedPreferencesState.loaded(feeds: feeds)); 41 + } else { 42 + final defaultFeeds = [_createDefaultTimelineFeed()]; 43 + await _cacheFeeds(defaultFeeds); 44 + emit(FeedPreferencesState.loaded(feeds: defaultFeeds)); 45 + } 46 + } catch (e) { 47 + final cachedFeeds = await _database.getSavedFeeds(_accountDid); 48 + if (cachedFeeds.isNotEmpty) { 49 + final feeds = cachedFeeds.map(_mapFromCached).toList(); 50 + emit(FeedPreferencesState.loaded(feeds: feeds)); 51 + } else { 52 + emit(FeedPreferencesState.error(message: e.toString())); 53 + } 54 + } 55 + } 56 + 57 + Future<void> pinFeed(String feedId) async { 58 + final currentFeeds = state.feeds; 59 + final feedToPin = currentFeeds.firstWhere((f) => f.id == feedId); 60 + final pinnedFeeds = currentFeeds.where((f) => f.pinned).toList(); 61 + final unpinnedFeeds = currentFeeds.where((f) => !f.pinned && f.id != feedId).toList(); 62 + 63 + final updatedFeed = SavedFeed(id: feedToPin.id, type: feedToPin.type, value: feedToPin.value, pinned: true); 64 + final newFeeds = [...pinnedFeeds, updatedFeed, ...unpinnedFeeds]; 65 + 66 + await _savePreferences(newFeeds); 67 + } 68 + 69 + Future<void> unpinFeed(String feedId) async { 70 + final currentFeeds = state.feeds; 71 + final feedToUnpin = currentFeeds.firstWhere((f) => f.id == feedId); 72 + final pinnedFeeds = currentFeeds.where((f) => f.pinned && f.id != feedId).toList(); 73 + final unpinnedFeeds = currentFeeds.where((f) => !f.pinned).toList(); 74 + 75 + final updatedFeed = SavedFeed(id: feedToUnpin.id, type: feedToUnpin.type, value: feedToUnpin.value, pinned: false); 76 + final newFeeds = [...pinnedFeeds, ...unpinnedFeeds, updatedFeed]; 77 + 78 + await _savePreferences(newFeeds); 79 + } 80 + 81 + Future<void> reorderPinnedFeeds(int oldIndex, int newIndex) async { 82 + final pinnedFeeds = List<SavedFeed>.from(state.pinnedFeeds); 83 + final unpinnedFeeds = state.unpinnedFeeds; 84 + 85 + if (oldIndex < 0 || oldIndex >= pinnedFeeds.length) return; 86 + 87 + final adjustedNewIndex = newIndex > oldIndex ? newIndex - 1 : newIndex; 88 + final item = pinnedFeeds.removeAt(oldIndex); 89 + pinnedFeeds.insert(adjustedNewIndex, item); 90 + 91 + final newFeeds = [...pinnedFeeds, ...unpinnedFeeds]; 92 + await _savePreferences(newFeeds); 93 + } 94 + 95 + Future<void> reorderFeeds(int oldIndex, int newIndex) async { 96 + final feeds = List<SavedFeed>.from(state.feeds); 97 + if (oldIndex < 0 || oldIndex >= feeds.length) return; 98 + 99 + final item = feeds.removeAt(oldIndex); 100 + final adjustedNewIndex = newIndex > oldIndex ? newIndex - 1 : newIndex; 101 + feeds.insert(adjustedNewIndex, item); 102 + 103 + await _savePreferences(feeds); 104 + } 105 + 106 + Future<void> removeFeed(String feedId) async { 107 + final feeds = state.feeds.where((f) => f.id != feedId).toList(); 108 + await _savePreferences(feeds); 109 + } 110 + 111 + Future<void> addFeed({required SavedFeedType type, required String value, bool pinned = false}) async { 112 + final newFeed = SavedFeed(id: _generateId(), type: type, value: value, pinned: pinned); 113 + final feeds = [...state.feeds, newFeed]; 114 + await _savePreferences(feeds); 115 + } 116 + 117 + Future<bool> _savePreferences(List<SavedFeed> feeds) async { 118 + final previousState = state; 119 + emit(state.copyWith(status: FeedPreferencesStatus.saving)); 120 + 121 + try { 122 + await _cacheFeeds(feeds); 123 + 124 + final result = await _feedRepository.getPreferences(); 125 + final preferences = List<UPreferences>.from(result.preferences); 126 + 127 + preferences.removeWhere((p) => p is UPreferencesSavedFeedsPrefV2); 128 + preferences.add(UPreferences.savedFeedsPrefV2(data: SavedFeedsPrefV2(items: feeds))); 129 + 130 + await _feedRepository.putPreferences(preferences: preferences); 131 + 132 + emit(FeedPreferencesState.loaded(feeds: feeds)); 133 + return true; 134 + } catch (e) { 135 + emit(FeedPreferencesState.saveError(feeds: feeds, message: e.toString(), previousState: previousState)); 136 + return false; 137 + } 138 + } 139 + 140 + void clearError() { 141 + if (state.status == FeedPreferencesStatus.saveError && state.previousState != null) { 142 + emit(state.previousState!); 143 + } 144 + } 145 + 146 + Future<void> _cacheFeeds(List<SavedFeed> feeds) async { 147 + final companions = feeds.asMap().entries.map((entry) { 148 + final index = entry.key; 149 + final feed = entry.value; 150 + return SavedFeedsCompanion( 151 + id: Value(feed.id), 152 + accountDid: Value(_accountDid), 153 + type: Value(feed.type.toJson()), 154 + value: Value(feed.value), 155 + pinned: Value(feed.pinned), 156 + sortOrder: Value(index), 157 + updatedAt: Value(DateTime.now()), 158 + ); 159 + }).toList(); 160 + 161 + await _database.replaceSavedFeeds(_accountDid, companions); 162 + } 163 + 164 + SavedFeed _mapFromCached(SavedFeedEntry entry) { 165 + return SavedFeed( 166 + id: entry.id, 167 + type: SavedFeedType.valueOf(entry.type) ?? const SavedFeedType.knownValue(data: KnownSavedFeedType.feed), 168 + value: entry.value, 169 + pinned: entry.pinned, 170 + ); 171 + } 172 + 173 + String _generateId() => const Uuid().v4(); 174 + 175 + SavedFeed _createDefaultTimelineFeed() { 176 + return SavedFeed( 177 + id: _generateId(), 178 + type: const SavedFeedType.knownValue(data: KnownSavedFeedType.timeline), 179 + value: 'timeline', 180 + pinned: true, 181 + ); 182 + } 183 + } 184 + 185 + enum FeedPreferencesStatus { initial, loading, loaded, saving, saveError, error } 186 + 187 + class FeedPreferencesState extends Equatable { 188 + const FeedPreferencesState._({required this.status, this.feeds = const [], this.message, this.previousState}); 189 + 190 + const FeedPreferencesState.initial() : this._(status: FeedPreferencesStatus.initial); 191 + 192 + const FeedPreferencesState.loaded({required List<SavedFeed> feeds}) 193 + : this._(status: FeedPreferencesStatus.loaded, feeds: feeds); 194 + 195 + const FeedPreferencesState.error({required String message}) 196 + : this._(status: FeedPreferencesStatus.error, message: message); 197 + 198 + const FeedPreferencesState.saveError({ 199 + required List<SavedFeed> feeds, 200 + required String message, 201 + required FeedPreferencesState previousState, 202 + }) : this._(status: FeedPreferencesStatus.saveError, feeds: feeds, message: message, previousState: previousState); 203 + 204 + final FeedPreferencesStatus status; 205 + final List<SavedFeed> feeds; 206 + final String? message; 207 + final FeedPreferencesState? previousState; 208 + 209 + List<SavedFeed> get pinnedFeeds => feeds.where((f) => f.pinned).toList(); 210 + 211 + List<SavedFeed> get unpinnedFeeds => feeds.where((f) => !f.pinned).toList(); 212 + 213 + FeedPreferencesState copyWith({ 214 + FeedPreferencesStatus? status, 215 + List<SavedFeed>? feeds, 216 + String? message, 217 + FeedPreferencesState? previousState, 218 + }) { 219 + return FeedPreferencesState._( 220 + status: status ?? this.status, 221 + feeds: feeds ?? this.feeds, 222 + message: message ?? this.message, 223 + previousState: previousState ?? this.previousState, 224 + ); 225 + } 226 + 227 + @override 228 + List<Object?> get props => [status, feeds, message, previousState]; 229 + }
+47 -2
lib/features/feed/data/feed_repository.dart
··· 1 + import 'package:atproto_core/atproto_core.dart'; 2 + import 'package:bluesky/app_bsky_actor_defs.dart'; 1 3 import 'package:bluesky/app_bsky_feed_defs.dart'; 2 4 import 'package:bluesky/app_bsky_feed_getauthorfeed.dart'; 5 + import 'package:bluesky/bluesky.dart'; 3 6 4 7 class FeedRepository { 5 - FeedRepository({required dynamic bluesky}) : _bluesky = bluesky; 8 + FeedRepository({required Bluesky bluesky}) : _bluesky = bluesky; 6 9 7 - final dynamic _bluesky; 10 + final Bluesky _bluesky; 8 11 9 12 Future<FeedResult> getAuthorFeed({ 10 13 required String actor, ··· 19 22 return FeedResult(posts: response.data.feed, cursor: response.data.cursor); 20 23 } 21 24 25 + Future<FeedResult> getTimeline({String? cursor, int limit = 50}) async { 26 + final response = await _bluesky.feed.getTimeline(cursor: cursor, limit: limit); 27 + 28 + return FeedResult(posts: response.data.feed, cursor: response.data.cursor); 29 + } 30 + 31 + Future<FeedResult> getFeed({required AtUri feedUri, String? cursor, int limit = 50}) async { 32 + final response = await _bluesky.feed.getFeed(feed: feedUri, cursor: cursor, limit: limit); 33 + 34 + return FeedResult(posts: response.data.feed, cursor: response.data.cursor); 35 + } 36 + 37 + Future<PreferencesResult> getPreferences() async { 38 + final response = await _bluesky.actor.getPreferences(); 39 + return PreferencesResult(preferences: response.data.preferences); 40 + } 41 + 42 + Future<void> putPreferences({required List<UPreferences> preferences}) async { 43 + await _bluesky.actor.putPreferences(preferences: preferences); 44 + } 45 + 46 + Future<List<GeneratorView>> getSuggestedFeeds({String? cursor, int limit = 50}) async { 47 + final response = await _bluesky.feed.getSuggestedFeeds(cursor: cursor, limit: limit); 48 + return response.data.feeds; 49 + } 50 + 51 + Future<GeneratorView> getFeedGenerator(AtUri feedUri) async { 52 + final response = await _bluesky.feed.getFeedGenerator(feed: feedUri); 53 + return response.data.view; 54 + } 55 + 56 + Future<List<GeneratorView>> getFeedGenerators(List<AtUri> feedUris) async { 57 + if (feedUris.isEmpty) return []; 58 + final response = await _bluesky.feed.getFeedGenerators(feeds: feedUris); 59 + return response.data.feeds; 60 + } 61 + 22 62 FeedGetAuthorFeedFilter? _mapToBskyFilter(FeedFilter filter) { 23 63 switch (filter) { 24 64 case FeedFilter.postsNoReplies: ··· 35 75 FeedResult({required this.posts, this.cursor}); 36 76 final List<FeedViewPost> posts; 37 77 final String? cursor; 78 + } 79 + 80 + class PreferencesResult { 81 + PreferencesResult({required this.preferences}); 82 + final List<UPreferences> preferences; 38 83 } 39 84 40 85 enum FeedFilter { postsNoReplies, postsWithMedia, postsAndAuthorThreads }
+364
lib/features/feed/presentation/feed_management_screen.dart
··· 1 + import 'package:atproto_core/atproto_core.dart'; 2 + import 'package:bluesky/app_bsky_actor_defs.dart'; 3 + import 'package:bluesky/app_bsky_feed_defs.dart'; 4 + import 'package:flutter/material.dart'; 5 + import 'package:flutter_bloc/flutter_bloc.dart'; 6 + import 'package:lazurite/features/feed/cubit/feed_preferences_cubit.dart'; 7 + import 'package:lazurite/features/feed/data/feed_repository.dart'; 8 + 9 + class FeedManagementScreen extends StatefulWidget { 10 + const FeedManagementScreen({super.key}); 11 + 12 + @override 13 + State<FeedManagementScreen> createState() => _FeedManagementScreenState(); 14 + } 15 + 16 + class _FeedManagementScreenState extends State<FeedManagementScreen> { 17 + List<GeneratorView>? _suggestedFeeds; 18 + bool _isLoadingSuggestions = false; 19 + bool _isReordering = false; 20 + 21 + @override 22 + void initState() { 23 + super.initState(); 24 + _loadSuggestedFeeds(); 25 + } 26 + 27 + Future<void> _loadSuggestedFeeds() async { 28 + setState(() => _isLoadingSuggestions = true); 29 + 30 + try { 31 + final feedRepository = context.read<FeedRepository>(); 32 + final feeds = await feedRepository.getSuggestedFeeds(limit: 10); 33 + setState(() { 34 + _suggestedFeeds = feeds; 35 + _isLoadingSuggestions = false; 36 + }); 37 + } catch (e) { 38 + setState(() => _isLoadingSuggestions = false); 39 + } 40 + } 41 + 42 + @override 43 + Widget build(BuildContext context) { 44 + return Scaffold( 45 + appBar: AppBar( 46 + title: const Text('My Feeds'), 47 + actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Done'))], 48 + ), 49 + body: BlocConsumer<FeedPreferencesCubit, FeedPreferencesState>( 50 + listener: (context, state) { 51 + if (state.status == FeedPreferencesStatus.saveError) { 52 + ScaffoldMessenger.of(context).showSnackBar( 53 + SnackBar( 54 + content: Text('Failed to sync: ${state.message}'), 55 + action: SnackBarAction( 56 + label: 'Dismiss', 57 + onPressed: () => context.read<FeedPreferencesCubit>().clearError(), 58 + ), 59 + ), 60 + ); 61 + } 62 + }, 63 + builder: (context, state) { 64 + if (state.status == FeedPreferencesStatus.loading) { 65 + return const Center(child: CircularProgressIndicator()); 66 + } 67 + 68 + return ListView( 69 + children: [ 70 + _buildSectionHeader( 71 + context, 72 + 'Pinned Feeds', 73 + showReorder: state.pinnedFeeds.length > 1 && !_isReordering, 74 + isReordering: _isReordering, 75 + onAction: () => setState(() => _isReordering = !_isReordering), 76 + ), 77 + if (_isReordering && state.pinnedFeeds.length > 1) 78 + _buildReorderablePinnedFeeds(context, state) 79 + else 80 + ...state.pinnedFeeds.map((feed) => _buildPinnedFeedItem(context, feed, state)), 81 + const SizedBox(height: 16), 82 + _buildSectionHeader(context, 'Saved Feeds'), 83 + if (state.unpinnedFeeds.isEmpty) 84 + Padding( 85 + padding: const EdgeInsets.all(16), 86 + child: Text( 87 + 'No saved feeds', 88 + style: Theme.of( 89 + context, 90 + ).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), 91 + ), 92 + ) 93 + else 94 + ...state.unpinnedFeeds.map((feed) => _buildSavedFeedItem(context, feed)), 95 + const SizedBox(height: 16), 96 + _buildSectionHeader(context, 'Discover Feeds', actionText: 'Refresh', onAction: _loadSuggestedFeeds), 97 + _buildDiscoverSection(context), 98 + const SizedBox(height: 24), 99 + ], 100 + ); 101 + }, 102 + ), 103 + ); 104 + } 105 + 106 + Widget _buildReorderablePinnedFeeds(BuildContext context, FeedPreferencesState state) { 107 + final pinnedFeeds = state.pinnedFeeds; 108 + 109 + return ReorderableListView.builder( 110 + shrinkWrap: true, 111 + physics: const NeverScrollableScrollPhysics(), 112 + buildDefaultDragHandles: false, 113 + itemCount: pinnedFeeds.length, 114 + onReorder: (oldIndex, newIndex) { 115 + context.read<FeedPreferencesCubit>().reorderPinnedFeeds(oldIndex, newIndex); 116 + }, 117 + itemBuilder: (context, index) { 118 + final feed = pinnedFeeds[index]; 119 + final isTimeline = 120 + feed.type is SavedFeedTypeKnownValue && 121 + (feed.type as SavedFeedTypeKnownValue).data == KnownSavedFeedType.timeline; 122 + 123 + return ListTile( 124 + key: ValueKey(feed.id), 125 + leading: isTimeline ? _buildTimelineIcon(context) : _buildFeedIcon(context, feed.value), 126 + title: Text(isTimeline ? 'Following' : _getFeedDisplayName(feed.value)), 127 + subtitle: Text(isTimeline ? 'Timeline' : 'Custom Feed'), 128 + trailing: ReorderableDragStartListener(index: index, child: const Icon(Icons.drag_handle)), 129 + ); 130 + }, 131 + ); 132 + } 133 + 134 + Widget _buildSectionHeader( 135 + BuildContext context, 136 + String title, { 137 + String? actionText, 138 + VoidCallback? onAction, 139 + bool showReorder = false, 140 + bool isReordering = false, 141 + }) { 142 + return Padding( 143 + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), 144 + child: Row( 145 + children: [ 146 + Text( 147 + title.toUpperCase(), 148 + style: Theme.of(context).textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w600, letterSpacing: 0.5), 149 + ), 150 + const Spacer(), 151 + if (showReorder) TextButton(onPressed: onAction, child: Text(isReordering ? 'Done' : 'Reorder')), 152 + if (actionText != null && !showReorder) TextButton(onPressed: onAction, child: Text(actionText)), 153 + ], 154 + ), 155 + ); 156 + } 157 + 158 + Widget _buildPinnedFeedItem(BuildContext context, SavedFeed feed, FeedPreferencesState state) { 159 + final isTimeline = 160 + feed.type is SavedFeedTypeKnownValue && 161 + (feed.type as SavedFeedTypeKnownValue).data == KnownSavedFeedType.timeline; 162 + 163 + return ListTile( 164 + leading: isTimeline ? _buildTimelineIcon(context) : _buildFeedIcon(context, feed.value), 165 + title: Text(isTimeline ? 'Following' : _getFeedDisplayName(feed.value)), 166 + subtitle: Text(isTimeline ? 'Timeline' : 'Custom Feed'), 167 + trailing: IconButton( 168 + icon: const Icon(Icons.check_circle), 169 + color: Theme.of(context).colorScheme.primary, 170 + onPressed: () => context.read<FeedPreferencesCubit>().unpinFeed(feed.id), 171 + ), 172 + ); 173 + } 174 + 175 + Widget _buildSavedFeedItem(BuildContext context, SavedFeed feed) { 176 + return ListTile( 177 + leading: _buildFeedIcon(context, feed.value), 178 + title: Text(_getFeedDisplayName(feed.value)), 179 + subtitle: const Text('Custom Feed'), 180 + trailing: Row( 181 + mainAxisSize: MainAxisSize.min, 182 + children: [ 183 + IconButton( 184 + icon: const Icon(Icons.pin_end_outlined), 185 + onPressed: () => context.read<FeedPreferencesCubit>().pinFeed(feed.id), 186 + ), 187 + IconButton( 188 + icon: Icon(Icons.close, color: Theme.of(context).colorScheme.error), 189 + onPressed: () => _confirmRemoveFeed(context, feed.id), 190 + ), 191 + ], 192 + ), 193 + ); 194 + } 195 + 196 + Widget _buildDiscoverSection(BuildContext context) { 197 + if (_isLoadingSuggestions) { 198 + return const Padding( 199 + padding: EdgeInsets.all(32), 200 + child: Center(child: CircularProgressIndicator()), 201 + ); 202 + } 203 + 204 + if (_suggestedFeeds == null || _suggestedFeeds!.isEmpty) { 205 + return Padding( 206 + padding: const EdgeInsets.all(16), 207 + child: Text( 208 + 'No suggested feeds available', 209 + style: Theme.of( 210 + context, 211 + ).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), 212 + ), 213 + ); 214 + } 215 + 216 + return Column(children: _suggestedFeeds!.map((feed) => _buildDiscoverCard(context, feed)).toList()); 217 + } 218 + 219 + Widget _buildDiscoverCard(BuildContext context, GeneratorView feed) { 220 + final prefsState = context.watch<FeedPreferencesCubit>().state; 221 + final isAdded = prefsState.feeds.any((f) => f.value == feed.uri.toString()); 222 + 223 + return Card( 224 + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), 225 + child: Padding( 226 + padding: const EdgeInsets.all(12), 227 + child: Row( 228 + children: [ 229 + _buildGeneratorIcon(context, feed), 230 + const SizedBox(width: 12), 231 + Expanded( 232 + child: Column( 233 + crossAxisAlignment: CrossAxisAlignment.start, 234 + children: [ 235 + Text( 236 + feed.displayName, 237 + style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w600), 238 + ), 239 + Text( 240 + 'by ${feed.creator.displayName ?? feed.creator.handle}', 241 + style: Theme.of( 242 + context, 243 + ).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), 244 + ), 245 + if (feed.description != null) ...[ 246 + const SizedBox(height: 4), 247 + Text( 248 + feed.description!, 249 + maxLines: 2, 250 + overflow: TextOverflow.ellipsis, 251 + style: Theme.of(context).textTheme.bodySmall, 252 + ), 253 + ], 254 + if (feed.likeCount != null) ...[ 255 + const SizedBox(height: 4), 256 + Text( 257 + '${feed.likeCount} likes', 258 + style: Theme.of( 259 + context, 260 + ).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant), 261 + ), 262 + ], 263 + ], 264 + ), 265 + ), 266 + const SizedBox(width: 8), 267 + OutlinedButton( 268 + onPressed: isAdded 269 + ? null 270 + : () => context.read<FeedPreferencesCubit>().addFeed( 271 + type: const SavedFeedType.knownValue(data: KnownSavedFeedType.feed), 272 + value: feed.uri.toString(), 273 + pinned: false, 274 + ), 275 + child: Text(isAdded ? 'Added' : '+ Add'), 276 + ), 277 + ], 278 + ), 279 + ), 280 + ); 281 + } 282 + 283 + Widget _buildTimelineIcon(BuildContext context) { 284 + return Container( 285 + width: 40, 286 + height: 40, 287 + decoration: BoxDecoration( 288 + gradient: const LinearGradient(colors: [Color(0xFF0066FF), Color(0xFF0EA5E9)]), 289 + borderRadius: BorderRadius.circular(10), 290 + ), 291 + child: const Icon(Icons.people, color: Colors.white), 292 + ); 293 + } 294 + 295 + Widget _buildFeedIcon(BuildContext context, String feedUri) { 296 + final gradients = [ 297 + const [Color(0xFFF59E0B), Color(0xFFFB923C)], 298 + const [Color(0xFF8B5CF6), Color(0xFFBE95FF)], 299 + const [Color(0xFF22C55E), Color(0xFF42BE65)], 300 + const [Color(0xFFEE5396), Color(0xFFFF7EB6)], 301 + ]; 302 + final index = feedUri.hashCode % gradients.length; 303 + 304 + return Container( 305 + width: 40, 306 + height: 40, 307 + decoration: BoxDecoration( 308 + gradient: LinearGradient(colors: gradients[index]), 309 + borderRadius: BorderRadius.circular(10), 310 + ), 311 + child: const Icon(Icons.rss_feed, color: Colors.white), 312 + ); 313 + } 314 + 315 + Widget _buildGeneratorIcon(BuildContext context, GeneratorView feed) { 316 + return Container( 317 + width: 40, 318 + height: 40, 319 + decoration: BoxDecoration( 320 + gradient: const LinearGradient(colors: [Color(0xFF08BDBA), Color(0xFF3DDBD9)]), 321 + borderRadius: BorderRadius.circular(10), 322 + ), 323 + child: feed.avatar != null 324 + ? ClipRRect( 325 + borderRadius: BorderRadius.circular(10), 326 + child: Image.network( 327 + feed.avatar!, 328 + fit: BoxFit.cover, 329 + errorBuilder: (_, _, _) => const Icon(Icons.rss_feed, color: Colors.white), 330 + ), 331 + ) 332 + : const Icon(Icons.rss_feed, color: Colors.white), 333 + ); 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 + } 344 + 345 + void _confirmRemoveFeed(BuildContext context, String feedId) { 346 + showDialog<void>( 347 + context: context, 348 + builder: (context) => AlertDialog( 349 + title: const Text('Remove Feed'), 350 + content: const Text('Are you sure you want to remove this feed from your saved feeds?'), 351 + actions: [ 352 + TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel')), 353 + TextButton( 354 + onPressed: () { 355 + context.read<FeedPreferencesCubit>().removeFeed(feedId); 356 + Navigator.of(context).pop(); 357 + }, 358 + child: Text('Remove', style: TextStyle(color: Theme.of(context).colorScheme.error)), 359 + ), 360 + ], 361 + ), 362 + ); 363 + } 364 + }
+280
lib/features/feed/presentation/home_feed_screen.dart
··· 1 + import 'package:atproto_core/atproto_core.dart'; 2 + import 'package:bluesky/app_bsky_actor_defs.dart'; 3 + import 'package:bluesky/app_bsky_feed_defs.dart'; 4 + import 'package:flutter/material.dart'; 5 + import 'package:flutter_bloc/flutter_bloc.dart'; 6 + import 'package:go_router/go_router.dart'; 7 + import 'package:lazurite/features/feed/cubit/feed_preferences_cubit.dart'; 8 + import 'package:lazurite/features/feed/data/feed_repository.dart'; 9 + import 'package:lazurite/features/feed/presentation/widgets/post_card.dart'; 10 + 11 + class HomeFeedScreen extends StatefulWidget { 12 + const HomeFeedScreen({super.key}); 13 + 14 + @override 15 + State<HomeFeedScreen> createState() => _HomeFeedScreenState(); 16 + } 17 + 18 + class _HomeFeedScreenState extends State<HomeFeedScreen> { 19 + late final PageController _pageController; 20 + int _currentTabIndex = 0; 21 + 22 + @override 23 + void initState() { 24 + super.initState(); 25 + _pageController = PageController(); 26 + } 27 + 28 + @override 29 + void dispose() { 30 + _pageController.dispose(); 31 + super.dispose(); 32 + } 33 + 34 + @override 35 + Widget build(BuildContext context) { 36 + return BlocBuilder<FeedPreferencesCubit, FeedPreferencesState>( 37 + builder: (context, prefsState) { 38 + final pinnedFeeds = prefsState.pinnedFeeds; 39 + 40 + if (pinnedFeeds.isEmpty) { 41 + return Scaffold( 42 + appBar: AppBar(title: const Text('Home')), 43 + body: const Center(child: Text('No feeds pinned. Add feeds from settings.')), 44 + ); 45 + } 46 + 47 + return Scaffold( 48 + appBar: AppBar( 49 + title: const Text('Home'), 50 + actions: [IconButton(icon: const Icon(Icons.settings_outlined), onPressed: () => context.push('/feeds'))], 51 + ), 52 + body: Column( 53 + children: [ 54 + _buildTabBar(context, pinnedFeeds), 55 + Expanded( 56 + child: PageView.builder( 57 + controller: _pageController, 58 + onPageChanged: (index) => setState(() => _currentTabIndex = index), 59 + itemCount: pinnedFeeds.length, 60 + itemBuilder: (context, index) => 61 + _FeedListView(feed: pinnedFeeds[index], key: ValueKey(pinnedFeeds[index].id)), 62 + ), 63 + ), 64 + ], 65 + ), 66 + ); 67 + }, 68 + ); 69 + } 70 + 71 + Widget _buildTabBar(BuildContext context, List<SavedFeed> feeds) { 72 + return Container( 73 + decoration: BoxDecoration( 74 + border: Border(bottom: BorderSide(color: Theme.of(context).dividerColor)), 75 + ), 76 + child: SingleChildScrollView( 77 + scrollDirection: Axis.horizontal, 78 + child: Row( 79 + children: feeds.asMap().entries.map((entry) { 80 + final index = entry.key; 81 + final feed = entry.value; 82 + final isSelected = _currentTabIndex == index; 83 + 84 + return GestureDetector( 85 + onTap: () { 86 + _pageController.animateToPage( 87 + index, 88 + duration: const Duration(milliseconds: 300), 89 + curve: Curves.easeInOut, 90 + ); 91 + setState(() => _currentTabIndex = index); 92 + }, 93 + child: Container( 94 + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), 95 + decoration: BoxDecoration( 96 + border: Border( 97 + bottom: BorderSide( 98 + color: isSelected ? Theme.of(context).colorScheme.primary : Colors.transparent, 99 + width: 2, 100 + ), 101 + ), 102 + ), 103 + child: Text( 104 + _getFeedName(feed), 105 + style: Theme.of(context).textTheme.bodyLarge?.copyWith( 106 + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, 107 + color: isSelected 108 + ? Theme.of(context).colorScheme.onSurface 109 + : Theme.of(context).colorScheme.onSurfaceVariant, 110 + ), 111 + ), 112 + ), 113 + ); 114 + }).toList(), 115 + ), 116 + ), 117 + ); 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 + } 129 + } 130 + 131 + class _FeedListView extends StatefulWidget { 132 + const _FeedListView({required this.feed, super.key}); 133 + 134 + final SavedFeed feed; 135 + 136 + @override 137 + State<_FeedListView> createState() => _FeedListViewState(); 138 + } 139 + 140 + class _FeedListViewState extends State<_FeedListView> with AutomaticKeepAliveClientMixin { 141 + final List<FeedViewPost> _posts = []; 142 + String? _cursor; 143 + bool _isLoading = false; 144 + bool _isLoadingMore = false; 145 + bool _hasError = false; 146 + String? _errorMessage; 147 + final ScrollController _scrollController = ScrollController(); 148 + 149 + @override 150 + bool get wantKeepAlive => true; 151 + 152 + @override 153 + void initState() { 154 + super.initState(); 155 + _scrollController.addListener(_onScroll); 156 + _loadFeed(); 157 + } 158 + 159 + @override 160 + void dispose() { 161 + _scrollController.removeListener(_onScroll); 162 + _scrollController.dispose(); 163 + super.dispose(); 164 + } 165 + 166 + void _onScroll() { 167 + if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { 168 + _loadMore(); 169 + } 170 + } 171 + 172 + Future<void> _loadFeed() async { 173 + if (_isLoading) return; 174 + 175 + setState(() { 176 + _isLoading = true; 177 + _hasError = false; 178 + _errorMessage = null; 179 + }); 180 + 181 + try { 182 + final feedRepository = context.read<FeedRepository>(); 183 + final result = await _fetchFeed(feedRepository, cursor: null); 184 + 185 + setState(() { 186 + _posts.clear(); 187 + _posts.addAll(result.posts); 188 + _cursor = result.cursor; 189 + _isLoading = false; 190 + _hasError = false; 191 + }); 192 + } catch (e) { 193 + setState(() { 194 + _isLoading = false; 195 + _hasError = true; 196 + _errorMessage = e.toString(); 197 + }); 198 + } 199 + } 200 + 201 + Future<void> _loadMore() async { 202 + if (_isLoadingMore || _cursor == null) return; 203 + 204 + setState(() => _isLoadingMore = true); 205 + 206 + try { 207 + final feedRepository = context.read<FeedRepository>(); 208 + final result = await _fetchFeed(feedRepository, cursor: _cursor); 209 + 210 + setState(() { 211 + _posts.addAll(result.posts); 212 + _cursor = result.cursor; 213 + _isLoadingMore = false; 214 + }); 215 + } catch (e) { 216 + setState(() => _isLoadingMore = false); 217 + } 218 + } 219 + 220 + Future<FeedResult> _fetchFeed(FeedRepository repo, {String? cursor}) async { 221 + final feedType = widget.feed.type; 222 + if (feedType is SavedFeedTypeKnownValue) { 223 + if (feedType.data == KnownSavedFeedType.timeline) { 224 + return repo.getTimeline(cursor: cursor); 225 + } 226 + } 227 + 228 + final feedUri = AtUri.parse(widget.feed.value); 229 + return repo.getFeed(feedUri: feedUri, cursor: cursor); 230 + } 231 + 232 + @override 233 + Widget build(BuildContext context) { 234 + super.build(context); 235 + 236 + if (_isLoading) { 237 + return const Center(child: CircularProgressIndicator()); 238 + } 239 + 240 + if (_hasError) { 241 + return Center( 242 + child: Column( 243 + mainAxisAlignment: MainAxisAlignment.center, 244 + children: [ 245 + Text('Failed to load feed', style: Theme.of(context).textTheme.bodyLarge), 246 + const SizedBox(height: 8), 247 + Text( 248 + _errorMessage ?? 'Unknown error', 249 + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.error), 250 + textAlign: TextAlign.center, 251 + ), 252 + const SizedBox(height: 16), 253 + FilledButton(onPressed: _loadFeed, child: const Text('Retry')), 254 + ], 255 + ), 256 + ); 257 + } 258 + 259 + if (_posts.isEmpty) { 260 + return Center(child: Text('No posts yet', style: Theme.of(context).textTheme.bodyLarge)); 261 + } 262 + 263 + return RefreshIndicator( 264 + onRefresh: _loadFeed, 265 + child: ListView.builder( 266 + controller: _scrollController, 267 + itemCount: _posts.length + (_isLoadingMore ? 1 : 0), 268 + itemBuilder: (context, index) { 269 + if (index == _posts.length) { 270 + return const Center( 271 + child: Padding(padding: EdgeInsets.all(16), child: CircularProgressIndicator()), 272 + ); 273 + } 274 + 275 + return PostCard(feedViewPost: _posts[index]); 276 + }, 277 + ), 278 + ); 279 + } 280 + }
+9 -1
lib/main.dart
··· 11 11 import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; 12 12 import 'package:lazurite/features/auth/data/auth_repository.dart'; 13 13 import 'package:lazurite/features/feed/bloc/feed_bloc.dart'; 14 + import 'package:lazurite/features/feed/cubit/feed_preferences_cubit.dart'; 14 15 import 'package:lazurite/features/feed/data/feed_repository.dart'; 15 16 import 'package:lazurite/features/profile/bloc/profile_bloc.dart'; 16 17 import 'package:lazurite/features/profile/data/profile_repository.dart'; ··· 92 93 return appShell; 93 94 } 94 95 96 + final feedRepository = FeedRepository(bluesky: bluesky); 97 + final accountDid = authState.tokens?.did ?? ''; 98 + 95 99 return MultiBlocProvider( 96 100 providers: [ 97 101 BlocProvider( ··· 99 103 profileRepository: ProfileRepository(database: database, bluesky: bluesky), 100 104 ), 101 105 ), 106 + BlocProvider(create: (_) => FeedBloc(feedRepository: feedRepository)), 102 107 BlocProvider( 103 - create: (_) => FeedBloc(feedRepository: FeedRepository(bluesky: bluesky)), 108 + create: (_) => 109 + FeedPreferencesCubit(feedRepository: feedRepository, database: database, accountDid: accountDid) 110 + ..loadPreferences(), 104 111 ), 112 + RepositoryProvider.value(value: feedRepository), 105 113 ], 106 114 child: appShell, 107 115 );
+1 -1
pubspec.lock
··· 1030 1030 source: hosted 1031 1031 version: "3.1.5" 1032 1032 uuid: 1033 - dependency: transitive 1033 + dependency: "direct main" 1034 1034 description: 1035 1035 name: uuid 1036 1036 sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
+1
pubspec.yaml
··· 31 31 logger: ^2.6.2 32 32 share_plus: ^10.1.4 33 33 http: ^1.2.2 34 + uuid: ^4.5.1 34 35 35 36 dev_dependencies: 36 37 flutter_test:
+327
test/features/feed/cubit/feed_preferences_cubit_test.dart
··· 1 + import 'package:bloc_test/bloc_test.dart'; 2 + import 'package:bluesky/app_bsky_actor_defs.dart'; 3 + import 'package:drift/drift.dart' hide isNotNull; 4 + import 'package:drift/native.dart'; 5 + import 'package:flutter_test/flutter_test.dart'; 6 + import 'package:lazurite/core/database/app_database.dart'; 7 + import 'package:lazurite/features/feed/cubit/feed_preferences_cubit.dart'; 8 + import 'package:lazurite/features/feed/data/feed_repository.dart'; 9 + import 'package:mocktail/mocktail.dart'; 10 + 11 + class MockFeedRepository extends Mock implements FeedRepository {} 12 + 13 + void main() { 14 + late AppDatabase database; 15 + late MockFeedRepository mockFeedRepository; 16 + 17 + setUp(() async { 18 + database = AppDatabase(executor: NativeDatabase.memory()); 19 + mockFeedRepository = MockFeedRepository(); 20 + }); 21 + 22 + tearDown(() async { 23 + await database.close(); 24 + }); 25 + 26 + SavedFeed createTestFeed({ 27 + required String id, 28 + String value = 'at://did:plc:test/app.bsky.feed.generator/test', 29 + bool pinned = false, 30 + SavedFeedType? type, 31 + }) { 32 + return SavedFeed( 33 + id: id, 34 + type: type ?? const SavedFeedType.knownValue(data: KnownSavedFeedType.feed), 35 + value: value, 36 + pinned: pinned, 37 + ); 38 + } 39 + 40 + group('FeedPreferencesCubit', () { 41 + test('initial state is initial', () { 42 + final cubit = FeedPreferencesCubit( 43 + feedRepository: mockFeedRepository, 44 + database: database, 45 + accountDid: 'did:plc:test', 46 + ); 47 + expect(cubit.state.status, FeedPreferencesStatus.initial); 48 + expect(cubit.state.feeds, isEmpty); 49 + }); 50 + 51 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 52 + 'loadPreferences loads from API and caches to database', 53 + build: () => 54 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 55 + setUp: () { 56 + when(() => mockFeedRepository.getPreferences()).thenAnswer( 57 + (_) async => PreferencesResult( 58 + preferences: [ 59 + UPreferences.savedFeedsPrefV2( 60 + data: SavedFeedsPrefV2( 61 + items: [ 62 + createTestFeed(id: 'feed-1', pinned: true), 63 + createTestFeed(id: 'feed-2', pinned: false), 64 + ], 65 + ), 66 + ), 67 + ], 68 + ), 69 + ); 70 + }, 71 + act: (cubit) => cubit.loadPreferences(), 72 + expect: () => [ 73 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.loading), 74 + isA<FeedPreferencesState>() 75 + .having((s) => s.status, 'status', FeedPreferencesStatus.loaded) 76 + .having((s) => s.feeds.length, 'feeds.length', 2) 77 + .having((s) => s.pinnedFeeds.length, 'pinnedFeeds.length', 1) 78 + .having((s) => s.unpinnedFeeds.length, 'unpinnedFeeds.length', 1), 79 + ], 80 + verify: (cubit) async { 81 + final cached = await database.getSavedFeeds('did:plc:test'); 82 + expect(cached.length, 2); 83 + }, 84 + ); 85 + 86 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 87 + 'loadPreferences creates default timeline feed when no preferences exist', 88 + build: () => 89 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 90 + setUp: () { 91 + when(() => mockFeedRepository.getPreferences()).thenAnswer((_) async => PreferencesResult(preferences: [])); 92 + }, 93 + act: (cubit) => cubit.loadPreferences(), 94 + expect: () => [ 95 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.loading), 96 + isA<FeedPreferencesState>() 97 + .having((s) => s.status, 'status', FeedPreferencesStatus.loaded) 98 + .having((s) => s.feeds.length, 'feeds.length', 1) 99 + .having((s) => s.pinnedFeeds.length, 'pinnedFeeds.length', 1), 100 + ], 101 + ); 102 + 103 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 104 + 'loadPreferences falls back to cached data on API error', 105 + build: () => 106 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 107 + setUp: () async { 108 + await database.replaceSavedFeeds('did:plc:test', [ 109 + SavedFeedsCompanion( 110 + id: const Value('cached-1'), 111 + accountDid: const Value('did:plc:test'), 112 + type: const Value('{"\$type":"app.bsky.actor.defs#savedFeedTypeKnownValue","data":"feed"}'), 113 + value: const Value('at://test'), 114 + pinned: const Value(true), 115 + sortOrder: const Value(0), 116 + updatedAt: Value(DateTime.now()), 117 + ), 118 + ]); 119 + when(() => mockFeedRepository.getPreferences()).thenThrow(Exception('Network error')); 120 + }, 121 + act: (cubit) => cubit.loadPreferences(), 122 + expect: () => [ 123 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.loading), 124 + isA<FeedPreferencesState>() 125 + .having((s) => s.status, 'status', FeedPreferencesStatus.loaded) 126 + .having((s) => s.feeds.length, 'feeds.length', 1), 127 + ], 128 + ); 129 + 130 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 131 + 'pinFeed moves feed to pinned section', 132 + build: () => 133 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 134 + seed: () => FeedPreferencesState.loaded( 135 + feeds: [ 136 + createTestFeed(id: 'feed-1', pinned: true), 137 + createTestFeed(id: 'feed-2', pinned: false), 138 + ], 139 + ), 140 + setUp: () { 141 + when(() => mockFeedRepository.getPreferences()).thenAnswer((_) async => PreferencesResult(preferences: [])); 142 + when(() => mockFeedRepository.putPreferences(preferences: any(named: 'preferences'))).thenAnswer((_) async {}); 143 + }, 144 + act: (cubit) => cubit.pinFeed('feed-2'), 145 + expect: () => [ 146 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.saving), 147 + isA<FeedPreferencesState>() 148 + .having((s) => s.status, 'status', FeedPreferencesStatus.loaded) 149 + .having((s) => s.pinnedFeeds.length, 'pinnedFeeds.length', 2), 150 + ], 151 + ); 152 + 153 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 154 + 'unpinFeed moves feed to unpinned section', 155 + build: () => 156 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 157 + seed: () => FeedPreferencesState.loaded( 158 + feeds: [ 159 + createTestFeed(id: 'feed-1', pinned: true), 160 + createTestFeed(id: 'feed-2', pinned: true), 161 + createTestFeed(id: 'feed-3', pinned: false), 162 + ], 163 + ), 164 + setUp: () { 165 + when(() => mockFeedRepository.getPreferences()).thenAnswer((_) async => PreferencesResult(preferences: [])); 166 + when(() => mockFeedRepository.putPreferences(preferences: any(named: 'preferences'))).thenAnswer((_) async {}); 167 + }, 168 + act: (cubit) => cubit.unpinFeed('feed-1'), 169 + expect: () => [ 170 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.saving), 171 + isA<FeedPreferencesState>() 172 + .having((s) => s.status, 'status', FeedPreferencesStatus.loaded) 173 + .having((s) => s.pinnedFeeds.length, 'pinnedFeeds.length', 1) 174 + .having((s) => s.unpinnedFeeds.length, 'unpinnedFeeds.length', 2) 175 + .having((s) => s.pinnedFeeds.first.id, 'remaining pinned', 'feed-2'), 176 + ], 177 + ); 178 + 179 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 180 + 'reorderPinnedFeeds reorders within pinned section only', 181 + build: () => 182 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 183 + seed: () => FeedPreferencesState.loaded( 184 + feeds: [ 185 + createTestFeed(id: 'pinned-1', value: 'at://a', pinned: true), 186 + createTestFeed(id: 'pinned-2', value: 'at://b', pinned: true), 187 + createTestFeed(id: 'pinned-3', value: 'at://c', pinned: true), 188 + createTestFeed(id: 'unpinned-1', value: 'at://d', pinned: false), 189 + ], 190 + ), 191 + setUp: () { 192 + when(() => mockFeedRepository.getPreferences()).thenAnswer((_) async => PreferencesResult(preferences: [])); 193 + when(() => mockFeedRepository.putPreferences(preferences: any(named: 'preferences'))).thenAnswer((_) async {}); 194 + }, 195 + act: (cubit) => cubit.reorderPinnedFeeds(0, 3), 196 + expect: () => [ 197 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.saving), 198 + predicate<FeedPreferencesState>( 199 + (s) => 200 + s.status == FeedPreferencesStatus.loaded && 201 + s.pinnedFeeds[0].id == 'pinned-2' && 202 + s.pinnedFeeds[1].id == 'pinned-3' && 203 + s.pinnedFeeds[2].id == 'pinned-1' && 204 + s.unpinnedFeeds.length == 1, 205 + ), 206 + ], 207 + ); 208 + 209 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 210 + 'removeFeed removes feed from list', 211 + build: () => 212 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 213 + seed: () => FeedPreferencesState.loaded( 214 + feeds: [ 215 + createTestFeed(id: 'feed-1', pinned: true), 216 + createTestFeed(id: 'feed-2', pinned: false), 217 + ], 218 + ), 219 + setUp: () { 220 + when(() => mockFeedRepository.getPreferences()).thenAnswer((_) async => PreferencesResult(preferences: [])); 221 + when(() => mockFeedRepository.putPreferences(preferences: any(named: 'preferences'))).thenAnswer((_) async {}); 222 + }, 223 + act: (cubit) => cubit.removeFeed('feed-2'), 224 + expect: () => [ 225 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.saving), 226 + isA<FeedPreferencesState>() 227 + .having((s) => s.status, 'status', FeedPreferencesStatus.loaded) 228 + .having((s) => s.feeds.length, 'feeds.length', 1) 229 + .having((s) => s.feeds.first.id, 'remaining feed', 'feed-1'), 230 + ], 231 + ); 232 + 233 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 234 + 'addFeed appends new feed to list', 235 + build: () => 236 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 237 + seed: () => FeedPreferencesState.loaded(feeds: [createTestFeed(id: 'feed-1', pinned: true)]), 238 + setUp: () { 239 + when(() => mockFeedRepository.getPreferences()).thenAnswer((_) async => PreferencesResult(preferences: [])); 240 + when(() => mockFeedRepository.putPreferences(preferences: any(named: 'preferences'))).thenAnswer((_) async {}); 241 + }, 242 + act: (cubit) => cubit.addFeed( 243 + type: const SavedFeedType.knownValue(data: KnownSavedFeedType.feed), 244 + value: 'at://new-feed', 245 + pinned: true, 246 + ), 247 + expect: () => [ 248 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.saving), 249 + isA<FeedPreferencesState>() 250 + .having((s) => s.status, 'status', FeedPreferencesStatus.loaded) 251 + .having((s) => s.feeds.length, 'feeds.length', 2) 252 + .having((s) => s.pinnedFeeds.length, 'pinnedFeeds.length', 2), 253 + ], 254 + ); 255 + 256 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 257 + '_savePreferences emits saveError on API failure', 258 + build: () => 259 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 260 + seed: () => FeedPreferencesState.loaded(feeds: [createTestFeed(id: 'feed-1', pinned: true)]), 261 + setUp: () { 262 + when(() => mockFeedRepository.getPreferences()).thenThrow(Exception('API error')); 263 + }, 264 + act: (cubit) => cubit.pinFeed('feed-1'), 265 + expect: () => [ 266 + isA<FeedPreferencesState>().having((s) => s.status, 'status', FeedPreferencesStatus.saving), 267 + isA<FeedPreferencesState>() 268 + .having((s) => s.status, 'status', FeedPreferencesStatus.saveError) 269 + .having((s) => s.message, 'message', isNotNull), 270 + ], 271 + ); 272 + 273 + blocTest<FeedPreferencesCubit, FeedPreferencesState>( 274 + 'clearError restores previous state', 275 + build: () => 276 + FeedPreferencesCubit(feedRepository: mockFeedRepository, database: database, accountDid: 'did:plc:test'), 277 + seed: () => FeedPreferencesState.saveError( 278 + feeds: [createTestFeed(id: 'feed-1', pinned: true)], 279 + message: 'Error', 280 + previousState: FeedPreferencesState.loaded(feeds: [createTestFeed(id: 'feed-1', pinned: false)]), 281 + ), 282 + act: (cubit) => cubit.clearError(), 283 + expect: () => [ 284 + isA<FeedPreferencesState>() 285 + .having((s) => s.status, 'status', FeedPreferencesStatus.loaded) 286 + .having((s) => s.feeds.first.pinned, 'pinned', false), 287 + ], 288 + ); 289 + }); 290 + 291 + group('FeedPreferencesState', () { 292 + test('pinnedFeeds returns only pinned feeds', () { 293 + final state = FeedPreferencesState.loaded( 294 + feeds: [ 295 + createTestFeed(id: '1', pinned: true), 296 + createTestFeed(id: '2', pinned: false), 297 + createTestFeed(id: '3', pinned: true), 298 + ], 299 + ); 300 + 301 + expect(state.pinnedFeeds.length, 2); 302 + expect(state.pinnedFeeds.every((f) => f.pinned), isTrue); 303 + }); 304 + 305 + test('unpinnedFeeds returns only unpinned feeds', () { 306 + final state = FeedPreferencesState.loaded( 307 + feeds: [ 308 + createTestFeed(id: '1', pinned: true), 309 + createTestFeed(id: '2', pinned: false), 310 + createTestFeed(id: '3', pinned: true), 311 + ], 312 + ); 313 + 314 + expect(state.unpinnedFeeds.length, 1); 315 + expect(state.unpinnedFeeds.every((f) => !f.pinned), isTrue); 316 + }); 317 + 318 + test('copyWith preserves unspecified values', () { 319 + final state = FeedPreferencesState.loaded(feeds: [createTestFeed(id: '1', pinned: true)]); 320 + 321 + final copied = state.copyWith(status: FeedPreferencesStatus.saving); 322 + 323 + expect(copied.status, FeedPreferencesStatus.saving); 324 + expect(copied.feeds.length, 1); 325 + }); 326 + }); 327 + }
+236
test/features/feed/data/feed_repository_test.dart
··· 1 + import 'package:atproto_core/atproto_core.dart'; 2 + import 'package:bluesky/app_bsky_actor_defs.dart'; 3 + import 'package:bluesky/app_bsky_feed_defs.dart'; 4 + import 'package:flutter_test/flutter_test.dart'; 5 + import 'package:lazurite/features/feed/data/feed_repository.dart'; 6 + import 'package:mocktail/mocktail.dart'; 7 + 8 + class MockFeedRepository extends Mock implements FeedRepository {} 9 + 10 + void main() { 11 + late MockFeedRepository mockRepository; 12 + 13 + setUp(() { 14 + mockRepository = MockFeedRepository(); 15 + }); 16 + 17 + group('FeedRepository contract', () { 18 + final samplePost = FeedViewPost( 19 + post: PostView( 20 + uri: const AtUri('at://did:plc:author/app.bsky.feed.post/abc'), 21 + cid: 'cid-123', 22 + author: const ProfileViewBasic(did: 'did:plc:author', handle: 'author.bsky.social'), 23 + record: { 24 + r'$type': 'app.bsky.feed.post', 25 + 'text': 'Hello world', 26 + 'createdAt': DateTime.utc(2026, 3, 15).toIso8601String(), 27 + }, 28 + indexedAt: DateTime.utc(2026, 3, 15), 29 + ), 30 + ); 31 + 32 + test('getTimeline returns FeedResult with posts and cursor', () async { 33 + when( 34 + () => mockRepository.getTimeline( 35 + cursor: any(named: 'cursor'), 36 + limit: any(named: 'limit'), 37 + ), 38 + ).thenAnswer((_) async => FeedResult(posts: [samplePost], cursor: 'next-cursor')); 39 + 40 + final result = await mockRepository.getTimeline(); 41 + 42 + expect(result.posts.length, 1); 43 + expect(result.cursor, 'next-cursor'); 44 + }); 45 + 46 + test('getTimeline with cursor returns paginated results', () async { 47 + when( 48 + () => mockRepository.getTimeline( 49 + cursor: 'page-2', 50 + limit: any(named: 'limit'), 51 + ), 52 + ).thenAnswer((_) async => FeedResult(posts: [samplePost], cursor: 'page-3')); 53 + 54 + final result = await mockRepository.getTimeline(cursor: 'page-2'); 55 + 56 + expect(result.cursor, 'page-3'); 57 + }); 58 + 59 + test('getFeed returns FeedResult for feed generator', () async { 60 + final feedUri = AtUri.parse('at://did:plc:gen/app.bsky.feed.generator/whats-hot'); 61 + when( 62 + () => mockRepository.getFeed( 63 + feedUri: feedUri, 64 + cursor: any(named: 'cursor'), 65 + limit: any(named: 'limit'), 66 + ), 67 + ).thenAnswer((_) async => FeedResult(posts: [samplePost], cursor: 'next-cursor')); 68 + 69 + final result = await mockRepository.getFeed(feedUri: feedUri); 70 + 71 + expect(result.posts.length, 1); 72 + }); 73 + 74 + test('getPreferences returns preferences list', () async { 75 + when(() => mockRepository.getPreferences()).thenAnswer((_) async => PreferencesResult(preferences: [])); 76 + 77 + final result = await mockRepository.getPreferences(); 78 + 79 + expect(result.preferences, isEmpty); 80 + }); 81 + 82 + test('putPreferences calls with correct parameters', () async { 83 + when(() => mockRepository.putPreferences(preferences: any(named: 'preferences'))).thenAnswer((_) async {}); 84 + 85 + await mockRepository.putPreferences(preferences: []); 86 + 87 + verify(() => mockRepository.putPreferences(preferences: [])).called(1); 88 + }); 89 + 90 + test('getSuggestedFeeds returns list of generators', () async { 91 + final generator = GeneratorView( 92 + uri: const AtUri('at://did:plc:gen/app.bsky.feed.generator/discover'), 93 + cid: 'cid-gen', 94 + creator: const ProfileView(did: 'did:plc:gen', handle: 'gen.bsky.social'), 95 + did: 'did:plc:gen', 96 + displayName: 'Discover', 97 + indexedAt: DateTime.now(), 98 + ); 99 + 100 + when( 101 + () => mockRepository.getSuggestedFeeds( 102 + cursor: any(named: 'cursor'), 103 + limit: any(named: 'limit'), 104 + ), 105 + ).thenAnswer((_) async => [generator]); 106 + 107 + final result = await mockRepository.getSuggestedFeeds(); 108 + 109 + expect(result.length, 1); 110 + expect(result.first.displayName, 'Discover'); 111 + }); 112 + 113 + test('getFeedGenerator returns single generator', () async { 114 + final feedUri = AtUri.parse('at://did:plc:gen/app.bsky.feed.generator/discover'); 115 + final generator = GeneratorView( 116 + uri: feedUri, 117 + cid: 'cid-gen', 118 + creator: const ProfileView(did: 'did:plc:gen', handle: 'gen.bsky.social'), 119 + did: 'did:plc:gen', 120 + displayName: 'Discover', 121 + indexedAt: DateTime.now(), 122 + ); 123 + 124 + when(() => mockRepository.getFeedGenerator(feedUri)).thenAnswer((_) async => generator); 125 + 126 + final result = await mockRepository.getFeedGenerator(feedUri); 127 + 128 + expect(result.displayName, 'Discover'); 129 + }); 130 + 131 + test('getFeedGenerators returns list for multiple URIs', () async { 132 + final feedUri = AtUri.parse('at://did:plc:gen/app.bsky.feed.generator/discover'); 133 + final generator = GeneratorView( 134 + uri: feedUri, 135 + cid: 'cid-gen', 136 + creator: const ProfileView(did: 'did:plc:gen', handle: 'gen.bsky.social'), 137 + did: 'did:plc:gen', 138 + displayName: 'Discover', 139 + indexedAt: DateTime.now(), 140 + ); 141 + 142 + when(() => mockRepository.getFeedGenerators([feedUri])).thenAnswer((_) async => [generator]); 143 + 144 + final result = await mockRepository.getFeedGenerators([feedUri]); 145 + 146 + expect(result.length, 1); 147 + }); 148 + 149 + test('getFeedGenerator returns single generator', () async { 150 + final feedUri = AtUri.parse('at://did:plc:gen/app.bsky.feed.generator/discover'); 151 + final generator = GeneratorView( 152 + uri: feedUri, 153 + cid: 'cid-gen', 154 + creator: const ProfileView(did: 'did:plc:gen', handle: 'gen.bsky.social'), 155 + did: 'did:plc:gen', 156 + displayName: 'Discover', 157 + indexedAt: DateTime.now(), 158 + ); 159 + 160 + when(() => mockRepository.getFeedGenerator(feedUri)).thenAnswer((_) async => generator); 161 + 162 + final result = await mockRepository.getFeedGenerator(feedUri); 163 + 164 + expect(result.displayName, 'Discover'); 165 + }); 166 + 167 + test('getFeedGenerators returns list for multiple URIs', () async { 168 + final feedUri = AtUri.parse('at://did:plc:gen/app.bsky.feed.generator/discover'); 169 + final generator = GeneratorView( 170 + uri: feedUri, 171 + cid: 'cid-gen', 172 + creator: const ProfileView(did: 'did:plc:gen', handle: 'gen.bsky.social'), 173 + did: 'did:plc:gen', 174 + displayName: 'Discover', 175 + indexedAt: DateTime.now(), 176 + ); 177 + 178 + when(() => mockRepository.getFeedGenerators([feedUri])).thenAnswer((_) async => [generator]); 179 + 180 + final result = await mockRepository.getFeedGenerators([feedUri]); 181 + 182 + expect(result.length, 1); 183 + }); 184 + 185 + test('getFeedGenerators returns empty list for empty input', () async { 186 + when(() => mockRepository.getFeedGenerators([])).thenAnswer((_) async => []); 187 + 188 + final result = await mockRepository.getFeedGenerators([]); 189 + 190 + expect(result, isEmpty); 191 + }); 192 + }); 193 + 194 + group('FeedResult', () { 195 + test('stores posts and cursor', () { 196 + final post = FeedViewPost( 197 + post: PostView( 198 + uri: const AtUri('at://did:plc:test/app.bsky.feed.post/1'), 199 + cid: 'cid', 200 + author: const ProfileViewBasic(did: 'did:plc:test', handle: 'test.bsky.social'), 201 + record: {'text': 'Test'}, 202 + indexedAt: DateTime.now(), 203 + ), 204 + ); 205 + 206 + final result = FeedResult(posts: [post], cursor: 'cursor-1'); 207 + 208 + expect(result.posts.length, 1); 209 + expect(result.cursor, 'cursor-1'); 210 + }); 211 + 212 + test('allows null cursor', () { 213 + final result = FeedResult(posts: const [], cursor: null); 214 + 215 + expect(result.posts, isEmpty); 216 + expect(result.cursor, isNull); 217 + }); 218 + }); 219 + 220 + group('PreferencesResult', () { 221 + test('stores preferences list', () { 222 + final result = PreferencesResult(preferences: []); 223 + 224 + expect(result.preferences, isEmpty); 225 + }); 226 + }); 227 + 228 + group('FeedFilter', () { 229 + test('has expected filter values', () { 230 + expect(FeedFilter.values.length, 3); 231 + expect(FeedFilter.values, contains(FeedFilter.postsNoReplies)); 232 + expect(FeedFilter.values, contains(FeedFilter.postsWithMedia)); 233 + expect(FeedFilter.values, contains(FeedFilter.postsAndAuthorThreads)); 234 + }); 235 + }); 236 + }