refactor: Pages folder structure

This commit is contained in:
Krille Fear 2021-11-09 21:32:16 +01:00
commit 1abb7310f3
88 changed files with 188 additions and 250 deletions

View file

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:fluffychat/pages/new_private_chat/new_private_chat_view.dart';
import 'package:fluffychat/pages/new_private_chat/qr_scanner_modal.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
import 'package:fluffychat/utils/url_launcher.dart';
import 'package:fluffychat/widgets/matrix.dart';
class NewPrivateChat extends StatefulWidget {
const NewPrivateChat({Key key}) : super(key: key);
@override
NewPrivateChatController createState() => NewPrivateChatController();
}
class NewPrivateChatController extends State<NewPrivateChat> {
TextEditingController controller = TextEditingController();
final formKey = GlobalKey<FormState>();
bool loading = false;
static const Set<String> supportedSigils = {'@', '!', '#'};
static const String prefix = 'https://matrix.to/#/';
void submitAction([_]) async {
controller.text = controller.text.trim();
if (!formKey.currentState.validate()) return;
UrlLauncher(context, '$prefix${controller.text}').openMatrixToUrl();
}
String validateForm(String value) {
if (value.isEmpty) {
return L10n.of(context).pleaseEnterAMatrixIdentifier;
}
if (!controller.text.isValidMatrixId ||
!supportedSigils.contains(controller.text.sigil)) {
return L10n.of(context).makeSureTheIdentifierIsValid;
}
if (controller.text == Matrix.of(context).client.userID) {
return L10n.of(context).youCannotInviteYourself;
}
return null;
}
void inviteAction() => FluffyShare.share(
'https://matrix.to/#/${Matrix.of(context).client.userID}',
context,
);
void openScannerAction() async {
final status = await Permission.camera.request();
if (!status.isGranted) return;
await showModalBottomSheet(
context: context,
useRootNavigator: false,
//useSafeArea: false,
builder: (_) => const QrScannerModal(),
);
}
@override
Widget build(BuildContext context) => NewPrivateChatView(this);
}

View file

@ -0,0 +1,117 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:vrouter/vrouter.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/new_private_chat/new_private_chat.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
import 'package:fluffychat/widgets/matrix.dart';
class NewPrivateChatView extends StatelessWidget {
final NewPrivateChatController controller;
const NewPrivateChatView(this.controller, {Key key}) : super(key: key);
static const double _qrCodePadding = 8;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: const BackButton(),
title: Text(L10n.of(context).newChat),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
elevation: 0,
actions: [
TextButton(
onPressed: () => VRouter.of(context).to('/newgroup'),
child: Text(
L10n.of(context).createNewGroup,
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
),
)
],
),
body: MaxWidthBody(
child: ListView(
children: [
Container(
margin: const EdgeInsets.all(_qrCodePadding),
alignment: Alignment.center,
padding: const EdgeInsets.all(_qrCodePadding * 2),
child: InkWell(
onTap: controller.inviteAction,
borderRadius: BorderRadius.circular(12),
child: Material(
borderRadius: BorderRadius.circular(12),
elevation: 4,
color: Colors.white,
child: QrImage(
data:
'https://matrix.to/#/${Matrix.of(context).client.userID}',
version: QrVersions.auto,
size: min(MediaQuery.of(context).size.width - 16, 200),
embeddedImage: const AssetImage('assets/share.png'),
embeddedImageStyle: QrEmbeddedImageStyle(
size: const Size(48, 48),
),
),
),
),
),
const Divider(),
ListTile(
subtitle: Text(L10n.of(context).createNewChatExplaination),
trailing: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.info_outline_rounded),
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(12),
child: Form(
key: controller.formKey,
child: TextFormField(
controller: controller.controller,
autocorrect: false,
textInputAction: TextInputAction.go,
onFieldSubmitted: controller.submitAction,
validator: controller.validateForm,
decoration: InputDecoration(
labelText: L10n.of(context).typeInInviteLinkManually,
hintText: '@username',
prefixText: 'https://matrix.to/#/',
suffixIcon: IconButton(
icon: const Icon(Icons.send_outlined),
onPressed: controller.submitAction,
),
),
),
),
),
Center(
child: Image.asset(
'assets/private_chat_wallpaper.png',
width: min(AppConfig.columnWidth - _qrCodePadding * 6,
MediaQuery.of(context).size.width - _qrCodePadding * 6),
),
),
],
),
),
floatingActionButton: PlatformInfos.isMobile
? FloatingActionButton.extended(
onPressed: controller.openScannerAction,
label: Text(L10n.of(context).scanQrCode),
icon: const Icon(Icons.camera_alt_outlined),
)
: null,
);
}
}

View file

@ -0,0 +1,75 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:fluffychat/utils/url_launcher.dart';
class QrScannerModal extends StatefulWidget {
const QrScannerModal({Key key}) : super(key: key);
@override
_QrScannerModalState createState() => _QrScannerModalState();
}
class _QrScannerModalState extends State<QrScannerModal> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
QRViewController controller;
@override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller.pauseCamera();
} else if (Platform.isIOS) {
controller.resumeCamera();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.close_outlined),
onPressed: Navigator.of(context).pop,
tooltip: L10n.of(context).close,
),
title: Text(L10n.of(context).scanQrCode),
),
body: Stack(
children: [
QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Theme.of(context).primaryColor,
borderRadius: 10,
borderLength: 30,
borderWidth: 8,
),
),
],
),
);
}
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
StreamSubscription sub;
sub = controller.scannedDataStream.listen((scanData) {
sub.cancel();
Navigator.of(context).pop();
UrlLauncher(context, scanData.code).openMatrixToUrl();
});
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
}

View file

@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import '../../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart';
import '../../utils/resize_image.dart';
import '../../utils/room_send_file_extension.dart';
class SendFileDialog extends StatefulWidget {
final Room room;
final MatrixFile file;
const SendFileDialog({
this.room,
this.file,
Key key,
}) : super(key: key);
@override
_SendFileDialogState createState() => _SendFileDialogState();
}
class _SendFileDialogState extends State<SendFileDialog> {
bool origImage = false;
bool _isSending = false;
static const maxWidth = 1600;
Future<void> _send() async {
var file = widget.file;
if (file is MatrixImageFile && !origImage) {
try {
file = await resizeImage(file, max: maxWidth);
} catch (e) {
// couldn't resize
}
}
await widget.room.sendFileEventWithThumbnail(file);
}
@override
Widget build(BuildContext context) {
var sendStr = L10n.of(context).sendFile;
if (widget.file is MatrixImageFile) {
sendStr = L10n.of(context).sendImage;
} else if (widget.file is MatrixAudioFile) {
sendStr = L10n.of(context).sendAudio;
} else if (widget.file is MatrixVideoFile) {
sendStr = L10n.of(context).sendVideo;
}
Widget contentWidget;
if (widget.file is MatrixImageFile) {
contentWidget = Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Flexible(
child: Image.memory(
widget.file.bytes,
fit: BoxFit.contain,
),
),
Text(widget.file.name),
Row(
children: <Widget>[
Checkbox(
value: origImage,
onChanged: (v) => setState(() => origImage = v),
),
InkWell(
onTap: () => setState(() => origImage = !origImage),
child: Text(L10n.of(context).sendOriginal +
' (${widget.file.sizeString})'),
),
],
)
]);
} else {
contentWidget = Text('${widget.file.name} (${widget.file.sizeString})');
}
return AlertDialog(
title: Text(sendStr),
content: contentWidget,
actions: <Widget>[
TextButton(
onPressed: () {
// just close the dialog
Navigator.of(context, rootNavigator: false).pop();
},
child: Text(L10n.of(context).cancel),
),
TextButton(
onPressed: _isSending
? null
: () async {
setState(() {
_isSending = true;
});
await showFutureLoadingDialog(
context: context, future: () => _send());
Navigator.of(context, rootNavigator: false).pop();
},
child: Text(L10n.of(context).send),
),
],
);
}
}

View file

@ -0,0 +1,146 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:geolocator/geolocator.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat/events/map_bubble.dart';
import 'package:fluffychat/utils/platform_infos.dart';
class SendLocationDialog extends StatefulWidget {
final Room room;
const SendLocationDialog({
this.room,
Key key,
}) : super(key: key);
@override
_SendLocationDialogState createState() => _SendLocationDialogState();
}
class _SendLocationDialogState extends State<SendLocationDialog> {
bool disabled = false;
bool denied = false;
bool isSending = false;
Position position;
Error error;
@override
void initState() {
super.initState();
requestLocation();
}
Future<void> requestLocation() async {
if (!(await Geolocator.isLocationServiceEnabled())) {
setState(() => disabled = true);
return;
}
var permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
setState(() => denied = true);
return;
}
}
if (permission == LocationPermission.deniedForever) {
setState(() => denied = true);
return;
}
try {
Position _position;
try {
_position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.best,
timeLimit: const Duration(seconds: 30),
);
} on TimeoutException {
_position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.medium,
timeLimit: const Duration(seconds: 30),
);
}
setState(() => position = _position);
} catch (e) {
setState(() => error = e);
}
}
void sendAction() async {
setState(() => isSending = true);
final body =
'https://www.openstreetmap.org/?mlat=${position.latitude}&mlon=${position.longitude}#map=16/${position.latitude}/${position.longitude}';
final uri =
'geo:${position.latitude},${position.longitude};u=${position.accuracy}';
await showFutureLoadingDialog(
context: context,
future: () => widget.room.sendLocation(body, uri),
);
Navigator.of(context, rootNavigator: false).pop();
}
@override
Widget build(BuildContext context) {
Widget contentWidget;
if (position != null) {
contentWidget = MapBubble(
latitude: position.latitude,
longitude: position.longitude,
);
} else if (disabled) {
contentWidget = Text(L10n.of(context).locationDisabledNotice);
} else if (denied) {
contentWidget = Text(L10n.of(context).locationPermissionDeniedNotice);
} else if (error != null) {
contentWidget =
Text(L10n.of(context).errorObtainingLocation(error.toString()));
} else {
contentWidget = Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const CupertinoActivityIndicator(),
const SizedBox(width: 12),
Text(L10n.of(context).obtainingLocation),
],
);
}
if (PlatformInfos.isCupertinoStyle) {
return CupertinoAlertDialog(
title: Text(L10n.of(context).shareLocation),
content: contentWidget,
actions: [
CupertinoDialogAction(
onPressed: Navigator.of(context, rootNavigator: false).pop,
child: Text(L10n.of(context).cancel),
),
CupertinoDialogAction(
onPressed: isSending ? null : sendAction,
child: Text(L10n.of(context).send),
),
],
);
}
return AlertDialog(
title: Text(L10n.of(context).shareLocation),
content: contentWidget,
actions: [
TextButton(
onPressed: Navigator.of(context, rootNavigator: false).pop,
child: Text(L10n.of(context).cancel),
),
if (position != null)
TextButton(
onPressed: isSending ? null : sendAction,
child: Text(L10n.of(context).send),
),
],
);
}
}