Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21d388fa02 | ||
|
|
2008d2c688 | ||
|
|
56961c7010 | ||
|
|
29c84858a0 | ||
|
|
a2d30ec086 | ||
|
|
60a97e6a24 |
22 changed files with 270 additions and 416 deletions
|
|
@ -123,7 +123,6 @@ class IRCMessage {
|
|||
void Function()? onTapName,
|
||||
void Function(String)? onTapPingedUser,
|
||||
bool showMessage = true,
|
||||
bool useReadableColors = false,
|
||||
Map<String, UserTwitch>? channelIdToUserTwitch,
|
||||
TimestampType timestamp = TimestampType.disabled,
|
||||
}) {
|
||||
|
|
@ -370,20 +369,18 @@ class IRCMessage {
|
|||
int.parse((tags['color'] ?? '#868686').replaceFirst('#', '0xFF')),
|
||||
);
|
||||
|
||||
if (useReadableColors) {
|
||||
final hsl = HSLColor.fromColor(color);
|
||||
if (isLightTheme == true) {
|
||||
if (hsl.lightness >= 0.5) {
|
||||
color = hsl
|
||||
.withLightness(hsl.lightness + ((0 - hsl.lightness) * 0.5))
|
||||
.toColor();
|
||||
}
|
||||
} else {
|
||||
if (hsl.lightness <= 0.5) {
|
||||
color = hsl
|
||||
.withLightness(hsl.lightness + ((1 - hsl.lightness) * 0.5))
|
||||
.toColor();
|
||||
}
|
||||
final hsl = HSLColor.fromColor(color);
|
||||
if (isLightTheme == true) {
|
||||
if (hsl.lightness >= 0.5) {
|
||||
color = hsl
|
||||
.withLightness(hsl.lightness + ((0 - hsl.lightness) * 0.5))
|
||||
.toColor();
|
||||
}
|
||||
} else {
|
||||
if (hsl.lightness <= 0.5) {
|
||||
color = hsl
|
||||
.withLightness(hsl.lightness + ((1 - hsl.lightness) * 0.5))
|
||||
.toColor();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ abstract class ChatStoreBase with Store {
|
|||
required this.displayName,
|
||||
}) {
|
||||
// Enable wakelock to prevent the chat from sleeping.
|
||||
if (settings.chatOnlyPreventSleep) WakelockPlus.enable();
|
||||
WakelockPlus.enable();
|
||||
|
||||
// Create a reaction that will reconnect to chat when logging in or out.
|
||||
// Closing the channel will trigger a reconnect with the new credentials.
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ class ChatBottomBar extends StatelessWidget {
|
|||
assetsStore: chatStore.assetsStore,
|
||||
emoteScale: chatStore.settings.emoteScale,
|
||||
badgeScale: chatStore.settings.badgeScale,
|
||||
useReadableColors: chatStore.settings.useReadableColors,
|
||||
launchExternal: chatStore.settings.launchUrlExternal,
|
||||
timestamp: chatStore.settings.timestampType,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ class ChatMessage extends StatelessWidget {
|
|||
assetsStore: chatStore.assetsStore,
|
||||
emoteScale: chatStore.settings.emoteScale,
|
||||
badgeScale: chatStore.settings.badgeScale,
|
||||
useReadableColors: chatStore.settings.useReadableColors,
|
||||
launchExternal: chatStore.settings.launchUrlExternal,
|
||||
timestamp: chatStore.settings.timestampType,
|
||||
channelIdToUserTwitch:
|
||||
|
|
@ -172,7 +171,6 @@ class ChatMessage extends StatelessWidget {
|
|||
assetsStore: chatStore.assetsStore,
|
||||
emoteScale: chatStore.settings.emoteScale,
|
||||
badgeScale: chatStore.settings.badgeScale,
|
||||
useReadableColors: chatStore.settings.useReadableColors,
|
||||
launchExternal: chatStore.settings.launchUrlExternal,
|
||||
timestamp: chatStore.settings.timestampType,
|
||||
channelIdToUserTwitch:
|
||||
|
|
@ -305,7 +303,6 @@ class ChatMessage extends StatelessWidget {
|
|||
emoteScale: chatStore.settings.emoteScale,
|
||||
badgeScale: chatStore.settings.badgeScale,
|
||||
showMessage: chatStore.settings.showDeletedMessages,
|
||||
useReadableColors: chatStore.settings.useReadableColors,
|
||||
launchExternal: chatStore.settings.launchUrlExternal,
|
||||
timestamp: chatStore.settings.timestampType,
|
||||
channelIdToUserTwitch:
|
||||
|
|
@ -399,8 +396,6 @@ class ChatMessage extends StatelessWidget {
|
|||
assetsStore: chatStore.assetsStore,
|
||||
emoteScale: chatStore.settings.emoteScale,
|
||||
badgeScale: chatStore.settings.badgeScale,
|
||||
useReadableColors:
|
||||
chatStore.settings.useReadableColors,
|
||||
launchExternal: chatStore.settings.launchUrlExternal,
|
||||
timestamp: chatStore.settings.timestampType,
|
||||
channelIdToUserTwitch:
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class VideoBar extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Tooltip(
|
||||
message: 'Streamer: $streamerName',
|
||||
message: streamerName,
|
||||
showDuration: const Duration(seconds: 3),
|
||||
child: Text(
|
||||
streamerName,
|
||||
|
|
@ -62,7 +62,7 @@ class VideoBar extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 2),
|
||||
Tooltip(
|
||||
message: 'Title: ${streamInfo.title.trim()}',
|
||||
message: streamTitle,
|
||||
showDuration: const Duration(seconds: 5),
|
||||
child: Text(
|
||||
streamTitle,
|
||||
|
|
@ -91,8 +91,7 @@ class VideoBar extends StatelessWidget {
|
|||
)
|
||||
: null,
|
||||
child: Tooltip(
|
||||
message:
|
||||
'Category: ${streamInfo.gameName.isNotEmpty ? streamInfo.gameName : 'None'}',
|
||||
message: category,
|
||||
showDuration: const Duration(seconds: 3),
|
||||
child: Text(
|
||||
category,
|
||||
|
|
|
|||
|
|
@ -352,9 +352,7 @@ class VideoOverlay extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (orientation == Orientation.landscape &&
|
||||
settingsStore.showLatency)
|
||||
latencyTooltip,
|
||||
if (orientation == Orientation.landscape) latencyTooltip,
|
||||
Tooltip(
|
||||
message: 'Enter picture-in-picture',
|
||||
preferBelow: false,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
|
@ -5,11 +7,12 @@ import 'package:frosty/screens/home/home_store.dart';
|
|||
import 'package:frosty/screens/home/search/search.dart';
|
||||
import 'package:frosty/screens/home/stream_list/stream_list_store.dart';
|
||||
import 'package:frosty/screens/home/stream_list/streams_list.dart';
|
||||
import 'package:frosty/screens/home/top/top.dart';
|
||||
import 'package:frosty/screens/home/top/categories/categories.dart';
|
||||
import 'package:frosty/screens/settings/settings.dart';
|
||||
import 'package:frosty/screens/settings/stores/auth_store.dart';
|
||||
import 'package:frosty/screens/settings/stores/settings_store.dart';
|
||||
import 'package:frosty/screens/settings/widgets/release_notes.dart';
|
||||
import 'package:frosty/widgets/app_bar.dart';
|
||||
import 'package:frosty/widgets/profile_picture.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
@ -63,14 +66,16 @@ class _HomeState extends State<Home> {
|
|||
return GestureDetector(
|
||||
onTap: FocusScope.of(context).unfocus,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
shape: const Border(),
|
||||
extendBodyBehindAppBar: true,
|
||||
extendBody: true,
|
||||
appBar: FrostyAppBar(
|
||||
showBackButton: false,
|
||||
title: Observer(
|
||||
builder: (_) {
|
||||
final titles = [
|
||||
if (_authStore.isLoggedIn) 'Following',
|
||||
'Top',
|
||||
'Top Streams',
|
||||
'Top Categories',
|
||||
'Search',
|
||||
];
|
||||
|
||||
|
|
@ -100,6 +105,8 @@ class _HomeState extends State<Home> {
|
|||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Observer(
|
||||
builder: (_) => IndexedStack(
|
||||
index: _homeStore.selectedIndex,
|
||||
|
|
@ -108,9 +115,15 @@ class _HomeState extends State<Home> {
|
|||
StreamsList(
|
||||
listType: ListType.followed,
|
||||
scrollController: _homeStore.followedScrollController,
|
||||
addTopPadding: true,
|
||||
),
|
||||
TopSection(
|
||||
homeStore: _homeStore,
|
||||
StreamsList(
|
||||
listType: ListType.top,
|
||||
scrollController: _homeStore.topSectionScrollControllers[0],
|
||||
addTopPadding: true,
|
||||
),
|
||||
Categories(
|
||||
scrollController: _homeStore.topSectionScrollControllers[1],
|
||||
),
|
||||
Search(
|
||||
scrollController: _homeStore.searchScrollController,
|
||||
|
|
@ -119,12 +132,15 @@ class _HomeState extends State<Home> {
|
|||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Divider(),
|
||||
Observer(
|
||||
bottomNavigationBar: ClipRRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 16, sigmaY: 16),
|
||||
child: Observer(
|
||||
builder: (_) => NavigationBar(
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withValues(alpha: 0.6),
|
||||
destinations: [
|
||||
if (_authStore.isLoggedIn)
|
||||
const NavigationDestination(
|
||||
|
|
@ -140,8 +156,16 @@ class _HomeState extends State<Home> {
|
|||
Icons.arrow_upward_rounded,
|
||||
),
|
||||
selectedIcon: Icon(Icons.arrow_upward_rounded),
|
||||
label: 'Top',
|
||||
tooltip: 'Top',
|
||||
label: 'Top streams',
|
||||
tooltip: 'Top streams',
|
||||
),
|
||||
const NavigationDestination(
|
||||
icon: Icon(
|
||||
Icons.gamepad_outlined,
|
||||
),
|
||||
selectedIcon: Icon(Icons.gamepad_rounded),
|
||||
label: 'Top categories',
|
||||
tooltip: 'Top categories',
|
||||
),
|
||||
const NavigationDestination(
|
||||
icon: Icon(
|
||||
|
|
@ -156,7 +180,7 @@ class _HomeState extends State<Home> {
|
|||
onDestinationSelected: _homeStore.handleTap,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -33,126 +33,133 @@ class _SearchState extends State<Search> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: TextField(
|
||||
controller: _searchStore.textEditingController,
|
||||
focusNode: _searchStore.textFieldFocusNode,
|
||||
autocorrect: false,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.search_rounded),
|
||||
hintText: 'Find a channel or category',
|
||||
suffixIcon: _searchStore.textFieldFocusNode.hasFocus ||
|
||||
_searchStore.searchText.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
tooltip: _searchStore.searchText.isEmpty
|
||||
? 'Cancel'
|
||||
: 'Clear',
|
||||
onPressed: () {
|
||||
if (_searchStore.searchText.isEmpty) {
|
||||
_searchStore.textFieldFocusNode.unfocus();
|
||||
}
|
||||
_searchStore.textEditingController.clear();
|
||||
},
|
||||
)
|
||||
: null,
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: TextField(
|
||||
controller: _searchStore.textEditingController,
|
||||
focusNode: _searchStore.textFieldFocusNode,
|
||||
autocorrect: false,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.search_rounded),
|
||||
hintText: 'Find a channel or category',
|
||||
suffixIcon: _searchStore.textFieldFocusNode.hasFocus ||
|
||||
_searchStore.searchText.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
tooltip: _searchStore.searchText.isEmpty
|
||||
? 'Cancel'
|
||||
: 'Clear',
|
||||
onPressed: () {
|
||||
if (_searchStore.searchText.isEmpty) {
|
||||
_searchStore.textFieldFocusNode.unfocus();
|
||||
}
|
||||
_searchStore.textEditingController.clear();
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
onSubmitted: _searchStore.handleQuery,
|
||||
),
|
||||
onSubmitted: _searchStore.handleQuery,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
AnimatedScrollBorder(scrollController: widget.scrollController),
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: widget.scrollController,
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
if (_searchStore.textEditingController.text.isEmpty) {
|
||||
if (_searchStore.searchHistory.isEmpty) {
|
||||
return const AlertMessage(message: 'No recent searches');
|
||||
}
|
||||
);
|
||||
},
|
||||
),
|
||||
AnimatedScrollBorder(scrollController: widget.scrollController),
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: widget.scrollController,
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
if (_searchStore.textEditingController.text.isEmpty) {
|
||||
if (_searchStore.searchHistory.isEmpty) {
|
||||
return const AlertMessage(
|
||||
message: 'No recent searches',
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SectionHeader(
|
||||
'History',
|
||||
padding: EdgeInsets.zero,
|
||||
isFirst: true,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _searchStore.searchHistory.clear,
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SectionHeader(
|
||||
'History',
|
||||
padding: EdgeInsets.zero,
|
||||
isFirst: true,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _searchStore.searchHistory.clear,
|
||||
child: const Text('Clear'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
controller: widget.scrollController,
|
||||
children: _searchStore.searchHistory
|
||||
.mapIndexed(
|
||||
(index, searchTerm) => ListTile(
|
||||
leading: const Icon(Icons.history_rounded),
|
||||
title: Text(searchTerm),
|
||||
onTap: () {
|
||||
_searchStore.textEditingController.text =
|
||||
searchTerm;
|
||||
_searchStore.handleQuery(searchTerm);
|
||||
_searchStore
|
||||
.textEditingController.selection =
|
||||
TextSelection.fromPosition(
|
||||
TextPosition(
|
||||
offset: _searchStore
|
||||
.textEditingController
|
||||
.text
|
||||
.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return CustomScrollView(
|
||||
controller: widget.scrollController,
|
||||
slivers: [
|
||||
const SliverToBoxAdapter(
|
||||
child: SectionHeader(
|
||||
'Channels',
|
||||
isFirst: true,
|
||||
padding: EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
controller: widget.scrollController,
|
||||
children: _searchStore.searchHistory
|
||||
.mapIndexed(
|
||||
(index, searchTerm) => ListTile(
|
||||
leading: const Icon(Icons.history_rounded),
|
||||
title: Text(searchTerm),
|
||||
onTap: () {
|
||||
_searchStore.textEditingController.text =
|
||||
searchTerm;
|
||||
_searchStore.handleQuery(searchTerm);
|
||||
_searchStore.textEditingController
|
||||
.selection = TextSelection.fromPosition(
|
||||
TextPosition(
|
||||
offset: _searchStore
|
||||
.textEditingController.text.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
SearchResultsChannels(
|
||||
searchStore: _searchStore,
|
||||
query: _searchStore.searchText,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SectionHeader(
|
||||
'Categories',
|
||||
),
|
||||
),
|
||||
SearchResultsCategories(searchStore: _searchStore),
|
||||
],
|
||||
);
|
||||
}
|
||||
return CustomScrollView(
|
||||
controller: widget.scrollController,
|
||||
slivers: [
|
||||
const SliverToBoxAdapter(
|
||||
child: SectionHeader(
|
||||
'Channels',
|
||||
isFirst: true,
|
||||
padding: EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
),
|
||||
),
|
||||
SearchResultsChannels(
|
||||
searchStore: _searchStore,
|
||||
query: _searchStore.searchText,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SectionHeader(
|
||||
'Categories',
|
||||
),
|
||||
),
|
||||
SearchResultsCategories(searchStore: _searchStore),
|
||||
],
|
||||
);
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ class StreamCard extends StatelessWidget {
|
|||
final streamerName =
|
||||
getReadableName(streamInfo.userName, streamInfo.userLogin);
|
||||
|
||||
final streamTitle = streamInfo.title.trim();
|
||||
final category =
|
||||
streamInfo.gameName.isNotEmpty ? streamInfo.gameName : 'No Category';
|
||||
|
||||
const subFontSize = 14.0;
|
||||
|
||||
final fontColor = DefaultTextStyle.of(context).style.color;
|
||||
|
|
@ -126,7 +130,7 @@ class StreamCard extends StatelessWidget {
|
|||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Tooltip(
|
||||
message: 'Streamer: $streamerName',
|
||||
message: streamerName,
|
||||
preferBelow: false,
|
||||
child: Text(
|
||||
streamerName,
|
||||
|
|
@ -143,10 +147,10 @@ class StreamCard extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 2),
|
||||
Tooltip(
|
||||
message: 'Title: ${streamInfo.title.trim()}',
|
||||
message: streamTitle,
|
||||
preferBelow: false,
|
||||
child: Text(
|
||||
streamInfo.title.trim(),
|
||||
streamTitle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: subFontSize,
|
||||
|
|
@ -168,13 +172,10 @@ class StreamCard extends StatelessWidget {
|
|||
)
|
||||
: null,
|
||||
child: Tooltip(
|
||||
message:
|
||||
'Category: ${streamInfo.gameName.isNotEmpty ? streamInfo.gameName : 'None'}',
|
||||
message: category,
|
||||
preferBelow: false,
|
||||
child: Text(
|
||||
streamInfo.gameName.isNotEmpty
|
||||
? streamInfo.gameName
|
||||
: 'No Category',
|
||||
category,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: subFontSize,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import 'package:frosty/screens/home/top/categories/category_card.dart';
|
|||
import 'package:frosty/screens/settings/stores/auth_store.dart';
|
||||
import 'package:frosty/screens/settings/stores/settings_store.dart';
|
||||
import 'package:frosty/widgets/alert_message.dart';
|
||||
import 'package:frosty/widgets/animated_scroll_border.dart';
|
||||
import 'package:frosty/widgets/loading_indicator.dart';
|
||||
import 'package:frosty/widgets/scroll_to_top_button.dart';
|
||||
import 'package:frosty/widgets/section_header.dart';
|
||||
|
|
@ -27,6 +26,7 @@ class StreamsList extends StatefulWidget {
|
|||
final ScrollController? scrollController;
|
||||
|
||||
final bool showJumpButton;
|
||||
final bool addTopPadding;
|
||||
|
||||
const StreamsList({
|
||||
super.key,
|
||||
|
|
@ -34,6 +34,7 @@ class StreamsList extends StatefulWidget {
|
|||
this.categoryId,
|
||||
this.scrollController,
|
||||
this.showJumpButton = false,
|
||||
this.addTopPadding = false,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -74,9 +75,13 @@ class _StreamsListState extends State<StreamsList>
|
|||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
final double topPadding =
|
||||
widget.addTopPadding ? MediaQuery.of(context).padding.top : 0;
|
||||
|
||||
_listStore.checkLastTimeRefreshedAndUpdate();
|
||||
|
||||
return RefreshIndicator.adaptive(
|
||||
edgeOffset: topPadding,
|
||||
onRefresh: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
|
||||
|
|
@ -130,12 +135,14 @@ class _StreamsListState extends State<StreamsList>
|
|||
|
||||
final settingsStore = context.watch<SettingsStore>();
|
||||
|
||||
final unpinnedStreams = _listStore.streams
|
||||
.where(
|
||||
(stream) =>
|
||||
!settingsStore.pinnedChannelIds.contains(stream.userId),
|
||||
)
|
||||
.toList();
|
||||
final unpinnedStreams = widget.listType == ListType.followed
|
||||
? _listStore.streams
|
||||
.where(
|
||||
(stream) =>
|
||||
!settingsStore.pinnedChannelIds.contains(stream.userId),
|
||||
)
|
||||
.toList()
|
||||
: _listStore.streams;
|
||||
|
||||
final isFollowingTab = widget.listType == ListType.followed;
|
||||
|
||||
|
|
@ -150,10 +157,6 @@ class _StreamsListState extends State<StreamsList>
|
|||
category: _listStore.categoryDetails!,
|
||||
isTappable: false,
|
||||
),
|
||||
if (_listStore.scrollController != null)
|
||||
AnimatedScrollBorder(
|
||||
scrollController: _listStore.scrollController!,
|
||||
),
|
||||
Expanded(
|
||||
child: Scrollbar(
|
||||
controller: _listStore.scrollController,
|
||||
|
|
@ -161,6 +164,12 @@ class _StreamsListState extends State<StreamsList>
|
|||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _listStore.scrollController,
|
||||
slivers: [
|
||||
if (widget.addTopPadding)
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
top: topPadding,
|
||||
),
|
||||
),
|
||||
if (isFollowingTab &&
|
||||
_listStore.pinnedStreams.isNotEmpty) ...[
|
||||
const SliverToBoxAdapter(
|
||||
|
|
@ -263,6 +272,11 @@ class _StreamsListState extends State<StreamsList>
|
|||
);
|
||||
},
|
||||
),
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class _CategoriesState extends State<Categories>
|
|||
_categoriesStore.checkLastTimeRefreshedAndUpdate();
|
||||
|
||||
return RefreshIndicator.adaptive(
|
||||
edgeOffset: MediaQuery.of(context).padding.top,
|
||||
onRefresh: () async {
|
||||
HapticFeedback.lightImpact();
|
||||
await _categoriesStore.refreshCategories();
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:frosty/screens/home/home_store.dart';
|
||||
import 'package:frosty/screens/home/stream_list/stream_list_store.dart';
|
||||
import 'package:frosty/screens/home/stream_list/streams_list.dart';
|
||||
import 'package:frosty/screens/home/top/categories/categories.dart';
|
||||
|
||||
class TopSection extends StatefulWidget {
|
||||
final HomeStore homeStore;
|
||||
|
||||
const TopSection({
|
||||
super.key,
|
||||
required this.homeStore,
|
||||
});
|
||||
|
||||
@override
|
||||
State<TopSection> createState() => _TopSectionState();
|
||||
}
|
||||
|
||||
class _TopSectionState extends State<TopSection>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final _tabBarController = TabController(length: 2, vsync: this);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabBarController.addListener(
|
||||
() => widget.homeStore.topSectionCurrentIndex = _tabBarController.index,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TabBar(
|
||||
isScrollable: true,
|
||||
controller: _tabBarController,
|
||||
tabs: const [
|
||||
Tab(
|
||||
child: Text('Streams'),
|
||||
),
|
||||
Tab(child: Text('Categories')),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabBarController,
|
||||
children: [
|
||||
StreamsList(
|
||||
listType: ListType.top,
|
||||
scrollController:
|
||||
widget.homeStore.topSectionScrollControllers[0],
|
||||
),
|
||||
Categories(
|
||||
scrollController:
|
||||
widget.homeStore.topSectionScrollControllers[1],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabBarController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,6 @@ class _ChatSettingsState extends State<ChatSettings> {
|
|||
|
||||
return Observer(
|
||||
builder: (context) => ListView(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
children: [
|
||||
const SectionHeader(
|
||||
'Message sizing',
|
||||
|
|
@ -146,14 +145,6 @@ class _ChatSettingsState extends State<ChatSettings> {
|
|||
onChanged: (newValue) => settingsStore.fontSize = newValue,
|
||||
),
|
||||
const SectionHeader('Message appearance'),
|
||||
SettingsListSwitch(
|
||||
title: 'Use readable name colors',
|
||||
subtitle: const Text(
|
||||
'Adjusts the lightness value of overly bright and dark names.',
|
||||
),
|
||||
value: settingsStore.useReadableColors,
|
||||
onChanged: (newValue) => settingsStore.useReadableColors = newValue,
|
||||
),
|
||||
SettingsListSwitch(
|
||||
title: 'Show deleted messages',
|
||||
subtitle: const Text(
|
||||
|
|
@ -275,16 +266,6 @@ class _ChatSettingsState extends State<ChatSettings> {
|
|||
onChanged: (newValue) =>
|
||||
settingsStore.fullScreenChatOverlayOpacity = newValue,
|
||||
),
|
||||
const SectionHeader('Sleep'),
|
||||
SettingsListSwitch(
|
||||
title: 'Prevent sleep in chat-only mode',
|
||||
subtitle: const Text(
|
||||
'Requires restarting the chat in order to take effect.',
|
||||
),
|
||||
value: settingsStore.chatOnlyPreventSleep,
|
||||
onChanged: (newValue) =>
|
||||
settingsStore.chatOnlyPreventSleep = newValue,
|
||||
),
|
||||
const SectionHeader('Muted keywords'),
|
||||
SettingsMutedWords(settingsStore: settingsStore),
|
||||
SettingsListSwitch(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ class GeneralSettings extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (context) => ListView(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
children: [
|
||||
const SectionHeader(
|
||||
'Theme',
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class Settings extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: FrostyAppBar(
|
||||
title: const Text('Settings'),
|
||||
actions: [
|
||||
|
|
@ -53,12 +54,12 @@ class Settings extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
children: [
|
||||
const SectionHeader(
|
||||
'Profile',
|
||||
'Account',
|
||||
isFirst: true,
|
||||
),
|
||||
ProfileCard(authStore: context.read<AuthStore>()),
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
// Player defaults
|
||||
static const defaultShowVideo = true;
|
||||
static const defaultDefaultToHighestQuality = false;
|
||||
static const defaultShowLatency = true;
|
||||
static const defaultUseEnhancedRendering = false;
|
||||
|
||||
// Overlay defaults
|
||||
|
|
@ -80,10 +79,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
@observable
|
||||
var defaultToHighestQuality = defaultDefaultToHighestQuality;
|
||||
|
||||
@JsonKey(defaultValue: defaultShowLatency)
|
||||
@observable
|
||||
var showLatency = defaultShowLatency;
|
||||
|
||||
@JsonKey(defaultValue: defaultUseEnhancedRendering)
|
||||
@observable
|
||||
var useEnhancedRendering = defaultUseEnhancedRendering;
|
||||
|
|
@ -105,7 +100,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
void resetVideoSettings() {
|
||||
showVideo = defaultShowVideo;
|
||||
defaultToHighestQuality = defaultDefaultToHighestQuality;
|
||||
showLatency = defaultShowLatency;
|
||||
useEnhancedRendering = defaultUseEnhancedRendering;
|
||||
|
||||
showOverlay = defaultShowOverlay;
|
||||
|
|
@ -122,7 +116,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
static const defaultFontSize = 12.0;
|
||||
|
||||
// Message appearance defaults
|
||||
static const defaultUseReadableColors = true;
|
||||
static const defaultShowDeletedMessages = false;
|
||||
static const defaultShowChatMessageDividers = false;
|
||||
static const defaultTimestampType = TimestampType.disabled;
|
||||
|
|
@ -147,9 +140,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
static const defaultChatWidth = 0.25;
|
||||
static const defaultFullScreenChatOverlayOpacity = 0.5;
|
||||
|
||||
// Sleep defaults
|
||||
static const defaultChatOnlyPreventSleep = false;
|
||||
|
||||
// mute words defaults
|
||||
static const defaultMutedWords = <String>[];
|
||||
static const defaultMatchWholeWord = true;
|
||||
|
|
@ -191,10 +181,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
var fontSize = defaultFontSize;
|
||||
|
||||
// Message appearance options
|
||||
@JsonKey(defaultValue: defaultUseReadableColors)
|
||||
@observable
|
||||
var useReadableColors = defaultUseReadableColors;
|
||||
|
||||
@JsonKey(defaultValue: defaultShowDeletedMessages)
|
||||
@observable
|
||||
var showDeletedMessages = defaultShowDeletedMessages;
|
||||
|
|
@ -262,11 +248,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
@observable
|
||||
var fullScreenChatOverlayOpacity = defaultFullScreenChatOverlayOpacity;
|
||||
|
||||
// Sleep options
|
||||
@JsonKey(defaultValue: defaultChatOnlyPreventSleep)
|
||||
@observable
|
||||
var chatOnlyPreventSleep = defaultChatOnlyPreventSleep;
|
||||
|
||||
// Autocomplete options
|
||||
@JsonKey(defaultValue: defaultAutocomplete)
|
||||
@observable
|
||||
|
|
@ -322,7 +303,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
messageSpacing = defaultMessageSpacing;
|
||||
fontSize = defaultFontSize;
|
||||
|
||||
useReadableColors = defaultUseReadableColors;
|
||||
showDeletedMessages = defaultShowDeletedMessages;
|
||||
showChatMessageDividers = defaultShowChatMessageDividers;
|
||||
timestampType = defaultTimestampType;
|
||||
|
|
@ -343,8 +323,6 @@ abstract class _SettingsStoreBase with Store {
|
|||
chatWidth = defaultChatWidth;
|
||||
fullScreenChatOverlayOpacity = defaultFullScreenChatOverlayOpacity;
|
||||
|
||||
chatOnlyPreventSleep = defaultChatOnlyPreventSleep;
|
||||
|
||||
mutedWords = defaultMutedWords;
|
||||
matchWholeWord = defaultMatchWholeWord;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ SettingsStore _$SettingsStoreFromJson(Map<String, dynamic> json) =>
|
|||
..showVideo = json['showVideo'] as bool? ?? true
|
||||
..defaultToHighestQuality =
|
||||
json['defaultToHighestQuality'] as bool? ?? false
|
||||
..showLatency = json['showLatency'] as bool? ?? true
|
||||
..useEnhancedRendering = json['useEnhancedRendering'] as bool? ?? false
|
||||
..showOverlay = json['showOverlay'] as bool? ?? true
|
||||
..toggleableOverlay = json['toggleableOverlay'] as bool? ?? false
|
||||
|
|
@ -28,7 +27,6 @@ SettingsStore _$SettingsStoreFromJson(Map<String, dynamic> json) =>
|
|||
..messageScale = (json['messageScale'] as num?)?.toDouble() ?? 1.0
|
||||
..messageSpacing = (json['messageSpacing'] as num?)?.toDouble() ?? 8.0
|
||||
..fontSize = (json['fontSize'] as num?)?.toDouble() ?? 12.0
|
||||
..useReadableColors = json['useReadableColors'] as bool? ?? true
|
||||
..showDeletedMessages = json['showDeletedMessages'] as bool? ?? false
|
||||
..showChatMessageDividers =
|
||||
json['showChatMessageDividers'] as bool? ?? false
|
||||
|
|
@ -54,7 +52,6 @@ SettingsStore _$SettingsStoreFromJson(Map<String, dynamic> json) =>
|
|||
..chatWidth = (json['chatWidth'] as num?)?.toDouble() ?? 0.25
|
||||
..fullScreenChatOverlayOpacity =
|
||||
(json['fullScreenChatOverlayOpacity'] as num?)?.toDouble() ?? 0.5
|
||||
..chatOnlyPreventSleep = json['chatOnlyPreventSleep'] as bool? ?? false
|
||||
..autocomplete = json['autocomplete'] as bool? ?? true
|
||||
..showTwitchEmotes = json['showTwitchEmotes'] as bool? ?? true
|
||||
..showTwitchBadges = json['showTwitchBadges'] as bool? ?? true
|
||||
|
|
@ -87,7 +84,6 @@ Map<String, dynamic> _$SettingsStoreToJson(SettingsStore instance) =>
|
|||
'launchUrlExternal': instance.launchUrlExternal,
|
||||
'showVideo': instance.showVideo,
|
||||
'defaultToHighestQuality': instance.defaultToHighestQuality,
|
||||
'showLatency': instance.showLatency,
|
||||
'useEnhancedRendering': instance.useEnhancedRendering,
|
||||
'showOverlay': instance.showOverlay,
|
||||
'toggleableOverlay': instance.toggleableOverlay,
|
||||
|
|
@ -97,7 +93,6 @@ Map<String, dynamic> _$SettingsStoreToJson(SettingsStore instance) =>
|
|||
'messageScale': instance.messageScale,
|
||||
'messageSpacing': instance.messageSpacing,
|
||||
'fontSize': instance.fontSize,
|
||||
'useReadableColors': instance.useReadableColors,
|
||||
'showDeletedMessages': instance.showDeletedMessages,
|
||||
'showChatMessageDividers': instance.showChatMessageDividers,
|
||||
'timestampType': _$TimestampTypeEnumMap[instance.timestampType]!,
|
||||
|
|
@ -114,7 +109,6 @@ Map<String, dynamic> _$SettingsStoreToJson(SettingsStore instance) =>
|
|||
_$LandscapeCutoutTypeEnumMap[instance.landscapeCutout]!,
|
||||
'chatWidth': instance.chatWidth,
|
||||
'fullScreenChatOverlayOpacity': instance.fullScreenChatOverlayOpacity,
|
||||
'chatOnlyPreventSleep': instance.chatOnlyPreventSleep,
|
||||
'autocomplete': instance.autocomplete,
|
||||
'showTwitchEmotes': instance.showTwitchEmotes,
|
||||
'showTwitchBadges': instance.showTwitchBadges,
|
||||
|
|
@ -271,22 +265,6 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
|
|||
});
|
||||
}
|
||||
|
||||
late final _$showLatencyAtom =
|
||||
Atom(name: '_SettingsStoreBase.showLatency', context: context);
|
||||
|
||||
@override
|
||||
bool get showLatency {
|
||||
_$showLatencyAtom.reportRead();
|
||||
return super.showLatency;
|
||||
}
|
||||
|
||||
@override
|
||||
set showLatency(bool value) {
|
||||
_$showLatencyAtom.reportWrite(value, super.showLatency, () {
|
||||
super.showLatency = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$useEnhancedRenderingAtom =
|
||||
Atom(name: '_SettingsStoreBase.useEnhancedRendering', context: context);
|
||||
|
||||
|
|
@ -432,22 +410,6 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
|
|||
});
|
||||
}
|
||||
|
||||
late final _$useReadableColorsAtom =
|
||||
Atom(name: '_SettingsStoreBase.useReadableColors', context: context);
|
||||
|
||||
@override
|
||||
bool get useReadableColors {
|
||||
_$useReadableColorsAtom.reportRead();
|
||||
return super.useReadableColors;
|
||||
}
|
||||
|
||||
@override
|
||||
set useReadableColors(bool value) {
|
||||
_$useReadableColorsAtom.reportWrite(value, super.useReadableColors, () {
|
||||
super.useReadableColors = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$showDeletedMessagesAtom =
|
||||
Atom(name: '_SettingsStoreBase.showDeletedMessages', context: context);
|
||||
|
||||
|
|
@ -696,23 +658,6 @@ mixin _$SettingsStore on _SettingsStoreBase, Store {
|
|||
});
|
||||
}
|
||||
|
||||
late final _$chatOnlyPreventSleepAtom =
|
||||
Atom(name: '_SettingsStoreBase.chatOnlyPreventSleep', context: context);
|
||||
|
||||
@override
|
||||
bool get chatOnlyPreventSleep {
|
||||
_$chatOnlyPreventSleepAtom.reportRead();
|
||||
return super.chatOnlyPreventSleep;
|
||||
}
|
||||
|
||||
@override
|
||||
set chatOnlyPreventSleep(bool value) {
|
||||
_$chatOnlyPreventSleepAtom.reportWrite(value, super.chatOnlyPreventSleep,
|
||||
() {
|
||||
super.chatOnlyPreventSleep = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$autocompleteAtom =
|
||||
Atom(name: '_SettingsStoreBase.autocomplete', context: context);
|
||||
|
||||
|
|
@ -1034,7 +979,6 @@ largeStreamCard: ${largeStreamCard},
|
|||
launchUrlExternal: ${launchUrlExternal},
|
||||
showVideo: ${showVideo},
|
||||
defaultToHighestQuality: ${defaultToHighestQuality},
|
||||
showLatency: ${showLatency},
|
||||
useEnhancedRendering: ${useEnhancedRendering},
|
||||
showOverlay: ${showOverlay},
|
||||
toggleableOverlay: ${toggleableOverlay},
|
||||
|
|
@ -1044,7 +988,6 @@ emoteScale: ${emoteScale},
|
|||
messageScale: ${messageScale},
|
||||
messageSpacing: ${messageSpacing},
|
||||
fontSize: ${fontSize},
|
||||
useReadableColors: ${useReadableColors},
|
||||
showDeletedMessages: ${showDeletedMessages},
|
||||
showChatMessageDividers: ${showChatMessageDividers},
|
||||
timestampType: ${timestampType},
|
||||
|
|
@ -1060,7 +1003,6 @@ landscapeForceVerticalChat: ${landscapeForceVerticalChat},
|
|||
landscapeCutout: ${landscapeCutout},
|
||||
chatWidth: ${chatWidth},
|
||||
fullScreenChatOverlayOpacity: ${fullScreenChatOverlayOpacity},
|
||||
chatOnlyPreventSleep: ${chatOnlyPreventSleep},
|
||||
autocomplete: ${autocomplete},
|
||||
showTwitchEmotes: ${showTwitchEmotes},
|
||||
showTwitchBadges: ${showTwitchBadges},
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ class VideoSettings extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (context) => ListView(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
children: [
|
||||
const SectionHeader(
|
||||
'Player',
|
||||
|
|
@ -35,11 +34,6 @@ class VideoSettings extends StatelessWidget {
|
|||
onChanged: (newValue) =>
|
||||
settingsStore.defaultToHighestQuality = newValue,
|
||||
),
|
||||
SettingsListSwitch(
|
||||
title: 'Show latency',
|
||||
value: settingsStore.showLatency,
|
||||
onChanged: (newValue) => settingsStore.showLatency = newValue,
|
||||
),
|
||||
if (Platform.isAndroid)
|
||||
SettingsListSwitch(
|
||||
title: 'Use enhanced rendering',
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ class SettingsTileRoute extends StatelessWidget {
|
|||
MaterialPageRoute(
|
||||
builder: (context) => useScaffold
|
||||
? Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: FrostyAppBar(title: Text(title)),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: child,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,24 +7,29 @@ class FrostyThemes {
|
|||
|
||||
const FrostyThemes({required this.colorSchemeSeed});
|
||||
|
||||
ThemeData get light => createBaseTheme(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: colorSchemeSeed),
|
||||
backgroundColor: const Color.fromRGBO(248, 248, 248, 1),
|
||||
);
|
||||
|
||||
ThemeData get dark => createBaseTheme(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: colorSchemeSeed,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
);
|
||||
|
||||
ThemeData createBaseTheme({
|
||||
required Brightness brightness,
|
||||
required Color colorSchemeSeed,
|
||||
required ColorScheme colorScheme,
|
||||
Color? backgroundColor,
|
||||
}) {
|
||||
final colorScheme = ColorScheme.fromSeed(
|
||||
seedColor: colorSchemeSeed,
|
||||
brightness: brightness,
|
||||
);
|
||||
|
||||
final borderColor = colorScheme.outlineVariant;
|
||||
|
||||
const borderWidth = 0.5;
|
||||
|
||||
return ThemeData(
|
||||
fontFamily: 'Inter',
|
||||
brightness: brightness,
|
||||
colorScheme: colorScheme,
|
||||
fontFamily: 'Inter',
|
||||
splashFactory: Platform.isIOS ? NoSplash.splashFactory : null,
|
||||
scaffoldBackgroundColor: backgroundColor,
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
|
|
@ -37,11 +42,14 @@ class FrostyThemes {
|
|||
surfaceTintColor: backgroundColor,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
backgroundColor: backgroundColor,
|
||||
backgroundColor: Colors.transparent,
|
||||
surfaceTintColor: backgroundColor,
|
||||
shape: Border(
|
||||
bottom: BorderSide(color: borderColor, width: borderWidth),
|
||||
),
|
||||
listTileTheme: ListTileThemeData(
|
||||
titleTextStyle: TextStyle(
|
||||
color: colorScheme.onSurface,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
|
|
@ -61,7 +69,6 @@ class FrostyThemes {
|
|||
),
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
elevation: 0,
|
||||
backgroundColor: backgroundColor,
|
||||
height: 64,
|
||||
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
|
||||
|
|
@ -71,6 +78,7 @@ class FrostyThemes {
|
|||
tabAlignment: TabAlignment.start,
|
||||
),
|
||||
tooltipTheme: TooltipThemeData(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
|
|
@ -133,24 +141,4 @@ class FrostyThemes {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData get light {
|
||||
final theme = createBaseTheme(
|
||||
brightness: Brightness.light,
|
||||
colorSchemeSeed: colorSchemeSeed,
|
||||
backgroundColor: const Color.fromRGBO(248, 248, 248, 1),
|
||||
);
|
||||
|
||||
return theme;
|
||||
}
|
||||
|
||||
ThemeData get dark {
|
||||
final theme = createBaseTheme(
|
||||
brightness: Brightness.dark,
|
||||
colorSchemeSeed: colorSchemeSeed,
|
||||
backgroundColor: Colors.black,
|
||||
);
|
||||
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,39 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FrostyAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final Widget title;
|
||||
final bool? centerTitle;
|
||||
final Widget? title;
|
||||
final List<Widget>? actions;
|
||||
final bool showBackButton;
|
||||
|
||||
const FrostyAppBar({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.centerTitle,
|
||||
this.title,
|
||||
this.actions,
|
||||
this.showBackButton = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
leading: IconButton(
|
||||
tooltip: 'Back',
|
||||
icon: Icon(Icons.adaptive.arrow_back_rounded),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
return ClipRRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 16, sigmaY: 16),
|
||||
child: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surface.withValues(alpha: 0.6),
|
||||
leading: showBackButton
|
||||
? IconButton(
|
||||
tooltip: 'Back',
|
||||
icon: Icon(Icons.adaptive.arrow_back_rounded),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
)
|
||||
: null,
|
||||
title: title,
|
||||
actions: actions,
|
||||
centerTitle: false,
|
||||
),
|
||||
),
|
||||
title: title,
|
||||
centerTitle: centerTitle,
|
||||
actions: actions,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,23 +17,17 @@ class SectionHeader extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!isFirst) ...[
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize ?? 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
padding: padding ??
|
||||
(isFirst
|
||||
? const EdgeInsets.fromLTRB(16, 16, 16, 8)
|
||||
: const EdgeInsets.fromLTRB(16, 32, 16, 8)),
|
||||
child: Text(
|
||||
text.toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontSize: fontSize ?? 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue