image preview bevor sending

This commit is contained in:
otsmr 2025-01-24 11:04:46 +01:00
parent 50efa6530f
commit b694b9d7fc
8 changed files with 261 additions and 60 deletions

View file

@ -34,6 +34,7 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT. https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

View file

@ -8,6 +8,9 @@
"registerSubmitButton": "Register now!", "registerSubmitButton": "Register now!",
"newMessageTitle": "New message", "newMessageTitle": "New message",
"chatsTitle": "Chats", "chatsTitle": "Chats",
"shareImagedEditorSendImage": "Send",
"shareImagedEditorSaveImage": "Save",
"shareImagedEditorSavedImage": "Saved",
"searchUsernameInput": "Username", "searchUsernameInput": "Username",
"searchUsernameTitle": "Search username", "searchUsernameTitle": "Search username",
"searchUsernameNotFound": "Username not found", "searchUsernameNotFound": "Username not found",

View file

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:gal/gal.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:twonly/main.dart'; import 'package:twonly/main.dart';
import 'package:twonly/src/model/contacts_model.dart'; import 'package:twonly/src/model/contacts_model.dart';
@ -25,6 +26,19 @@ Future<bool> isUserCreated() async {
return true; return true;
} }
Future<String?> saveImageToGallery(path) async {
final hasAccess = await Gal.hasAccess();
if (!hasAccess) {
await Gal.requestAccess();
}
try {
await Gal.putImage(path);
return null;
} on GalException catch (e) {
return e.type.message;
}
}
Future<UserData?> getUser() async { Future<UserData?> getUser() async {
final storage = getSecureStorage(); final storage = getSecureStorage();
String? userJson = await storage.read(key: "user_data"); String? userJson = await storage.read(key: "user_data");

View file

@ -3,7 +3,37 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:camerawesome/camerawesome_plugin.dart'; import 'package:camerawesome/camerawesome_plugin.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/views/share_image_view.dart'; import 'package:twonly/src/views/permissions_view.dart';
import 'package:twonly/src/views/share_image_editor_view.dart';
class CameraPreviewViewPermission extends StatefulWidget {
const CameraPreviewViewPermission({super.key});
@override
State<CameraPreviewViewPermission> createState() =>
_CameraPreviewViewPermission();
}
class _CameraPreviewViewPermission extends State<CameraPreviewViewPermission> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: checkPermissions(),
builder: (context, snap) {
if (snap.hasData) {
if (snap.data!) {
return CameraPreviewView();
} else {
return PermissionHandlerView(onSuccess: () {
setState(() {});
});
}
} else {
return const CircularProgressIndicator();
}
});
}
}
class CameraPreviewView extends StatefulWidget { class CameraPreviewView extends StatefulWidget {
const CameraPreviewView({super.key}); const CameraPreviewView({super.key});
@ -15,6 +45,7 @@ class CameraPreviewView extends StatefulWidget {
class _CameraPreviewViewState extends State<CameraPreviewView> { class _CameraPreviewViewState extends State<CameraPreviewView> {
double _lastZoom = 1; double _lastZoom = 1;
double _basePanY = 0; double _basePanY = 0;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override @override
void initState() { void initState() {
@ -23,11 +54,16 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Container(
padding: EdgeInsets.only(top: 50, bottom: 30, left: 5, right: 5), padding: EdgeInsets.symmetric(vertical: 50, horizontal: 0),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
child: CameraAwesomeBuilder.custom( child: CameraAwesomeBuilder.custom(
sensorConfig: SensorConfig.single(
aspectRatio: CameraAspectRatios.ratio_16_9,
zoom: 0.07,
),
previewFit: CameraPreviewFit.contain,
progressIndicator: Container(), progressIndicator: Container(),
onMediaCaptureEvent: (event) { onMediaCaptureEvent: (event) {
switch ((event.status, event.isPicture, event.isVideo)) { switch ((event.status, event.isPicture, event.isVideo)) {
@ -40,9 +76,16 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (path == null) return; if (path == null) return;
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( PageRouteBuilder(
builder: (context) => ShareImageView(image: path), opaque: false,
), pageBuilder: (context, a1, a2) =>
ShareImageEditorView(image: path),
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return child;
},
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero),
); );
debugPrint('Picture saved: ${path}'); debugPrint('Picture saved: ${path}');
}, },
@ -93,6 +136,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (tmp != _lastZoom) { if (tmp != _lastZoom) {
cameraState.sensorConfig.setZoom(tmp); cameraState.sensorConfig.setZoom(tmp);
setState(() { setState(() {
print(tmp);
_lastZoom = tmp; _lastZoom = tmp;
}); });
} }
@ -161,7 +205,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
// ); // );
}, },
), ),
previewPadding: const EdgeInsets.all(10),
// onPreviewTapBuilder: (state) => OnPreviewTap( // onPreviewTapBuilder: (state) => OnPreviewTap(
// onTap: (Offset position, PreviewSize flutterPreviewSize, // onTap: (Offset position, PreviewSize flutterPreviewSize,
// PreviewSize pixelPreviewSize) { // PreviewSize pixelPreviewSize) {

View file

@ -1,6 +1,5 @@
import 'camera_preview_view.dart'; import 'camera_preview_view.dart';
import 'chat_list_view.dart'; import 'chat_list_view.dart';
import 'permissions_view.dart';
import 'profile_view.dart'; import 'profile_view.dart';
import '../settings/settings_controller.dart'; import '../settings/settings_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -18,11 +17,6 @@ class HomeViewState extends State<HomeView> {
final PageController _pageController = PageController(initialPage: 0); final PageController _pageController = PageController(initialPage: 0);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FutureBuilder(
future: checkPermissions(),
builder: (context, snap) {
if (snap.hasData) {
if (snap.data!) {
return Scaffold( return Scaffold(
body: PageView( body: PageView(
controller: _pageController, controller: _pageController,
@ -33,23 +27,22 @@ class HomeViewState extends State<HomeView> {
}, },
children: [ children: [
ChatListView(), ChatListView(),
CameraPreviewView(), CameraPreviewViewPermission(),
ProfileView(settingsController: widget.settingsController) ProfileView(settingsController: widget.settingsController)
], ],
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
showSelectedLabels: false, showSelectedLabels: false,
showUnselectedLabels: false, showUnselectedLabels: false,
selectedIconTheme: IconThemeData( selectedIconTheme:
color: const Color.fromARGB(255, 255, 255, 255)), IconThemeData(color: const Color.fromARGB(255, 255, 255, 255)),
items: [ items: [
BottomNavigationBarItem(icon: Icon(Icons.chat), label: ""), BottomNavigationBarItem(icon: Icon(Icons.chat), label: ""),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.camera_alt), icon: Icon(Icons.camera_alt),
label: "", label: "",
), ),
BottomNavigationBarItem( BottomNavigationBarItem(icon: Icon(Icons.verified_user), label: ""),
icon: Icon(Icons.verified_user), label: ""),
], ],
onTap: (int index) { onTap: (int index) {
setState(() { setState(() {
@ -62,13 +55,5 @@ class HomeViewState extends State<HomeView> {
currentIndex: _activePageIdx, currentIndex: _activePageIdx,
), ),
); );
} else {
return PermissionHandlerView(onSuccess: () {
setState(() {});
});
}
}
return const CircularProgressIndicator();
});
} }
} }

View file

@ -0,0 +1,146 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:twonly/src/utils.dart';
import 'package:twonly/src/views/share_image_view.dart';
class ShareImageEditorView extends StatefulWidget {
const ShareImageEditorView({super.key, required this.image});
final String image;
@override
State<ShareImageEditorView> createState() => _ShareImageEditorView();
}
class _ShareImageEditorView extends State<ShareImageEditorView> {
bool _isImageLoaded = false;
bool _imageSaved = false;
@override
void initState() {
// TODO: implement initState
super.initState();
imageIsLoaded();
}
Future imageIsLoaded() async {
Future.delayed(Duration(milliseconds: 600), () {
setState(() {
_isImageLoaded = true;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _isImageLoaded
? Theme.of(context).colorScheme.surface
: Colors.white.withAlpha(0),
body: Stack(
fit: StackFit.expand,
children: [
Positioned(
top: 0,
// bottom: 0,
left: 0,
right: 0,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 50),
child: ClipRRect(
borderRadius: BorderRadius.circular(22),
child: Image.file(
File(widget.image),
fit: BoxFit.contain,
),
),
),
),
_isImageLoaded
? Positioned(
left: 10,
top: 60,
child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(
Icons.close,
size: 30,
),
color: Colors.white,
onPressed: () async {
Navigator.pop(context);
},
),
],
),
)
: Container(),
_isImageLoaded
? Positioned(
bottom: 70,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton.icon(
icon: _imageSaved
? Icon(Icons.check)
: Icon(Icons.save_rounded),
style: OutlinedButton.styleFrom(
iconColor: _imageSaved
? Theme.of(context).colorScheme.outline
: Theme.of(context).colorScheme.primary,
foregroundColor: _imageSaved
? Theme.of(context).colorScheme.outline
: Theme.of(context).colorScheme.primary,
),
onPressed: () async {
if (_imageSaved) return;
final res = await saveImageToGallery(widget.image);
if (res == null) {
setState(() {
_imageSaved = true;
});
}
},
label: Text(_imageSaved
? AppLocalizations.of(context)!
.shareImagedEditorSavedImage
: AppLocalizations.of(context)!
.shareImagedEditorSaveImage),
),
const SizedBox(width: 20),
FilledButton.icon(
icon: Icon(Icons.send),
onPressed: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ShareImageView(image: widget.image)),
);
},
style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>(
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
),
),
label: Text(
AppLocalizations.of(context)!
.shareImagedEditorSendImage,
style: TextStyle(fontSize: 17),
),
),
],
),
)
: Container(),
],
),
);
}
}

View file

@ -450,6 +450,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
gal:
dependency: "direct main"
description:
name: gal
sha256: "2771519c8b29f784d5e27f4efc2667667eef51c6c47cccaa0435a8fe8aa208e4"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
glob: glob:
dependency: transitive dependency: transitive
description: description:

View file

@ -19,6 +19,7 @@ dependencies:
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_secure_storage: ^9.2.2 flutter_secure_storage: ^9.2.2
gal: ^2.3.1
google_fonts: ^6.2.1 google_fonts: ^6.2.1
image: ^4.3.0 image: ^4.3.0
intl: any intl: any