💄 Video player optimized

This commit is contained in:
2025-08-01 20:36:39 +08:00
parent 84b1d6a346
commit b39e2e2d64
24 changed files with 500 additions and 299 deletions

View File

@@ -93,7 +93,7 @@ class _AboutScreenState extends ConsumerState<AboutScreen> {
final theme = Theme.of(context); final theme = Theme.of(context);
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar(title: Text('about'.tr()), elevation: 0), appBar: AppBar(title: Text('about'.tr()), elevation: 0),
body: body:
_isLoading _isLoading

View File

@@ -64,7 +64,7 @@ class AccountScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
noBackground: isWide, isNoBackground: isWide,
appBar: AppBar(backgroundColor: Colors.transparent, toolbarHeight: 0), appBar: AppBar(backgroundColor: Colors.transparent, toolbarHeight: 0),
body: SingleChildScrollView( body: SingleChildScrollView(
padding: getTabbedPadding(context), padding: getTabbedPadding(context),

View File

@@ -46,7 +46,7 @@ class EventCalanderScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
leading: const PageBackButton(), leading: const PageBackButton(),
title: Text('eventCalander').tr(), title: Text('eventCalander').tr(),

View File

@@ -12,6 +12,7 @@ import 'package:island/pods/event_calendar.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/services/color.dart'; import 'package:island/services/color.dart';
import 'package:island/services/responsive.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
import 'package:island/services/timezone/native.dart'; import 'package:island/services/timezone/native.dart';
import 'package:island/widgets/account/account_name.dart'; import 'package:island/widgets/account/account_name.dart';
@@ -248,294 +249,367 @@ class AccountProfileScreen extends HookConsumerWidget {
final user = ref.watch(userInfoProvider); final user = ref.watch(userInfoProvider);
Widget accountBasicInfo(SnAccount data) => Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfilePictureWidget(file: data.profile.picture, radius: 32),
const Gap(20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
AccountName(account: data, style: TextStyle(fontSize: 20)),
const Gap(6),
Text('@${data.name}').fontSize(14).opacity(0.85),
],
),
AccountStatusWidget(uname: name, padding: EdgeInsets.zero),
],
),
),
],
),
);
Widget accountProfileDetail(SnAccount data) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 24,
children: [
if (buildSubcolumn(data).isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 2,
children: buildSubcolumn(data),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('bio').tr().bold(),
Text(
data.profile.bio.isEmpty
? 'descriptionNone'.tr()
: data.profile.bio,
),
],
),
if (data.profile.timeZone.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('timeZone').tr().bold(),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
spacing: 6,
children: [
Text(data.profile.timeZone),
Text(
getTzInfo(
data.profile.timeZone,
).$2.formatCustomGlobal('HH:mm'),
),
Text(
getTzInfo(data.profile.timeZone).$1.formatOffsetLocal(),
).fontSize(11),
Text(
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}',
).fontSize(11).opacity(0.75),
],
),
],
),
],
).padding(horizontal: 24);
Widget accountAction(SnAccount data) => Card(
child: Column(
children: [
Row(
spacing: 8,
children: [
if (accountRelationship.value == null ||
accountRelationship.value!.status > -100)
Expanded(
child: FilledButton.icon(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
accountRelationship.value == null
? null
: Theme.of(context).colorScheme.secondary,
),
foregroundColor: WidgetStatePropertyAll(
accountRelationship.value == null
? null
: Theme.of(context).colorScheme.onSecondary,
),
),
onPressed: relationshipAction,
label:
Text(
accountRelationship.value == null
? 'addFriendShort'
: 'added',
).tr(),
icon:
accountRelationship.value == null
? const Icon(Symbols.person_add)
: const Icon(Symbols.person_check),
),
),
if (accountRelationship.value == null ||
accountRelationship.value!.status <= -100)
Expanded(
child: FilledButton.icon(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
accountRelationship.value == null
? null
: Theme.of(context).colorScheme.secondary,
),
foregroundColor: WidgetStatePropertyAll(
accountRelationship.value == null
? null
: Theme.of(context).colorScheme.onSecondary,
),
),
onPressed: blockAction,
label:
Text(
accountRelationship.value == null
? 'blockUser'
: 'unblockUser',
).tr(),
icon:
accountRelationship.value == null
? const Icon(Symbols.block)
: const Icon(Symbols.person_cancel),
),
),
],
).padding(horizontal: 16),
Row(
spacing: 8,
children: [
Expanded(
child: FilledButton.icon(
onPressed: directMessageAction,
icon: const Icon(Symbols.message),
label:
Text(
accountChat.value == null
? 'createDirectMessage'
: 'gotoDirectMessage',
maxLines: 1,
).tr(),
),
),
IconButton.filled(
onPressed: () {
showAbuseReportSheet(
context,
resourceIdentifier: 'account/${data.id}',
);
},
icon: Icon(
Symbols.flag,
color: Theme.of(context).colorScheme.onError,
),
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
Theme.of(context).colorScheme.error,
),
),
),
],
),
],
).padding(horizontal: 16, vertical: 8),
);
return account.when( return account.when(
data: data:
(data) => AppScaffold( (data) => AppScaffold(
body: CustomScrollView( isNoBackground: false,
slivers: [ appBar:
SliverAppBar( isWideScreen(context)
foregroundColor: appbarColor.value, ? AppBar(
expandedHeight: 180, foregroundColor: appbarColor.value,
pinned: true, leading: PageBackButton(
leading: PageBackButton( color: appbarColor.value,
color: appbarColor.value, shadows: [appbarShadow],
shadows: [appbarShadow],
),
flexibleSpace: Stack(
children: [
Positioned.fill(
child:
data.profile.background?.id != null
? CloudImageWidget(
file: data.profile.background,
)
: Container(
color:
Theme.of(
context,
).appBarTheme.backgroundColor,
),
), ),
FlexibleSpaceBar( flexibleSpace: Stack(
title: Text( children: [
data.nick, Positioned.fill(
style: TextStyle( child:
color: data.profile.background?.id != null
appbarColor.value ?? ? CloudImageWidget(
Theme.of(context).appBarTheme.foregroundColor, file: data.profile.background,
shadows: [appbarShadow], )
: Container(
color:
Theme.of(
context,
).appBarTheme.backgroundColor,
),
),
FlexibleSpaceBar(
title: Text(
data.nick,
style: TextStyle(
color:
appbarColor.value ??
Theme.of(
context,
).appBarTheme.foregroundColor,
shadows: [appbarShadow],
),
),
),
],
),
)
: null,
body:
isWideScreen(context)
? Row(
children: [
Flexible(
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(child: accountBasicInfo(data)),
if (data.badges.isNotEmpty)
SliverToBoxAdapter(
child: BadgeList(
badges: data.badges,
).padding(horizontal: 24, bottom: 24),
),
SliverToBoxAdapter(
child: Column(
spacing: 12,
children: [
LevelingProgressCard(
level: data.profile.level,
experience: data.profile.experience,
progress: data.profile.levelingProgress,
),
if (data.profile.verification != null)
VerificationStatusCard(
mark: data.profile.verification!,
),
],
).padding(horizontal: 20),
),
],
), ),
), ),
), Flexible(
], child: CustomScrollView(
), slivers: [
), SliverToBoxAdapter(
SliverToBoxAdapter( child: accountProfileDetail(data),
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfilePictureWidget(
file: data.profile.picture,
radius: 32,
),
const Gap(20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
AccountName(
account: data,
style: TextStyle(fontSize: 20),
),
const Gap(6),
Text(
'@${data.name}',
).fontSize(14).opacity(0.85),
],
), ),
AccountStatusWidget(
uname: name, if (user.value != null)
padding: EdgeInsets.zero, SliverToBoxAdapter(child: accountAction(data)),
SliverToBoxAdapter(
child: Card(
child: FortuneGraphWidget(
events: accountEvents,
eventCalanderUser: data.name,
),
).padding(all: 8),
), ),
], ],
), ),
), ),
], ],
), )
), : CustomScrollView(
), slivers: [
if (data.badges.isNotEmpty) SliverAppBar(
SliverToBoxAdapter( foregroundColor: appbarColor.value,
child: BadgeList( expandedHeight: 180,
badges: data.badges, pinned: true,
).padding(horizontal: 24, bottom: 24), leading: PageBackButton(
), color: appbarColor.value,
SliverToBoxAdapter( shadows: [appbarShadow],
child: Column(
spacing: 12,
children: [
LevelingProgressCard(
level: data.profile.level,
experience: data.profile.experience,
progress: data.profile.levelingProgress,
),
if (data.profile.verification != null)
VerificationStatusCard(
mark: data.profile.verification!,
),
],
).padding(horizontal: 20),
),
SliverToBoxAdapter(
child: const Divider(height: 1).padding(vertical: 24),
),
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: 24,
children: [
if (buildSubcolumn(data).isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 2,
children: buildSubcolumn(data),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('bio').tr().bold(),
Text(
data.profile.bio.isEmpty
? 'descriptionNone'.tr()
: data.profile.bio,
), ),
], flexibleSpace: Stack(
), children: [
if (data.profile.timeZone.isNotEmpty) Positioned.fill(
Column( child:
crossAxisAlignment: CrossAxisAlignment.start, data.profile.background?.id != null
children: [ ? CloudImageWidget(
Text('timeZone').tr().bold(), file: data.profile.background,
Row( )
crossAxisAlignment: CrossAxisAlignment.baseline, : Container(
textBaseline: TextBaseline.alphabetic, color:
spacing: 6, Theme.of(
children: [ context,
Text(data.profile.timeZone), ).appBarTheme.backgroundColor,
Text( ),
getTzInfo( ),
data.profile.timeZone, FlexibleSpaceBar(
).$2.formatCustomGlobal('HH:mm'), title: Text(
), data.nick,
Text( style: TextStyle(
getTzInfo( color:
data.profile.timeZone, appbarColor.value ??
).$1.formatOffsetLocal(), Theme.of(
).fontSize(11), context,
Text( ).appBarTheme.foregroundColor,
'UTC${getTzInfo(data.profile.timeZone).$1.formatOffset()}', shadows: [appbarShadow],
).fontSize(11).opacity(0.75), ),
],
),
],
),
],
).padding(horizontal: 24),
),
if (user.value != null)
SliverToBoxAdapter(
child: const Divider(
height: 1,
).padding(top: 24, bottom: 12),
),
if (user.value != null)
SliverToBoxAdapter(
child: Row(
spacing: 8,
children: [
if (accountRelationship.value == null ||
accountRelationship.value!.status > -100)
Expanded(
child: FilledButton.icon(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
accountRelationship.value == null
? null
: Theme.of(context).colorScheme.secondary,
),
foregroundColor: WidgetStatePropertyAll(
accountRelationship.value == null
? null
: Theme.of(
context,
).colorScheme.onSecondary,
), ),
), ),
onPressed: relationshipAction, ],
label:
Text(
accountRelationship.value == null
? 'addFriendShort'
: 'added',
).tr(),
icon:
accountRelationship.value == null
? const Icon(Symbols.person_add)
: const Icon(Symbols.person_check),
),
), ),
if (accountRelationship.value == null || ),
accountRelationship.value!.status <= -100) SliverToBoxAdapter(child: accountBasicInfo(data)),
Expanded( if (data.badges.isNotEmpty)
child: FilledButton.icon( SliverToBoxAdapter(
style: ButtonStyle( child: BadgeList(
backgroundColor: WidgetStatePropertyAll( badges: data.badges,
accountRelationship.value == null ).padding(horizontal: 24, bottom: 24),
? null ),
: Theme.of(context).colorScheme.secondary, SliverToBoxAdapter(
), child: Column(
foregroundColor: WidgetStatePropertyAll( spacing: 12,
accountRelationship.value == null children: [
? null LevelingProgressCard(
: Theme.of( level: data.profile.level,
context, experience: data.profile.experience,
).colorScheme.onSecondary, progress: data.profile.levelingProgress,
),
), ),
onPressed: blockAction, if (data.profile.verification != null)
label: VerificationStatusCard(
Text( mark: data.profile.verification!,
accountRelationship.value == null ),
? 'blockUser' ],
: 'unblockUser', ).padding(horizontal: 20),
).tr(), ),
icon:
accountRelationship.value == null SliverToBoxAdapter(child: accountProfileDetail(data)),
? const Icon(Symbols.block)
: const Icon(Symbols.person_cancel), if (user.value != null)
), SliverToBoxAdapter(child: accountAction(data)),
), SliverToBoxAdapter(
child: Column(
children: [
FortuneGraphWidget(
events: accountEvents,
eventCalanderUser: data.name,
),
],
).padding(all: 8),
),
], ],
).padding(horizontal: 16), ),
),
SliverToBoxAdapter(
child: Row(
spacing: 8,
children: [
Expanded(
child: FilledButton.icon(
onPressed: directMessageAction,
icon: const Icon(Symbols.message),
label:
Text(
accountChat.value == null
? 'createDirectMessage'
: 'gotoDirectMessage',
maxLines: 1,
).tr(),
),
),
IconButton.filled(
onPressed: () {
showAbuseReportSheet(
context,
resourceIdentifier: 'account/${data.id}',
);
},
icon: Icon(
Symbols.flag,
color: Theme.of(context).colorScheme.onError,
),
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
Theme.of(context).colorScheme.error,
),
),
),
],
).padding(horizontal: 16, top: 4),
),
SliverToBoxAdapter(
child: const Divider(height: 1).padding(top: 12),
),
SliverToBoxAdapter(
child: Column(
children: [
FortuneGraphWidget(
events: accountEvents,
eventCalanderUser: data.name,
),
],
).padding(all: 8),
),
],
),
), ),
error: error:
(error, stackTrace) => AppScaffold( (error, stackTrace) => AppScaffold(

View File

@@ -73,7 +73,7 @@ class CreateAccountScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
leading: const PageBackButton(), leading: const PageBackButton(),
title: Text('createAccount').tr(), title: Text('createAccount').tr(),

View File

@@ -55,7 +55,7 @@ class LoginScreen extends HookConsumerWidget {
final factorPicked = useState<SnAuthFactor?>(null); final factorPicked = useState<SnAuthFactor?>(null);
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
leading: const PageBackButton(), leading: const PageBackButton(),
title: Text('login').tr(), title: Text('login').tr(),

View File

@@ -40,7 +40,7 @@ class CallScreen extends HookConsumerWidget {
); );
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
leading: PageBackButton(), leading: PageBackButton(),
title: Column( title: Column(

View File

@@ -111,7 +111,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
); );
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
leading: !isWide ? const PageBackButton() : null, leading: !isWide ? const PageBackButton() : null,
title: Text('developerHub').tr(), title: Text('developerHub').tr(),

View File

@@ -17,7 +17,7 @@ class DiscoveryRealmsScreen extends HookConsumerWidget {
final currentQuery = useState<String?>(null); final currentQuery = useState<String?>(null);
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar(title: Text('discoverRealms'.tr())), appBar: AppBar(title: Text('discoverRealms'.tr())),
body: Stack( body: Stack(
children: [ children: [

View File

@@ -87,7 +87,7 @@ class ExploreScreen extends HookConsumerWidget {
final user = ref.watch(userInfoProvider); final user = ref.watch(userInfoProvider);
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
toolbarHeight: 0, toolbarHeight: 0,
bottom: PreferredSize( bottom: PreferredSize(

View File

@@ -53,13 +53,13 @@ class PostEditScreen extends HookConsumerWidget {
data: (post) => PostComposeScreen(originalPost: post), data: (post) => PostComposeScreen(originalPost: post),
loading: loading:
() => AppScaffold( () => AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: const Center(child: CircularProgressIndicator()), body: const Center(child: CircularProgressIndicator()),
), ),
error: error:
(e, _) => AppScaffold( (e, _) => AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: Text('Error: $e', textAlign: TextAlign.center), body: Text('Error: $e', textAlign: TextAlign.center),
), ),
@@ -287,7 +287,7 @@ class PostComposeScreen extends HookConsumerWidget {
} }
}, },
child: AppScaffold( child: AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
leading: const PageBackButton(), leading: const PageBackButton(),
actions: [ actions: [

View File

@@ -352,7 +352,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
} }
}, },
child: AppScaffold( child: AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
leading: const PageBackButton(), leading: const PageBackButton(),
title: ValueListenableBuilder<TextEditingValue>( title: ValueListenableBuilder<TextEditingValue>(

View File

@@ -54,7 +54,7 @@ class PostDetailScreen extends HookConsumerWidget {
final user = ref.watch(userInfoProvider); final user = ref.watch(userInfoProvider);
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar(title: const Text('Post')), appBar: AppBar(title: const Text('Post')),
body: postState.when( body: postState.when(
data: (post) { data: (post) {

View File

@@ -110,7 +110,7 @@ class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: TextField( title: TextField(
controller: _searchController, controller: _searchController,

View File

@@ -259,7 +259,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
return publisher.when( return publisher.when(
data: data:
(data) => AppScaffold( (data) => AppScaffold(
noBackground: false, isNoBackground: false,
appBar: appBar:
isWideScreen(context) isWideScreen(context)
? AppBar( ? AppBar(
@@ -389,13 +389,13 @@ class PublisherProfileScreen extends HookConsumerWidget {
), ),
error: error:
(error, stackTrace) => AppScaffold( (error, stackTrace) => AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: Center(child: Text(error.toString())), body: Center(child: Text(error.toString())),
), ),
loading: loading:
() => AppScaffold( () => AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: Center(child: CircularProgressIndicator()), body: Center(child: CircularProgressIndicator()),
), ),

View File

@@ -79,7 +79,7 @@ class RealmDetailScreen extends HookConsumerWidget {
); );
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
body: realmState.when( body: realmState.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(child: Text('Error: $error')), error: (error, _) => Center(child: Text('Error: $error')),

View File

@@ -41,7 +41,7 @@ class RealmListScreen extends HookConsumerWidget {
final realmInvites = ref.watch(realmInvitesProvider); final realmInvites = ref.watch(realmInvitesProvider);
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: const Text('realms').tr(), title: const Text('realms').tr(),
actions: [ actions: [
@@ -279,7 +279,7 @@ class EditRealmScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: Text(slug == null ? 'createRealm'.tr() : 'editRealm'.tr()), title: Text(slug == null ? 'createRealm'.tr() : 'editRealm'.tr()),
leading: const PageBackButton(), leading: const PageBackButton(),

View File

@@ -552,7 +552,7 @@ class SettingsScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
noBackground: false, isNoBackground: false,
appBar: AppBar( appBar: AppBar(
title: Text('settings').tr(), title: Text('settings').tr(),
actions: actions:

View File

@@ -165,7 +165,7 @@ class AppScaffold extends StatelessWidget {
final AppBar? appBar; final AppBar? appBar;
final DrawerCallback? onDrawerChanged; final DrawerCallback? onDrawerChanged;
final DrawerCallback? onEndDrawerChanged; final DrawerCallback? onEndDrawerChanged;
final bool? noBackground; final bool? isNoBackground;
final bool? extendBody; final bool? extendBody;
const AppScaffold({ const AppScaffold({
@@ -181,7 +181,7 @@ class AppScaffold extends StatelessWidget {
this.endDrawer, this.endDrawer,
this.onDrawerChanged, this.onDrawerChanged,
this.onEndDrawerChanged, this.onEndDrawerChanged,
this.noBackground, this.isNoBackground,
this.extendBody, this.extendBody,
}); });
@@ -190,7 +190,7 @@ class AppScaffold extends StatelessWidget {
final appBarHeight = appBar?.preferredSize.height ?? 0; final appBarHeight = appBar?.preferredSize.height ?? 0;
final safeTop = MediaQuery.of(context).padding.top; final safeTop = MediaQuery.of(context).padding.top;
final noBackground = this.noBackground ?? isWideScreen(context); final noBackground = isNoBackground ?? isWideScreen(context);
final content = Column( final content = Column(
children: [ children: [

View File

@@ -1,8 +1,11 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
import 'package:island/services/time.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
@@ -45,7 +48,7 @@ class CloudFileWidget extends ConsumerWidget {
), ),
"video" => AspectRatio( "video" => AspectRatio(
aspectRatio: ratio, aspectRatio: ratio,
child: UniversalVideo(uri: uri, aspectRatio: ratio), child: CloudVideoWidget(item: item),
), ),
_ => Text('Unable render for ${item.mimeType}'), _ => Text('Unable render for ${item.mimeType}'),
}; };
@@ -58,6 +61,119 @@ class CloudFileWidget extends ConsumerWidget {
} }
} }
class CloudVideoWidget extends HookConsumerWidget {
final SnCloudFile item;
const CloudVideoWidget({super.key, required this.item});
@override
Widget build(BuildContext context, WidgetRef ref) {
final open = useState(false);
final serverUrl = ref.watch(serverUrlProvider);
final uri = '$serverUrl/drive/files/${item.id}';
var ratio =
item.fileMeta?['ratio'] is num
? item.fileMeta!['ratio'].toDouble()
: 1.0;
if (ratio == 0) ratio = 1.0;
if (open.value) {
return UniversalVideo(uri: uri, aspectRatio: ratio, autoplay: true);
}
return GestureDetector(
child: Stack(
children: [
UniversalImage(uri: '$uri?thumbnail=true'),
Positioned.fill(
child: Center(
child: const Icon(
Symbols.play_arrow,
fill: 1,
size: 32,
shadows: [
BoxShadow(
color: Colors.black54,
offset: Offset(1, 1),
spreadRadius: 8,
blurRadius: 8,
),
],
),
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
spacing: 8,
children: [
if (item.fileMeta?['duration'] != null)
Text(
Duration(
milliseconds:
((item.fileMeta?['duration'] as num) * 1000)
.toInt(),
).formatDuration(),
style: TextStyle(
shadows: [
BoxShadow(
color: Colors.black54,
offset: Offset(1, 1),
spreadRadius: 8,
blurRadius: 8,
),
],
),
),
if (item.fileMeta?['bit_rate'] != null)
Text(
'${int.parse(item.fileMeta?['bit_rate'] as String) ~/ 1000} Kbps',
style: TextStyle(
shadows: [
BoxShadow(
color: Colors.black54,
offset: Offset(1, 1),
spreadRadius: 8,
blurRadius: 8,
),
],
),
),
],
),
Text(
item.name,
style: TextStyle(
fontWeight: FontWeight.bold,
shadows: [
BoxShadow(
color: Colors.black54,
offset: Offset(1, 1),
spreadRadius: 8,
blurRadius: 8,
),
],
),
),
],
),
).padding(horizontal: 16, bottom: 12),
],
),
onTap: () {
open.value = true;
},
);
}
}
class CloudImageWidget extends ConsumerWidget { class CloudImageWidget extends ConsumerWidget {
final String? fileId; final String? fileId;
final SnCloudFile? file; final SnCloudFile? file;

View File

@@ -11,10 +11,12 @@ import 'package:media_kit_video/media_kit_video.dart';
class UniversalVideo extends ConsumerStatefulWidget { class UniversalVideo extends ConsumerStatefulWidget {
final String uri; final String uri;
final double aspectRatio; final double aspectRatio;
final bool autoplay;
const UniversalVideo({ const UniversalVideo({
super.key, super.key,
required this.uri, required this.uri,
this.aspectRatio = 16 / 9, this.aspectRatio = 16 / 9,
this.autoplay = false,
}); });
@override @override
@@ -47,7 +49,7 @@ class _UniversalVideoState extends ConsumerState<UniversalVideo> {
log('[MediaPlayer] Hit cache: $url'); log('[MediaPlayer] Hit cache: $url');
} }
_player!.open(Media(uri), play: false); _player!.open(Media(uri), play: widget.autoplay);
} }
@override @override

View File

@@ -27,9 +27,13 @@ class PublisherCard extends ConsumerWidget {
Widget card = Card( Widget card = Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
margin: EdgeInsets.zero,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
context.pushNamed('publisherProfile', pathParameters: {'name': publisher.name}); context.pushNamed(
'publisherProfile',
pathParameters: {'name': publisher.name},
);
}, },
child: AspectRatio( child: AspectRatio(
aspectRatio: 16 / 7, aspectRatio: 16 / 7,

View File

@@ -29,9 +29,13 @@ class RealmCard extends ConsumerWidget {
Widget card = Card( Widget card = Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
margin: EdgeInsets.zero,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
context.pushNamed('realmDetail', pathParameters: {'slug': realm.slug}); context.pushNamed(
'realmDetail',
pathParameters: {'slug': realm.slug},
);
}, },
child: AspectRatio( child: AspectRatio(
aspectRatio: 16 / 7, aspectRatio: 16 / 7,

View File

@@ -28,6 +28,7 @@ class WebArticleCard extends StatelessWidget {
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
child: Card( child: Card(
margin: EdgeInsets.zero,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: InkWell( child: InkWell(
onTap: () => _onTap(context), onTap: () => _onTap(context),