merge main

This commit is contained in:
ggurdin 2024-06-19 12:34:08 -04:00
commit eedb050ce1
14 changed files with 138 additions and 62 deletions

View file

@ -804,11 +804,18 @@ class ChatController extends State<ChatPageWithRoom>
builder: (c) => const RecordingDialog(),
);
if (result == null) return;
final audioFile = File(result.path);
// #Pangea
// enable web recording
// final audioFile = File(result.path);
// final file = MatrixAudioFile(
// bytes: audioFile.readAsBytesSync(),
// name: audioFile.path,
// );
final file = MatrixAudioFile(
bytes: audioFile.readAsBytesSync(),
name: audioFile.path,
bytes: result.bytes,
name: result.path,
);
// Pangea#
await room.sendFileEvent(
file,
inReplyTo: replyEvent,

View file

@ -286,6 +286,8 @@ class MessageContent extends StatelessWidget {
final bigEmotes = event.onlyEmotes &&
event.numberEmotes > 0 &&
event.numberEmotes <= 10;
// #Pangea
// return Linkify(
final messageTextStyle = TextStyle(
color: textColor,
fontSize: bigEmotes ? fontSize * 3 : fontSize,
@ -301,11 +303,10 @@ class MessageContent extends StatelessWidget {
);
} else if (pangeaMessageEvent != null) {
toolbarController?.toolbar?.textSelection.setMessageText(
pangeaMessageEvent!.body,
(event.getDisplayEvent(pangeaMessageEvent!.timeline).body),
);
}
// return Linkify(
return SelectableLinkify(
onSelectionChanged: (selection, cause) {
if (cause == SelectionChangedCause.longPress &&

View file

@ -1,11 +1,14 @@
import 'dart:async';
import 'dart:io';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/utils/update_version_dialog.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:record/record.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
@ -39,9 +42,15 @@ class RecordingDialogState extends State<RecordingDialog> {
Future<void> startRecording() async {
try {
final tempDir = await getTemporaryDirectory();
final path = _recordedPath =
'${tempDir.path}/recording${DateTime.now().microsecondsSinceEpoch}.${RecordingDialog.recordingFileType}';
// #Pangea
// enable recording on web
// final tempDir = await getTemporaryDirectory();
// final path = _recordedPath =
// '${tempDir.path}/recording${DateTime.now().microsecondsSinceEpoch}.${RecordingDialog.recordingFileType}';
final tempDirPath = kIsWeb ? "." : (await getTemporaryDirectory()).path;
_recordedPath =
'$tempDirPath/recording${DateTime.now().microsecondsSinceEpoch}.${RecordingDialog.recordingFileType}';
// Pangea#
final result = await _audioRecorder.hasPermission();
if (result != true) {
@ -110,9 +119,25 @@ class RecordingDialogState extends State<RecordingDialog> {
void _stopAndSend() async {
_recorderSubscription?.cancel();
await _audioRecorder.stop();
// #Pangea
// await _audioRecorder.stop();
final outputPath = await _audioRecorder.stop();
// Pangea#
final path = _recordedPath;
if (path == null) throw ('Recording failed!');
// #Pangea
Uint8List bytes;
if (kIsWeb) {
if (outputPath == null) throw ('Recording failed!');
final response = await http.get(Uri.parse(outputPath));
bytes = response.bodyBytes;
} else {
final audioFile = File(path);
bytes = audioFile.readAsBytesSync();
}
// Pangea#
const waveCount = AudioPlayerWidget.wavesCount;
final step = amplitudeTimeline.length < waveCount
? 1
@ -126,6 +151,9 @@ class RecordingDialogState extends State<RecordingDialog> {
path: path,
duration: _duration.inMilliseconds,
waveform: waveform,
// #Pangea
bytes: bytes,
// Pangea#
),
);
}
@ -236,11 +264,17 @@ class RecordingResult {
final String path;
final int duration;
final List<int> waveform;
// #Pangea
final Uint8List bytes;
// Pangea#
const RecordingResult({
required this.path,
required this.duration,
required this.waveform,
// #Pangea
required this.bytes,
// Pangea#
});
factory RecordingResult.fromJson(Map<String, dynamic> json) =>
@ -248,11 +282,17 @@ class RecordingResult {
path: json['path'],
duration: json['duration'],
waveform: List<int>.from(json['waveform']),
// #Pangea
bytes: Uint8List.fromList(json['bytes']),
// Pangea#
);
Map<String, dynamic> toJson() => {
'path': path,
'duration': duration,
'waveform': waveform,
// #Pangea
'bytes': bytes,
// Pangea#
};
}

View file

@ -183,9 +183,10 @@ class ChatListController extends State<ChatList>
bool Function(Room) getRoomFilterByActiveFilter(ActiveFilter activeFilter) {
switch (activeFilter) {
case ActiveFilter.allChats:
return (room) => !room.isSpace; // #Pangea
// &&
// !room.isAnalyticsRoom;
return (room) =>
!room.isSpace // #Pangea
&&
!room.isAnalyticsRoom;
// Pangea#;
case ActiveFilter.groups:
return (room) =>
@ -799,6 +800,10 @@ class ChatListController extends State<ChatList>
// Pangea#
Future<void> addToSpace() async {
// #Pangea
final firstSelectedRoom =
Matrix.of(context).client.getRoomById(selectedRoomIds.toList().first);
// Pangea#
final selectedSpace = await showConfirmationDialog<String>(
context: context,
title: L10n.of(context)!.addToSpace,
@ -817,8 +822,9 @@ class ChatListController extends State<ChatList>
&&
selectedRoomIds
.map((id) => Matrix.of(context).client.getRoomById(id))
.where((e) => !(e?.isSpace ?? false))
.every((e) => r.canIAddSpaceChild(e)),
// Only show non-recursion-causing spaces
// Performs a few other checks as well
.every((e) => r.canAddAsParentOf(e)),
//Pangea#
)
.map(
@ -828,6 +834,13 @@ class ChatListController extends State<ChatList>
// label: space
// .getLocalizedDisplayname(MatrixLocals(L10n.of(context)!)),
label: space.nameIncludingParents(context),
// If user is not admin of space, button is grayed out
textStyle: TextStyle(
color: (firstSelectedRoom == null ||
(firstSelectedRoom.isSpace && !space.isRoomAdmin))
? Theme.of(context).colorScheme.outline
: Theme.of(context).colorScheme.surfaceTint,
),
// Pangea#
),
)
@ -839,12 +852,20 @@ class ChatListController extends State<ChatList>
future: () async {
final space = Matrix.of(context).client.getRoomById(selectedSpace)!;
// #Pangea
if (firstSelectedRoom == null) {
throw L10n.of(context)!.nonexistentSelection;
}
// If user is not admin of the would-be parent space, does not allow
if (firstSelectedRoom.isSpace && !space.isRoomAdmin) {
throw L10n.of(context)!.cantAddSpaceChild;
}
await pangeaAddToSpace(
space,
selectedRoomIds.toList(),
context,
pangeaController,
);
// if (space.canSendDefaultStates) {
// for (final roomId in selectedRoomIds) {
// await space.setSpaceChild(roomId);
@ -857,7 +878,10 @@ class ChatListController extends State<ChatList>
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(L10n.of(context)!.chatHasBeenAddedToThisSpace),
// #Pangea
// content: Text(L10n.of(context)!.chatHasBeenAddedToThisSpace),
content: Text(L10n.of(context)!.roomAddedToSpace),
// Pangea#
),
);
}

View file

@ -338,6 +338,16 @@ class _SpaceViewState extends State<SpaceView> {
widget.controller.cancelAction();
// #Pangea
if (room == null || room.membership == Membership.leave) return;
if (room.isSpace) {
await room.archiveSpace(
context,
Matrix.of(context).client,
onlyAdmin: false,
);
} else {
widget.controller.toggleSelection(room.id);
await widget.controller.archiveAction();
}
// Pangea#
_refresh();
break;

View file

@ -123,9 +123,9 @@ extension ChildrenAndParentsRoomExtension on Room {
// Checks if has permissions to add child chat
// Or whether potential child space is ancestor of this
bool _canAddAsParentOf(Room? child, {bool spaceMode = false}) {
bool _canAddAsParentOf(Room? child) {
if (child == null || !child.isSpace) {
return _canIAddSpaceChild(child, spaceMode: spaceMode);
return _canIAddSpaceChild(child);
}
if (id == child.id) return false;
return !child._allSpaceChildRoomIds.contains(id);

View file

@ -60,9 +60,14 @@ extension EventsRoomExtension on Room {
future: () async {
final List<Room> children = await getChildRooms();
for (final Room child in children) {
await child.archive();
if (!child.isAnalyticsRoom) {
if (child.membership != Membership.join) {
child.join;
}
await child.archive();
}
}
await archive();
await _archive();
},
);
MatrixState.pangeaController.classController

View file

@ -109,10 +109,9 @@ extension PangeaRoom on Room {
List<String> get allSpaceChildRoomIds => _allSpaceChildRoomIds;
bool canAddAsParentOf(Room? child, {spaceMode = false}) =>
_canAddAsParentOf(child, spaceMode: spaceMode);
bool canAddAsParentOf(Room? child) => _canAddAsParentOf(child);
// space settings
// class_and_exchange_settings
DateTime? get rulesUpdatedAt => _rulesUpdatedAt;

View file

@ -66,18 +66,16 @@ extension RoomInformationRoomExtension on Room {
return (eventsDefaultPowerLevel ?? 0) >=
ClassDefaultValues.powerLevelOfAdmin;
}
int joinedRooms = 0;
for (final child in spaceChildren) {
if (child.roomId == null) continue;
final Room? room = client.getRoomById(child.roomId!);
if (room?.isLocked == false) {
if (room == null || room.isAnalyticsRoom || room.isArchived) continue;
if (!room._isLocked) {
return false;
}
if (room != null) {
joinedRooms += 1;
}
}
return joinedRooms > 0 ? true : false;
return (eventsDefaultPowerLevel ?? 0) >=
ClassDefaultValues.powerLevelOfAdmin;
}
bool _isAnalyticsRoomOfUser(String userId) =>

View file

@ -1,3 +1,4 @@
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:matrix/matrix.dart';
Future<void> lockRoom(Room room, Client client) async {
@ -65,7 +66,7 @@ Future<void> lockSpace(Room space, Client client) async {
continue;
}
}
if (child == null) continue;
if (child == null || child.isArchived || child.isAnalyticsRoom) continue;
child.isSpace
? await lockSpace(child, client)
: await lockChat(child, client);

View file

@ -15,14 +15,12 @@ class OverlayMessage extends StatelessWidget {
final Event? previousEvent;
final bool selected;
final Timeline timeline;
// #Pangea
// final LanguageModel? selectedDisplayLang;
final bool immersionMode;
// final bool definitions;
final bool ownMessage;
final ToolbarDisplayController toolbarController;
final double? width;
// Pangea#
const OverlayMessage(
this.event, {
@ -30,12 +28,10 @@ class OverlayMessage extends StatelessWidget {
this.previousEvent,
this.selected = false,
required this.timeline,
// #Pangea
required this.immersionMode,
required this.ownMessage,
required this.toolbarController,
this.width,
// Pangea#
super.key,
});
@ -46,14 +42,12 @@ class OverlayMessage extends StatelessWidget {
return const SizedBox.shrink();
}
var color = Theme.of(context).colorScheme.surfaceVariant;
// #Pangea
var color = Theme.of(context).colorScheme.surfaceContainerHighest;
final isLight = Theme.of(context).brightness == Brightness.light;
var lightness = isLight ? .05 : .85;
// Pangea#
final textColor = ownMessage
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onBackground;
: Theme.of(context).colorScheme.onSurface;
const hardCorner = Radius.circular(4);
@ -118,13 +112,11 @@ class OverlayMessage extends StatelessWidget {
: (color.blue * lightness).round(),
);
// #Pangea
final pangeaMessageEvent = PangeaMessageEvent(
event: event,
timeline: timeline,
ownMessage: ownMessage,
);
// Pangea#
return Material(
color: noBubble ? Colors.transparent : color,
@ -152,7 +144,7 @@ class OverlayMessage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MessageContent(
event,
event.getDisplayEvent(timeline),
textColor: textColor,
borderRadius: borderRadius,
selected: selected,
@ -162,13 +154,10 @@ class OverlayMessage extends StatelessWidget {
isOverlay: true,
),
if (event.hasAggregatedEvents(
timeline,
RelationshipTypes.edit,
) // #Pangea
||
(pangeaMessageEvent.showUseType)
// Pangea#
)
timeline,
RelationshipTypes.edit,
) ||
(pangeaMessageEvent.showUseType))
Padding(
padding: const EdgeInsets.only(
top: 4.0,
@ -176,7 +165,6 @@ class OverlayMessage extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// #Pangea
if (pangeaMessageEvent.showUseType) ...[
pangeaMessageEvent.useType.iconView(
context,
@ -188,14 +176,13 @@ class OverlayMessage extends StatelessWidget {
timeline,
RelationshipTypes.edit,
)) ...[
// Pangea#
Icon(
Icons.edit_outlined,
color: textColor.withAlpha(164),
size: 14,
),
Text(
' - ${event.originServerTs.localizedTimeShort(context)}',
' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}',
style: TextStyle(
color: textColor.withAlpha(164),
fontSize: 12,

View file

@ -140,10 +140,13 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
Widget getAddToSpaceToggleItem(int index) {
final Room possibleParent = possibleParents[index];
final bool canAdd = possibleParent.canAddAsParentOf(
room,
spaceMode: widget.spaceMode,
);
final bool canAdd = (room?.isSpace ?? false)
// Room is space
? possibleParent.isRoomAdmin &&
room!.isRoomAdmin &&
possibleParent.canAddAsParentOf(room)
// Room is null or chat
: possibleParent.canAddAsParentOf(room);
return Opacity(
opacity: canAdd ? 1 : 0.5,

View file

@ -70,12 +70,11 @@ class PangeaRichTextState extends State<PangeaRichText> {
void setTextSpan() {
if (_fetchingRepresentation == true) {
_setTextSpan(textSpan = widget.pangeaMessageEvent.body);
return;
}
if (repEvent != null) {
_setTextSpan(repEvent!.text);
_setTextSpan(
textSpan = widget.pangeaMessageEvent.event
.getDisplayEvent(widget.pangeaMessageEvent.timeline)
.body,
);
return;
}

View file

@ -2,7 +2,6 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
@ -29,7 +28,10 @@ abstract class PlatformInfos {
static bool get usesTouchscreen => !isMobile;
static bool get platformCanRecord => (isMobile || isMacOS);
// #Pangea
// static bool get platformCanRecord => (isMobile || isMacOS);
static bool get platformCanRecord => (isMobile || isMacOS || kIsWeb);
// Pangea#
static String get clientName =>
'${AppConfig.applicationName} ${isWeb ? 'web' : Platform.operatingSystem}${kReleaseMode ? '' : 'Debug'}';