Improve haptics & fix thumbnail card sizings (#90)
Also fixed full name not showing on buttons and modal for non-english channels.
This commit is contained in:
parent
c3b7b80b27
commit
23f48d3061
12 changed files with 196 additions and 188 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/screens/channel/chat/emote_menu/emote_menu.dart';
|
||||
import 'package:frosty/screens/channel/chat/widgets/chat_bottom_bar.dart';
|
||||
|
|
@ -65,17 +66,21 @@ class Chat extends StatelessWidget {
|
|||
FocusScope.of(context).unfocus();
|
||||
if (chatStore.assetsStore.showEmoteMenu) chatStore.assetsStore.showEmoteMenu = false;
|
||||
},
|
||||
onLongPress: () => showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => ChatUserModal(
|
||||
chatStore: chatStore,
|
||||
username: message.user!,
|
||||
userId: message.tags['user-id']!,
|
||||
displayName: message.tags['display-name']!,
|
||||
),
|
||||
),
|
||||
onLongPress: () {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => ChatUserModal(
|
||||
chatStore: chatStore,
|
||||
username: message.user!,
|
||||
userId: message.tags['user-id']!,
|
||||
displayName: message.tags['display-name']!,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: chatMessage,
|
||||
);
|
||||
}
|
||||
|
|
@ -87,17 +92,20 @@ class Chat extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: chatStore.autoScroll
|
||||
? null
|
||||
: Button(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
onPressed: chatStore.resumeScroll,
|
||||
icon: const Icon(Icons.arrow_circle_down),
|
||||
child: const Text('Resume Scroll'),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
child: Observer(
|
||||
builder: (_) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: chatStore.autoScroll
|
||||
? null
|
||||
: Button(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
|
||||
onPressed: chatStore.resumeScroll,
|
||||
icon: const Icon(Icons.arrow_circle_down),
|
||||
child: const Text('Resume Scroll'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ class _ChattersListState extends State<ChattersList> {
|
|||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
await chatDetailsStore.updateChatters(widget.userLogin);
|
||||
},
|
||||
child: Stack(
|
||||
|
|
@ -148,32 +149,34 @@ class _ChattersListState extends State<ChattersList> {
|
|||
),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => InkWell(
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => InkWell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
|
||||
child: Text(users[index]),
|
||||
onLongPress: () async {
|
||||
final userInfo = await context
|
||||
.read<TwitchApi>()
|
||||
.getUser(headers: context.read<AuthStore>().headersTwitch, userLogin: users[index]);
|
||||
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => ChatUserModal(
|
||||
chatStore: widget.chatStore,
|
||||
username: userInfo.login,
|
||||
userId: userInfo.id,
|
||||
displayName: userInfo.displayName,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
childCount: users.length,
|
||||
onLongPress: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
final userInfo = await context
|
||||
.read<TwitchApi>()
|
||||
.getUser(headers: context.read<AuthStore>().headersTwitch, userLogin: users[index]);
|
||||
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => ChatUserModal(
|
||||
chatStore: widget.chatStore,
|
||||
username: userInfo.login,
|
||||
userId: userInfo.id,
|
||||
displayName: userInfo.displayName,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
childCount: users.length,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/screens/channel/chat/widgets/chat_message.dart';
|
||||
import 'package:frosty/screens/channel/stores/chat_store.dart';
|
||||
import 'package:frosty/widgets/block_report_modal.dart';
|
||||
|
|
@ -30,6 +31,7 @@ class _ChatUserModalState extends State<ChatUserModal> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userMessages = widget.chatStore.messages.reversed.where((message) => message.user == widget.username).toList();
|
||||
final name = regexEnglish.hasMatch(widget.displayName) ? widget.displayName : '${widget.displayName} (${widget.username})';
|
||||
|
||||
return FrostyModal(
|
||||
child: SizedBox(
|
||||
|
|
@ -44,12 +46,12 @@ class _ChatUserModalState extends State<ChatUserModal> {
|
|||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
Flexible(
|
||||
child: Tooltip(
|
||||
preferBelow: false,
|
||||
message: widget.displayName,
|
||||
message: name,
|
||||
child: Text(
|
||||
widget.displayName,
|
||||
name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
|
|
@ -68,16 +70,20 @@ class _ChatUserModalState extends State<ChatUserModal> {
|
|||
child: const Text('Reply'),
|
||||
)
|
||||
: null,
|
||||
onTap: () => showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => BlockReportModal(
|
||||
authStore: widget.chatStore.auth,
|
||||
name: widget.displayName,
|
||||
userLogin: widget.username,
|
||||
userId: widget.userId,
|
||||
),
|
||||
),
|
||||
onLongPress: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => BlockReportModal(
|
||||
authStore: widget.chatStore.auth,
|
||||
name: name,
|
||||
userLogin: widget.username,
|
||||
userId: widget.userId,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SectionHeader(
|
||||
'Recent Messages',
|
||||
|
|
@ -93,6 +99,8 @@ class _ChatUserModalState extends State<ChatUserModal> {
|
|||
reverse: true,
|
||||
itemBuilder: (context, index) => InkWell(
|
||||
onLongPress: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
await Clipboard.setData(ClipboardData(text: userMessages[index].message));
|
||||
|
||||
if (!mounted) return;
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ abstract class VideoStoreBase with Store {
|
|||
@action
|
||||
Future<void> handleToggleOverlay() async {
|
||||
if (settingsStore.toggleableOverlay) {
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
settingsStore.showOverlay = !settingsStore.showOverlay;
|
||||
|
||||
await controller?.loadUrl(videoUrl);
|
||||
|
|
|
|||
|
|
@ -13,12 +13,6 @@ class SearchResultsCategories extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
final artWidth = (size.width * pixelRatio) ~/ 3;
|
||||
final artHeight = (artWidth * (4 / 3)).toInt();
|
||||
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
final future = searchStore.categoryFuture;
|
||||
|
|
@ -64,8 +58,6 @@ class SearchResultsCategories extends StatelessWidget {
|
|||
(category) => GridTile(
|
||||
child: CategoryCard(
|
||||
category: category,
|
||||
width: artWidth,
|
||||
height: artHeight,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/models/channel.dart';
|
||||
|
|
@ -110,16 +111,20 @@ class _SearchResultsChannelsState extends State<SearchResultsChannels> {
|
|||
),
|
||||
),
|
||||
),
|
||||
onLongPress: () => showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => BlockReportModal(
|
||||
authStore: widget.searchStore.authStore,
|
||||
name: displayName,
|
||||
userLogin: channel.broadcasterLogin,
|
||||
userId: channel.id,
|
||||
),
|
||||
),
|
||||
onLongPress: () {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => BlockReportModal(
|
||||
authStore: widget.searchStore.authStore,
|
||||
name: displayName,
|
||||
userLogin: channel.broadcasterLogin,
|
||||
userId: channel.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -26,12 +26,6 @@ class _CategoriesState extends State<Categories> with AutomaticKeepAliveClientMi
|
|||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
final size = MediaQuery.of(context).size;
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
final artWidth = (size.width * pixelRatio) ~/ 3;
|
||||
final artHeight = (artWidth * (4 / 3)).toInt();
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
|
|
@ -68,8 +62,6 @@ class _CategoriesState extends State<Categories> with AutomaticKeepAliveClientMi
|
|||
}
|
||||
return CategoryCard(
|
||||
category: _categoriesStore.categories[index],
|
||||
width: artWidth,
|
||||
height: artHeight,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -27,9 +27,6 @@ class _CategoryStreamsState extends State<CategoryStreams> {
|
|||
final artWidth = (size.width * pixelRatio).toInt();
|
||||
final artHeight = (artWidth * (4 / 3)).toInt();
|
||||
|
||||
final thumbnailWidth = (size.width * pixelRatio) ~/ 3;
|
||||
final thumbnailHeight = (thumbnailWidth * (9 / 16)).toInt();
|
||||
|
||||
return Scaffold(
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
|
|
@ -99,8 +96,6 @@ class _CategoryStreamsState extends State<CategoryStreams> {
|
|||
builder: (context) => StreamCard(
|
||||
listStore: widget.listStore,
|
||||
streamInfo: widget.listStore.streams[index],
|
||||
width: thumbnailWidth,
|
||||
height: thumbnailHeight,
|
||||
showUptime: context.read<SettingsStore>().showThumbnailUptime,
|
||||
showThumbnail: context.read<SettingsStore>().showThumbnails,
|
||||
large: context.read<SettingsStore>().largeStreamCard,
|
||||
|
|
|
|||
|
|
@ -11,14 +11,10 @@ import 'package:provider/provider.dart';
|
|||
/// A tappable card widget that displays a category's box art and name.
|
||||
class CategoryCard extends StatefulWidget {
|
||||
final CategoryTwitch category;
|
||||
final int width;
|
||||
final int height;
|
||||
|
||||
const CategoryCard({
|
||||
Key? key,
|
||||
required this.category,
|
||||
required this.width,
|
||||
required this.height,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -33,37 +29,43 @@ class _CategoryCardState extends State<CategoryCard> with SingleTickerProviderSt
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CategoryStreams(
|
||||
listStore: ListStore(
|
||||
twitchApi: context.read<TwitchApi>(),
|
||||
authStore: context.read<AuthStore>(),
|
||||
listType: ListType.category,
|
||||
categoryInfo: widget.category,
|
||||
),
|
||||
final size = MediaQuery.of(context).size;
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
final artWidth = (size.width * pixelRatio) ~/ 3;
|
||||
final artHeight = (artWidth * (4 / 3)).toInt();
|
||||
|
||||
return InkWell(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CategoryStreams(
|
||||
listStore: ListStore(
|
||||
twitchApi: context.read<TwitchApi>(),
|
||||
authStore: context.read<AuthStore>(),
|
||||
listType: ListType.category,
|
||||
categoryInfo: widget.category,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTapDown: (_) => _animationController.animateTo(
|
||||
_animationController.upperBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
),
|
||||
onTapUp: (_) => _animationController.animateTo(
|
||||
_animationController.lowerBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
onTapCancel: () => _animationController.animateTo(
|
||||
_animationController.lowerBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
),
|
||||
onTapDown: (_) => _animationController.animateTo(
|
||||
_animationController.upperBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
),
|
||||
onTapUp: (_) => _animationController.animateTo(
|
||||
_animationController.lowerBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
onTapCancel: () => _animationController.animateTo(
|
||||
_animationController.lowerBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
child: AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
|
||||
child: Column(
|
||||
|
|
@ -74,8 +76,8 @@ class _CategoryCardState extends State<CategoryCard> with SingleTickerProviderSt
|
|||
child: AspectRatio(
|
||||
aspectRatio: 3 / 4,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: widget.category.boxArtUrl
|
||||
.replaceRange(widget.category.boxArtUrl.lastIndexOf('-') + 1, null, '${widget.width}x${widget.height}.jpg'),
|
||||
imageUrl:
|
||||
widget.category.boxArtUrl.replaceRange(widget.category.boxArtUrl.lastIndexOf('-') + 1, null, '${artWidth}x$artHeight.jpg'),
|
||||
placeholder: (context, url) => const LoadingIndicator(),
|
||||
),
|
||||
),
|
||||
|
|
@ -95,10 +97,10 @@ class _CategoryCardState extends State<CategoryCard> with SingleTickerProviderSt
|
|||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
builder: (context, child) => Transform.scale(
|
||||
scale: 1 - _animationController.value,
|
||||
child: child,
|
||||
builder: (context, child) => Transform.scale(
|
||||
scale: 1 - _animationController.value,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
|
|
@ -17,8 +20,6 @@ import 'package:provider/provider.dart';
|
|||
class StreamCard extends StatefulWidget {
|
||||
final ListStore listStore;
|
||||
final StreamTwitch streamInfo;
|
||||
final int width;
|
||||
final int height;
|
||||
final bool showUptime;
|
||||
final bool showThumbnail;
|
||||
final bool large;
|
||||
|
|
@ -28,8 +29,6 @@ class StreamCard extends StatefulWidget {
|
|||
Key? key,
|
||||
required this.listStore,
|
||||
required this.streamInfo,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.showUptime,
|
||||
required this.showThumbnail,
|
||||
required this.large,
|
||||
|
|
@ -51,10 +50,18 @@ class _StreamCardState extends State<StreamCard> with SingleTickerProviderStateM
|
|||
final time = DateTime.now();
|
||||
final cacheUrlExtension = time.day.toString() + time.hour.toString() + (time.minute ~/ 5).toString();
|
||||
|
||||
final size = MediaQuery.of(context).size;
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
// Calculate the width and height of the thumbnail based on the device width and the stream card size setting.
|
||||
// Constraint the resolution to 1920x1080 since that's the max resolution of the Twitch API.
|
||||
final thumbnailWidth = min((size.width * pixelRatio) ~/ (widget.large ? 1 : 3), 1920);
|
||||
final thumbnailHeight = min((thumbnailWidth * (9 / 16)).toInt(), 1080);
|
||||
|
||||
final image = AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: widget.streamInfo.thumbnailUrl.replaceFirst('-{width}x{height}', '-${widget.width}x${widget.height}') + cacheUrlExtension,
|
||||
imageUrl: widget.streamInfo.thumbnailUrl.replaceFirst('-{width}x{height}', '-${thumbnailWidth}x$thumbnailHeight') + cacheUrlExtension,
|
||||
placeholder: (context, url) => const LoadingIndicator(),
|
||||
useOldImageOnUrlChange: true,
|
||||
),
|
||||
|
|
@ -208,20 +215,21 @@ class _StreamCardState extends State<StreamCard> with SingleTickerProviderStateM
|
|||
),
|
||||
);
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => VideoChat(
|
||||
userId: widget.streamInfo.userId,
|
||||
userName: widget.streamInfo.userName,
|
||||
userLogin: widget.streamInfo.userLogin,
|
||||
),
|
||||
return InkWell(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => VideoChat(
|
||||
userId: widget.streamInfo.userId,
|
||||
userName: widget.streamInfo.userName,
|
||||
userLogin: widget.streamInfo.userLogin,
|
||||
),
|
||||
),
|
||||
onLongPress: () => showModalBottomSheet(
|
||||
),
|
||||
onLongPress: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (context) => BlockReportModal(
|
||||
|
|
@ -230,22 +238,25 @@ class _StreamCardState extends State<StreamCard> with SingleTickerProviderStateM
|
|||
userLogin: widget.streamInfo.userLogin,
|
||||
userId: widget.streamInfo.userId,
|
||||
),
|
||||
),
|
||||
onTapDown: (_) => _animationController.animateTo(
|
||||
_animationController.upperBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
),
|
||||
onTapUp: (_) => _animationController.animateTo(
|
||||
_animationController.lowerBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
onTapCancel: () => _animationController.animateTo(
|
||||
_animationController.lowerBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
);
|
||||
},
|
||||
onTapDown: (_) => _animationController.animateTo(
|
||||
_animationController.upperBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
),
|
||||
onTapUp: (_) => _animationController.animateTo(
|
||||
_animationController.lowerBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
onTapCancel: () => _animationController.animateTo(
|
||||
_animationController.lowerBound,
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
),
|
||||
child: AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: widget.showThumbnail ? 15.0 : 5.0),
|
||||
child: widget.large
|
||||
|
|
@ -267,13 +278,11 @@ class _StreamCardState extends State<StreamCard> with SingleTickerProviderStateM
|
|||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
builder: (context, child) {
|
||||
return Transform.scale(
|
||||
builder: (context, child) => Transform.scale(
|
||||
scale: 1 - _animationController.value,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
|
@ -38,12 +36,11 @@ class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClient
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final size = MediaQuery.of(context).size;
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
await _listStore.refreshStreams();
|
||||
|
||||
if (_listStore.error != null) {
|
||||
|
|
@ -58,11 +55,6 @@ class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClient
|
|||
},
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
// Calculate the width and height of the thumbnail based on the device width and the stream card size setting.
|
||||
// Constraint the resolution to 1920x1080 since that's the max resolution of the Twitch API.
|
||||
final thumbnailWidth = min((size.width * pixelRatio) ~/ (context.read<SettingsStore>().largeStreamCard ? 1 : 3), 1920);
|
||||
final thumbnailHeight = min((thumbnailWidth * (9 / 16)).toInt(), 1080);
|
||||
|
||||
if (_listStore.streams.isEmpty && _listStore.isLoading && _listStore.error == null) {
|
||||
return const LoadingIndicator(subtitle: Text('Loading streams...'));
|
||||
}
|
||||
|
|
@ -81,8 +73,6 @@ class _StreamsListState extends State<StreamsList> with AutomaticKeepAliveClient
|
|||
builder: (context) => StreamCard(
|
||||
listStore: _listStore,
|
||||
streamInfo: _listStore.streams[index],
|
||||
width: thumbnailWidth,
|
||||
height: thumbnailHeight,
|
||||
showThumbnail: context.read<SettingsStore>().showThumbnails,
|
||||
large: context.read<SettingsStore>().largeStreamCard,
|
||||
showUptime: context.read<SettingsStore>().showThumbnailUptime,
|
||||
|
|
|
|||
|
|
@ -16,20 +16,22 @@ class FrostyModal extends StatelessWidget {
|
|||
topRight: Radius.circular(20.0),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FractionallySizedBox(
|
||||
widthFactor: 0.25,
|
||||
child: SizedBox(
|
||||
child: Divider(
|
||||
height: 25.0,
|
||||
thickness: 3.0,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FractionallySizedBox(
|
||||
widthFactor: 0.25,
|
||||
child: SizedBox(
|
||||
child: Divider(
|
||||
height: 25.0,
|
||||
thickness: 3.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SafeArea(child: child),
|
||||
],
|
||||
Material(child: child),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue