Compare commits

..

No commits in common. "812478bc37192c7d18e05bff42f9ccfe03493f4e" and "3cf07a82144214e87cf1a4bcc00be5926480b6f8" have entirely different histories.

4 changed files with 49 additions and 99 deletions

View file

@ -55,9 +55,6 @@ class ScheduleDetail {
final String? content; final String? content;
final List<String> imageUrls; final List<String> imageUrls;
final String? postUrl; final String? postUrl;
// X
final String? profileDisplayName;
final String? profileAvatarUrl;
ScheduleDetail({ ScheduleDetail({
required this.id, required this.id,
@ -78,8 +75,6 @@ class ScheduleDetail {
this.content, this.content,
this.imageUrls = const [], this.imageUrls = const [],
this.postUrl, this.postUrl,
this.profileDisplayName,
this.profileAvatarUrl,
}); });
factory ScheduleDetail.fromJson(Map<String, dynamic> json) { factory ScheduleDetail.fromJson(Map<String, dynamic> json) {
@ -108,8 +103,6 @@ class ScheduleDetail {
content: json['content'] as String?, content: json['content'] as String?,
imageUrls: (json['imageUrls'] as List<dynamic>?)?.cast<String>() ?? [], imageUrls: (json['imageUrls'] as List<dynamic>?)?.cast<String>() ?? [],
postUrl: json['postUrl'] as String?, postUrl: json['postUrl'] as String?,
profileDisplayName: (json['profile'] as Map<String, dynamic>?)?['displayName'] as String?,
profileAvatarUrl: (json['profile'] as Map<String, dynamic>?)?['avatarUrl'] as String?,
); );
} }

View file

@ -176,9 +176,8 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
/// ///
Widget _buildContent(ScheduleDetail schedule) { Widget _buildContent(ScheduleDetail schedule) {
final bottomPadding = MediaQuery.of(context).padding.bottom;
return SingleChildScrollView( return SingleChildScrollView(
padding: EdgeInsets.fromLTRB(16, 16, 16, 16 + bottomPadding), padding: const EdgeInsets.all(16),
child: _buildCategorySection(schedule), child: _buildCategorySection(schedule),
); );
} }
@ -424,12 +423,9 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
/// X /// X
Widget _buildXSection(ScheduleDetail schedule) { Widget _buildXSection(ScheduleDetail schedule) {
final username = schedule.username ?? 'Unknown'; final displayName = schedule.username ?? 'Unknown';
final displayName = schedule.profileDisplayName ?? username;
final avatarUrl = schedule.profileAvatarUrl;
return Container( return Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -444,24 +440,28 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
child: Row( child: Row(
children: [ children: [
// //
avatarUrl != null Container(
? ClipOval(
child: CachedNetworkImage(
imageUrl: avatarUrl,
width: 40, width: 40,
height: 40, height: 40,
fit: BoxFit.cover,
placeholder: (_, _) => Container(
width: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[300], gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.grey[700]!, Colors.grey[900]!],
),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Center(
child: Text(
displayName[0].toUpperCase(),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
), ),
errorWidget: (_, _, _) => _buildAvatarFallback(displayName),
), ),
)
: _buildAvatarFallback(displayName),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
@ -484,7 +484,7 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
], ],
), ),
Text( Text(
'@$username', '@$displayName',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: Colors.grey[500], color: Colors.grey[500],
@ -565,7 +565,6 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[50], color: Colors.grey[50],
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(12)),
border: Border(top: BorderSide(color: Colors.grey[100]!)), border: Border(top: BorderSide(color: Colors.grey[100]!)),
), ),
child: SizedBox( child: SizedBox(
@ -692,32 +691,6 @@ class _ScheduleDetailViewState extends ConsumerState<ScheduleDetailView> {
); );
} }
/// ()
Widget _buildAvatarFallback(String name) {
return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.grey[700]!, Colors.grey[900]!],
),
shape: BoxShape.circle,
),
child: Center(
child: Text(
name[0].toUpperCase(),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
);
}
/// ///
Widget _buildVerifiedBadge() { Widget _buildVerifiedBadge() {
return SizedBox( return SizedBox(

View file

@ -13,37 +13,26 @@ import Birthday from './Birthday';
function linkifyText(text) { function linkifyText(text) {
if (!text) return null; if (!text) return null;
// URL // URL : http(s):// URL
const pattern = /(#[^\s#]+)|(https?:\/\/[^\s]+|(?:bit\.ly|youtu\.be|t\.co|goo\.gl|tinyurl\.com)\/[^\s]+)/gi; const urlPattern = /(https?:\/\/[^\s]+|(?:bit\.ly|youtu\.be|t\.co|goo\.gl|tinyurl\.com)\/[^\s]+)/gi;
const parts = []; const parts = [];
let lastIndex = 0; let lastIndex = 0;
let match; let match;
while ((match = pattern.exec(text)) !== null) { while ((match = urlPattern.exec(text)) !== null) {
if (match.index > lastIndex) { if (match.index > lastIndex) {
parts.push(text.slice(lastIndex, match.index)); parts.push(text.slice(lastIndex, match.index));
} }
const matched = match[0]; let url = match[0];
const href = url.startsWith('http') ? url : `https://${url}`;
if (matched.startsWith('#')) {
//
const tag = matched.slice(1);
parts.push( parts.push(
<a key={match.index} href={`https://x.com/hashtag/${encodeURIComponent(tag)}?src=hashtag_click`} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline"> <a key={match.index} href={href} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">
{matched} {url}
</a> </a>
); );
} else {
// URL
const href = matched.startsWith('http') ? matched : `https://${matched}`;
parts.push(
<a key={match.index} href={href} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">
{matched}
</a>
);
}
lastIndex = match.index + match[0].length; lastIndex = match.index + match[0].length;
} }

View file

@ -9,39 +9,34 @@ import { Lightbox } from '@/components/common';
function linkifyText(text) { function linkifyText(text) {
if (!text) return null; if (!text) return null;
// URL // URL : http(s)://
const pattern = /(#[^\s#]+)|(https?:\/\/[^\s]+|(?:bit\.ly|youtu\.be|t\.co|goo\.gl|tinyurl\.com)\/[^\s]+)/gi; const urlPattern = /(https?:\/\/[^\s]+|(?:bit\.ly|youtu\.be|t\.co|goo\.gl|tinyurl\.com)\/[^\s]+)/gi;
const parts = []; const parts = [];
let lastIndex = 0; let lastIndex = 0;
let match; let match;
while ((match = pattern.exec(text)) !== null) { while ((match = urlPattern.exec(text)) !== null) {
//
if (match.index > lastIndex) { if (match.index > lastIndex) {
parts.push(text.slice(lastIndex, match.index)); parts.push(text.slice(lastIndex, match.index));
} }
const matched = match[0]; // URL
let url = match[0];
// http(s)://
const href = url.startsWith('http') ? url : `https://${url}`;
if (matched.startsWith('#')) {
const tag = matched.slice(1);
parts.push( parts.push(
<a key={match.index} href={`https://x.com/hashtag/${encodeURIComponent(tag)}?src=hashtag_click`} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline"> <a key={match.index} href={href} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">
{matched} {url}
</a> </a>
); );
} else {
const href = matched.startsWith('http') ? matched : `https://${matched}`;
parts.push(
<a key={match.index} href={href} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">
{matched}
</a>
);
}
lastIndex = match.index + match[0].length; lastIndex = match.index + match[0].length;
} }
//
if (lastIndex < text.length) { if (lastIndex < text.length) {
parts.push(text.slice(lastIndex)); parts.push(text.slice(lastIndex));
} }