Restrict width of chat view on desktop

This commit is contained in:
JHubi1 2024-06-21 20:23:55 +02:00
parent 36eaf42d1d
commit 443acf8a81
No known key found for this signature in database
GPG Key ID: 7BF82570CBBBD050
2 changed files with 648 additions and 582 deletions

View File

@ -823,14 +823,26 @@ class _MainAppState extends State<MainApp> {
: Colors.grey[900]))
: const SizedBox.shrink(),
Expanded(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Flexible(
child: Container(
constraints: const BoxConstraints(maxWidth: 1000),
child: Chat(
messages: messages,
textMessageBuilder: (p0,
{required messageWidth, required showName}) {
var white = const TextStyle(color: Colors.white);
var white =
const TextStyle(color: Colors.white);
return Padding(
padding: const EdgeInsets.only(
left: 20, right: 23, top: 17, bottom: 17),
left: 20,
right: 23,
top: 17,
bottom: 17),
child: MarkdownBody(
data: p0.text,
onTapLink: (text, href, title) async {
@ -839,25 +851,28 @@ class _MainAppState extends State<MainApp> {
var url = Uri.parse(href!);
if (await canLaunchUrl(url)) {
launchUrl(
mode: LaunchMode.inAppBrowserView,
mode: LaunchMode
.inAppBrowserView,
url);
} else {
throw Exception();
}
} catch (_) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
content: Text(
// ignore: use_build_context_synchronously
AppLocalizations.of(context)!
AppLocalizations.of(
context)!
.settingsHostInvalid(
"url")),
showCloseIcon: true));
}
},
extensionSet: md.ExtensionSet(
md.ExtensionSet.gitHubFlavored.blockSyntaxes,
md.ExtensionSet.gitHubFlavored
.blockSyntaxes,
<md.InlineSyntax>[
md.EmojiSyntax(),
...md.ExtensionSet.gitHubFlavored
@ -867,29 +882,34 @@ class _MainAppState extends State<MainApp> {
imageBuilder: (uri, title, alt) {
if (uri.isAbsolute) {
return Image.network(uri.toString(),
errorBuilder:
(context, error, stackTrace) {
errorBuilder: (context, error,
stackTrace) {
return InkWell(
onTap: () {
selectionHaptic();
ScaffoldMessenger.of(context)
ScaffoldMessenger.of(
context)
.showSnackBar(SnackBar(
content: Text(
AppLocalizations.of(
context)!
.notAValidImage),
showCloseIcon: true));
showCloseIcon:
true));
},
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(8),
BorderRadius
.circular(8),
color: Theme.of(context)
.brightness ==
Brightness.light
Brightness
.light
? Colors.white
: Colors.black),
padding: const EdgeInsets.only(
padding:
const EdgeInsets.only(
left: 100,
right: 100,
top: 32),
@ -912,14 +932,18 @@ class _MainAppState extends State<MainApp> {
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(8),
BorderRadius
.circular(8),
color: Theme.of(context)
.brightness ==
Brightness.light
? Colors.white
: Colors.black),
padding: const EdgeInsets.only(
left: 100, right: 100, top: 32),
padding:
const EdgeInsets.only(
left: 100,
right: 100,
top: 32),
child: const Image(
image: AssetImage(
"assets/logo512error.png"))));
@ -930,19 +954,22 @@ class _MainAppState extends State<MainApp> {
p: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500),
blockquoteDecoration: BoxDecoration(
fontWeight:
FontWeight.w500),
blockquoteDecoration:
BoxDecoration(
color: Colors.grey[800],
borderRadius:
BorderRadius.circular(8),
),
code: const TextStyle(
color: Colors.black,
backgroundColor: Colors.white),
backgroundColor:
Colors.white),
codeblockDecoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(8)),
borderRadius: BorderRadius.circular(
8)),
h1: white,
h2: white,
h3: white,
@ -950,10 +977,12 @@ class _MainAppState extends State<MainApp> {
h5: white,
h6: white,
listBullet: white,
horizontalRuleDecoration: BoxDecoration(
horizontalRuleDecoration:
BoxDecoration(
border: Border(
top: BorderSide(
color: Colors.grey[800]!,
color: Colors
.grey[800]!,
width: 1))),
tableBorder: TableBorder.all(
color: Colors.white),
@ -964,47 +993,39 @@ class _MainAppState extends State<MainApp> {
p: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500),
blockquoteDecoration: BoxDecoration(
fontWeight:
FontWeight.w500),
blockquoteDecoration:
BoxDecoration(
color: Colors.grey[200],
borderRadius:
BorderRadius.circular(8),
BorderRadius.circular(
8),
),
code: const TextStyle(
color: Colors.white,
backgroundColor: Colors.black),
codeblockDecoration: BoxDecoration(
color: Colors.black,
borderRadius:
BorderRadius.circular(8)),
horizontalRuleDecoration: BoxDecoration(
border: Border(
top: BorderSide(
color:
Colors.grey[200]!,
width: 1))))
codeblockDecoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(8)),
horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1))))
: MarkdownStyleSheet(
p: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500),
p: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500),
blockquoteDecoration: BoxDecoration(
color: Colors.grey[800]!,
borderRadius:
BorderRadius.circular(8),
BorderRadius.circular(
8),
),
code: const TextStyle(
color: Colors.black,
backgroundColor: Colors.white),
codeblockDecoration:
BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
code: const TextStyle(color: Colors.black, backgroundColor: Colors.white),
codeblockDecoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
horizontalRuleDecoration: BoxDecoration(border: Border(top: BorderSide(color: Colors.grey[200]!, width: 1))))));
},
imageMessageBuilder: (p0, {required messageWidth}) {
imageMessageBuilder: (p0,
{required messageWidth}) {
return SizedBox(
width: desktopLayout(context) ? 360.0 : 160.0,
child:
MarkdownBody(data: "![${p0.name}](${p0.uri})"));
width:
desktopLayout(context) ? 360.0 : 160.0,
child: MarkdownBody(
data: "![${p0.name}](${p0.uri})"));
},
disableImageGallery: true,
// keyboardDismissBehavior:
@ -1012,7 +1033,8 @@ class _MainAppState extends State<MainApp> {
emptyState: Center(
child: VisibilityDetector(
key: const Key("logoVisible"),
onVisibilityChanged: (VisibilityInfo info) {
onVisibilityChanged:
(VisibilityInfo info) {
if (settingsOpen) return;
logoVisible = info.visibleFraction > 0;
try {
@ -1021,8 +1043,10 @@ class _MainAppState extends State<MainApp> {
},
child: AnimatedOpacity(
opacity: logoVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
child: const ImageIcon(AssetImage("assets/logo512.png"),
duration:
const Duration(milliseconds: 500),
child: const ImageIcon(
AssetImage("assets/logo512.png"),
size: 44)))),
onSendPressed: (p0) {
send(p0.text, context, setState);
@ -1034,12 +1058,15 @@ class _MainAppState extends State<MainApp> {
for (var i = 0; i < messages.length; i++) {
if (messages[i].id == p1.id) {
List messageList =
(jsonDecode(jsonEncode(messages)) as List)
(jsonDecode(jsonEncode(messages))
as List)
.reversed
.toList();
bool found = false;
List index = [];
for (var j = 0; j < messageList.length; j++) {
for (var j = 0;
j < messageList.length;
j++) {
if (messageList[j]["id"] == p1.id) {
found = true;
}
@ -1048,7 +1075,9 @@ class _MainAppState extends State<MainApp> {
}
}
for (var j = 0; j < index.length; j++) {
for (var k = 0; k < messages.length; k++) {
for (var k = 0;
k < messages.length;
k++) {
if (messages[k].id == index[j]) {
messages.removeAt(k);
}
@ -1063,7 +1092,8 @@ class _MainAppState extends State<MainApp> {
onMessageLongPress: (context, p1) async {
selectionHaptic();
if (!(prefs!.getBool("enableEditing") ?? true)) {
if (!(prefs!.getBool("enableEditing") ??
true)) {
return;
}
@ -1076,7 +1106,8 @@ class _MainAppState extends State<MainApp> {
}
}
var text = (messages[index] as types.TextMessage).text;
var text =
(messages[index] as types.TextMessage).text;
var input = await prompt(
context,
title: AppLocalizations.of(context)!
@ -1098,7 +1129,8 @@ class _MainAppState extends State<MainApp> {
setState(() {});
},
onAttachmentPressed: (!multimodal)
? (prefs?.getBool("voiceModeEnabled") ?? false)
? (prefs?.getBool("voiceModeEnabled") ??
false)
? (model != null)
? () {
selectionHaptic();
@ -1123,7 +1155,8 @@ class _MainAppState extends State<MainApp> {
if (!multimodal) return;
var encoded = base64.encode(
await File(value.files.first.path!)
await File(
value.files.first.path!)
.readAsBytes());
messages.insert(
0,
@ -1146,22 +1179,25 @@ class _MainAppState extends State<MainApp> {
return Container(
width: double.infinity,
padding: const EdgeInsets.only(
left: 16, right: 16, top: 16),
left: 16,
right: 16,
top: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisSize:
MainAxisSize.min,
children: [
(prefs?.getBool(
"voiceModeEnabled") ??
false)
? SizedBox(
width: double.infinity,
child:
OutlinedButton.icon(
width: double
.infinity,
child: OutlinedButton
.icon(
onPressed:
() async {
selectionHaptic();
Navigator.of(
context)
Navigator.of(context)
.pop();
setMainState =
setState;
@ -1169,32 +1205,36 @@ class _MainAppState extends State<MainApp> {
true;
logoVisible =
false;
Navigator.of(
context)
.push(MaterialPageRoute(
builder:
(context) =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
const ScreenVoice()));
},
icon: const Icon(Icons
icon: const Icon(
Icons
.headphones_rounded),
label: Text(
AppLocalizations.of(
context)!
AppLocalizations.of(context)!
.settingsTitleVoice)))
: const SizedBox.shrink(),
: const SizedBox
.shrink(),
(prefs?.getBool(
"voiceModeEnabled") ??
false)
? const SizedBox(height: 8)
: const SizedBox.shrink(),
? const SizedBox(
height: 8)
: const SizedBox
.shrink(),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: () async {
width:
double.infinity,
child: OutlinedButton
.icon(
onPressed:
() async {
selectionHaptic();
Navigator.of(context)
Navigator.of(
context)
.pop();
final result =
await ImagePicker()
@ -1202,7 +1242,8 @@ class _MainAppState extends State<MainApp> {
source: ImageSource
.camera,
);
if (result == null) {
if (result ==
null) {
return;
}
@ -1214,40 +1255,54 @@ class _MainAppState extends State<MainApp> {
bytes);
final message =
types.ImageMessage(
author: user,
createdAt: DateTime
.now()
types
.ImageMessage(
author:
user,
createdAt:
DateTime.now()
.millisecondsSinceEpoch,
height: image.height
height: image
.height
.toDouble(),
id: const Uuid().v4(),
name: result.name,
size: bytes.length,
uri: result.path,
width: image.width
id: const Uuid()
.v4(),
name: result
.name,
size: bytes
.length,
uri: result
.path,
width: image
.width
.toDouble(),
);
messages.insert(
0, message);
setState(() {});
0,
message);
setState(
() {});
selectionHaptic();
},
icon: const Icon(Icons
icon: const Icon(
Icons
.photo_camera_rounded),
label: Text(
AppLocalizations.of(
label: Text(AppLocalizations.of(
context)!
.takeImage))),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: () async {
width:
double.infinity,
child: OutlinedButton
.icon(
onPressed:
() async {
selectionHaptic();
Navigator.of(context)
Navigator.of(
context)
.pop();
final result =
await ImagePicker()
@ -1255,7 +1310,8 @@ class _MainAppState extends State<MainApp> {
source: ImageSource
.gallery,
);
if (result == null) {
if (result ==
null) {
return;
}
@ -1267,42 +1323,55 @@ class _MainAppState extends State<MainApp> {
bytes);
final message =
types.ImageMessage(
author: user,
createdAt: DateTime
.now()
types
.ImageMessage(
author:
user,
createdAt:
DateTime.now()
.millisecondsSinceEpoch,
height: image.height
height: image
.height
.toDouble(),
id: const Uuid().v4(),
name: result.name,
size: bytes.length,
uri: result.path,
width: image.width
id: const Uuid()
.v4(),
name: result
.name,
size: bytes
.length,
uri: result
.path,
width: image
.width
.toDouble(),
);
messages.insert(
0, message);
setState(() {});
0,
message);
setState(
() {});
selectionHaptic();
},
icon: const Icon(
Icons.image_rounded),
label: Text(
AppLocalizations.of(
Icons
.image_rounded),
label: Text(AppLocalizations.of(
context)!
.uploadImage)))
]));
});
},
l10n: ChatL10nEn(
inputPlaceholder: AppLocalizations.of(context)!
inputPlaceholder:
AppLocalizations.of(context)!
.messageInputPlaceholder,
attachmentButtonAccessibilityLabel:
AppLocalizations.of(context)!.tooltipAttachment,
AppLocalizations.of(context)!
.tooltipAttachment,
sendButtonAccessibilityLabel:
AppLocalizations.of(context)!.tooltipSend),
AppLocalizations.of(context)!
.tooltipSend),
inputOptions: InputOptions(
keyboardType: TextInputType.multiline,
onTextChanged: (p0) {
@ -1317,30 +1386,29 @@ class _MainAppState extends State<MainApp> {
: SendButtonVisibilityMode.hidden),
user: user,
hideBackgroundOnEmojiMessages: false,
theme: (Theme.of(context).brightness == Brightness.light)
theme: (Theme.of(context).brightness ==
Brightness.light)
? DefaultChatTheme(
backgroundColor:
(theme ?? ThemeData()).colorScheme.surface,
primaryColor:
(theme ?? ThemeData()).colorScheme.primary,
backgroundColor: (theme ?? ThemeData())
.colorScheme
.surface,
primaryColor: (theme ?? ThemeData()).colorScheme.primary,
attachmentButtonIcon: !multimodal
? (prefs?.getBool("voiceModeEnabled") ??
false)
? Icon(Icons.headphones_rounded,
color:
Theme.of(context).iconTheme.color)
? (prefs?.getBool("voiceModeEnabled") ?? false)
? Icon(Icons.headphones_rounded, color: Theme.of(context).iconTheme.color)
: null
: Icon(Icons.add_a_photo_rounded,
color: Theme.of(context).iconTheme.color),
: Icon(Icons.add_a_photo_rounded, color: Theme.of(context).iconTheme.color),
sendButtonIcon: SizedBox(
height: 24,
child: CircleAvatar(
backgroundColor:
Theme.of(context).iconTheme.color,
backgroundColor: Theme.of(context)
.iconTheme
.color,
radius: 12,
child: Icon(Icons.arrow_upward_rounded,
color:
(prefs?.getBool("useDeviceTheme") ??
child: Icon(
Icons.arrow_upward_rounded,
color: (prefs?.getBool(
"useDeviceTheme") ??
false)
? Theme.of(context)
.colorScheme
@ -1349,14 +1417,9 @@ class _MainAppState extends State<MainApp> {
),
sendButtonMargin: EdgeInsets.zero,
attachmentButtonMargin: EdgeInsets.zero,
inputBackgroundColor: (theme ?? ThemeData())
.colorScheme
.onSurface
.withAlpha(10),
inputTextColor:
(theme ?? ThemeData()).colorScheme.onSurface,
inputBorderRadius:
const BorderRadius.all(Radius.circular(64)),
inputBackgroundColor: (theme ?? ThemeData()).colorScheme.onSurface.withAlpha(10),
inputTextColor: (theme ?? ThemeData()).colorScheme.onSurface,
inputBorderRadius: const BorderRadius.all(Radius.circular(64)),
inputPadding: const EdgeInsets.all(16),
inputMargin: EdgeInsets.only(left: 8, right: 8, bottom: (MediaQuery.of(context).viewInsets.bottom == 0.0 && !desktopFeature()) ? 0 : 8),
messageMaxWidth: (MediaQuery.of(context).size.width >= 1000)
@ -1378,12 +1441,14 @@ class _MainAppState extends State<MainApp> {
sendButtonIcon: SizedBox(
height: 24,
child: CircleAvatar(
backgroundColor:
Theme.of(context).iconTheme.color,
backgroundColor: Theme.of(context)
.iconTheme
.color,
radius: 12,
child: Icon(Icons.arrow_upward_rounded,
color:
(prefs?.getBool("useDeviceTheme") ??
child: Icon(
Icons.arrow_upward_rounded,
color: (prefs?.getBool(
"useDeviceTheme") ??
false)
? Theme.of(context)
.colorScheme
@ -1403,7 +1468,13 @@ class _MainAppState extends State<MainApp> {
? 1900
: 1300
: 700
: 440))),
: 440)),
),
),
],
),
),
),
],
),
drawerEdgeDragWidth:

View File

@ -5,28 +5,23 @@ import 'package:flutter/foundation.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart';
bool desktopFeature() {
return (Platform.isWindows || Platform.isLinux || Platform.isMacOS);
bool desktopFeature({bool web = false}) {
return (Platform.isWindows ||
Platform.isLinux ||
Platform.isMacOS ||
(web ? kIsWeb : false));
}
bool desktopWebFeature() {
return (Platform.isWindows || Platform.isLinux || Platform.isMacOS || kIsWeb);
bool desktopLayout(BuildContext context,
{bool web = false, double? value, double valueCap = 1000}) {
value ??= MediaQuery.of(context).size.width;
return (desktopFeature(web: web) || value >= valueCap);
}
bool desktopLayout(BuildContext context) {
return (desktopFeature() || MediaQuery.of(context).size.width >= 1000);
}
bool desktopLayoutRequired(BuildContext context) {
return (desktopFeature() && MediaQuery.of(context).size.width >= 1000);
}
bool desktopWebLayout(BuildContext context) {
return (desktopWebFeature() || MediaQuery.of(context).size.width >= 1000);
}
bool desktopWebLayoutRequired(BuildContext context) {
return (desktopWebFeature() && MediaQuery.of(context).size.width >= 1000);
bool desktopLayoutRequired(BuildContext context,
{bool web = false, double? value, double valueCap = 1000}) {
value ??= MediaQuery.of(context).size.width;
return (desktopFeature(web: web) && value >= valueCap);
}
Widget desktopControls(BuildContext context) {