mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 18:08:40 +00:00
starting with #30
This commit is contained in:
parent
3a9234740e
commit
9edb22e4f4
2 changed files with 209 additions and 2 deletions
194
lib/src/components/zoom_selector.dart
Normal file
194
lib/src/components/zoom_selector.dart
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ZoomSelector extends StatefulWidget {
|
||||||
|
final CameraState state;
|
||||||
|
|
||||||
|
const ZoomSelector({
|
||||||
|
super.key,
|
||||||
|
required this.state,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ZoomSelector> createState() => _ZoomSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ZoomSelectorState extends State<ZoomSelector> {
|
||||||
|
double? minZoom;
|
||||||
|
double? maxZoom;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
initAsync() async {
|
||||||
|
minZoom = await CamerawesomePlugin.getMinZoom();
|
||||||
|
maxZoom = await CamerawesomePlugin.getMaxZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder<SensorConfig>(
|
||||||
|
stream: widget.state.sensorConfig$,
|
||||||
|
builder: (context, sensorConfigSnapshot) {
|
||||||
|
initAsync();
|
||||||
|
if (sensorConfigSnapshot.data == null ||
|
||||||
|
minZoom == null ||
|
||||||
|
maxZoom == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return StreamBuilder<double>(
|
||||||
|
stream: sensorConfigSnapshot.requireData.zoom$,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return _ZoomIndicatorLayout(
|
||||||
|
zoom: snapshot.requireData,
|
||||||
|
min: minZoom!,
|
||||||
|
max: maxZoom!,
|
||||||
|
sensorConfig: widget.state.sensorConfig,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ZoomIndicatorLayout extends StatelessWidget {
|
||||||
|
final double zoom;
|
||||||
|
final double min;
|
||||||
|
final double max;
|
||||||
|
final SensorConfig sensorConfig;
|
||||||
|
|
||||||
|
const _ZoomIndicatorLayout({
|
||||||
|
required this.zoom,
|
||||||
|
required this.min,
|
||||||
|
required this.max,
|
||||||
|
required this.sensorConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final displayZoom = (max - min) * zoom + min;
|
||||||
|
if (min == 1.0) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Show 3 dots for zooming: min, 1.0X and max zoom. The closer one shows
|
||||||
|
// text, the other ones a dot.
|
||||||
|
_ZoomIndicator(
|
||||||
|
normalValue: 0.0,
|
||||||
|
zoom: zoom,
|
||||||
|
selected: displayZoom < 1.0,
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
sensorConfig: sensorConfig,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: _ZoomIndicator(
|
||||||
|
normalValue: 0.5,
|
||||||
|
zoom: zoom,
|
||||||
|
selected: !(displayZoom < 1.0 || displayZoom == max),
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
sensorConfig: sensorConfig,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_ZoomIndicator(
|
||||||
|
normalValue: 1.0,
|
||||||
|
zoom: zoom,
|
||||||
|
selected: displayZoom == max,
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
sensorConfig: sensorConfig,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ZoomIndicator extends StatelessWidget {
|
||||||
|
final double zoom;
|
||||||
|
final double min;
|
||||||
|
final double max;
|
||||||
|
final double normalValue;
|
||||||
|
final SensorConfig sensorConfig;
|
||||||
|
final bool selected;
|
||||||
|
|
||||||
|
const _ZoomIndicator({
|
||||||
|
required this.zoom,
|
||||||
|
required this.min,
|
||||||
|
required this.max,
|
||||||
|
required this.normalValue,
|
||||||
|
required this.sensorConfig,
|
||||||
|
required this.selected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final baseTheme = AwesomeThemeProvider.of(context).theme;
|
||||||
|
final baseButtonTheme = baseTheme.buttonTheme;
|
||||||
|
final displayZoom = (max - min) * zoom + min;
|
||||||
|
Widget content = AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
transitionBuilder: (child, anim) {
|
||||||
|
return ScaleTransition(scale: anim, child: child);
|
||||||
|
},
|
||||||
|
child: selected
|
||||||
|
? AwesomeBouncingWidget(
|
||||||
|
key: ValueKey("zoomIndicator_${normalValue}_selected"),
|
||||||
|
onTap: () {
|
||||||
|
sensorConfig.setZoom(normalValue);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
padding: const EdgeInsets.all(0.0),
|
||||||
|
child: AwesomeCircleWidget(
|
||||||
|
theme: baseTheme,
|
||||||
|
child: Text(
|
||||||
|
"${displayZoom.toStringAsFixed(1)}X",
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: AwesomeBouncingWidget(
|
||||||
|
key: ValueKey("zoomIndicator_${normalValue}_unselected"),
|
||||||
|
onTap: () {
|
||||||
|
sensorConfig.setZoom(normalValue);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: AwesomeCircleWidget(
|
||||||
|
theme: baseTheme.copyWith(
|
||||||
|
buttonTheme: baseButtonTheme.copyWith(
|
||||||
|
backgroundColor: baseButtonTheme.foregroundColor,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const SizedBox(width: 6, height: 6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Same width for each dot to keep them in their position
|
||||||
|
return SizedBox(
|
||||||
|
width: 56,
|
||||||
|
child: Center(
|
||||||
|
child: content,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:camerawesome/camerawesome_plugin.dart';
|
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:twonly/src/components/zoom_selector.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/components/image_editor/action_button.dart';
|
import 'package:twonly/src/components/image_editor/action_button.dart';
|
||||||
import 'package:twonly/src/components/media_view_sizing.dart';
|
import 'package:twonly/src/components/media_view_sizing.dart';
|
||||||
|
|
@ -65,7 +66,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
previewAlignment: Alignment.topLeft,
|
previewAlignment: Alignment.topLeft,
|
||||||
sensorConfig: SensorConfig.single(
|
sensorConfig: SensorConfig.single(
|
||||||
aspectRatio: CameraAspectRatios.ratio_16_9,
|
aspectRatio: CameraAspectRatios.ratio_16_9,
|
||||||
zoom: 0.07,
|
zoom: 0.5,
|
||||||
),
|
),
|
||||||
previewFit: CameraPreviewFit.contain,
|
previewFit: CameraPreviewFit.contain,
|
||||||
progressIndicator: Container(),
|
progressIndicator: Container(),
|
||||||
|
|
@ -138,11 +139,19 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onPanStart: (details) async {
|
onPanStart: (details) async {
|
||||||
|
if (cameraState.sensorConfig.sensors.first.position ==
|
||||||
|
SensorPosition.front) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_basePanY = details.localPosition.dy;
|
_basePanY = details.localPosition.dy;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPanUpdate: (details) async {
|
onPanUpdate: (details) async {
|
||||||
|
if (cameraState.sensorConfig.sensors.first.position ==
|
||||||
|
SensorPosition.front) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var diff = _basePanY - details.localPosition.dy;
|
var diff = _basePanY - details.localPosition.dy;
|
||||||
if (diff > 200) diff = 200;
|
if (diff > 200) diff = 200;
|
||||||
if (diff < 0) diff = 0;
|
if (diff < 0) diff = 0;
|
||||||
|
|
@ -156,9 +165,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDoubleTap: () async {
|
onDoubleTap: () async {
|
||||||
|
bool isFront =
|
||||||
|
cameraState.sensorConfig.sensors.first.position ==
|
||||||
|
SensorPosition.front;
|
||||||
cameraState.switchCameraSensor(
|
cameraState.switchCameraSensor(
|
||||||
aspectRatio: CameraAspectRatios.ratio_16_9,
|
aspectRatio: CameraAspectRatios.ratio_16_9,
|
||||||
flash: isFlashOn ? FlashMode.on : FlashMode.none,
|
flash: isFlashOn ? FlashMode.on : FlashMode.none,
|
||||||
|
zoom: isFront ? 0.5 : 0,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -222,7 +235,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
AwesomeZoomSelector(state: cameraState),
|
ZoomSelector(state: cameraState),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue