mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
image preview bevor sending
This commit is contained in:
parent
50efa6530f
commit
b694b9d7fc
8 changed files with 261 additions and 60 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
146
lib/src/views/share_image_editor_view.dart
Normal file
146
lib/src/views/share_image_editor_view.dart
Normal 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(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue