Add usernames next to non-English names
Thanks to /u/DanMizu for reporting!
This commit is contained in:
parent
2879df55d8
commit
24cdfd699a
7 changed files with 174 additions and 150 deletions
|
|
@ -21,3 +21,6 @@ const zeroWidthEmotes = [
|
|||
"cvMask",
|
||||
"cvHazmat",
|
||||
];
|
||||
|
||||
// Regex for matching strings that contain lower or upper case English characters.
|
||||
final regexEnglish = RegExp(r'[a-zA-Z]');
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
import 'package:frosty/models/badges.dart';
|
||||
import 'package:frosty/models/emotes.dart';
|
||||
|
|
@ -183,7 +184,8 @@ abstract class _ChatStoreBase with Store {
|
|||
_channel?.sink.add('PONG :tmi.twitch.tv');
|
||||
return;
|
||||
} else if (message.contains('Welcome, GLHF!')) {
|
||||
_messages.add(IRCMessage.createNotice(message: "Connected to $displayName's chat!"));
|
||||
_messages
|
||||
.add(IRCMessage.createNotice(message: "Connected to $displayName${regexEnglish.hasMatch(displayName) ? '' : ' ($channelName)'}'s chat!"));
|
||||
|
||||
// Fetch the assets used in chat including badges and emotes.
|
||||
assetsStore.assetsFuture(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/screens/channel/stores/video_store.dart';
|
||||
import 'package:frosty/screens/settings/settings.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
|
@ -15,43 +16,79 @@ class VideoOverlay extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final orientation = MediaQuery.of(context).orientation;
|
||||
|
||||
final backButton = Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: IconButton(
|
||||
tooltip: 'Back',
|
||||
icon: Icon(
|
||||
Icons.adaptive.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
);
|
||||
|
||||
final settingsButton = Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: IconButton(
|
||||
tooltip: 'Settings',
|
||||
icon: const Icon(
|
||||
Icons.settings,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () => showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.8,
|
||||
child: Settings(settingsStore: videoStore.settingsStore),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final refreshButton = IconButton(
|
||||
tooltip: 'Refresh',
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: videoStore.handleRefresh,
|
||||
);
|
||||
|
||||
final streamInfo = videoStore.streamInfo;
|
||||
|
||||
if (streamInfo == null) {
|
||||
return Stack(
|
||||
children: [
|
||||
backButton,
|
||||
settingsButton,
|
||||
Align(alignment: Alignment.bottomRight, child: refreshButton),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final streamer = Text(
|
||||
regexEnglish.hasMatch(streamInfo.userName) ? streamInfo.userName : streamInfo.userName + ' (${streamInfo.userLogin})',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
|
||||
return Observer(
|
||||
builder: (context) => Stack(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: IconButton(
|
||||
tooltip: 'Back',
|
||||
icon: Icon(
|
||||
Icons.adaptive.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: IconButton(
|
||||
tooltip: 'Settings',
|
||||
icon: const Icon(
|
||||
Icons.settings,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () => showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.8,
|
||||
child: Settings(settingsStore: videoStore.settingsStore),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
backButton,
|
||||
settingsButton,
|
||||
// Add a play button when paused for Android
|
||||
// When an ad is paused on Android there is no way to unpause, so a play button is necessary.
|
||||
if (Platform.isAndroid)
|
||||
Center(
|
||||
child: IconButton(
|
||||
tooltip: videoStore.paused ? 'Play' : 'Pause',
|
||||
iconSize: 80.0,
|
||||
iconSize: 50.0,
|
||||
icon: videoStore.paused
|
||||
? const Icon(
|
||||
Icons.play_arrow,
|
||||
|
|
@ -82,67 +119,44 @@ class VideoOverlay extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: videoStore.streamInfo != null
|
||||
? GestureDetector(
|
||||
onTap: videoStore.handleExpand,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: videoStore.settingsStore.expandInfo
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
videoStore.streamInfo!.userName,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5.0),
|
||||
Tooltip(
|
||||
message: videoStore.streamInfo!.title,
|
||||
preferBelow: false,
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Text(
|
||||
videoStore.streamInfo!.title,
|
||||
maxLines: orientation == Orientation.portrait ? 1 : 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5.0),
|
||||
Text(
|
||||
'${videoStore.streamInfo?.gameName} for ${NumberFormat().format(videoStore.streamInfo?.viewerCount)} viewers',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
videoStore.streamInfo!.userName,
|
||||
child: GestureDetector(
|
||||
onTap: videoStore.handleExpand,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: videoStore.settingsStore.expandInfo
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
streamer,
|
||||
const SizedBox(height: 5.0),
|
||||
Tooltip(
|
||||
message: videoStore.streamInfo!.title,
|
||||
preferBelow: false,
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Text(
|
||||
videoStore.streamInfo!.title,
|
||||
maxLines: orientation == Orientation.portrait ? 1 : 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: 'Refresh',
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(height: 5.0),
|
||||
Text(
|
||||
'${videoStore.streamInfo?.gameName} for ${NumberFormat().format(videoStore.streamInfo?.viewerCount)} viewers',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: streamer,
|
||||
),
|
||||
),
|
||||
onPressed: videoStore.handleRefresh,
|
||||
),
|
||||
refreshButton,
|
||||
if (Platform.isIOS && videoStore.settingsStore.pictureInPicture)
|
||||
IconButton(
|
||||
tooltip: 'Picture-in-Picture',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/screens/channel/chat/chat.dart';
|
||||
import 'package:frosty/screens/channel/stores/chat_store.dart';
|
||||
import 'package:frosty/screens/channel/stores/video_store.dart';
|
||||
|
|
@ -42,7 +43,7 @@ class _VideoChatState extends State<VideoChat> {
|
|||
|
||||
final appBar = AppBar(
|
||||
title: Text(
|
||||
chatStore.displayName,
|
||||
regexEnglish.hasMatch(chatStore.displayName) ? chatStore.displayName : chatStore.displayName + ' (${chatStore.channelName})',
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
actions: [
|
||||
|
|
|
|||
|
|
@ -48,77 +48,78 @@ class _SearchState extends State<Search> {
|
|||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
onSubmitted: (query) => setState(() => searchStore.handleQuery(query)),
|
||||
onSubmitted: searchStore.handleQuery,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
if (textEditingController.text.isEmpty || searchStore.channelFuture == null || searchStore.categoryFuture == null) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (searchStore.searchHistory.isNotEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(15.0),
|
||||
child: Text(
|
||||
'HISTORY',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
onTap: FocusManager.instance.primaryFocus?.unfocus,
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
if (textEditingController.text.isEmpty || searchStore.channelFuture == null || searchStore.categoryFuture == null) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (searchStore.searchHistory.isNotEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(15.0),
|
||||
child: Text(
|
||||
'HISTORY',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: searchStore.searchHistory
|
||||
.mapIndexed(
|
||||
(index, searchTerm) => ListTile(
|
||||
leading: const Icon(Icons.history),
|
||||
title: Text(searchTerm),
|
||||
trailing: IconButton(
|
||||
tooltip: 'Remove',
|
||||
icon: const Icon(Icons.cancel),
|
||||
onPressed: () => setState(() {
|
||||
searchStore.searchHistory.removeAt(index);
|
||||
}),
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
textEditingController.text = searchTerm;
|
||||
searchStore.handleQuery(searchTerm);
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: searchStore.searchHistory
|
||||
.mapIndexed(
|
||||
(index, searchTerm) => ListTile(
|
||||
leading: const Icon(Icons.history),
|
||||
title: Text(searchTerm),
|
||||
trailing: IconButton(
|
||||
tooltip: 'Remove',
|
||||
icon: const Icon(Icons.cancel),
|
||||
onPressed: () => setState(() {
|
||||
searchStore.searchHistory.removeAt(index);
|
||||
}),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
const SliverToBoxAdapter(
|
||||
child: SectionHeader(
|
||||
'Channels',
|
||||
padding: headerPadding,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
textEditingController.text = searchTerm;
|
||||
searchStore.handleQuery(searchTerm);
|
||||
});
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
SearchResultsChannels(
|
||||
searchStore: searchStore,
|
||||
query: textEditingController.text,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SectionHeader(
|
||||
'Categories',
|
||||
padding: headerPadding,
|
||||
),
|
||||
),
|
||||
SearchResultsCategories(searchStore: searchStore),
|
||||
],
|
||||
);
|
||||
},
|
||||
)),
|
||||
}
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
const SliverToBoxAdapter(
|
||||
child: SectionHeader(
|
||||
'Channels',
|
||||
padding: headerPadding,
|
||||
),
|
||||
),
|
||||
SearchResultsChannels(
|
||||
searchStore: searchStore,
|
||||
query: textEditingController.text,
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SectionHeader(
|
||||
'Categories',
|
||||
padding: headerPadding,
|
||||
),
|
||||
),
|
||||
SearchResultsCategories(searchStore: searchStore),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:frosty/api/bttv_api.dart';
|
|||
import 'package:frosty/api/ffz_api.dart';
|
||||
import 'package:frosty/api/seventv_api.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/models/channel.dart';
|
||||
import 'package:frosty/screens/channel/stores/chat_assets_store.dart';
|
||||
import 'package:frosty/screens/channel/stores/chat_details_store.dart';
|
||||
|
|
@ -87,7 +88,8 @@ class SearchResultsChannels extends StatelessWidget {
|
|||
delegate: SliverChildListDelegate.fixed([
|
||||
...results.map(
|
||||
(channel) => ListTile(
|
||||
title: Text(channel.displayName),
|
||||
title: Text(
|
||||
regexEnglish.hasMatch(channel.displayName) ? channel.displayName : channel.displayName + ' (${channel.broadcasterLogin})'),
|
||||
leading: ProfilePicture(userLogin: channel.broadcasterLogin),
|
||||
trailing: channel.isLive
|
||||
? ClipRRect(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:frosty/api/bttv_api.dart';
|
|||
import 'package:frosty/api/ffz_api.dart';
|
||||
import 'package:frosty/api/seventv_api.dart';
|
||||
import 'package:frosty/api/twitch_api.dart';
|
||||
import 'package:frosty/constants/constants.dart';
|
||||
import 'package:frosty/core/auth/auth_store.dart';
|
||||
import 'package:frosty/models/stream.dart';
|
||||
import 'package:frosty/screens/channel/stores/chat_assets_store.dart';
|
||||
|
|
@ -102,7 +103,7 @@ class StreamCard extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(width: 5.0),
|
||||
Text(
|
||||
streamInfo.userName,
|
||||
regexEnglish.hasMatch(streamInfo.userName) ? streamInfo.userName : streamInfo.userName + ' (${streamInfo.userLogin})',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue