mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Merge branch 'main' into feat/custom-date-range
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
|
||||
class AssetMarkerIcon extends StatelessWidget {
|
||||
const AssetMarkerIcon({required this.id, required this.thumbhash, super.key});
|
||||
|
||||
final String id;
|
||||
final String thumbhash;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final imageUrl = getThumbnailUrlForRemoteId(id);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final pinHeight = constraints.maxHeight * 0.14;
|
||||
final pinWidth = constraints.maxWidth * 0.14;
|
||||
return SizedOverflowBox(
|
||||
size: Size(pinWidth, pinHeight),
|
||||
child: Stack(
|
||||
// alignment: AlignmentGeometry.center,
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: constraints.maxWidth * 0.5,
|
||||
child: CustomPaint(
|
||||
painter: _PinPainter(
|
||||
primaryColor: context.colorScheme.onSurface,
|
||||
secondaryColor: context.colorScheme.surface,
|
||||
primaryRadius: constraints.maxHeight * 0.06,
|
||||
secondaryRadius: constraints.maxHeight * 0.038,
|
||||
),
|
||||
child: SizedBox(height: pinHeight, width: pinWidth),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: constraints.maxHeight * 0.07,
|
||||
left: constraints.maxWidth * 0.17,
|
||||
child: CircleAvatar(
|
||||
radius: constraints.maxHeight * 0.40,
|
||||
backgroundColor: context.colorScheme.onSurface,
|
||||
child: CircleAvatar(
|
||||
radius: constraints.maxHeight * 0.37,
|
||||
backgroundImage: RemoteImageProvider(url: imageUrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PinPainter extends CustomPainter {
|
||||
final Color primaryColor;
|
||||
final Color secondaryColor;
|
||||
final double primaryRadius;
|
||||
final double secondaryRadius;
|
||||
|
||||
const _PinPainter({
|
||||
required this.primaryColor,
|
||||
required this.secondaryColor,
|
||||
required this.primaryRadius,
|
||||
required this.secondaryRadius,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint primaryBrush = Paint()
|
||||
..color = primaryColor
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
Paint secondaryBrush = Paint()
|
||||
..color = secondaryColor
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
Paint lineBrush = Paint()
|
||||
..color = primaryColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2;
|
||||
|
||||
canvas.drawCircle(Offset(size.width / 2, size.height), primaryRadius, primaryBrush);
|
||||
canvas.drawCircle(Offset(size.width / 2, size.height), secondaryRadius, secondaryBrush);
|
||||
canvas.drawPath(getTrianglePath(size.width, size.height), primaryBrush);
|
||||
// The line is to make the above triangluar path more prominent since it has a slight curve
|
||||
canvas.drawLine(Offset(size.width / 2, 0), Offset(size.width / 2, size.height), lineBrush);
|
||||
}
|
||||
|
||||
Path getTrianglePath(double x, double y) {
|
||||
final firstEndPoint = Offset(x / 2, y);
|
||||
final controlPoint = Offset(x / 2, y * 0.3);
|
||||
final secondEndPoint = Offset(x, 0);
|
||||
|
||||
return Path()
|
||||
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, firstEndPoint.dx, firstEndPoint.dy)
|
||||
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, secondEndPoint.dx, secondEndPoint.dy)
|
||||
..lineTo(0, 0);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_PinPainter old) {
|
||||
return old.primaryColor != primaryColor || old.secondaryColor != secondaryColor;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
|
||||
import 'package:immich_mobile/widgets/map/map_theme_override.dart';
|
||||
import 'package:immich_mobile/widgets/map/positioned_asset_marker_icon.dart';
|
||||
import 'package:immich_mobile/widgets/map/asset_marker_icon.dart';
|
||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||
|
||||
/// A non-interactive thumbnail of a map in the given coordinates with optional markers
|
||||
@@ -45,21 +45,12 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final offsettedCentre = LatLng(centre.latitude + 0.002, centre.longitude);
|
||||
final controller = useRef<MapLibreMapController?>(null);
|
||||
final styleLoaded = useState(false);
|
||||
final position = useValueNotifier<Point<num>?>(null);
|
||||
|
||||
Future<void> onMapCreated(MapLibreMapController mapController) async {
|
||||
controller.value = mapController;
|
||||
styleLoaded.value = false;
|
||||
if (assetMarkerRemoteId != null) {
|
||||
// The iOS impl returns wrong toScreenLocation without the delay
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
() async => position.value = await mapController.toScreenLocation(centre),
|
||||
);
|
||||
}
|
||||
onCreated?.call(mapController);
|
||||
}
|
||||
|
||||
@@ -90,11 +81,11 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
alignment: AlignmentGeometry.topCenter,
|
||||
children: [
|
||||
style.widgetWhen(
|
||||
onData: (style) => MapLibreMap(
|
||||
initialCameraPosition: CameraPosition(target: offsettedCentre, zoom: zoom),
|
||||
initialCameraPosition: CameraPosition(target: centre, zoom: zoom),
|
||||
styleString: style,
|
||||
onMapCreated: onMapCreated,
|
||||
onStyleLoadedCallback: onStyleLoaded,
|
||||
@@ -109,17 +100,16 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
attributionButtonMargins: showAttribution == false ? const Point(-100, 0) : null,
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: position,
|
||||
builder: (_, value, __) => value != null && assetMarkerRemoteId != null && assetThumbhash != null
|
||||
? PositionedAssetMarkerIcon(
|
||||
size: height / 2,
|
||||
point: value,
|
||||
assetRemoteId: assetMarkerRemoteId!,
|
||||
assetThumbhash: assetThumbhash!,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
if (assetMarkerRemoteId != null && assetThumbhash != null)
|
||||
Container(
|
||||
width: width,
|
||||
height: height / 2,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SizedBox.square(
|
||||
dimension: height / 2.5,
|
||||
child: AssetMarkerIcon(id: assetMarkerRemoteId!, thumbhash: assetThumbhash!),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,8 +3,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||
import 'package:immich_mobile/widgets/map/asset_marker_icon.dart';
|
||||
|
||||
class PositionedAssetMarkerIcon extends StatelessWidget {
|
||||
final Point<num> point;
|
||||
@@ -36,106 +35,9 @@ class PositionedAssetMarkerIcon extends StatelessWidget {
|
||||
onTap: () => onTap?.call(),
|
||||
child: SizedBox.square(
|
||||
dimension: size,
|
||||
child: _AssetMarkerIcon(id: assetRemoteId, thumbhash: assetThumbhash, key: Key(assetRemoteId)),
|
||||
child: AssetMarkerIcon(id: assetRemoteId, thumbhash: assetThumbhash, key: Key(assetRemoteId)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AssetMarkerIcon extends StatelessWidget {
|
||||
const _AssetMarkerIcon({required this.id, required this.thumbhash, super.key});
|
||||
|
||||
final String id;
|
||||
final String thumbhash;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final imageUrl = getThumbnailUrlForRemoteId(id);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: constraints.maxWidth * 0.5,
|
||||
child: CustomPaint(
|
||||
painter: _PinPainter(
|
||||
primaryColor: context.colorScheme.onSurface,
|
||||
secondaryColor: context.colorScheme.surface,
|
||||
primaryRadius: constraints.maxHeight * 0.06,
|
||||
secondaryRadius: constraints.maxHeight * 0.038,
|
||||
),
|
||||
child: SizedBox(height: constraints.maxHeight * 0.14, width: constraints.maxWidth * 0.14),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: constraints.maxHeight * 0.07,
|
||||
left: constraints.maxWidth * 0.17,
|
||||
child: CircleAvatar(
|
||||
radius: constraints.maxHeight * 0.40,
|
||||
backgroundColor: context.colorScheme.onSurface,
|
||||
child: CircleAvatar(
|
||||
radius: constraints.maxHeight * 0.37,
|
||||
backgroundImage: RemoteImageProvider(url: imageUrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PinPainter extends CustomPainter {
|
||||
final Color primaryColor;
|
||||
final Color secondaryColor;
|
||||
final double primaryRadius;
|
||||
final double secondaryRadius;
|
||||
|
||||
const _PinPainter({
|
||||
required this.primaryColor,
|
||||
required this.secondaryColor,
|
||||
required this.primaryRadius,
|
||||
required this.secondaryRadius,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint primaryBrush = Paint()
|
||||
..color = primaryColor
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
Paint secondaryBrush = Paint()
|
||||
..color = secondaryColor
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
Paint lineBrush = Paint()
|
||||
..color = primaryColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2;
|
||||
|
||||
canvas.drawCircle(Offset(size.width / 2, size.height), primaryRadius, primaryBrush);
|
||||
canvas.drawCircle(Offset(size.width / 2, size.height), secondaryRadius, secondaryBrush);
|
||||
canvas.drawPath(getTrianglePath(size.width, size.height), primaryBrush);
|
||||
// The line is to make the above triangluar path more prominent since it has a slight curve
|
||||
canvas.drawLine(Offset(size.width / 2, 0), Offset(size.width / 2, size.height), lineBrush);
|
||||
}
|
||||
|
||||
Path getTrianglePath(double x, double y) {
|
||||
final firstEndPoint = Offset(x / 2, y);
|
||||
final controlPoint = Offset(x / 2, y * 0.3);
|
||||
final secondEndPoint = Offset(x, 0);
|
||||
|
||||
return Path()
|
||||
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, firstEndPoint.dx, firstEndPoint.dy)
|
||||
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, secondEndPoint.dx, secondEndPoint.dy)
|
||||
..lineTo(0, 0);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_PinPainter old) {
|
||||
return old.primaryColor != primaryColor || old.secondaryColor != secondaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user