Added double pull to exit
This commit is contained in:
parent
5d68e9893d
commit
c41fb53d9c
|
@ -5,6 +5,11 @@
|
|||
"description": "Title of the application",
|
||||
"context": "Visible in the side bar"
|
||||
},
|
||||
"backToExit": "Press back again to exit",
|
||||
"@backToExit": {
|
||||
"description": "Text displayed when the back button is pressed to exit the app",
|
||||
"context": "Visible in the chat view"
|
||||
},
|
||||
"optionNewChat": "New Chat",
|
||||
"@optionNewChat": {
|
||||
"description": "Text displayed for new chat option",
|
||||
|
|
218
lib/main.dart
218
lib/main.dart
|
@ -4,6 +4,7 @@ import 'dart:math';
|
|||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
|
@ -69,6 +70,7 @@ String hoveredChat = "";
|
|||
final user = types.User(id: const Uuid().v4());
|
||||
final assistant = types.User(id: const Uuid().v4());
|
||||
|
||||
int backPullTimestamp = 0;
|
||||
bool settingsOpen = false;
|
||||
bool desktopTitleVisible = true;
|
||||
bool logoVisible = true;
|
||||
|
@ -112,6 +114,16 @@ class _AppState extends State<App> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
|
||||
SystemChannels.lifecycle.setMessageHandler((msg) async {
|
||||
if (msg == AppLifecycleState.resumed.toString()) {
|
||||
// reset back pull timestamp
|
||||
setState(() {
|
||||
backPullTimestamp = 0;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
void load() async {
|
||||
try {
|
||||
await FlutterDisplayMode.setHighRefreshRate();
|
||||
|
@ -971,6 +983,20 @@ class _MainAppState extends State<MainApp> {
|
|||
|
||||
return WindowBorder(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: PopScope(
|
||||
canPop:
|
||||
DateTime.now().millisecondsSinceEpoch < (backPullTimestamp + 2000),
|
||||
onPopInvoked: (didPop) {
|
||||
if (!didPop) {
|
||||
setState(() {
|
||||
backPullTimestamp = DateTime.now().microsecondsSinceEpoch;
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.backToExit),
|
||||
duration: const Duration(seconds: 2),
|
||||
showCloseIcon: true));
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
|
@ -979,7 +1005,9 @@ class _MainAppState extends State<MainApp> {
|
|||
? desktopLayoutRequired(context)
|
||||
? [
|
||||
SizedBox(
|
||||
width: 304, height: 200, child: MoveWindow()),
|
||||
width: 304,
|
||||
height: 200,
|
||||
child: MoveWindow()),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: AnimatedOpacity(
|
||||
|
@ -1008,7 +1036,9 @@ class _MainAppState extends State<MainApp> {
|
|||
]
|
||||
: [
|
||||
SizedBox(
|
||||
width: 90, height: 200, child: MoveWindow()),
|
||||
width: 90,
|
||||
height: 200,
|
||||
child: MoveWindow()),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 200, child: MoveWindow())),
|
||||
|
@ -1069,7 +1099,8 @@ class _MainAppState extends State<MainApp> {
|
|||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(AppLocalizations.of(context)!
|
||||
Text(AppLocalizations.of(
|
||||
context)!
|
||||
.deleteDialogDescription),
|
||||
]),
|
||||
actions: [
|
||||
|
@ -1078,8 +1109,8 @@ class _MainAppState extends State<MainApp> {
|
|||
selectionHaptic();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
child: Text(AppLocalizations.of(
|
||||
context)!
|
||||
.deleteDialogCancel)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
|
@ -1111,8 +1142,8 @@ class _MainAppState extends State<MainApp> {
|
|||
chatUuid = null;
|
||||
setState(() {});
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
child: Text(AppLocalizations.of(
|
||||
context)!
|
||||
.deleteDialogDelete))
|
||||
]);
|
||||
});
|
||||
|
@ -1201,7 +1232,8 @@ class _MainAppState extends State<MainApp> {
|
|||
child: Chat(
|
||||
messages: messages,
|
||||
textMessageBuilder: (p0,
|
||||
{required messageWidth, required showName}) {
|
||||
{required messageWidth,
|
||||
required showName}) {
|
||||
var white =
|
||||
const TextStyle(color: Colors.white);
|
||||
bool greyed = false;
|
||||
|
@ -1256,16 +1288,54 @@ class _MainAppState extends State<MainApp> {
|
|||
.blockSyntaxes,
|
||||
<md.InlineSyntax>[
|
||||
md.EmojiSyntax(),
|
||||
...md.ExtensionSet.gitHubFlavored
|
||||
...md
|
||||
.ExtensionSet
|
||||
.gitHubFlavored
|
||||
.inlineSyntaxes
|
||||
],
|
||||
),
|
||||
imageBuilder: (uri, title, alt) {
|
||||
if (uri.isAbsolute) {
|
||||
return Image.network(
|
||||
uri.toString(), errorBuilder:
|
||||
(context, error,
|
||||
stackTrace) {
|
||||
uri.toString(),
|
||||
errorBuilder: (context,
|
||||
error, stackTrace) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
selectionHaptic();
|
||||
ScaffoldMessenger.of(
|
||||
context)
|
||||
.showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(
|
||||
context)!
|
||||
.notAValidImage),
|
||||
showCloseIcon:
|
||||
true));
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius
|
||||
.circular(
|
||||
8),
|
||||
color: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness
|
||||
.light
|
||||
? Colors.white
|
||||
: Colors
|
||||
.black),
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
left: 100,
|
||||
right: 100,
|
||||
top: 32),
|
||||
child: const Image(
|
||||
image: AssetImage(
|
||||
"assets/logo512error.png"))));
|
||||
});
|
||||
} else {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
selectionHaptic();
|
||||
|
@ -1300,40 +1370,6 @@ class _MainAppState extends State<MainApp> {
|
|||
child: const Image(
|
||||
image: AssetImage(
|
||||
"assets/logo512error.png"))));
|
||||
});
|
||||
} else {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
selectionHaptic();
|
||||
ScaffoldMessenger.of(
|
||||
context)
|
||||
.showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(
|
||||
context)!
|
||||
.notAValidImage),
|
||||
showCloseIcon:
|
||||
true));
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius
|
||||
.circular(8),
|
||||
color: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness
|
||||
.light
|
||||
? Colors.white
|
||||
: Colors.black),
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
left: 100,
|
||||
right: 100,
|
||||
top: 32),
|
||||
child: const Image(
|
||||
image: AssetImage(
|
||||
"assets/logo512error.png"))));
|
||||
}
|
||||
},
|
||||
styleSheet: (p0.author == user)
|
||||
|
@ -1354,9 +1390,11 @@ class _MainAppState extends State<MainApp> {
|
|||
color: Colors.black,
|
||||
backgroundColor:
|
||||
Colors.white),
|
||||
codeblockDecoration: BoxDecoration(
|
||||
codeblockDecoration:
|
||||
BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
8)),
|
||||
h1: white,
|
||||
h2: white,
|
||||
|
@ -1378,22 +1416,17 @@ class _MainAppState extends State<MainApp> {
|
|||
Brightness.light)
|
||||
? MarkdownStyleSheet(
|
||||
p: TextStyle(
|
||||
color: greyed
|
||||
? Colors.grey
|
||||
: Colors.black,
|
||||
color: greyed ? Colors.grey : Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight:
|
||||
FontWeight.w500),
|
||||
blockquoteDecoration:
|
||||
BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
fontWeight: FontWeight.w500),
|
||||
blockquoteDecoration: BoxDecoration(
|
||||
color:
|
||||
Colors.grey[200],
|
||||
borderRadius:
|
||||
BorderRadius
|
||||
.circular(8),
|
||||
),
|
||||
code: const TextStyle(
|
||||
color: Colors.white,
|
||||
backgroundColor: Colors.black),
|
||||
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))))
|
||||
: MarkdownStyleSheet(
|
||||
|
@ -1413,8 +1446,9 @@ class _MainAppState extends State<MainApp> {
|
|||
imageMessageBuilder: (p0,
|
||||
{required messageWidth}) {
|
||||
return SizedBox(
|
||||
width:
|
||||
desktopLayout(context) ? 360.0 : 160.0,
|
||||
width: desktopLayout(context)
|
||||
? 360.0
|
||||
: 160.0,
|
||||
child: MarkdownBody(
|
||||
data: ""));
|
||||
},
|
||||
|
@ -1425,17 +1459,17 @@ class _MainAppState extends State<MainApp> {
|
|||
onVisibilityChanged:
|
||||
(VisibilityInfo info) {
|
||||
if (settingsOpen) return;
|
||||
logoVisible = info.visibleFraction > 0;
|
||||
logoVisible =
|
||||
info.visibleFraction > 0;
|
||||
try {
|
||||
setState(() {});
|
||||
} catch (_) {}
|
||||
},
|
||||
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);
|
||||
|
@ -1496,7 +1530,8 @@ class _MainAppState extends State<MainApp> {
|
|||
}
|
||||
|
||||
var text =
|
||||
(messages[index] as types.TextMessage).text;
|
||||
(messages[index] as types.TextMessage)
|
||||
.text;
|
||||
var input = await prompt(
|
||||
context,
|
||||
title: AppLocalizations.of(context)!
|
||||
|
@ -1535,7 +1570,8 @@ class _MainAppState extends State<MainApp> {
|
|||
: null
|
||||
: () {
|
||||
selectionHaptic();
|
||||
if (!chatAllowed || model == null) return;
|
||||
if (!chatAllowed || model == null)
|
||||
return;
|
||||
if (desktopFeature()) {
|
||||
FilePicker.platform
|
||||
.pickFiles(type: FileType.image)
|
||||
|
@ -1552,8 +1588,10 @@ class _MainAppState extends State<MainApp> {
|
|||
types.ImageMessage(
|
||||
author: user,
|
||||
id: const Uuid().v4(),
|
||||
name: value.files.first.name,
|
||||
size: value.files.first.size,
|
||||
name:
|
||||
value.files.first.name,
|
||||
size:
|
||||
value.files.first.size,
|
||||
uri:
|
||||
"data:image/png;base64,$encoded"));
|
||||
|
||||
|
@ -1567,7 +1605,8 @@ class _MainAppState extends State<MainApp> {
|
|||
builder: (context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(
|
||||
padding:
|
||||
const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 16),
|
||||
|
@ -1594,15 +1633,12 @@ class _MainAppState extends State<MainApp> {
|
|||
true;
|
||||
logoVisible =
|
||||
false;
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const ScreenVoice()));
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => const ScreenVoice()));
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons
|
||||
icon: const Icon(Icons
|
||||
.headphones_rounded),
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!
|
||||
label: Text(AppLocalizations.of(context)!
|
||||
.settingsTitleVoice)))
|
||||
: const SizedBox
|
||||
.shrink(),
|
||||
|
@ -1628,8 +1664,8 @@ class _MainAppState extends State<MainApp> {
|
|||
final result =
|
||||
await ImagePicker()
|
||||
.pickImage(
|
||||
source: ImageSource
|
||||
.camera,
|
||||
source:
|
||||
ImageSource.camera,
|
||||
);
|
||||
if (result ==
|
||||
null) {
|
||||
|
@ -1649,8 +1685,7 @@ class _MainAppState extends State<MainApp> {
|
|||
author:
|
||||
user,
|
||||
createdAt:
|
||||
DateTime.now()
|
||||
.millisecondsSinceEpoch,
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
height: image
|
||||
.height
|
||||
.toDouble(),
|
||||
|
@ -1677,10 +1712,11 @@ class _MainAppState extends State<MainApp> {
|
|||
icon: const Icon(
|
||||
Icons
|
||||
.photo_camera_rounded),
|
||||
label: Text(AppLocalizations.of(
|
||||
context)!
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.takeImage))),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(
|
||||
height: 8),
|
||||
SizedBox(
|
||||
width:
|
||||
double.infinity,
|
||||
|
@ -1696,8 +1732,8 @@ class _MainAppState extends State<MainApp> {
|
|||
final result =
|
||||
await ImagePicker()
|
||||
.pickImage(
|
||||
source: ImageSource
|
||||
.gallery,
|
||||
source:
|
||||
ImageSource.gallery,
|
||||
);
|
||||
if (result ==
|
||||
null) {
|
||||
|
@ -1717,8 +1753,7 @@ class _MainAppState extends State<MainApp> {
|
|||
author:
|
||||
user,
|
||||
createdAt:
|
||||
DateTime.now()
|
||||
.millisecondsSinceEpoch,
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
height: image
|
||||
.height
|
||||
.toDouble(),
|
||||
|
@ -1745,8 +1780,8 @@ class _MainAppState extends State<MainApp> {
|
|||
icon: const Icon(
|
||||
Icons
|
||||
.image_rounded),
|
||||
label: Text(AppLocalizations.of(
|
||||
context)!
|
||||
label: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.uploadImage)))
|
||||
]));
|
||||
});
|
||||
|
@ -1886,6 +1921,7 @@ class _MainAppState extends State<MainApp> {
|
|||
selectedIndex: 1,
|
||||
children: sidebar(context, setState));
|
||||
})),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"de": [
|
||||
"backToExit",
|
||||
"deleteChat",
|
||||
"renameChat",
|
||||
"settingsDescriptionBehavior",
|
||||
|
@ -21,6 +22,7 @@
|
|||
],
|
||||
|
||||
"it": [
|
||||
"backToExit",
|
||||
"deleteChat",
|
||||
"renameChat",
|
||||
"settingsDescriptionBehavior",
|
||||
|
@ -42,6 +44,7 @@
|
|||
],
|
||||
|
||||
"tr": [
|
||||
"backToExit",
|
||||
"deleteChat",
|
||||
"renameChat",
|
||||
"settingsDescriptionBehavior",
|
||||
|
@ -63,6 +66,7 @@
|
|||
],
|
||||
|
||||
"zh": [
|
||||
"backToExit",
|
||||
"deleteChat",
|
||||
"renameChat",
|
||||
"settingsDescriptionBehavior",
|
||||
|
|
Loading…
Reference in New Issue