Skip to content

Conversation

@HachiHalation
Copy link
Contributor

Pull Request Description

Adds a special features row to show details and season details screens, similar to the official jellyfin client. Unfortunately, due to how the special features API is set up, it is really difficult to group them by season in the show screen without making a bunch of API calls.

  • Add SpecialFeatureModel
  • Add SpecialFeatureRow widget (based on EpisodePosters)
  • Add the required data to relevant providers
  • Add Special Feature localization string to EN file

Issue Being Fixed

Resolves #632

Screenshots / Recordings

show screen:

image

season screen:

image

Checklist

  • If a new package was added, did you ensure it works for all supported platforms? Is the package well maintained
  • Check that any changes are related to the issue at hand.

Copy link
Collaborator

@PartyDonut PartyDonut left a comment

Choose a reason for hiding this comment

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

Thank you for implementing this. I added a few comments let me know if anything is unclear.

I don't have any special features so not fully able to test it. But the code looks fine logic wise.

@MappableClass()
class SpecialFeatureModel extends ItemStreamModel with SpecialFeatureModelMappable {
final List<Chapter> chapters;
final ItemLocation? location;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems unused probably better to remove it.

static List<SpecialFeatureModel> specialFeaturesFromDto(List<dto.BaseItemDto>? dto, Ref ref) {
return dto?.map((e) => SpecialFeatureModel.fromBaseDto(e, ref)).toList() ?? [];
}
} No newline at end of file
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add the newline as well. So we can keep consistent formatting.

try {
return api.itemsItemIdSpecialFeaturesGet(itemId: itemId, userId: account?.id);
} catch (e) {
return Response(http.Response("", 400), []);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any errors here will be obfuscated because we throw away whatever the exception is telling us.
Remove the try{} catch{}. So we can monitor the original response.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that we shouldnt swallow the exception here. But I do think the current callers should catch them and emit a log if there is a problem since in this context a failure should not stop the application.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Makes sense. Currently the handling of errors is a bit of a mess of my making.

Lets just leave it as is without the try/catch same as most of the other calls. I will have to come up with a proper solution when I can find the time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I already moved the try catch before I saw this. Let me know if you want me to remove it anyways.

Comment on lines 43 to 45
List<SpecialFeatureModel> get specialFeatures {
return widget.specialFeatures;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
List<SpecialFeatureModel> get specialFeatures {
return widget.specialFeatures;
}
List<SpecialFeatureModel> specialFeatures = widget.specialFeatures;

This can be moved to the build part of the widget. After that make the widget stateless as there is no state we need to maintain.

final String? label;
final ValueChanged<SpecialFeatureModel> playSpecialFeature;
final EdgeInsets contentPadding;
final Function(VoidCallback action, SpecialFeatureModel specialFeatureModel)? onSpecialFeatureTap;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
final Function(VoidCallback action, SpecialFeatureModel specialFeatureModel)? onSpecialFeatureTap;
final Function(SpecialFeatureModel specialFeatureModel)? onSpecialFeatureTap;

Why does this take another VoidCallback as argument? I would suggest to only return the SpecialFeatureModel.

Comment on lines 54 to 74
startIndex: null,
items: specialFeatures,
itemBuilder: (context, index) {
final specialFeature = specialFeatures[index];
final tag = UniqueKey();
return SpecialFeaturePoster(
specialFeature: specialFeature,
heroTag: tag,
blur: false,
onTap: widget.onSpecialFeatureTap != null
? () {
widget.onSpecialFeatureTap?.call(
() {
specialFeature.play(context, ref);
},
specialFeature,
);
}
: () {
specialFeature.play(context, ref);
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
startIndex: null,
items: specialFeatures,
itemBuilder: (context, index) {
final specialFeature = specialFeatures[index];
final tag = UniqueKey();
return SpecialFeaturePoster(
specialFeature: specialFeature,
heroTag: tag,
blur: false,
onTap: widget.onSpecialFeatureTap != null
? () {
widget.onSpecialFeatureTap?.call(
() {
specialFeature.play(context, ref);
},
specialFeature,
);
}
: () {
specialFeature.play(context, ref);
},
onTap: widget.onSpecialFeatureTap != null
? () => widget.onSpecialFeatureTap?.call(
specialFeature,
)
: () => specialFeature.play(context, ref),

Ok now we know why there was a action. But I'm not sure why that behaviour is there.
I would expect the function to be called if not null and we then supply the tapped specialFeature. Where we can then do whatever action needed in the details screens.

It's ok to fallback to a "default" action, but when the function has a override it should not still call the "default" action.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This behavior is consistent with the existing behavior from EpisodePosters widget. I don't really understand the concern, it is up to the caller to invoke the default action (in this case play) using the callback provided in onSpecialFeatureTap if they wish to.

From what I understand from looking at the code, onSpecialFeatureTap provides a hook to callers to change the behavior of the tap action. onSpecialFeatureTap also provides a callback to the default action so that the caller can use it if they still want to perform the default actions during the customized logic.

This all makes sense to me and is what was already implemented in EpisodePosters so I am keeping it like this for now. But please correct me if I am missing something and I will fix it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It's more that I like to explicitly know what "action" is.
This makes the code just a little bit less readable because we have to dive into the widget itself to see what action actually does.

I know it also happens in the EpisodePosters but I don't like it there either, I should probably remove it there as well just did not get to that yet.

So it's fine to leave it as is, this works perfectly fine. But I would prefer the callback to be more explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made some changes to hopefully make it more readable. If you still don't like it I will take your recommended solution instead.

Copy link
Collaborator

@PartyDonut PartyDonut left a comment

Choose a reason for hiding this comment

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

Looks good to me, thanks for implementing the changes.

@PartyDonut PartyDonut changed the title Display specials feat: Display specials Jan 21, 2026
@PartyDonut PartyDonut added the feature New feature or request label Jan 21, 2026
@PartyDonut PartyDonut merged commit 50a22fa into DonutWare:develop Jan 21, 2026
1 check passed
popdollar pushed a commit to popdollar/Fladder-Tizen that referenced this pull request Jan 22, 2026
@chocolate-neko chocolate-neko mentioned this pull request Jan 24, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants