Skip to content

Commit

Permalink
feat: youtube top comments
Browse files Browse the repository at this point in the history
MSOB7YY committed Dec 13, 2023

Verified

This commit was signed with the committer’s verified signature. The key has expired.
ericek111 Erik Bročko
1 parent f30cff3 commit a9a8739
Showing 8 changed files with 977 additions and 504 deletions.
13 changes: 13 additions & 0 deletions lib/controller/navigator_controller.dart
Original file line number Diff line number Diff line change
@@ -31,7 +31,10 @@ class NamidaNavigator {

final ytLocalSearchNavigatorKey = Get.nestedKey(9);

final ytMiniplayerCommentsPageKey = Get.nestedKey(11);

bool isytLocalSearchInFullPage = false;
bool isInYTCommentsSubpage = false;

final RxList<NamidaRoute> currentWidgetStack = <NamidaRoute>[].obs;
NamidaRoute? get currentRoute => currentWidgetStack.lastOrNull;
@@ -316,16 +319,26 @@ class NamidaNavigator {
innerDrawerKey.currentState?.close();
return;
}

final ytmpcpks = ytMiniplayerCommentsPageKey?.currentState;
if (ytmpcpks != null) {
ytmpcpks.pop();
isInYTCommentsSubpage = false;
return;
}

if (MiniPlayerController.inst.ytMiniplayerKey.currentState?.isExpanded == true) {
MiniPlayerController.inst.ytMiniplayerKey.currentState?.animateToState(false);
return;
}

final ytsnvks = ytLocalSearchNavigatorKey?.currentState;
if (ytsnvks != null) {
ytsnvks.pop();
isytLocalSearchInFullPage = false;
return;
}

if (ScrollSearchController.inst.isGlobalSearchMenuShown.value) {
_hideSearchMenuAndUnfocus();
return;
7 changes: 7 additions & 0 deletions lib/controller/settings_controller.dart
Original file line number Diff line number Diff line change
@@ -170,6 +170,7 @@ class SettingsController {
final RxBool enableClipboardMonitoring = false.obs;
final RxBool ytIsAudioOnlyMode = false.obs;
final RxBool ytRememberAudioOnly = false.obs;
final RxBool ytTopComments = false.obs;
final RxBool artworkGestureScale = false.obs;
final RxBool artworkGestureDoubleTapLRC = true.obs;
final RxList<TagField> tagFieldsToEdit = <TagField>[
@@ -446,6 +447,7 @@ class SettingsController {
enableClipboardMonitoring.value = json['enableClipboardMonitoring'] ?? enableClipboardMonitoring.value;
ytRememberAudioOnly.value = json['ytRememberAudioOnly'] ?? ytRememberAudioOnly.value;
if (ytRememberAudioOnly.value) ytIsAudioOnlyMode.value = json['ytIsAudioOnlyMode'] ?? ytIsAudioOnlyMode.value;
ytTopComments.value = json['ytTopComments'] ?? ytTopComments.value;
artworkGestureScale.value = json['artworkGestureScale'] ?? artworkGestureScale.value;
artworkGestureDoubleTapLRC.value = json['artworkGestureDoubleTapLRC'] ?? artworkGestureDoubleTapLRC.value;

@@ -666,6 +668,7 @@ class SettingsController {
'enableClipboardMonitoring': enableClipboardMonitoring.value,
'ytIsAudioOnlyMode': ytIsAudioOnlyMode.value,
'ytRememberAudioOnly': ytRememberAudioOnly.value,
'ytTopComments': ytTopComments.value,
'artworkGestureScale': artworkGestureScale.value,
'artworkGestureDoubleTapLRC': artworkGestureDoubleTapLRC.value,
'tagFieldsToEdit': tagFieldsToEdit.mapped((element) => element.convertToString),
@@ -855,6 +858,7 @@ class SettingsController {
bool? enableClipboardMonitoring,
bool? ytIsAudioOnlyMode,
bool? ytRememberAudioOnly,
bool? ytTopComments,
bool? artworkGestureScale,
bool? artworkGestureDoubleTapLRC,
List<TagField>? tagFieldsToEdit,
@@ -1330,6 +1334,9 @@ class SettingsController {
if (ytRememberAudioOnly != null) {
this.ytRememberAudioOnly.value = ytRememberAudioOnly;
}
if (ytTopComments != null) {
this.ytTopComments.value = ytTopComments;
}
if (artworkGestureScale != null) {
this.artworkGestureScale.value = artworkGestureScale;
}
2 changes: 2 additions & 0 deletions lib/core/translations/keys.dart
Original file line number Diff line number Diff line change
@@ -555,6 +555,8 @@ abstract class LanguageKeys {
String get THEME_SETTINGS => _getKey('THEME_SETTINGS');
String get THUMBNAILS => _getKey('THUMBNAILS');
String get TITLE => _getKey('TITLE');
String get TOP_COMMENTS => _getKey('TOP_COMMENTS');
String get TOP_COMMENTS_SUBTITLE => _getKey('TOP_COMMENTS_SUBTITLE');
String get TOP_RECENTS => _getKey('TOP_RECENTS');
String get TOP_RECENT_ALBUMS => _getKey('TOP_RECENT_ALBUMS');
String get TOP_RECENT_ARTISTS => _getKey('TOP_RECENT_ARTISTS');
27 changes: 27 additions & 0 deletions lib/ui/widgets/settings/youtube_settings.dart
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ import 'package:namida/ui/widgets/settings_card.dart';

enum _YoutubeSettingKeys {
rememberAudioOnly,
topComments,
preferNewComments,
dimMiniplayerAfter,
dimIntensity,
@@ -29,6 +30,7 @@ class YoutubeSettings extends SettingSubpageProvider {
@override
Map<Enum, List<String>> get lookupMap => {
_YoutubeSettingKeys.rememberAudioOnly: [lang.REMEMBER_AUDIO_ONLY_MODE],
_YoutubeSettingKeys.topComments: [lang.TOP_COMMENTS, lang.TOP_COMMENTS_SUBTITLE],
_YoutubeSettingKeys.preferNewComments: [lang.YT_PREFER_NEW_COMMENTS, lang.YT_PREFER_NEW_COMMENTS_SUBTITLE],
_YoutubeSettingKeys.dimMiniplayerAfter: [lang.DIM_MINIPLAYER_AFTER_SECONDS],
_YoutubeSettingKeys.dimIntensity: [lang.DIM_INTENSITY],
@@ -56,6 +58,31 @@ class YoutubeSettings extends SettingSubpageProvider {
),
),
),
getItemWrapper(
key: _YoutubeSettingKeys.topComments,
child: Obx(
() => CustomSwitchListTile(
bgColor: getBgColor(_YoutubeSettingKeys.topComments),
leading: const StackedIcon(
baseIcon: Broken.document,
secondaryIcon: Broken.arrow_circle_up,
secondaryIconSize: 12.0,
),
title: lang.TOP_COMMENTS,
subtitle: lang.TOP_COMMENTS_SUBTITLE,
value: settings.ytTopComments.value,
onChanged: (isTrue) {
settings.save(ytTopComments: !isTrue);

// -- pop comments subpage in case was inside.
if (settings.ytTopComments.value == false) {
NamidaNavigator.inst.ytMiniplayerCommentsPageKey?.currentState?.pop();
NamidaNavigator.inst.isInYTCommentsSubpage = false;
}
},
),
),
),
getItemWrapper(
key: _YoutubeSettingKeys.preferNewComments,
child: Obx(
146 changes: 145 additions & 1 deletion lib/youtube/widgets/yt_comment_card.dart
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@ class YTCommentCard extends StatelessWidget {
NamidaDummyContainer(
width: 38.0,
height: 38.0,
borderRadius: 999,
isCircle: true,
shimmerEnabled: uploaderAvatar == null,
child: YoutubeThumbnail(
isImportantInCache: false,
@@ -215,3 +215,147 @@ class YTCommentCard extends StatelessWidget {
);
}
}

class YTCommentCardCompact extends StatelessWidget {
final YoutubeComment? comment;
const YTCommentCardCompact({super.key, required this.comment});

@override
Widget build(BuildContext context) {
final uploaderAvatar = comment?.uploaderAvatarUrl;
final author = comment?.author;
final uploadedFrom = comment?.uploadDate;
final commentText = comment?.commentText;
final likeCount = comment?.likeCount;
final repliesCount = comment?.replyCount == -1 ? null : comment?.replyCount;
final isHearted = comment?.hearted ?? false;
final isPinned = comment?.pinned ?? false;

final cid = comment?.commentId;

return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NamidaDummyContainer(
width: 28.0,
height: 28.0,
isCircle: true,
shimmerEnabled: uploaderAvatar == null,
child: YoutubeThumbnail(
isImportantInCache: false,
channelUrl: uploaderAvatar,
width: 28.0,
isCircle: true,
),
),
const SizedBox(width: 10.0),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 2.0),
NamidaDummyContainer(
width: context.width * 0.5,
height: 12.0,
borderRadius: 6.0,
shimmerEnabled: author == null,
child: Row(
children: [
Text(
[
author,
if (uploadedFrom != null) uploadedFrom,
].join(' • '),
style: context.textTheme.displaySmall?.copyWith(
fontSize: 11.5.multipliedFontScale,
fontWeight: FontWeight.w400,
color: context.theme.colorScheme.onBackground.withAlpha(180),
),
),
if (isPinned) ...[
const SizedBox(width: 4.0),
const Icon(
Broken.path,
size: 14.0,
),
],
if (isHearted) ...[
const SizedBox(width: 4.0),
const Icon(
Broken.heart_tick,
size: 14.0,
color: Color.fromARGB(200, 250, 90, 80),
),
],
],
),
),
const SizedBox(height: 2.0),
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: commentText == null
? Column(
children: [
...List.filled(
3,
const Padding(
padding: EdgeInsets.only(top: 2.0),
child: NamidaDummyContainer(
width: null,
height: 12.0,
borderRadius: 4.0,
shimmerEnabled: true,
child: null,
),
),
),
],
)
: Text(
YoutubeController.inst.commentToParsedHtml[cid] ?? commentText,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: context.textTheme.displaySmall?.copyWith(
fontSize: 12.5.multipliedFontScale,
fontWeight: FontWeight.w500,
color: context.theme.colorScheme.onBackground.withAlpha(220),
),
),
),
const SizedBox(height: 4.0),
Row(
children: [
const SizedBox(width: 4.0),
const Icon(Broken.like_1, size: 12.0),
const SizedBox(width: 4.0),
NamidaDummyContainer(
width: 18.0,
height: 8.0,
borderRadius: 4.0,
shimmerEnabled: likeCount == null,
child: Text(
likeCount?.formatDecimalShort() ?? '?',
style: context.textTheme.displaySmall?.copyWith(
fontSize: 11.5.multipliedFontScale,
fontWeight: FontWeight.w300,
),
),
),
const SizedBox(width: 12.0),
if (repliesCount != null && repliesCount > 0)
Text(
[
lang.REPLIES,
repliesCount,
].join(' • '),
style: context.textTheme.displaySmall?.copyWith(fontWeight: FontWeight.w300),
),
],
),
],
),
),
],
);
}
}
5 changes: 4 additions & 1 deletion lib/youtube/widgets/yt_shimmer.dart
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ class NamidaDummyContainer extends StatelessWidget {
final Widget? child;
final bool shimmerEnabled;
final double borderRadius;
final bool isCircle;

const NamidaDummyContainer({
super.key,
@@ -17,6 +18,7 @@ class NamidaDummyContainer extends StatelessWidget {
required this.child,
required this.shimmerEnabled,
this.borderRadius = 12.0,
this.isCircle = false,
});

@override
@@ -29,7 +31,8 @@ class NamidaDummyContainer extends StatelessWidget {
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: context.theme.colorScheme.background,
borderRadius: BorderRadius.circular(borderRadius.multipliedRadius),
borderRadius: isCircle ? null : BorderRadius.circular(borderRadius.multipliedRadius),
shape: isCircle ? BoxShape.circle : BoxShape.rectangle,
),
);
}
1,100 changes: 598 additions & 502 deletions lib/youtube/youtube_miniplayer.dart

Large diffs are not rendered by default.

181 changes: 181 additions & 0 deletions lib/youtube/yt_miniplayer_comments_subpage.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'package:namida/controller/connectivity.dart';
import 'package:namida/controller/player_controller.dart';
import 'package:namida/core/extensions.dart';
import 'package:namida/core/icon_fonts/broken_icons.dart';
import 'package:namida/core/namida_converter_ext.dart';
import 'package:namida/core/translations/language.dart';
import 'package:namida/ui/widgets/custom_widgets.dart';
import 'package:namida/ui/widgets/settings/extra_settings.dart';
import 'package:namida/youtube/controller/youtube_controller.dart';
import 'package:namida/youtube/widgets/yt_comment_card.dart';

class YTMiniplayerCommentsSubpage extends StatefulWidget {
const YTMiniplayerCommentsSubpage({super.key});

@override
State<YTMiniplayerCommentsSubpage> createState() => _YTMiniplayerCommentsSubpageState();
}

class _YTMiniplayerCommentsSubpageState extends State<YTMiniplayerCommentsSubpage> {
late final ScrollController sc;
@override
void initState() {
super.initState();
sc = ScrollController();
}

@override
void dispose() {
sc.dispose();
super.dispose();
}

String? get currentId {
final videoInfo = YoutubeController.inst.currentYoutubeMetadataVideo.value ?? Player.inst.currentVideoInfo;
return videoInfo?.id ?? Player.inst.nowPlayingVideoID?.id;
}

@override
Widget build(BuildContext context) {
return BackgroundWrapper(
child: Column(
children: [
Obx(
() {
final totalCommentsCount = YoutubeController.inst.currentTotalCommentsCount.value;
return DecoratedBox(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
blurRadius: 12.0,
color: context.theme.shadowColor.withOpacity(0.5),
)
]),
child: Padding(
key: Key("${currentId}_comments_header"),
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(Broken.document),
const SizedBox(width: 8.0),
Text(
[
lang.COMMENTS,
if (totalCommentsCount != null) totalCommentsCount.formatDecimalShort(),
].join(' • '),
style: context.textTheme.displayLarge,
textAlign: TextAlign.start,
),
const Spacer(),
NamidaIconButton(
tooltip: YoutubeController.inst.isCurrentCommentsFromCache ? lang.CACHE : null,
icon: Broken.refresh,
iconSize: 22.0,
onPressed: () async {
if (!ConnectivityController.inst.hasConnection) return;
sc.jumpTo(0);
await YoutubeController.inst.updateCurrentComments(
currentId ?? '',
forceRequest: ConnectivityController.inst.hasConnection,
);
},
child: YoutubeController.inst.isCurrentCommentsFromCache
? const StackedIcon(
baseIcon: Broken.refresh,
secondaryIcon: Broken.global,
)
: Icon(
Broken.refresh,
color: context.defaultIconColor(),
),
)
],
),
),
);
},
),
Expanded(
child: NamidaScrollbar(
controller: sc,
child: LazyLoadListView(
onReachingEnd: () async => await YoutubeController.inst.updateCurrentComments(currentId ?? '', fetchNextOnly: true),
extend: 400,
scrollController: sc,
listview: (controller) => CustomScrollView(
restorationId: currentId,
physics: const ClampingScrollPhysics(),
controller: controller,
slivers: [
Obx(
() {
final comments = YoutubeController.inst.currentComments;
if (comments.isNotEmpty && comments.first == null) {
return SliverToBoxAdapter(
key: Key("${currentId}_comments_shimmer"),
child: ShimmerWrapper(
transparent: false,
shimmerEnabled: true,
child: ListView.builder(
// key: Key(currentId),
physics: const NeverScrollableScrollPhysics(),
itemCount: comments.length,
shrinkWrap: true,
itemBuilder: (context, index) {
const comment = null;
return YTCommentCard(
key: Key("${comment == null}_${context.hashCode}"),
margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
comment: comment,
);
},
),
),
);
}
return SliverList.builder(
key: Key("${currentId}_comments"),
itemCount: comments.length,
itemBuilder: (context, i) {
final comment = comments[i];
return ShimmerWrapper(
transparent: false,
shimmerDurationMS: 550,
shimmerDelayMS: 250,
shimmerEnabled: comment == null,
child: YTCommentCard(
key: Key("${comment == null}_${context.hashCode}_${comment?.commentId}"),
margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
comment: comment,
),
);
},
);
},
),
Obx(
() {
final isLoadingComments = YoutubeController.inst.isLoadingComments.value;
return isLoadingComments
? SliverPadding(
padding: const EdgeInsets.all(12.0),
sliver: const Center(
child: LoadingIndicator(),
).toSliver(),
)
: const SizedBox().toSliver();
},
),
],
),
),
),
),
],
),
);
}
}

0 comments on commit a9a8739

Please sign in to comment.