From c709a653d3283f8a577965ec2c47215cf9705f0e Mon Sep 17 00:00:00 2001 From: livinglist Date: Wed, 1 Apr 2026 11:25:18 -0700 Subject: [PATCH 1/2] fix: UI for hidden stories --- lib/config/constants.dart | 1 + lib/screens/widgets/items_list_view.dart | 347 +++++++++++---------- lib/screens/widgets/stories_list_view.dart | 6 + 3 files changed, 182 insertions(+), 172 deletions(-) diff --git a/lib/config/constants.dart b/lib/config/constants.dart index 95e18843..095f0fb6 100644 --- a/lib/config/constants.dart +++ b/lib/config/constants.dart @@ -64,6 +64,7 @@ abstract class Constants { 'Download stories in settings to read stories offline', 'Open any Hacker News links in Hacki via the system share menu', '''Swipe right on a comment and tap the dots icon to search for the poster within the thread or across HN''', + '''Disabling `Mark Read Stories` in settings will clear your existing read history''', ].randomlyPicked!; static final String happyFace = [ diff --git a/lib/screens/widgets/items_list_view.dart b/lib/screens/widgets/items_list_view.dart index 65f8b85d..d569847f 100644 --- a/lib/screens/widgets/items_list_view.dart +++ b/lib/screens/widgets/items_list_view.dart @@ -82,200 +82,203 @@ class ItemsListView extends StatelessWidget { shouldShowExitButton: true, ), if (header != null) header!, - ...List.generate(items.length, (_) => _).map((int index) { - final T e = items.elementAt(index); - if (e is Story) { - final bool hasRead = context.read().hasRead(e); - final bool swipeGestureEnabled = - context.read().state.isSwipeGestureEnabled; - if (isHideInsteadOfMarkingGrayEnabled && - context.watch().isHidden(e.id)) { - return const []; - } - return [ - if (shouldShowDivider && items.first.id != e.id) - Padding( - padding: EdgeInsetsGeometry.only( - bottom: shouldShowWebPreviewOnStoryTile - ? Dimens.pt8 - : Dimens.zero, - ), - child: const Divider( - height: Dimens.zero, - ), - ) - else if (context.read().state.enabled) - const Divider( - height: Dimens.pt6, - color: Palette.transparent, - ), - if (shouldUseMinimalTileForStory) - FadeIn( - child: InkWell( - onTap: () => onTap(e), + ...List.generate(items.length, (_) => _) + .map((int index) { + final T e = items.elementAt(index); + if (e is Story) { + final bool hasRead = context.read().hasRead(e); + final bool swipeGestureEnabled = + context.read().state.isSwipeGestureEnabled; - /// If swipe gesture is enabled on home screen, use - /// long press instead of slide action to trigger - /// the action menu. - onLongPress: swipeGestureEnabled - ? () => onMoreTapped?.call(e, context.rect) - : null, - child: Padding( - padding: const EdgeInsets.only( - top: Dimens.pt8, - bottom: Dimens.pt8, - left: Dimens.pt12, - right: Dimens.pt6, + return [ + if (shouldShowDivider && items.first.id != e.id) + Padding( + padding: EdgeInsetsGeometry.only( + bottom: shouldShowWebPreviewOnStoryTile + ? Dimens.pt8 + : Dimens.zero, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( + child: const Divider( + height: Dimens.zero, + ), + ) + else if (context.read().state.enabled) + const Divider( + height: Dimens.pt6, + color: Palette.transparent, + ), + if (shouldUseMinimalTileForStory) + FadeIn( + child: InkWell( + onTap: () => onTap(e), + + /// If swipe gesture is enabled on home screen, use + /// long press instead of slide action to trigger + /// the action menu. + onLongPress: swipeGestureEnabled + ? () => onMoreTapped?.call(e, context.rect) + : null, + child: Padding( + padding: const EdgeInsets.only( + top: Dimens.pt8, + bottom: Dimens.pt8, + left: Dimens.pt12, + right: Dimens.pt6, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Text( - shouldShowAuthor - ? '''${e.timeAgo} by ${e.by}''' - : e.timeAgo, - style: TextStyle( - color: Theme.of(context).metadataColor, - ), + Row( + children: [ + Text( + shouldShowAuthor + ? '''${e.timeAgo} by ${e.by}''' + : e.timeAgo, + style: TextStyle( + color: Theme.of(context).metadataColor, + ), + ), + const SizedBox( + width: Dimens.pt12, + ), + ], ), - const SizedBox( - width: Dimens.pt12, + Linkify( + text: e.title, + maxLines: 4, + style: const TextStyle( + fontSize: TextDimens.pt16, + ), + linkStyle: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + onOpen: (LinkableElement link) => + LinkUtils.launch( + link.url, + context, + ), ), ], ), - Linkify( - text: e.title, - maxLines: 4, - style: const TextStyle( - fontSize: TextDimens.pt16, - ), - linkStyle: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - onOpen: (LinkableElement link) => LinkUtils.launch( - link.url, - context, - ), - ), - ], + ), + ), + ) + else ...[ + GestureDetector( + /// If swipe gesture is enabled on home screen, use + /// long press instead of slide action to trigger + /// the action menu. + onLongPress: swipeGestureEnabled + ? () => onMoreTapped?.call(e, context.rect) + : null, + child: FadeIn( + child: StoryTile( + key: ValueKey(e.id), + story: e, + onTap: () => onTap(e), + index: index, + shouldShowWebPreview: shouldShowWebPreviewOnStoryTile, + shouldShowMetadata: shouldShowMetadataOnStoryTile, + shouldShowUrl: shouldShowUrl, + shouldShowFavicon: shouldShowFavicon, + shouldShowPreviewImage: shouldShowPreviewImage, + isExpandedTileEnabled: isExpandedTileEnabled, + isIndexedStoryTileEnabled: isIndexedStoryTileEnabled, + isImageLeftAligned: isPreviewImageLeftAligned, + hasRead: shouldMarkReadStories && hasRead, + ), ), ), - ), - ) - else ...[ - GestureDetector( - /// If swipe gesture is enabled on home screen, use long press - /// instead of slide action to trigger the action menu. - onLongPress: swipeGestureEnabled - ? () => onMoreTapped?.call(e, context.rect) - : null, - child: FadeIn( - child: StoryTile( - key: ValueKey(e.id), - story: e, + if (shouldShowDivider && shouldShowWebPreviewOnStoryTile) + const SizedBox( + height: Dimens.pt8, + ), + ], + ]; + } else if (e is Comment) { + return [ + FadeIn( + child: InkWell( onTap: () => onTap(e), - index: index, - shouldShowWebPreview: shouldShowWebPreviewOnStoryTile, - shouldShowMetadata: shouldShowMetadataOnStoryTile, - shouldShowUrl: shouldShowUrl, - shouldShowFavicon: shouldShowFavicon, - shouldShowPreviewImage: shouldShowPreviewImage, - isExpandedTileEnabled: isExpandedTileEnabled, - isIndexedStoryTileEnabled: isIndexedStoryTileEnabled, - isImageLeftAligned: isPreviewImageLeftAligned, - hasRead: shouldMarkReadStories && hasRead, - ), - ), - ), - if (shouldShowDivider && shouldShowWebPreviewOnStoryTile) - const SizedBox( - height: Dimens.pt8, - ), - ], - ]; - } else if (e is Comment) { - return [ - FadeIn( - child: InkWell( - onTap: () => onTap(e), - child: Padding( - padding: const EdgeInsets.only( - top: Dimens.pt8, - bottom: Dimens.pt8, - left: Dimens.pt12, - right: Dimens.pt6, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (e.deleted) - const Center( - child: Padding( - padding: EdgeInsets.only( - top: Dimens.pt6, - ), - child: Text( - 'deleted', - style: TextStyle(color: Palette.grey), - ), - ), - ), - Column( + child: Padding( + padding: const EdgeInsets.only( + top: Dimens.pt8, + bottom: Dimens.pt8, + left: Dimens.pt12, + right: Dimens.pt6, + ), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, children: [ - Row( - children: [ - Text( - shouldShowAuthor - ? '''${e.timeAgo} by ${e.by}''' - : e.timeAgo, - style: TextStyle( - color: Theme.of(context).metadataColor, + if (e.deleted) + const Center( + child: Padding( + padding: EdgeInsets.only( + top: Dimens.pt6, + ), + child: Text( + 'deleted', + style: TextStyle(color: Palette.grey), ), ), - const SizedBox( - width: Dimens.pt12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Text( + shouldShowAuthor + ? '''${e.timeAgo} by ${e.by}''' + : e.timeAgo, + style: TextStyle( + color: Theme.of(context).metadataColor, + ), + ), + const SizedBox( + width: Dimens.pt12, + ), + ], + ), + Linkify( + text: e.text, + maxLines: 4, + style: const TextStyle( + fontSize: TextDimens.pt16, + ), + linkStyle: TextStyle( + color: + Theme.of(context).colorScheme.primary, + ), + onOpen: (LinkableElement link) => + LinkUtils.launch( + link.url, + context, + ), ), ], ), - Linkify( - text: e.text, - maxLines: 4, - style: const TextStyle( - fontSize: TextDimens.pt16, - ), - linkStyle: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - onOpen: (LinkableElement link) => - LinkUtils.launch( - link.url, - context, - ), - ), ], ), - ], + ), ), ), - ), - ), - const Divider( - height: Dimens.zero, - ), - ]; - } + const Divider( + height: Dimens.zero, + ), + ]; + } - return [Container()]; - }).mapIndexed( - (int index, List e) => itemBuilder == null - ? Column(children: e) - : itemBuilder!(Column(children: e), items.elementAt(index)), - ), + return [Container()]; + }) + .where((List list) => list.isNotEmpty) + .mapIndexed( + (int index, List e) => itemBuilder == null + ? Column(children: e) + : itemBuilder!(Column(children: e), items.elementAt(index)), + ), if (footer != null) footer!, const SizedBox( height: Dimens.pt40, diff --git a/lib/screens/widgets/stories_list_view.dart b/lib/screens/widgets/stories_list_view.dart index 77dbbc90..fcf15247 100644 --- a/lib/screens/widgets/stories_list_view.dart +++ b/lib/screens/widgets/stories_list_view.dart @@ -168,6 +168,12 @@ class _StoriesListViewState extends State : const SizedBox.shrink(), onMoreTapped: onMoreTapped, itemBuilder: (Widget child, Story story) { + if (story.hidden) { + return const SizedBox.shrink(); + } else if (preferenceState.isHideInsteadOfMarkingGrayEnabled && + context.read().isHidden(story.id)) { + return const SizedBox.shrink(); + } return Slidable( key: ValueKey(story), enabled: !preferenceState.isSwipeGestureEnabled, From b4f3acfc5685bda08e256c5fea6a62e1bf9c22ab Mon Sep 17 00:00:00 2001 From: livinglist Date: Wed, 1 Apr 2026 11:26:38 -0700 Subject: [PATCH 2/2] upate --- lib/screens/widgets/items_list_view.dart | 345 +++++++++++------------ 1 file changed, 170 insertions(+), 175 deletions(-) diff --git a/lib/screens/widgets/items_list_view.dart b/lib/screens/widgets/items_list_view.dart index d569847f..b0904d7b 100644 --- a/lib/screens/widgets/items_list_view.dart +++ b/lib/screens/widgets/items_list_view.dart @@ -82,203 +82,198 @@ class ItemsListView extends StatelessWidget { shouldShowExitButton: true, ), if (header != null) header!, - ...List.generate(items.length, (_) => _) - .map((int index) { - final T e = items.elementAt(index); - if (e is Story) { - final bool hasRead = context.read().hasRead(e); - final bool swipeGestureEnabled = - context.read().state.isSwipeGestureEnabled; + ...List.generate(items.length, (_) => _).map((int index) { + final T e = items.elementAt(index); + if (e is Story) { + final bool hasRead = context.read().hasRead(e); + final bool swipeGestureEnabled = + context.read().state.isSwipeGestureEnabled; - return [ - if (shouldShowDivider && items.first.id != e.id) - Padding( - padding: EdgeInsetsGeometry.only( - bottom: shouldShowWebPreviewOnStoryTile - ? Dimens.pt8 - : Dimens.zero, - ), - child: const Divider( - height: Dimens.zero, - ), - ) - else if (context.read().state.enabled) - const Divider( - height: Dimens.pt6, - color: Palette.transparent, - ), - if (shouldUseMinimalTileForStory) - FadeIn( - child: InkWell( - onTap: () => onTap(e), + return [ + if (shouldShowDivider && items.first.id != e.id) + Padding( + padding: EdgeInsetsGeometry.only( + bottom: shouldShowWebPreviewOnStoryTile + ? Dimens.pt8 + : Dimens.zero, + ), + child: const Divider( + height: Dimens.zero, + ), + ) + else if (context.read().state.enabled) + const Divider( + height: Dimens.pt6, + color: Palette.transparent, + ), + if (shouldUseMinimalTileForStory) + FadeIn( + child: InkWell( + onTap: () => onTap(e), - /// If swipe gesture is enabled on home screen, use - /// long press instead of slide action to trigger - /// the action menu. - onLongPress: swipeGestureEnabled - ? () => onMoreTapped?.call(e, context.rect) - : null, - child: Padding( - padding: const EdgeInsets.only( - top: Dimens.pt8, - bottom: Dimens.pt8, - left: Dimens.pt12, - right: Dimens.pt6, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + /// If swipe gesture is enabled on home screen, use + /// long press instead of slide action to trigger + /// the action menu. + onLongPress: swipeGestureEnabled + ? () => onMoreTapped?.call(e, context.rect) + : null, + child: Padding( + padding: const EdgeInsets.only( + top: Dimens.pt8, + bottom: Dimens.pt8, + left: Dimens.pt12, + right: Dimens.pt6, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( children: [ - Row( - children: [ - Text( - shouldShowAuthor - ? '''${e.timeAgo} by ${e.by}''' - : e.timeAgo, - style: TextStyle( - color: Theme.of(context).metadataColor, - ), - ), - const SizedBox( - width: Dimens.pt12, - ), - ], - ), - Linkify( - text: e.title, - maxLines: 4, - style: const TextStyle( - fontSize: TextDimens.pt16, - ), - linkStyle: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - onOpen: (LinkableElement link) => - LinkUtils.launch( - link.url, - context, + Text( + shouldShowAuthor + ? '''${e.timeAgo} by ${e.by}''' + : e.timeAgo, + style: TextStyle( + color: Theme.of(context).metadataColor, ), ), + const SizedBox( + width: Dimens.pt12, + ), ], ), - ), - ), - ) - else ...[ - GestureDetector( - /// If swipe gesture is enabled on home screen, use - /// long press instead of slide action to trigger - /// the action menu. - onLongPress: swipeGestureEnabled - ? () => onMoreTapped?.call(e, context.rect) - : null, - child: FadeIn( - child: StoryTile( - key: ValueKey(e.id), - story: e, - onTap: () => onTap(e), - index: index, - shouldShowWebPreview: shouldShowWebPreviewOnStoryTile, - shouldShowMetadata: shouldShowMetadataOnStoryTile, - shouldShowUrl: shouldShowUrl, - shouldShowFavicon: shouldShowFavicon, - shouldShowPreviewImage: shouldShowPreviewImage, - isExpandedTileEnabled: isExpandedTileEnabled, - isIndexedStoryTileEnabled: isIndexedStoryTileEnabled, - isImageLeftAligned: isPreviewImageLeftAligned, - hasRead: shouldMarkReadStories && hasRead, - ), + Linkify( + text: e.title, + maxLines: 4, + style: const TextStyle( + fontSize: TextDimens.pt16, + ), + linkStyle: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + onOpen: (LinkableElement link) => LinkUtils.launch( + link.url, + context, + ), + ), + ], ), ), - if (shouldShowDivider && shouldShowWebPreviewOnStoryTile) - const SizedBox( - height: Dimens.pt8, - ), - ], - ]; - } else if (e is Comment) { - return [ - FadeIn( - child: InkWell( + ), + ) + else ...[ + GestureDetector( + /// If swipe gesture is enabled on home screen, use + /// long press instead of slide action to trigger + /// the action menu. + onLongPress: swipeGestureEnabled + ? () => onMoreTapped?.call(e, context.rect) + : null, + child: FadeIn( + child: StoryTile( + key: ValueKey(e.id), + story: e, onTap: () => onTap(e), - child: Padding( - padding: const EdgeInsets.only( - top: Dimens.pt8, - bottom: Dimens.pt8, - left: Dimens.pt12, - right: Dimens.pt6, - ), - child: Column( + index: index, + shouldShowWebPreview: shouldShowWebPreviewOnStoryTile, + shouldShowMetadata: shouldShowMetadataOnStoryTile, + shouldShowUrl: shouldShowUrl, + shouldShowFavicon: shouldShowFavicon, + shouldShowPreviewImage: shouldShowPreviewImage, + isExpandedTileEnabled: isExpandedTileEnabled, + isIndexedStoryTileEnabled: isIndexedStoryTileEnabled, + isImageLeftAligned: isPreviewImageLeftAligned, + hasRead: shouldMarkReadStories && hasRead, + ), + ), + ), + if (shouldShowDivider && shouldShowWebPreviewOnStoryTile) + const SizedBox( + height: Dimens.pt8, + ), + ], + ]; + } else if (e is Comment) { + return [ + FadeIn( + child: InkWell( + onTap: () => onTap(e), + child: Padding( + padding: const EdgeInsets.only( + top: Dimens.pt8, + bottom: Dimens.pt8, + left: Dimens.pt12, + right: Dimens.pt6, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (e.deleted) + const Center( + child: Padding( + padding: EdgeInsets.only( + top: Dimens.pt6, + ), + child: Text( + 'deleted', + style: TextStyle(color: Palette.grey), + ), + ), + ), + Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - if (e.deleted) - const Center( - child: Padding( - padding: EdgeInsets.only( - top: Dimens.pt6, - ), - child: Text( - 'deleted', - style: TextStyle(color: Palette.grey), - ), - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + Row( children: [ - Row( - children: [ - Text( - shouldShowAuthor - ? '''${e.timeAgo} by ${e.by}''' - : e.timeAgo, - style: TextStyle( - color: Theme.of(context).metadataColor, - ), - ), - const SizedBox( - width: Dimens.pt12, - ), - ], - ), - Linkify( - text: e.text, - maxLines: 4, - style: const TextStyle( - fontSize: TextDimens.pt16, - ), - linkStyle: TextStyle( - color: - Theme.of(context).colorScheme.primary, - ), - onOpen: (LinkableElement link) => - LinkUtils.launch( - link.url, - context, + Text( + shouldShowAuthor + ? '''${e.timeAgo} by ${e.by}''' + : e.timeAgo, + style: TextStyle( + color: Theme.of(context).metadataColor, ), ), + const SizedBox( + width: Dimens.pt12, + ), ], ), + Linkify( + text: e.text, + maxLines: 4, + style: const TextStyle( + fontSize: TextDimens.pt16, + ), + linkStyle: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + onOpen: (LinkableElement link) => + LinkUtils.launch( + link.url, + context, + ), + ), ], ), - ), + ], ), ), - const Divider( - height: Dimens.zero, - ), - ]; - } + ), + ), + const Divider( + height: Dimens.zero, + ), + ]; + } - return [Container()]; - }) - .where((List list) => list.isNotEmpty) - .mapIndexed( - (int index, List e) => itemBuilder == null - ? Column(children: e) - : itemBuilder!(Column(children: e), items.elementAt(index)), - ), + return [Container()]; + }).mapIndexed( + (int index, List e) => itemBuilder == null + ? Column(children: e) + : itemBuilder!(Column(children: e), items.elementAt(index)), + ), if (footer != null) footer!, const SizedBox( height: Dimens.pt40,