Merge pull request #8 from innereq/swipe-to-reply

Swipe to reply
This commit is contained in:
Inex Code 2020-10-14 06:43:22 +03:00 committed by GitHub
commit 326f100520
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1035 additions and 315 deletions

1
.gitignore vendored
View file

@ -11,6 +11,7 @@
.svn/
lib/generated_plugin_registrant.dart
google-services.json
prime
# libolm package
/assets/js/package/*

View file

@ -158,8 +158,7 @@ upload_to_fdroid_repo:
- export UPDATE_VERSION=$(pcregrep -o1 'version:\\s([0-9]*\\.[0-9]*\\.[0-9]*)\\+[0-9]*' pubspec.yaml) && mv app-release.apk "${UPDATE_VERSION}.apk"
- rsync -rav -e ssh ./ fluffy@fdroid.nordgedanken.dev:/fdroid/repo
- ssh fluffy@fdroid.nordgedanken.dev "cd fdroid && mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc && fdroid update"
dependencies:
- build_android_apk
needs: ["build_android_apk"]
only:
- tags
@ -174,8 +173,7 @@ pages:
- cd build/web/ && bundle install && cd ../../
- cd build/web/ && bundle exec jekyll build -d public && cd ../../
- mv build/web/public ./
dependencies:
- build_web
needs: ["build_web"]
artifacts:
paths:
- public
@ -215,3 +213,4 @@ snap:publish:
- './*.snap'
when: on_success
expire_in: 1 week
needs: []

View file

@ -3,10 +3,12 @@
An experimental fork of FluffyChat.
# Changes from FluffyChat
* Swipe to reply (or forward/edit)
* Reworked auth flow
* Removed Sentry
* Double check of .well-known
* Get Jitsi instance from .well-known
* Redesigned settings
# Features
* Single and group chats
@ -35,8 +37,8 @@ An experimental fork of FluffyChat.
2. Clone the repo:
```
git clone --recurse-submodules https://gitlab.com/ChristianPauly/fluffychat-flutter
cd fluffychat-flutter
git clone --recurse-submodules https://github.com/innereq/FurryChat.git
cd FurryChat
```
3. Choose your target platform below and enable support for it.
@ -80,13 +82,6 @@ flutter build windows --release
flutter build macos --release
```
### Docker
Don't even ask.
`docker run -ti --privileged -v /dev/bus/usb:/dev/bus/usb -v ${PWD}:/build -v /home/inex/.pub-cache:/home/inex/.pub-cache -v /home/inex/flutter:/home/inex/flutter -d flutter-fluffy:1.0`
## How to add translations for your language
You can use Weblate to translate the app to your language:

View file

@ -2,3 +2,4 @@ arb-dir: lib/l10n
template-arb-file: intl_en.arb
output-localization-file: l10n.dart
output-class: L10n
preferred-supported-locales: ["en"]

View file

@ -11,7 +11,6 @@ import 'package:furrychat/utils/user_status.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:localstorage/localstorage.dart';
import 'package:universal_html/prefer_universal/html.dart' as html;
import 'package:url_launcher/url_launcher.dart';
@ -72,13 +71,15 @@ class MatrixState extends State<Matrix> {
File wallpaper;
bool renderHtml = false;
String swipeToEndAction;
String swipeToStartAction = 'reply';
String jitsiInstance = 'https://meet.jit.si/';
void clean() async {
if (!kIsWeb) return;
final storage = LocalStorage('LocalStorage');
await storage.ready;
final storage = await getLocalStorage();
await storage.deleteItem(widget.clientName);
}
@ -285,6 +286,16 @@ class MatrixState extends State<Matrix> {
store.getItem('chat.fluffy.renderHtml').then((final render) async {
renderHtml = render == '1';
});
store
.getItem('dev.inex.furrychat.swipeToEndAction')
.then((final action) async {
swipeToEndAction = action ?? swipeToEndAction;
});
store
.getItem('dev.inex.furrychat.swipeToStartAction')
.then((final action) async {
swipeToStartAction = action ?? swipeToStartAction;
});
}
if (kIsWeb) {
onFocusSub = html.window.onFocus.listen((_) => webHasFocus = true);

View file

@ -189,7 +189,7 @@
"username": {}
}
},
"changedTheDisplaynameTo": "{username} غيّر اسمه الى {displayname}",
"changedTheDisplaynameTo": "{username} غيّر اسمه العلني الى {displayname}",
"@changedTheDisplaynameTo": {
"type": "text",
"placeholders": {
@ -375,7 +375,7 @@
"type": "text",
"placeholders": {}
},
"couldNotSetDisplayname": "تعذر تعيين الاسم",
"couldNotSetDisplayname": "تعذر تعيين الاسم العلني",
"@couldNotSetDisplayname": {
"type": "text",
"placeholders": {}
@ -489,7 +489,7 @@
"type": "text",
"placeholders": {}
},
"displaynameHasBeenChanged": "غُيِّر الاسم",
"displaynameHasBeenChanged": "غُيِّر الاسم العلني",
"@displaynameHasBeenChanged": {
"type": "text",
"placeholders": {}
@ -499,7 +499,7 @@
"type": "text",
"placeholders": {}
},
"editDisplayname": "حرر الاسم",
"editDisplayname": "حرر الاسم العلني",
"@editDisplayname": {
"type": "text",
"placeholders": {}
@ -514,7 +514,7 @@
"type": "text",
"placeholders": {}
},
"emoteWarnNeedToPick": "اختر صورة ورمزا للانفعالة",
"emoteWarnNeedToPick": "اختر صورة ورمزا للانفعالة!",
"@emoteWarnNeedToPick": {
"type": "text",
"placeholders": {}
@ -1374,7 +1374,7 @@
"type": "text",
"placeholders": {}
},
"useAmoledTheme": "",
"useAmoledTheme": "هل تريد استخدم ألوان متوافقة مع Amoled؟",
"@useAmoledTheme": {
"type": "text",
"placeholders": {}
@ -1507,14 +1507,14 @@
"unreadCount": {}
}
},
"unreadMessages": "",
"unreadMessages": "{unreadEvents} رسالة غير مقروءة",
"@unreadMessages": {
"type": "text",
"placeholders": {
"unreadEvents": {}
}
},
"unreadMessagesInChats": "",
"unreadMessagesInChats": "{unreadEvents} رسالة غير مقروءة من {unreadChats} محادثة",
"@unreadMessagesInChats": {
"type": "text",
"placeholders": {
@ -1702,5 +1702,10 @@
"@yourOwnUsername": {
"type": "text",
"placeholders": {}
},
"privacy": "الخصوصية",
"@privacy": {
"type": "text",
"placeholders": {}
}
}

View file

@ -514,6 +514,11 @@
"type": "text",
"placeholders": {}
},
"edit": "Edit",
"@edit": {
"type": "text",
"placeholders": {}
},
"editDisplayname": "Edit displayname",
"@editDisplayname": {
"type": "text",
@ -1439,6 +1444,16 @@
"type": "text",
"placeholders": {}
},
"swipeToEndAction": "Swipe to right action",
"@swipeToEndAction": {
"type": "text",
"placeholders": {}
},
"swipeToStartAction": "Swipe to left action",
"@swipeToStartAction": {
"type": "text",
"placeholders": {}
},
"donate": "Donate",
"@donate": {
"type": "text",

View file

@ -194,5 +194,272 @@
"@about": {
"type": "text",
"placeholders": {}
},
"deleteMessage": "Cancella messaggio",
"@deleteMessage": {
"type": "text",
"placeholders": {}
},
"deleteAccount": "Elimina account",
"@deleteAccount": {
"type": "text",
"placeholders": {}
},
"deactivateAccountWarning": "Disabiliterà il tuo account. Non puoi tornare indietro! Sei sicuro?",
"@deactivateAccountWarning": {
"type": "text",
"placeholders": {}
},
"delete": "Cancella",
"@delete": {
"type": "text",
"placeholders": {}
},
"dateWithYear": "{day}-{month}-{year}",
"@dateWithYear": {
"type": "text",
"placeholders": {
"year": {},
"month": {},
"day": {}
}
},
"dateWithoutYear": "{month}-{day}",
"@dateWithoutYear": {
"type": "text",
"placeholders": {
"month": {},
"day": {}
}
},
"dateAndTimeOfDay": "{date}, {timeOfDay}",
"@dateAndTimeOfDay": {
"type": "text",
"placeholders": {
"date": {},
"timeOfDay": {}
}
},
"currentlyActive": "Attualmente attivo",
"@currentlyActive": {
"type": "text",
"placeholders": {}
},
"createNewGroup": "Crea un nuovo gruppo",
"@createNewGroup": {
"type": "text",
"placeholders": {}
},
"createdTheChat": "{username} ha creato la chat",
"@createdTheChat": {
"type": "text",
"placeholders": {
"username": {}
}
},
"createAccountNow": "Crea ora un account",
"@createAccountNow": {
"type": "text",
"placeholders": {}
},
"create": "Crea",
"@create": {
"type": "text",
"placeholders": {}
},
"countParticipants": "{count} partecipanti",
"@countParticipants": {
"type": "text",
"placeholders": {
"count": {}
}
},
"couldNotSetDisplayname": "Impossibile impostare nome",
"@couldNotSetDisplayname": {
"type": "text",
"placeholders": {}
},
"couldNotSetAvatar": "Impossibile impostare avatar",
"@couldNotSetAvatar": {
"type": "text",
"placeholders": {}
},
"couldNotDecryptMessage": "Impossibile decriptare messaggio: {error}",
"@couldNotDecryptMessage": {
"type": "text",
"placeholders": {
"error": {}
}
},
"copy": "Copia",
"@copy": {
"type": "text",
"placeholders": {}
},
"copiedToClipboard": "Copiato negli Appunti",
"@copiedToClipboard": {
"type": "text",
"placeholders": {}
},
"contentViewer": "Visualizzatore contenuti",
"@contentViewer": {
"type": "text",
"placeholders": {}
},
"contactHasBeenInvitedToTheGroup": "Il contatto è stato invitato nel gruppo",
"@contactHasBeenInvitedToTheGroup": {
"type": "text",
"placeholders": {}
},
"connectionAttemptFailed": "Tentativo di connessione fallito",
"@connectionAttemptFailed": {
"type": "text",
"placeholders": {}
},
"connect": "Connetti",
"@connect": {
"type": "text",
"placeholders": {}
},
"confirm": "Conferma",
"@confirm": {
"type": "text",
"placeholders": {}
},
"compareNumbersMatch": "Confronta e assicurati che le seguenti emoji corrispondano a quelle dell'altro dispositivo:",
"@compareNumbersMatch": {
"type": "text",
"placeholders": {}
},
"compareEmojiMatch": "Confronta e assicurati che le seguenti emoji corrispondano a quelle dell'altro dispositivo:",
"@compareEmojiMatch": {
"type": "text",
"placeholders": {}
},
"close": "Chiudi",
"@close": {
"type": "text",
"placeholders": {}
},
"chooseAUsername": "Scegli un username",
"@chooseAUsername": {
"type": "text",
"placeholders": {}
},
"chooseAStrongPassword": "Scegli una password complessa",
"@chooseAStrongPassword": {
"type": "text",
"placeholders": {}
},
"chatDetails": "Dettagli chat",
"@chatDetails": {
"type": "text",
"placeholders": {}
},
"chat": "Chat",
"@chat": {
"type": "text",
"placeholders": {}
},
"channelCorruptedDecryptError": "La crittografia è corrotta",
"@channelCorruptedDecryptError": {
"type": "text",
"placeholders": {}
},
"changeTheServer": "Cambia server",
"@changeTheServer": {
"type": "text",
"placeholders": {}
},
"changeWallpaper": "Cambia sfondo",
"@changeWallpaper": {
"type": "text",
"placeholders": {}
},
"changeTheNameOfTheGroup": "Cambia il nome del gruppo",
"@changeTheNameOfTheGroup": {
"type": "text",
"placeholders": {}
},
"changelog": "Registro cambiamenti",
"@changelog": {
"type": "text",
"placeholders": {}
},
"changedTheRoomInvitationLink": "{username} ha cambiato il link di invito",
"@changedTheRoomInvitationLink": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheRoomAliases": "{username} ha cambiato il nome delle stanze",
"@changedTheRoomAliases": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheProfileAvatar": "{username} ha cambiato il loro avatar",
"@changedTheProfileAvatar": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheJoinRulesTo": "{username} ha cambiato le regole per unirsi in: {joinRules}",
"@changedTheJoinRulesTo": {
"type": "text",
"placeholders": {
"username": {},
"joinRules": {}
}
},
"changedTheJoinRules": "{username} ha cambiato le regole per unirsi",
"@changedTheJoinRules": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheHistoryVisibilityTo": "{username} ha cambiato la visibilità della cronologia in: {rules}",
"@changedTheHistoryVisibilityTo": {
"type": "text",
"placeholders": {
"username": {},
"rules": {}
}
},
"changedTheHistoryVisibility": "{username} ha cambiato la visibilità della cronologia",
"@changedTheHistoryVisibility": {
"type": "text",
"placeholders": {
"username": {}
}
},
"changedTheGuestAccessRulesTo": "{username} ha cambiato le regole di accesso per ospiti con: {rules}",
"@changedTheGuestAccessRulesTo": {
"type": "text",
"placeholders": {
"username": {},
"rules": {}
}
},
"changedTheChatAvatar": "{username} ha cambiato avatar",
"@changedTheChatAvatar": {
"type": "text",
"placeholders": {
"username": {}
}
},
"askSSSSSign": "Per entrare con l'altro utente, per favore inserisci la tua passphrase o recovery key.",
"@askSSSSSign": {
"type": "text",
"placeholders": {}
},
"askSSSSCache": "Per favore inserisci la tua passphrase o recovery key per la cache delle chiavi.",
"@askSSSSCache": {
"type": "text",
"placeholders": {}
}
}

View file

@ -114,6 +114,11 @@
"type": "text",
"placeholders": {}
},
"avatar": "Аватар",
"@avatar": {
"type": "text",
"placeholders": {}
},
"avatarHasBeenChanged": "Аватар был изменён",
"@avatarHasBeenChanged": {
"type": "text",
@ -197,6 +202,11 @@
"displayname": {}
}
},
"changeThePassword": "Сменить пароль",
"@changeThePassword": {
"type": "text",
"placeholders": {}
},
"changedTheGuestAccessRules": "{username} изменил(а) правила гостевого доступа",
"@changedTheGuestAccessRules": {
"type": "text",
@ -509,6 +519,11 @@
"type": "text",
"placeholders": {}
},
"edit": "Редактировать",
"@edit": {
"type": "text",
"placeholders": {}
},
"editDisplayname": "Отображаемое имя",
"@editDisplayname": {
"type": "text",
@ -681,11 +696,21 @@
"type": "text",
"placeholders": {}
},
"homeserver": "Сервер Matrix",
"@homeserver": {
"type": "text",
"placeholders": {}
},
"homeserverIsNotCompatible": "Несовместимый сервер Matrix",
"@homeserverIsNotCompatible": {
"type": "text",
"placeholders": {}
},
"homeserverOrMXID": "Сервер или полное имя пользователя",
"@homeserverOrMXID": {
"type": "text",
"placeholders": {}
},
"id": "ID",
"@id": {
"type": "text",
@ -1372,6 +1397,16 @@
"type": "text",
"placeholders": {}
},
"swipeToEndAction": "Действие по жесту вправо",
"@swipeToEndAction": {
"type": "text",
"placeholders": {}
},
"swipeToStartAction": "Действие по жесту влево",
"@swipeToStartAction": {
"type": "text",
"placeholders": {}
},
"systemTheme": "Системная",
"@systemTheme": {
"type": "text",

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:localstorage/localstorage.dart';
import 'package:universal_html/prefer_universal/html.dart' as html;
import 'components/matrix.dart';
@ -21,8 +20,6 @@ void main() {
runZonedGuarded(
() => runApp(App()),
(error, stackTrace) async {
final storage = LocalStorage('LocalStorage');
await storage.ready;
debugPrint(error.toString());
debugPrint(stackTrace.toString());
},

View file

@ -6,12 +6,22 @@ import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:localstorage/localstorage.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'dart:core';
import './database/shared.dart';
import 'package:olm/olm.dart' as olm; // needed for migration
import 'package:random_string/random_string.dart';
Future<LocalStorage> getLocalStorage() async {
final directory = PlatformInfos.isBetaDesktop
? await getApplicationSupportDirectory()
: await getApplicationDocumentsDirectory();
final localStorage = LocalStorage('LocalStorage', directory.path);
await localStorage.ready;
return localStorage;
}
Future<Database> getDatabase(Client client) async {
while (_generateDatabaseLock) {
await Future.delayed(Duration(milliseconds: 50));

View file

@ -18,8 +18,8 @@ Future<MatrixImageFile> resizeImage(MatrixImageFile file,
try {
final nativeImg = native.Image();
await nativeImg.loadEncoded(file.bytes);
file.width = nativeImg.width();
file.height = nativeImg.height();
file.width = nativeImg.width;
file.height = nativeImg.height;
args = _IsolateArgs(
width: file.width, height: file.height, bytes: file.bytes, max: max);
nativeImg.free();
@ -96,8 +96,8 @@ Future<_IsolateResponse> _isolateFunction(_IsolateArgs args) async {
final ret = _IsolateResponse(
blurhash: blurhash,
jpegBytes: jpegBytes,
width: nativeImg.width(),
height: nativeImg.height());
width: nativeImg.width,
height: nativeImg.height);
nativeImg.free();

View file

@ -28,6 +28,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:image_picker/image_picker.dart';
import 'package:pedantic/pedantic.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:swipe_to_action/swipe_to_action.dart';
import '../components/dialogs/send_file_dialog.dart';
import '../components/input_bar.dart';
@ -316,8 +317,10 @@ class _ChatState extends State<_Chat> {
return true;
}
void forwardEventsAction(BuildContext context) async {
if (selectedEvents.length == 1) {
void forwardEventsAction(BuildContext context, {Event event}) async {
if (event != null) {
Matrix.of(context).shareContent = event.content;
} else if (selectedEvents.length == 1) {
Matrix.of(context).shareContent = selectedEvents.first.content;
} else {
Matrix.of(context).shareContent = {
@ -343,9 +346,9 @@ class _ChatState extends State<_Chat> {
setState(() => selectedEvents.clear());
}
void replyAction() {
void replyAction({Event replyTo}) {
setState(() {
replyEvent = selectedEvents.first;
replyEvent = replyTo ?? selectedEvents.first;
selectedEvents.clear();
});
inputFocus.requestFocus();
@ -411,6 +414,128 @@ class _ChatState extends State<_Chat> {
e.type != 'm.reaction')
.toList();
SwipeDirection _getSwipeDirection(Event event) {
var swipeToEndAction = Matrix.of(context).swipeToEndAction;
var swipeToStartAction = Matrix.of(context).swipeToStartAction;
var client = Matrix.of(context).client;
if (event.senderId != client.userID && swipeToEndAction == 'edit') {
swipeToEndAction = null;
}
if (event.senderId != client.userID && swipeToStartAction == 'edit') {
swipeToStartAction = null;
}
if (swipeToEndAction != null && swipeToStartAction != null) {
return SwipeDirection.horizontal;
}
if (swipeToEndAction != null) {
return SwipeDirection.startToEnd;
}
if (swipeToStartAction != null) {
return SwipeDirection.endToStart;
}
return null;
}
Widget _getSwipeBackground(Event event, {bool isSecondary = false}) {
var alignToRight, action;
if (_getSwipeDirection(event) == SwipeDirection.horizontal) {
if (isSecondary) {
alignToRight = true;
action = Matrix.of(context).swipeToStartAction;
} else {
alignToRight = false;
action = Matrix.of(context).swipeToEndAction;
}
} else if (isSecondary) {
return null;
} else if (_getSwipeDirection(event) == SwipeDirection.endToStart) {
alignToRight = true;
action = Matrix.of(context).swipeToStartAction;
} else {
alignToRight = false;
action = Matrix.of(context).swipeToStartAction;
}
switch (action) {
case 'reply':
return Container(
color: Theme.of(context).primaryColor.withAlpha(100),
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Row(
mainAxisAlignment:
alignToRight ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Icon(Icons.reply_outlined),
SizedBox(width: 2.0),
Text(L10n.of(context).reply)
],
),
);
case 'forward':
return Container(
color: Theme.of(context).primaryColor.withAlpha(100),
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Row(
mainAxisAlignment:
alignToRight ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Icon(Icons.forward_outlined),
SizedBox(width: 2.0),
Text(L10n.of(context).forward)
],
),
);
case 'edit':
return Container(
color: Theme.of(context).primaryColor.withAlpha(100),
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Row(
mainAxisAlignment:
alignToRight ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Icon(Icons.edit_outlined),
SizedBox(width: 2.0),
Text(L10n.of(context).edit)
],
),
);
default:
return Container(
color: Theme.of(context).primaryColor.withAlpha(100),
);
}
}
void _handleSwipe(SwipeDirection direction, Event event) {
var action;
if (direction == SwipeDirection.endToStart) {
action = Matrix.of(context).swipeToStartAction;
} else {
action = Matrix.of(context).swipeToEndAction;
}
switch (action) {
case 'reply':
replyAction(replyTo: event);
break;
case 'forward':
forwardEventsAction(context, event: event);
break;
case 'edit':
setState(() {
editEvent = event;
sendController.text = editEvent
.getDisplayEvent(timeline)
.getLocalizedBody(MatrixLocals(L10n.of(context)),
withSenderNamePrefix: false, hideReply: true);
selectedEvents.clear();
});
inputFocus.requestFocus();
break;
default:
}
}
@override
Widget build(BuildContext context) {
matrix = Matrix.of(context);
@ -668,6 +793,19 @@ class _ChatState extends State<_Chat> {
key: ValueKey(i - 1),
index: i - 1,
controller: _scrollController,
child: Swipeable(
key: ValueKey(
filteredEvents[i - 1].eventId),
background: _getSwipeBackground(
filteredEvents[i - 1]),
secondaryBackground:
_getSwipeBackground(
filteredEvents[i - 1],
isSecondary: true),
direction: _getSwipeDirection(
filteredEvents[i - 1]),
onSwipe: (direction) => _handleSwipe(
direction, filteredEvents[i - 1]),
child: Message(filteredEvents[i - 1],
onAvatarTab: (Event event) {
sendController.text +=
@ -683,8 +821,8 @@ class _ChatState extends State<_Chat> {
);
} else {
setState(
() =>
selectedEvents.add(event),
() => selectedEvents
.add(event),
);
}
selectedEvents.sort(
@ -699,12 +837,13 @@ class _ChatState extends State<_Chat> {
context: context),
longPressSelect:
selectedEvents.isEmpty,
selected: selectedEvents
.contains(filteredEvents[i - 1]),
selected: selectedEvents.contains(
filteredEvents[i - 1]),
timeline: timeline,
nextEvent: i >= 2
? filteredEvents[i - 2]
: null),
),
);
});
},

View file

@ -171,7 +171,6 @@ class _LoginState extends State<Login> {
readOnly: loading,
autocorrect: false,
autofocus: true,
keyboardType: TextInputType.emailAddress,
onChanged: (t) => _checkWellKnownWithCoolDown(t, context),
controller: usernameController,
decoration: InputDecoration(

View file

@ -22,6 +22,74 @@ class ChatSettings extends StatefulWidget {
}
class _ChatSettingsState extends State<ChatSettings> {
String _getActionDescription(String action) {
switch (action) {
case 'reply':
return L10n.of(context).reply;
case 'forward':
return L10n.of(context).forward;
case 'edit':
return L10n.of(context).edit;
default:
return L10n.of(context).none;
}
}
void _changeSwipeAction(bool isToEnd, String action) async {
if (isToEnd) {
Matrix.of(context).swipeToEndAction = action;
await Matrix.of(context)
.store
.setItem('chat.fluffy.swipeToEndAction', action);
setState(() => null);
} else {
Matrix.of(context).swipeToStartAction = action;
await Matrix.of(context)
.store
.setItem('chat.fluffy.swipeToStartAction', action);
setState(() => null);
}
}
Widget _swipeActionChooser(BuildContext context, bool isToEnd) {
return ListView(
children: [
ListTile(
title: Text(L10n.of(context).none),
leading: Icon(Icons.clear_outlined),
onTap: () {
_changeSwipeAction(isToEnd, null);
Navigator.of(context).pop();
},
),
ListTile(
title: Text(L10n.of(context).reply),
leading: Icon(Icons.reply_outlined),
onTap: () {
_changeSwipeAction(isToEnd, 'reply');
Navigator.of(context).pop();
},
),
ListTile(
title: Text(L10n.of(context).forward),
leading: Icon(Icons.forward_outlined),
onTap: () {
_changeSwipeAction(isToEnd, 'forward');
Navigator.of(context).pop();
},
),
ListTile(
title: Text(L10n.of(context).edit),
leading: Icon(Icons.edit_outlined),
onTap: () {
_changeSwipeAction(isToEnd, 'edit');
Navigator.of(context).pop();
},
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -42,6 +110,27 @@ class _ChatSettingsState extends State<ChatSettings> {
},
),
),
Divider(thickness: 1),
ListTile(
title: Text(L10n.of(context).swipeToEndAction),
onTap: () => showModalBottomSheet(
context: context,
builder: (BuildContext context) =>
_swipeActionChooser(context, true),
),
subtitle: Text(
_getActionDescription(Matrix.of(context).swipeToEndAction)),
),
ListTile(
title: Text(L10n.of(context).swipeToStartAction),
onTap: () => showModalBottomSheet(
context: context,
builder: (BuildContext context) =>
_swipeActionChooser(context, false),
),
subtitle: Text(
_getActionDescription(Matrix.of(context).swipeToStartAction)),
),
],
),
);

View file

@ -553,7 +553,7 @@ packages:
description:
path: "."
ref: master
resolved-ref: bd24832f96537447174aa34ba78eaed7ff05bb8e
resolved-ref: c8eb59c25c4e3a568bd64e4722108ec45259e157
url: "https://gitlab.com/famedly/libraries/native_imaging.git"
source: git
version: "0.0.1"
@ -870,6 +870,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-nullsafety.1"
swipe_to_action:
dependency: "direct main"
description:
name: swipe_to_action
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
synchronized:
dependency: transitive
description:

View file

@ -69,6 +69,7 @@ dependencies:
ref: master
flutter_blurhash: ^0.5.0
scroll_to_index: ^1.0.6
swipe_to_action: ^0.1.0
dev_dependencies:
flutter_test:

59
snap/snapcraft.yaml Normal file
View file

@ -0,0 +1,59 @@
name: fluffychat
base: core18 # the base snap is the execution environment for this snap
version: git # just for humans, typically '1.2+git' or '1.3.2'
summary: Open. Nonprofit. Cute ♥
description: |
FluffyChat - Chat with your friends
9 greatest FluffyChat features:
1. Opensource and open development where everyone can join.
2. Nonprofit - FluffyChat is donation funded.
3. Cute design and many theme settings including a dark mode.
4. Unlimited groups and direct chats.
5. FluffyChat is made as simple to use as possible.
6. Free to use for everyone without ads.
7. FluffyChat can use your addressbook to find your friends or you can use
usernames.
8. There is no "FluffyChat server" you are forced to use. Use the server
you find trustworthy or host your own.
9. Compatible with Riot, Fractal, Nekho and all matrix messengers.
Join the community: fluffychat://+ubports_community:matrix.org
Website: http://fluffy.chat
Microblog: https://metalhead.club/@krille
grade: devel # must be 'stable' to release into candidate/stable channels
confinement: strict # use 'strict' once you have the right plugs and slots
parts:
olm:
plugin: cmake
source: https://gitlab.matrix.org/matrix-org/olm.git
source-type: git
source-tag: 3.2.1
fluffychat:
plugin: flutter
source: .
flutter-target: lib/main.dart
stage-packages:
- libsqlite3-0
override-prime: |
snapcraftctl prime
ln -sf libsqlite3.so.0 ${SNAPCRAFT_PRIME}/usr/lib/x86_64-linux-gnu/libsqlite3.so
slots:
dbus-svc:
interface: dbus
bus: session
name: chat.fluffy.fluffychat
apps:
fluffychat:
command: fluffychat
extensions:
- flutter-dev
plugs:
- network
- home
slots:
- dbus-svc