Skip to content

Improve post tracking, visuals, and bookmark handling#53

Merged
wrngwrld merged 12 commits into
mainfrom
fix/various-fixes
May 2, 2026
Merged

Improve post tracking, visuals, and bookmark handling#53
wrngwrld merged 12 commits into
mainfrom
fix/various-fixes

Conversation

@wrngwrld

@wrngwrld wrngwrld commented May 1, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the app’s “last seen” tracking from watched media to watched posts, refreshes several UI elements (blur-pill controls, board/bookmark visuals), and bumps app/dependency versions to support the changes.

Changes:

  • Replace watched-media persistence/behavior with watched-post tracking (including auto-scroll to last seen).
  • Introduce a shared buildBlurPill UI helper and apply it across media controls / FABs.
  • Redesign bookmark and board list tiles, plus update package/app versions.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
pubspec.yaml Version bump + dependency updates; switches ffmpeg_kit_flutter_new to a git dependency.
lib/widgets/floating_action_buttons.dart Replaces FAB styling with blur-pill UI.
lib/widgets/feed_video_player.dart Updates mute toggle UI to use blur-pill.
lib/utils/build_blur_pill.dart Adds reusable blur-pill widget helper.
lib/pages/thread/thread_replies.dart Passes postIndex into ThreadPagePost.
lib/pages/thread/thread_replied_to.dart Passes postIndex into ThreadPagePost.
lib/pages/thread/thread_page_post.dart Adds required postIndex parameter; removes watched-media corner UI.
lib/pages/thread/thread_page.dart Switches visible-item tracking + auto-scroll to watched-posts provider.
lib/pages/thread/thread_media_viewer_page.dart Replaces local blur-pill implementation with shared helper.
lib/pages/thread/thread_image_viewer_page.dart Removes legacy image viewer page implementation.
lib/pages/settings/setting_pages/data_settings.dart Updates retention/clear logic to watched-posts provider/settings methods.
lib/pages/savedAttachments/saved_media_viewer_page.dart Replaces local blur-pill implementation with shared helper.
lib/pages/bookmarks/bookmarks_post.dart Refactors bookmark card UI + lifecycle for fetching bookmark status.
lib/pages/bookmarks/bookmarks.dart Adds stable keys for bookmark list items.
lib/pages/bookmark_button.dart Changes favorite icon semantics from heart to bookmark + color behavior.
lib/pages/boards/board_tile.dart Refreshes board tile UI (badge, colors, slidable actions).
lib/pages/boards/board_list_header.dart Adds reusable header widget for board list sections.
lib/pages/boards/board_list.dart Uses new headers; renames filtered boards variable; tweaks nav icons.
lib/pages/board/list_post.dart Updates thread list post layout + bookmark affordance UI.
lib/main.dart Removes GalleryProvider; replaces watched-media provider with watched-posts provider.
lib/blocs/watched_media_model.dart Renames provider to WatchedPostsProvider and persists watched-post indices.
lib/blocs/settings_model.dart Renames retention setting to watched-posts retention days.
lib/blocs/gallery_model.dart Removes unused gallery provider.
lib/Models/watched_media.dart Renames model to WatchedPosts and changes stored data shape.
ios/Runner.xcodeproj/project.pbxproj iOS marketing version bump to 0.11.4.
Comments suppressed due to low confidence (1)

lib/pages/settings/setting_pages/data_settings.dart:276

  • The snackbar message still says “Watched media history cleared!” but the action now clears watched posts. Update the message to reflect what was cleared to avoid user confusion.
                    await watchedPostsProvider.clearAllWatchedPosts();
                    if (mounted) {
                      showCupertinoSnackbar(
                        const Duration(milliseconds: 1800),
                        true,
                        context,
                        'Watched media history cleared!',
                      );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 261 to 263
title: const Text('Clear Watched Media History'),
trailing: const CupertinoListTileChevron(),
onTap: () async {

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action is now clearAllWatchedPosts(), but the tile title still says “Clear Watched Media History”. Update the label to “watched posts/threads” (or similar) so users understand what will be cleared.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
import 'dart:ui';

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dart:ui is imported but not used in this file. This will trigger an unused_import warning; remove the import (the blur helper already imports dart:ui).

Suggested change
import 'dart:ui';

Copilot uses AI. Check for mistakes.
Comment thread lib/pages/bookmarks/bookmarks_post.dart Outdated
Comment on lines +61 to +71
return FutureBuilder<BookmarkStatus>(
future: _fetchBookmarkStatus,
builder: (BuildContext context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return createBookmarkPost(
ThreadStatus.online,
null,
theme,
);
default:
if (snapshot.data!.status == ThreadStatus.deleted) {
isDeleted = true;
}
return Slidable(
endActionPane: ActionPane(
extentRatio: 0.3,
motion: const BehindMotion(),
children: [
SlidableAction(
label: 'Delete',
backgroundColor: Colors.red,
icon: Icons.delete,
onPressed: (context) => {
bookmarks.removeBookmarks(
widget.favorite,
),
},
)
],
future: _fetchBookmarkStatus,
builder: (BuildContext context, snapshot) {
final ThreadStatus status =
snapshot.connectionState == ConnectionState.waiting
? ThreadStatus.online
: (snapshot.data!.status ?? ThreadStatus.online);
final ThreadReplyCount? replyCount =
snapshot.connectionState == ConnectionState.waiting
? null
: snapshot.data!.replies;

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FutureBuilder unconditionally dereferences snapshot.data! when not waiting. If fetchBookmarkStatus completes with an error (network failure) snapshot.data will be null and this will throw. Handle snapshot.hasError / !snapshot.hasData (e.g., show an offline/deleted state) before reading snapshot.data.

Copilot uses AI. Check for mistakes.
Comment thread lib/pages/bookmarks/bookmarks_post.dart Outdated
overflow: TextOverflow.ellipsis,
if (status == ThreadStatus.archived ||
status == ThreadStatus.deleted) ...[
const SizedBox(width: 6),

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This SizedBox uses width but is placed in a Column, so it won’t add spacing between the chips. Use SizedBox(height: ...) for vertical spacing, or wrap the chips in a Row if they are meant to be inline.

Suggested change
const SizedBox(width: 6),
const SizedBox(height: 6),

Copilot uses AI. Check for mistakes.
Comment on lines 18 to 37
class ThreadPagePost extends StatefulWidget {
const ThreadPagePost({
Key? key,
required this.board,
required this.post,
required this.thread,
required this.allPosts,
required this.onDismiss,
required this.postIndex,
this.replies,
}) : super(key: key);

final String board;
final int thread;
final Post post;
final List<Post> allPosts;
final Function(int? postId) onDismiss;
final List<Post>? replies;
final int postIndex;

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

postIndex was added as a required parameter/field but is not used anywhere in this widget. If it’s no longer needed, remove it; if it is needed for watched/scroll logic, wire it into the relevant behavior to avoid dead API surface.

Copilot uses AI. Check for mistakes.
Comment on lines 218 to 224
trailing: CupertinoButton(
padding: EdgeInsets.zero,
child: Text(
'${settings.getWatchedMediaRetentionDays()} days',
'${settings.getWatchedPostsRetentionDays()} days',
style: const TextStyle(color: CupertinoColors.systemGrey),
),
onPressed: () async {

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This setting now uses getWatchedPostsRetentionDays but the surrounding UI copy still says “Watched Media Retention Period” / “watched status of all media”. Update the title/subtitle text so it matches the new watched-post tracking behavior.

Copilot uses AI. Check for mistakes.
Comment thread lib/pages/bookmark_button.dart Outdated
isFavorite ? Icons.favorite : Icons.favorite_border_outlined,
color: CupertinoColors.systemRed,
isFavorite ? Icons.bookmark : Icons.bookmark_border,
color: isFavorite ? CupertinoColors.activeBlue : Colors.white,

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When isFavorite is false the bookmark icon color is hard-coded to Colors.white, which has very low contrast on the light navigation background. Consider deriving the color from the current theme/brightness (or using CupertinoColors.label) so it remains visible in both light/dark modes.

Suggested change
color: isFavorite ? CupertinoColors.activeBlue : Colors.white,
color: isFavorite
? CupertinoColors.activeBlue
: CupertinoColors.label.resolveFrom(context),

Copilot uses AI. Check for mistakes.
Comment on lines 112 to 134
@@ -140,22 +120,16 @@ class ThreadPageState extends State<ThreadPage> {
return;
}

final latestWatchedMedia = watchedMedia.getLatestWatchedMedia(
final latestWatchedPosts = watchedPosts.getLatestWatchedPosts(
widget.thread,
);

if (latestWatchedMedia != null) {
final index = allPosts.indexWhere(
(post) =>
post.tim == latestWatchedMedia.mediaId ||
post.no == latestWatchedMedia.mediaId,
);

if (index != -1) {
Future.delayed(const Duration(milliseconds: 250), () {
if (latestWatchedPosts != null) {
if (latestWatchedPosts.postIndex != -1) {
Future.delayed(const Duration(milliseconds: 50), () {
if (mounted && itemScrollController.isAttached) {
itemScrollController.scrollTo(
index: index,
index: latestWatchedPosts.postIndex,
alignment: 0,
duration: const Duration(milliseconds: 500),

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scrollToLastWatchedPosts scrolls to latestWatchedPosts.postIndex without validating it against the current allPosts.length. If the stored index is out of range (e.g., thread data changed), scrollTo can throw. Use the allPosts parameter to bounds-check/clamp before scrolling (and/or early return when out of range).

Copilot uses AI. Check for mistakes.
Comment thread lib/blocs/watched_media_model.dart Outdated
Comment on lines +5 to +10
import 'package:flutter_chan/Models/watched_media.dart';
import 'package:shared_preferences/shared_preferences.dart';

class WatchedMediaProvider with ChangeNotifier, WidgetsBindingObserver {
WatchedMediaProvider() {
loadWatchedMedia();
class WatchedPostsProvider with ChangeNotifier, WidgetsBindingObserver {
WatchedPostsProvider() {
loadWatchedPosts();

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This provider and its model were renamed to WatchedPosts*, but the files are still named watched_media_model.dart / watched_media.dart. This mismatch makes the codebase harder to navigate and can be confusing for future refactors. Consider renaming the files to match the new responsibility (and updating imports).

Copilot uses AI. Check for mistakes.
Comment thread pubspec.yaml
ffmpeg_kit_flutter_new:
git:
url: https://github.com/dart-technologies/ffmpeg_kit_flutter.git
ref: arm64-simulator-support

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ffmpeg_kit_flutter_new dependency is pinned to a named git ref (arm64-simulator-support), which can move over time and makes builds non-reproducible. Prefer pinning to an immutable commit SHA (or a tagged release) to ensure consistent dependency resolution.

Suggested change
ref: arm64-simulator-support
ref: <FULL_40_CHAR_COMMIT_SHA_FOR_ARM64_SIMULATOR_SUPPORT>

Copilot uses AI. Check for mistakes.
wrngwrld and others added 4 commits May 2, 2026 13:46
…es/gif

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
…FeedVideoPlayer

Co-authored-by: Copilot <copilot@github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 32 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

lib/pages/thread/thread_page.dart:137

  • latestWatchedPosts.postIndex is only checked against -1 before calling itemScrollController.scrollTo. If the stored index is >= allPosts.length (e.g., thread shrank/archived, or indices shifted), scrollTo can throw. Consider clamping/validating postIndex against allPosts.length - 1 before scrolling.
    final latestWatchedPosts = watchedPosts.getLatestWatchedPosts(
      widget.thread,
    );

    if (latestWatchedPosts != null) {
      if (latestWatchedPosts.postIndex != -1) {
        Future.delayed(const Duration(milliseconds: 50), () {
          if (mounted && itemScrollController.isAttached) {
            itemScrollController.scrollTo(
              index: latestWatchedPosts.postIndex,
              alignment: 0,
              duration: const Duration(milliseconds: 500),
              curve: Curves.easeInOutCubic,
            );
          }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (prefs.getInt('watchedMediaRetentionDays') != null) {
watchedMediaRetentionDays = prefs.getInt('watchedMediaRetentionDays')!;
if (prefs.getInt('watchedPostsRetentionDays') != null) {
watchedPostsRetentionDays = prefs.getInt('watchedPostsRetentionDays')!;
Comment thread README.md Outdated
Comment on lines 744 to 777
@@ -768,10 +765,15 @@ class _FeedVideoPlayerState extends State<FeedVideoPlayer> {
// Ignore toggle failures.
}
},
child: Icon(
_isMuted ? Icons.volume_off : Icons.volume_up,
color: Colors.white,
size: 16,
child: buildBlurPill(
child: Container(
padding: const EdgeInsets.all(8),
child: Icon(
_isMuted ? Icons.volume_off : Icons.volume_up,
color: Colors.white,
size: 16,
),
),
),
Comment on lines +70 to +79
snapshot.connectionState == ConnectionState.waiting
? ThreadStatus.online
: failedToLoad
? ThreadStatus.deleted
: (snapshot.data?.status ?? ThreadStatus.online);
final ThreadReplyCount? replyCount =
snapshot.connectionState == ConnectionState.waiting || failedToLoad
? null
: snapshot.data?.replies;
final bool isDeleted = status == ThreadStatus.deleted;
Comment on lines +64 to +86
Future<void> markAsWatched({
required int postIndex,
required int thread,
}) async {
final WatchedPosts newWatchedPost = WatchedPosts(
postIndex: postIndex,
thread: thread,
watchedAt: DateTime.now(),
);

final existingMediaIndex = _watchedPosts.indexWhere(
(media) => media.postIndex == postIndex && media.thread == thread,
);

if (existingMediaIndex != -1) {
_watchedPosts[existingMediaIndex] = newWatchedPost;
} else {
_watchedPosts.add(newWatchedPost);
}

await _saveWatchedPosts();
notifyListeners();
}
Comment on lines +38 to +53
GestureDetector(
onTap: () => {if (goUp == null) animateToTop() else goUp!()},
child: buildBlurPill(
child: const Icon(
Icons.keyboard_arrow_up_rounded,
size: 28,
color: CupertinoColors.white,
),
padding: const EdgeInsets.all(8),
),
),
const SizedBox(height: 14),
FloatingActionButton(
child: const Icon(Icons.keyboard_arrow_down_rounded, size: 28),
elevation: 0,
backgroundColor: theme.getTheme() == ThemeData.light()
? Colors.white.withValues(alpha: 0.92)
: const Color(0xFF171A20).withValues(alpha: 0.92),
foregroundColor: CupertinoColors.activeBlue,
onPressed: () => {
if (goDown == null) animateToBottom() else goDown!(),
},
heroTag: null,

GestureDetector(
onTap: () => {if (goDown == null) animateToBottom() else goDown!()},
child: buildBlurPill(
Comment on lines +340 to +362
GestureDetector(
onTap: () => {
if (isFavorite)
bookmarks.removeBookmarks(favorite)
else
bookmarks.addBookmarks(favorite),
},

child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.35),
borderRadius: BorderRadius.circular(999),
),
child: Icon(
isFavorite
? CupertinoIcons.bookmark_fill
: CupertinoIcons.bookmark,
size: 18,
color: isFavorite
? CupertinoColors.activeBlue
: secondaryText,
),
wrngwrld and others added 2 commits May 2, 2026 14:45
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…rsistence logic

Co-authored-by: Copilot <copilot@github.com>
@wrngwrld
wrngwrld merged commit 4fd78dd into main May 2, 2026
@wrngwrld
wrngwrld deleted the fix/various-fixes branch May 2, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants