Custom radius (#1039)
This commit is contained in:
commit
299d1bb117
|
@ -60,8 +60,8 @@ const publicLinkDeviceLimits = [50, 25, 10, 5, 2, 1];
|
||||||
|
|
||||||
const kilometersPerDegree = 111.16;
|
const kilometersPerDegree = 111.16;
|
||||||
|
|
||||||
const radiusValues = <int>[1, 2, 10, 20, 40, 80, 200, 400, 1200];
|
const defaultRadiusValues = <double>[1, 2, 10, 20, 40, 80, 200, 400, 1200];
|
||||||
|
|
||||||
const defaultRadiusValueIndex = 4;
|
const defaultRadiusValue = 40.0;
|
||||||
|
|
||||||
const galleryGridSpacing = 2.0;
|
const galleryGridSpacing = 2.0;
|
||||||
|
|
|
@ -1399,7 +1399,8 @@ class FilesDB {
|
||||||
// user and upload time is greater than 20 April 2023 epoch time and less than
|
// user and upload time is greater than 20 April 2023 epoch time and less than
|
||||||
// 15 May 2023 epoch time
|
// 15 May 2023 epoch time
|
||||||
Future<List<String>> getFilesWithLocationUploadedBtw20AprTo15May2023(
|
Future<List<String>> getFilesWithLocationUploadedBtw20AprTo15May2023(
|
||||||
int ownerID) async {
|
int ownerID,
|
||||||
|
) async {
|
||||||
final db = await database;
|
final db = await database;
|
||||||
final result = await db.query(
|
final result = await db.query(
|
||||||
filesTable,
|
filesTable,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import "package:photos/core/constants.dart";
|
|
||||||
import 'package:photos/models/location/location.dart';
|
import 'package:photos/models/location/location.dart';
|
||||||
|
|
||||||
part 'location_tag.freezed.dart';
|
part 'location_tag.freezed.dart';
|
||||||
|
@ -10,7 +9,7 @@ class LocationTag with _$LocationTag {
|
||||||
const LocationTag._();
|
const LocationTag._();
|
||||||
const factory LocationTag({
|
const factory LocationTag({
|
||||||
required String name,
|
required String name,
|
||||||
required int radius,
|
required double radius,
|
||||||
required double aSquare,
|
required double aSquare,
|
||||||
required double bSquare,
|
required double bSquare,
|
||||||
required Location centerPoint,
|
required Location centerPoint,
|
||||||
|
@ -19,7 +18,7 @@ class LocationTag with _$LocationTag {
|
||||||
factory LocationTag.fromJson(Map<String, Object?> json) =>
|
factory LocationTag.fromJson(Map<String, Object?> json) =>
|
||||||
_$LocationTagFromJson(json);
|
_$LocationTagFromJson(json);
|
||||||
|
|
||||||
int get radiusIndex {
|
int radiusIndex(List<double> radiusValues) {
|
||||||
return radiusValues.indexOf(radius);
|
return radiusValues.indexOf(radius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ LocationTag _$LocationTagFromJson(Map<String, dynamic> json) {
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$LocationTag {
|
mixin _$LocationTag {
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name => throw _privateConstructorUsedError;
|
||||||
int get radius => throw _privateConstructorUsedError;
|
double get radius => throw _privateConstructorUsedError;
|
||||||
double get aSquare => throw _privateConstructorUsedError;
|
double get aSquare => throw _privateConstructorUsedError;
|
||||||
double get bSquare => throw _privateConstructorUsedError;
|
double get bSquare => throw _privateConstructorUsedError;
|
||||||
Location get centerPoint => throw _privateConstructorUsedError;
|
Location get centerPoint => throw _privateConstructorUsedError;
|
||||||
|
@ -40,7 +40,7 @@ abstract class $LocationTagCopyWith<$Res> {
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{String name,
|
{String name,
|
||||||
int radius,
|
double radius,
|
||||||
double aSquare,
|
double aSquare,
|
||||||
double bSquare,
|
double bSquare,
|
||||||
Location centerPoint});
|
Location centerPoint});
|
||||||
|
@ -75,7 +75,7 @@ class _$LocationTagCopyWithImpl<$Res, $Val extends LocationTag>
|
||||||
radius: null == radius
|
radius: null == radius
|
||||||
? _value.radius
|
? _value.radius
|
||||||
: radius // ignore: cast_nullable_to_non_nullable
|
: radius // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as double,
|
||||||
aSquare: null == aSquare
|
aSquare: null == aSquare
|
||||||
? _value.aSquare
|
? _value.aSquare
|
||||||
: aSquare // ignore: cast_nullable_to_non_nullable
|
: aSquare // ignore: cast_nullable_to_non_nullable
|
||||||
|
@ -110,7 +110,7 @@ abstract class _$$_LocationTagCopyWith<$Res>
|
||||||
@useResult
|
@useResult
|
||||||
$Res call(
|
$Res call(
|
||||||
{String name,
|
{String name,
|
||||||
int radius,
|
double radius,
|
||||||
double aSquare,
|
double aSquare,
|
||||||
double bSquare,
|
double bSquare,
|
||||||
Location centerPoint});
|
Location centerPoint});
|
||||||
|
@ -144,7 +144,7 @@ class __$$_LocationTagCopyWithImpl<$Res>
|
||||||
radius: null == radius
|
radius: null == radius
|
||||||
? _value.radius
|
? _value.radius
|
||||||
: radius // ignore: cast_nullable_to_non_nullable
|
: radius // ignore: cast_nullable_to_non_nullable
|
||||||
as int,
|
as double,
|
||||||
aSquare: null == aSquare
|
aSquare: null == aSquare
|
||||||
? _value.aSquare
|
? _value.aSquare
|
||||||
: aSquare // ignore: cast_nullable_to_non_nullable
|
: aSquare // ignore: cast_nullable_to_non_nullable
|
||||||
|
@ -178,7 +178,7 @@ class _$_LocationTag extends _LocationTag {
|
||||||
@override
|
@override
|
||||||
final String name;
|
final String name;
|
||||||
@override
|
@override
|
||||||
final int radius;
|
final double radius;
|
||||||
@override
|
@override
|
||||||
final double aSquare;
|
final double aSquare;
|
||||||
@override
|
@override
|
||||||
|
@ -226,7 +226,7 @@ class _$_LocationTag extends _LocationTag {
|
||||||
abstract class _LocationTag extends LocationTag {
|
abstract class _LocationTag extends LocationTag {
|
||||||
const factory _LocationTag(
|
const factory _LocationTag(
|
||||||
{required final String name,
|
{required final String name,
|
||||||
required final int radius,
|
required final double radius,
|
||||||
required final double aSquare,
|
required final double aSquare,
|
||||||
required final double bSquare,
|
required final double bSquare,
|
||||||
required final Location centerPoint}) = _$_LocationTag;
|
required final Location centerPoint}) = _$_LocationTag;
|
||||||
|
@ -238,7 +238,7 @@ abstract class _LocationTag extends LocationTag {
|
||||||
@override
|
@override
|
||||||
String get name;
|
String get name;
|
||||||
@override
|
@override
|
||||||
int get radius;
|
double get radius;
|
||||||
@override
|
@override
|
||||||
double get aSquare;
|
double get aSquare;
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -9,7 +9,7 @@ part of 'location_tag.dart';
|
||||||
_$_LocationTag _$$_LocationTagFromJson(Map<String, dynamic> json) =>
|
_$_LocationTag _$$_LocationTagFromJson(Map<String, dynamic> json) =>
|
||||||
_$_LocationTag(
|
_$_LocationTag(
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
radius: json['radius'] as int,
|
radius: (json['radius'] as num).toDouble(),
|
||||||
aSquare: (json['aSquare'] as num).toDouble(),
|
aSquare: (json['aSquare'] as num).toDouble(),
|
||||||
bSquare: (json['bSquare'] as num).toDouble(),
|
bSquare: (json['bSquare'] as num).toDouble(),
|
||||||
centerPoint:
|
centerPoint:
|
||||||
|
|
|
@ -2,10 +2,14 @@ import 'dart:async';
|
||||||
|
|
||||||
import "package:photos/models/location/location.dart";
|
import "package:photos/models/location/location.dart";
|
||||||
|
|
||||||
typedef FutureVoidCallback = Future<void> Function();
|
|
||||||
typedef BoolCallBack = bool Function();
|
typedef BoolCallBack = bool Function();
|
||||||
typedef FutureVoidCallbackParamStr = Future<void> Function(String);
|
|
||||||
typedef VoidCallbackParamStr = void Function(String);
|
typedef VoidCallbackParamStr = void Function(String);
|
||||||
typedef FutureOrVoidCallback = FutureOr<void> Function();
|
|
||||||
typedef VoidCallbackParamInt = void Function(int);
|
typedef VoidCallbackParamInt = void Function(int);
|
||||||
|
typedef VoidCallbackParamDouble = Function(double);
|
||||||
|
typedef VoidCallbackParamListDouble = void Function(List<double>);
|
||||||
typedef VoidCallbackParamLocation = void Function(Location);
|
typedef VoidCallbackParamLocation = void Function(Location);
|
||||||
|
|
||||||
|
typedef FutureVoidCallback = Future<void> Function();
|
||||||
|
typedef FutureOrVoidCallback = FutureOr<void> Function();
|
||||||
|
typedef FutureVoidCallbackParamStr = Future<void> Function(String);
|
||||||
|
|
|
@ -38,7 +38,7 @@ class LocationService {
|
||||||
Future<void> addLocation(
|
Future<void> addLocation(
|
||||||
String location,
|
String location,
|
||||||
Location centerPoint,
|
Location centerPoint,
|
||||||
int radius,
|
double radius,
|
||||||
) async {
|
) async {
|
||||||
//The area enclosed by the location tag will be a circle on a 3D spherical
|
//The area enclosed by the location tag will be a circle on a 3D spherical
|
||||||
//globe and an ellipse on a 2D Mercator projection (2D map)
|
//globe and an ellipse on a 2D Mercator projection (2D map)
|
||||||
|
@ -101,7 +101,7 @@ class LocationService {
|
||||||
bool isFileInsideLocationTag(
|
bool isFileInsideLocationTag(
|
||||||
Location centerPoint,
|
Location centerPoint,
|
||||||
Location fileCoordinates,
|
Location fileCoordinates,
|
||||||
int radius,
|
double radius,
|
||||||
) {
|
) {
|
||||||
final a =
|
final a =
|
||||||
(radius * _scaleFactor(centerPoint.latitude!)) / kilometersPerDegree;
|
(radius * _scaleFactor(centerPoint.latitude!)) / kilometersPerDegree;
|
||||||
|
@ -134,7 +134,7 @@ class LocationService {
|
||||||
///Will only update if there is a change in the locationTag's properties
|
///Will only update if there is a change in the locationTag's properties
|
||||||
Future<void> updateLocationTag({
|
Future<void> updateLocationTag({
|
||||||
required LocalEntity<LocationTag> locationTagEntity,
|
required LocalEntity<LocationTag> locationTagEntity,
|
||||||
int? newRadius,
|
double? newRadius,
|
||||||
Location? newCenterPoint,
|
Location? newCenterPoint,
|
||||||
String? newName,
|
String? newName,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
|
@ -15,7 +15,6 @@ import 'package:photos/models/search/album_search_result.dart';
|
||||||
import 'package:photos/models/search/generic_search_result.dart';
|
import 'package:photos/models/search/generic_search_result.dart';
|
||||||
import 'package:photos/models/search/search_result.dart';
|
import 'package:photos/models/search/search_result.dart';
|
||||||
import 'package:photos/services/collections_service.dart';
|
import 'package:photos/services/collections_service.dart';
|
||||||
import "package:photos/services/feature_flag_service.dart";
|
|
||||||
import "package:photos/services/location_service.dart";
|
import "package:photos/services/location_service.dart";
|
||||||
import "package:photos/states/location_screen_state.dart";
|
import "package:photos/states/location_screen_state.dart";
|
||||||
import "package:photos/ui/viewer/location/location_screen.dart";
|
import "package:photos/ui/viewer/location/location_screen.dart";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import "dart:async";
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:collection/collection.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:photos/core/constants.dart";
|
import "package:photos/core/constants.dart";
|
||||||
import "package:photos/core/event_bus.dart";
|
import "package:photos/core/event_bus.dart";
|
||||||
|
@ -27,20 +28,29 @@ class LocationTagStateProvider extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LocationTagStateProviderState extends State<LocationTagStateProvider> {
|
class _LocationTagStateProviderState extends State<LocationTagStateProvider> {
|
||||||
int _selectedRaduisIndex = defaultRadiusValueIndex;
|
late double _selectedRadius;
|
||||||
|
|
||||||
late Location? _centerPoint;
|
late Location? _centerPoint;
|
||||||
late LocalEntity<LocationTag>? _locationTagEntity;
|
late LocalEntity<LocationTag>? _locationTagEntity;
|
||||||
final Debouncer _selectedRadiusDebouncer =
|
final Debouncer _selectedRadiusDebouncer =
|
||||||
Debouncer(const Duration(milliseconds: 300));
|
Debouncer(const Duration(milliseconds: 300));
|
||||||
late final StreamSubscription _locTagEntityListener;
|
late final StreamSubscription _locTagEntityListener;
|
||||||
|
late final List<double> _radiusValues;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_locationTagEntity = widget.locationTagEntity;
|
_locationTagEntity = widget.locationTagEntity;
|
||||||
_centerPoint = widget.centerPoint;
|
_centerPoint = widget.centerPoint;
|
||||||
assert(_centerPoint != null || _locationTagEntity != null);
|
assert(_centerPoint != null || _locationTagEntity != null);
|
||||||
_centerPoint = _locationTagEntity?.item.centerPoint ?? _centerPoint!;
|
_centerPoint = _locationTagEntity?.item.centerPoint ?? _centerPoint!;
|
||||||
_selectedRaduisIndex =
|
|
||||||
_locationTagEntity?.item.radiusIndex ?? defaultRadiusValueIndex;
|
///If the location tag has a custom radius value, we add the custom radius
|
||||||
|
///value to the list of default radius values only for this location tag and
|
||||||
|
///keep it in the state of this widget.
|
||||||
|
_radiusValues = _getRadiusValuesOfLocTag(_locationTagEntity?.item.radius);
|
||||||
|
|
||||||
|
_selectedRadius = _locationTagEntity?.item.radius ?? defaultRadiusValue;
|
||||||
|
|
||||||
_locTagEntityListener =
|
_locTagEntityListener =
|
||||||
Bus.instance.on<LocationTagUpdatedEvent>().listen((event) {
|
Bus.instance.on<LocationTagUpdatedEvent>().listen((event) {
|
||||||
_locationTagUpdateListener(event);
|
_locationTagUpdateListener(event);
|
||||||
|
@ -57,10 +67,11 @@ class _LocationTagStateProviderState extends State<LocationTagStateProvider> {
|
||||||
void _locationTagUpdateListener(LocationTagUpdatedEvent event) {
|
void _locationTagUpdateListener(LocationTagUpdatedEvent event) {
|
||||||
if (event.type == LocTagEventType.update) {
|
if (event.type == LocTagEventType.update) {
|
||||||
if (event.updatedLocTagEntities!.first.id == _locationTagEntity!.id) {
|
if (event.updatedLocTagEntities!.first.id == _locationTagEntity!.id) {
|
||||||
//Update state when locationTag is updated.
|
|
||||||
setState(() {
|
setState(() {
|
||||||
final updatedLocTagEntity = event.updatedLocTagEntities!.first;
|
final updatedLocTagEntity = event.updatedLocTagEntities!.first;
|
||||||
_selectedRaduisIndex = updatedLocTagEntity.item.radiusIndex;
|
|
||||||
|
_selectedRadius = updatedLocTagEntity.item.radius;
|
||||||
|
|
||||||
_centerPoint = updatedLocTagEntity.item.centerPoint;
|
_centerPoint = updatedLocTagEntity.item.centerPoint;
|
||||||
_locationTagEntity = updatedLocTagEntity;
|
_locationTagEntity = updatedLocTagEntity;
|
||||||
});
|
});
|
||||||
|
@ -68,12 +79,12 @@ class _LocationTagStateProviderState extends State<LocationTagStateProvider> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateSelectedIndex(int index) {
|
void _updateSelectedRadius(double radius) {
|
||||||
_selectedRadiusDebouncer.cancelDebounce();
|
_selectedRadiusDebouncer.cancelDebounce();
|
||||||
_selectedRadiusDebouncer.run(() async {
|
_selectedRadiusDebouncer.run(() async {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedRaduisIndex = index;
|
_selectedRadius = radius;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -87,14 +98,42 @@ class _LocationTagStateProviderState extends State<LocationTagStateProvider> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateRadiusValues(List<double> radiusValues) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
for (double radiusValue in radiusValues) {
|
||||||
|
if (!_radiusValues.contains(radiusValue)) {
|
||||||
|
_radiusValues.add(radiusValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_radiusValues.sort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Returns the list of radius values for the location tag entity. If radius of
|
||||||
|
///the location tag is not present in the default list, it returns the list
|
||||||
|
///with the custom radius value.
|
||||||
|
List<double> _getRadiusValuesOfLocTag(double? radiusOfLocTag) {
|
||||||
|
final radiusValues = <double>[...defaultRadiusValues];
|
||||||
|
if (radiusOfLocTag != null &&
|
||||||
|
!defaultRadiusValues.contains(radiusOfLocTag)) {
|
||||||
|
radiusValues.add(radiusOfLocTag);
|
||||||
|
radiusValues.sort();
|
||||||
|
}
|
||||||
|
return radiusValues;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InheritedLocationTagData(
|
return InheritedLocationTagData(
|
||||||
_selectedRaduisIndex,
|
_selectedRadius,
|
||||||
_centerPoint!,
|
_centerPoint!,
|
||||||
_updateSelectedIndex,
|
_updateSelectedRadius,
|
||||||
_locationTagEntity,
|
_locationTagEntity,
|
||||||
_updateCenterPoint,
|
_updateCenterPoint,
|
||||||
|
_updateRadiusValues,
|
||||||
|
_radiusValues,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -102,18 +141,22 @@ class _LocationTagStateProviderState extends State<LocationTagStateProvider> {
|
||||||
|
|
||||||
///This InheritedWidget's state is used in add & edit location sheets
|
///This InheritedWidget's state is used in add & edit location sheets
|
||||||
class InheritedLocationTagData extends InheritedWidget {
|
class InheritedLocationTagData extends InheritedWidget {
|
||||||
final int selectedRadiusIndex;
|
final double selectedRadius;
|
||||||
final Location centerPoint;
|
final Location centerPoint;
|
||||||
//locationTag is null when we are creating a new location tag in add location sheet
|
//locationTag is null when we are creating a new location tag in add location sheet
|
||||||
final LocalEntity<LocationTag>? locationTagEntity;
|
final LocalEntity<LocationTag>? locationTagEntity;
|
||||||
final VoidCallbackParamInt updateSelectedIndex;
|
final VoidCallbackParamDouble updateSelectedRadius;
|
||||||
final VoidCallbackParamLocation updateCenterPoint;
|
final VoidCallbackParamLocation updateCenterPoint;
|
||||||
|
final VoidCallbackParamListDouble updateRadiusValues;
|
||||||
|
final List<double> radiusValues;
|
||||||
const InheritedLocationTagData(
|
const InheritedLocationTagData(
|
||||||
this.selectedRadiusIndex,
|
this.selectedRadius,
|
||||||
this.centerPoint,
|
this.centerPoint,
|
||||||
this.updateSelectedIndex,
|
this.updateSelectedRadius,
|
||||||
this.locationTagEntity,
|
this.locationTagEntity,
|
||||||
this.updateCenterPoint, {
|
this.updateCenterPoint,
|
||||||
|
this.updateRadiusValues,
|
||||||
|
this.radiusValues, {
|
||||||
required super.child,
|
required super.child,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
@ -125,7 +168,10 @@ class InheritedLocationTagData extends InheritedWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(InheritedLocationTagData oldWidget) {
|
bool updateShouldNotify(InheritedLocationTagData oldWidget) {
|
||||||
return oldWidget.selectedRadiusIndex != selectedRadiusIndex ||
|
print(selectedRadius);
|
||||||
|
print(oldWidget.selectedRadius != selectedRadius);
|
||||||
|
return oldWidget.selectedRadius != selectedRadius ||
|
||||||
|
!oldWidget.radiusValues.equals(radiusValues) ||
|
||||||
oldWidget.centerPoint != centerPoint ||
|
oldWidget.centerPoint != centerPoint ||
|
||||||
oldWidget.locationTagEntity != locationTagEntity;
|
oldWidget.locationTagEntity != locationTagEntity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import "package:flutter/services.dart";
|
||||||
import 'package:photos/core/constants.dart';
|
import 'package:photos/core/constants.dart';
|
||||||
import "package:photos/generated/l10n.dart";
|
import "package:photos/generated/l10n.dart";
|
||||||
import "package:photos/models/search/button_result.dart";
|
import "package:photos/models/search/button_result.dart";
|
||||||
|
@ -174,6 +175,9 @@ class TextInputDialog extends StatefulWidget {
|
||||||
final TextCapitalization? textCapitalization;
|
final TextCapitalization? textCapitalization;
|
||||||
final bool alwaysShowSuccessState;
|
final bool alwaysShowSuccessState;
|
||||||
final bool isPasswordInput;
|
final bool isPasswordInput;
|
||||||
|
final TextEditingController? textEditingController;
|
||||||
|
final List<TextInputFormatter>? textInputFormatter;
|
||||||
|
final TextInputType? textInputType;
|
||||||
const TextInputDialog({
|
const TextInputDialog({
|
||||||
required this.title,
|
required this.title,
|
||||||
this.body,
|
this.body,
|
||||||
|
@ -191,6 +195,9 @@ class TextInputDialog extends StatefulWidget {
|
||||||
this.showOnlyLoadingState = false,
|
this.showOnlyLoadingState = false,
|
||||||
this.alwaysShowSuccessState = false,
|
this.alwaysShowSuccessState = false,
|
||||||
this.isPasswordInput = false,
|
this.isPasswordInput = false,
|
||||||
|
this.textEditingController,
|
||||||
|
this.textInputFormatter,
|
||||||
|
this.textInputType,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -201,10 +208,28 @@ class TextInputDialog extends StatefulWidget {
|
||||||
class _TextInputDialogState extends State<TextInputDialog> {
|
class _TextInputDialogState extends State<TextInputDialog> {
|
||||||
//the value of this ValueNotifier has no significance
|
//the value of this ValueNotifier has no significance
|
||||||
final _submitNotifier = ValueNotifier(false);
|
final _submitNotifier = ValueNotifier(false);
|
||||||
|
late final ValueNotifier<bool> _inputIsEmptyNotifier;
|
||||||
|
late final TextEditingController _textEditingController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_textEditingController =
|
||||||
|
widget.textEditingController ?? TextEditingController();
|
||||||
|
_inputIsEmptyNotifier = widget.initialValue?.isEmpty ?? true
|
||||||
|
? ValueNotifier(true)
|
||||||
|
: ValueNotifier(false);
|
||||||
|
_textEditingController.addListener(() {
|
||||||
|
if (_textEditingController.text.isEmpty != _inputIsEmptyNotifier.value) {
|
||||||
|
_inputIsEmptyNotifier.value = _textEditingController.text.isEmpty;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_submitNotifier.dispose();
|
_submitNotifier.dispose();
|
||||||
|
_inputIsEmptyNotifier.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,6 +276,9 @@ class _TextInputDialogState extends State<TextInputDialog> {
|
||||||
textCapitalization: widget.textCapitalization,
|
textCapitalization: widget.textCapitalization,
|
||||||
alwaysShowSuccessState: widget.alwaysShowSuccessState,
|
alwaysShowSuccessState: widget.alwaysShowSuccessState,
|
||||||
isPasswordInput: widget.isPasswordInput,
|
isPasswordInput: widget.isPasswordInput,
|
||||||
|
textEditingController: _textEditingController,
|
||||||
|
textInputFormatter: widget.textInputFormatter,
|
||||||
|
textInputType: widget.textInputType,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 36),
|
const SizedBox(height: 36),
|
||||||
|
@ -267,12 +295,18 @@ class _TextInputDialogState extends State<TextInputDialog> {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ButtonWidget(
|
child: ValueListenableBuilder(
|
||||||
buttonSize: ButtonSize.small,
|
valueListenable: _inputIsEmptyNotifier,
|
||||||
buttonType: ButtonType.neutral,
|
builder: (context, bool value, _) {
|
||||||
labelText: widget.submitButtonLabel,
|
return ButtonWidget(
|
||||||
onTap: () async {
|
buttonSize: ButtonSize.small,
|
||||||
_submitNotifier.value = !_submitNotifier.value;
|
buttonType: ButtonType.neutral,
|
||||||
|
labelText: widget.submitButtonLabel,
|
||||||
|
isDisabled: value,
|
||||||
|
onTap: () async {
|
||||||
|
_submitNotifier.value = !_submitNotifier.value;
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -41,6 +41,8 @@ class TextInputWidget extends StatefulWidget {
|
||||||
final VoidCallback? onCancel;
|
final VoidCallback? onCancel;
|
||||||
final TextEditingController? textEditingController;
|
final TextEditingController? textEditingController;
|
||||||
final ValueNotifier? isEmptyNotifier;
|
final ValueNotifier? isEmptyNotifier;
|
||||||
|
final List<TextInputFormatter>? textInputFormatter;
|
||||||
|
final TextInputType? textInputType;
|
||||||
const TextInputWidget({
|
const TextInputWidget({
|
||||||
this.onSubmit,
|
this.onSubmit,
|
||||||
this.onChange,
|
this.onChange,
|
||||||
|
@ -67,6 +69,8 @@ class TextInputWidget extends StatefulWidget {
|
||||||
this.onCancel,
|
this.onCancel,
|
||||||
this.textEditingController,
|
this.textEditingController,
|
||||||
this.isEmptyNotifier,
|
this.isEmptyNotifier,
|
||||||
|
this.textInputFormatter,
|
||||||
|
this.textInputType,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,12 +94,8 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
||||||
widget.cancelNotifier?.addListener(_onCancel);
|
widget.cancelNotifier?.addListener(_onCancel);
|
||||||
_textController = widget.textEditingController ?? TextEditingController();
|
_textController = widget.textEditingController ?? TextEditingController();
|
||||||
|
|
||||||
if (widget.initialValue != null) {
|
_setInitialValue();
|
||||||
_textController.value = TextEditingValue(
|
|
||||||
text: widget.initialValue!,
|
|
||||||
selection: TextSelection.collapsed(offset: widget.initialValue!.length),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (widget.onChange != null) {
|
if (widget.onChange != null) {
|
||||||
_textController.addListener(() {
|
_textController.addListener(() {
|
||||||
widget.onChange!.call(_textController.text);
|
widget.onChange!.call(_textController.text);
|
||||||
|
@ -143,13 +143,15 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
||||||
borderRadius: BorderRadius.all(Radius.circular(widget.borderRadius)),
|
borderRadius: BorderRadius.all(Radius.circular(widget.borderRadius)),
|
||||||
child: Material(
|
child: Material(
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
keyboardType: widget.textInputType,
|
||||||
textCapitalization: widget.textCapitalization!,
|
textCapitalization: widget.textCapitalization!,
|
||||||
autofocus: widget.autoFocus ?? false,
|
autofocus: widget.autoFocus ?? false,
|
||||||
controller: _textController,
|
controller: _textController,
|
||||||
focusNode: widget.focusNode,
|
focusNode: widget.focusNode,
|
||||||
inputFormatters: widget.maxLength != null
|
inputFormatters: widget.textInputFormatter ??
|
||||||
? [LengthLimitingTextInputFormatter(50)]
|
(widget.maxLength != null
|
||||||
: null,
|
? [LengthLimitingTextInputFormatter(50)]
|
||||||
|
: null),
|
||||||
obscureText: _obscureTextNotifier.value,
|
obscureText: _obscureTextNotifier.value,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: widget.hintText,
|
hintText: widget.hintText,
|
||||||
|
@ -343,6 +345,42 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
||||||
void _popNavigatorStack(BuildContext context, {Exception? e}) {
|
void _popNavigatorStack(BuildContext context, {Exception? e}) {
|
||||||
Navigator.of(context).canPop() ? Navigator.of(context).pop(e) : null;
|
Navigator.of(context).canPop() ? Navigator.of(context).pop(e) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setInitialValue() {
|
||||||
|
if (widget.initialValue != null) {
|
||||||
|
final formattedInitialValue = _formatInitialValue(
|
||||||
|
widget.initialValue!,
|
||||||
|
widget.textInputFormatter,
|
||||||
|
);
|
||||||
|
_textController.value = TextEditingValue(
|
||||||
|
text: formattedInitialValue,
|
||||||
|
selection:
|
||||||
|
TextSelection.collapsed(offset: formattedInitialValue.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatInitialValue(
|
||||||
|
String initialValue,
|
||||||
|
List<TextInputFormatter>? formatters,
|
||||||
|
) {
|
||||||
|
if (formatters == null || formatters.isEmpty) {
|
||||||
|
return initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String formattedValue = initialValue;
|
||||||
|
|
||||||
|
for (final formatter in formatters) {
|
||||||
|
formattedValue = formatter
|
||||||
|
.formatEditUpdate(
|
||||||
|
TextEditingValue.empty,
|
||||||
|
TextEditingValue(text: formattedValue),
|
||||||
|
)
|
||||||
|
.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: Add clear and custom icon for suffic icon
|
//todo: Add clear and custom icon for suffic icon
|
||||||
|
|
|
@ -51,14 +51,17 @@ class AddLocationSheet extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AddLocationSheetState extends State<AddLocationSheet> {
|
class _AddLocationSheetState extends State<AddLocationSheet> {
|
||||||
//The value of these notifiers has no significance.
|
//The value of this notifier has no significance.
|
||||||
//When memoriesCountNotifier is null, we show the loading widget in the
|
//When memoriesCountNotifier is null, we show the loading widget in the
|
||||||
//memories count section which also means the gallery is loading.
|
//memories count section which also means the gallery is loading.
|
||||||
final ValueNotifier<int?> _memoriesCountNotifier = ValueNotifier(null);
|
final ValueNotifier<int?> _memoriesCountNotifier = ValueNotifier(null);
|
||||||
|
|
||||||
|
//The value of this notifier has no significance.
|
||||||
final ValueNotifier<bool> _submitNotifer = ValueNotifier(false);
|
final ValueNotifier<bool> _submitNotifer = ValueNotifier(false);
|
||||||
|
|
||||||
final ValueNotifier<bool> _cancelNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _cancelNotifier = ValueNotifier(false);
|
||||||
final ValueNotifier<int> _selectedRadiusIndexNotifier =
|
final ValueNotifier<double> _selectedRadiusNotifier =
|
||||||
ValueNotifier(defaultRadiusValueIndex);
|
ValueNotifier(defaultRadiusValue);
|
||||||
final _focusNode = FocusNode();
|
final _focusNode = FocusNode();
|
||||||
final _textEditingController = TextEditingController();
|
final _textEditingController = TextEditingController();
|
||||||
final _isEmptyNotifier = ValueNotifier(true);
|
final _isEmptyNotifier = ValueNotifier(true);
|
||||||
|
@ -67,7 +70,7 @@ class _AddLocationSheetState extends State<AddLocationSheet> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_focusNode.addListener(_focusNodeListener);
|
_focusNode.addListener(_focusNodeListener);
|
||||||
_selectedRadiusIndexNotifier.addListener(_selectedRadiusIndexListener);
|
_selectedRadiusNotifier.addListener(_selectedRadiusListener);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +79,7 @@ class _AddLocationSheetState extends State<AddLocationSheet> {
|
||||||
_focusNode.removeListener(_focusNodeListener);
|
_focusNode.removeListener(_focusNodeListener);
|
||||||
_submitNotifer.dispose();
|
_submitNotifer.dispose();
|
||||||
_cancelNotifier.dispose();
|
_cancelNotifier.dispose();
|
||||||
_selectedRadiusIndexNotifier.dispose();
|
_selectedRadiusNotifier.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,9 +152,9 @@ class _AddLocationSheetState extends State<AddLocationSheet> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
RadiusPickerWidget(
|
RadiusPickerWidget(
|
||||||
_selectedRadiusIndexNotifier,
|
_selectedRadiusNotifier,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
S.of(context).locationTagFeatureDescription,
|
S.of(context).locationTagFeatureDescription,
|
||||||
style: textTheme.smallMuted,
|
style: textTheme.smallMuted,
|
||||||
|
@ -230,7 +233,8 @@ class _AddLocationSheetState extends State<AddLocationSheet> {
|
||||||
Future<void> _addLocationTag() async {
|
Future<void> _addLocationTag() async {
|
||||||
final locationData = InheritedLocationTagData.of(context);
|
final locationData = InheritedLocationTagData.of(context);
|
||||||
final coordinates = locationData.centerPoint;
|
final coordinates = locationData.centerPoint;
|
||||||
final radius = radiusValues[locationData.selectedRadiusIndex];
|
final radius = locationData.selectedRadius;
|
||||||
|
|
||||||
await LocationService.instance.addLocation(
|
await LocationService.instance.addLocation(
|
||||||
_textEditingController.text.trim(),
|
_textEditingController.text.trim(),
|
||||||
coordinates,
|
coordinates,
|
||||||
|
@ -256,11 +260,11 @@ class _AddLocationSheetState extends State<AddLocationSheet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _selectedRadiusIndexListener() {
|
void _selectedRadiusListener() {
|
||||||
InheritedLocationTagData.of(
|
InheritedLocationTagData.of(
|
||||||
context,
|
context,
|
||||||
).updateSelectedIndex(
|
).updateSelectedRadius(
|
||||||
_selectedRadiusIndexNotifier.value,
|
_selectedRadiusNotifier.value,
|
||||||
);
|
);
|
||||||
_memoriesCountNotifier.value = null;
|
_memoriesCountNotifier.value = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ class _DynamicLocationGalleryWidgetState
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const galleryFilesLimit = 1000;
|
const galleryFilesLimit = 1000;
|
||||||
final selectedRadius = _selectedRadius();
|
final selectedRadius = InheritedLocationTagData.of(context).selectedRadius;
|
||||||
Future<FileLoadResult> filterFiles() async {
|
Future<FileLoadResult> filterFiles() async {
|
||||||
final FileLoadResult result = await fileLoadResult;
|
final FileLoadResult result = await fileLoadResult;
|
||||||
//wait for ignored files to be removed after init
|
//wait for ignored files to be removed after init
|
||||||
|
@ -121,11 +121,6 @@ class _DynamicLocationGalleryWidgetState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int _selectedRadius() {
|
|
||||||
return radiusValues[
|
|
||||||
InheritedLocationTagData.of(context).selectedRadiusIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
double _galleryHeight(int fileCount) {
|
double _galleryHeight(int fileCount) {
|
||||||
final photoGridSize = LocalSettings.instance.getPhotoGridSize();
|
final photoGridSize = LocalSettings.instance.getPhotoGridSize();
|
||||||
final totalWhiteSpaceBetweenPhotos =
|
final totalWhiteSpaceBetweenPhotos =
|
||||||
|
|
|
@ -17,8 +17,8 @@ class EditCenterPointTileWidget extends StatelessWidget {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 48,
|
width: 52,
|
||||||
height: 48,
|
height: 52,
|
||||||
color: colorScheme.fillFaint,
|
color: colorScheme.fillFaint,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.location_on_outlined,
|
Icons.location_on_outlined,
|
||||||
|
|
|
@ -61,8 +61,8 @@ class _EditLocationSheetState extends State<EditLocationSheet> {
|
||||||
final ValueNotifier<int?> _memoriesCountNotifier = ValueNotifier(null);
|
final ValueNotifier<int?> _memoriesCountNotifier = ValueNotifier(null);
|
||||||
final ValueNotifier<bool> _submitNotifer = ValueNotifier(false);
|
final ValueNotifier<bool> _submitNotifer = ValueNotifier(false);
|
||||||
final ValueNotifier<bool> _cancelNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _cancelNotifier = ValueNotifier(false);
|
||||||
final ValueNotifier<int> _selectedRadiusIndexNotifier =
|
final ValueNotifier<double> _selectedRadiusNotifier =
|
||||||
ValueNotifier(defaultRadiusValueIndex);
|
ValueNotifier(defaultRadiusValue);
|
||||||
final _focusNode = FocusNode();
|
final _focusNode = FocusNode();
|
||||||
final _textEditingController = TextEditingController();
|
final _textEditingController = TextEditingController();
|
||||||
final _isEmptyNotifier = ValueNotifier(false);
|
final _isEmptyNotifier = ValueNotifier(false);
|
||||||
|
@ -71,7 +71,7 @@ class _EditLocationSheetState extends State<EditLocationSheet> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_focusNode.addListener(_focusNodeListener);
|
_focusNode.addListener(_focusNodeListener);
|
||||||
_selectedRadiusIndexNotifier.addListener(_selectedRadiusIndexListener);
|
_selectedRadiusNotifier.addListener(_selectedRadiusListener);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ class _EditLocationSheetState extends State<EditLocationSheet> {
|
||||||
_focusNode.removeListener(_focusNodeListener);
|
_focusNode.removeListener(_focusNodeListener);
|
||||||
_submitNotifer.dispose();
|
_submitNotifer.dispose();
|
||||||
_cancelNotifier.dispose();
|
_cancelNotifier.dispose();
|
||||||
_selectedRadiusIndexNotifier.dispose();
|
_selectedRadiusNotifier.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,9 +162,9 @@ class _EditLocationSheetState extends State<EditLocationSheet> {
|
||||||
const EditCenterPointTileWidget(),
|
const EditCenterPointTileWidget(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
RadiusPickerWidget(
|
RadiusPickerWidget(
|
||||||
_selectedRadiusIndexNotifier,
|
_selectedRadiusNotifier,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -240,7 +240,7 @@ class _EditLocationSheetState extends State<EditLocationSheet> {
|
||||||
final locationTagState = InheritedLocationTagData.of(context);
|
final locationTagState = InheritedLocationTagData.of(context);
|
||||||
await LocationService.instance.updateLocationTag(
|
await LocationService.instance.updateLocationTag(
|
||||||
locationTagEntity: locationTagState.locationTagEntity!,
|
locationTagEntity: locationTagState.locationTagEntity!,
|
||||||
newRadius: radiusValues[locationTagState.selectedRadiusIndex],
|
newRadius: locationTagState.selectedRadius,
|
||||||
newName: _textEditingController.text.trim(),
|
newName: _textEditingController.text.trim(),
|
||||||
newCenterPoint: InheritedLocationTagData.of(context).centerPoint,
|
newCenterPoint: InheritedLocationTagData.of(context).centerPoint,
|
||||||
);
|
);
|
||||||
|
@ -264,11 +264,11 @@ class _EditLocationSheetState extends State<EditLocationSheet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _selectedRadiusIndexListener() {
|
void _selectedRadiusListener() {
|
||||||
InheritedLocationTagData.of(
|
InheritedLocationTagData.of(
|
||||||
context,
|
context,
|
||||||
).updateSelectedIndex(
|
).updateSelectedRadius(
|
||||||
_selectedRadiusIndexNotifier.value,
|
_selectedRadiusNotifier.value,
|
||||||
);
|
);
|
||||||
_memoriesCountNotifier.value = null;
|
_memoriesCountNotifier.value = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:photos/core/constants.dart";
|
import "package:flutter/services.dart";
|
||||||
|
import "package:logging/logging.dart";
|
||||||
import "package:photos/generated/l10n.dart";
|
import "package:photos/generated/l10n.dart";
|
||||||
import "package:photos/states/location_state.dart";
|
import "package:photos/states/location_state.dart";
|
||||||
import "package:photos/theme/colors.dart";
|
import "package:photos/theme/colors.dart";
|
||||||
import "package:photos/theme/ente_theme.dart";
|
import "package:photos/theme/ente_theme.dart";
|
||||||
|
import "package:photos/utils/dialog_util.dart";
|
||||||
|
|
||||||
class CustomTrackShape extends RoundedRectSliderTrackShape {
|
class CustomTrackShape extends RoundedRectSliderTrackShape {
|
||||||
@override
|
@override
|
||||||
|
@ -21,11 +23,11 @@ class CustomTrackShape extends RoundedRectSliderTrackShape {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RadiusPickerWidget extends StatefulWidget {
|
class RadiusPickerWidget extends StatefulWidget {
|
||||||
///This notifier can be listened to get the selected radius index from
|
///This notifier can be listened from a parent widget to get the selected radius
|
||||||
///a parent widget.
|
final ValueNotifier<double> selectedRadiusNotifier;
|
||||||
final ValueNotifier<int> selectedRadiusIndexNotifier;
|
|
||||||
const RadiusPickerWidget(
|
const RadiusPickerWidget(
|
||||||
this.selectedRadiusIndexNotifier, {
|
this.selectedRadiusNotifier, {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,6 +36,7 @@ class RadiusPickerWidget extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RadiusPickerWidgetState extends State<RadiusPickerWidget> {
|
class _RadiusPickerWidgetState extends State<RadiusPickerWidget> {
|
||||||
|
final _logger = Logger("RadiusPickerWidget");
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -41,51 +44,62 @@ class _RadiusPickerWidgetState extends State<RadiusPickerWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
widget.selectedRadiusIndexNotifier.value =
|
widget.selectedRadiusNotifier.value =
|
||||||
InheritedLocationTagData.of(context).selectedRadiusIndex;
|
InheritedLocationTagData.of(context).selectedRadius;
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectedRadiusIndex = widget.selectedRadiusIndexNotifier.value;
|
final radiusValues = InheritedLocationTagData.of(context).radiusValues;
|
||||||
final radiusValue = radiusValues[selectedRadiusIndex];
|
final selectedRadius = widget.selectedRadiusNotifier.value;
|
||||||
final textTheme = getEnteTextTheme(context);
|
final textTheme = getEnteTextTheme(context);
|
||||||
final colorScheme = getEnteColorScheme(context);
|
final colorScheme = getEnteColorScheme(context);
|
||||||
|
final roundedRadius = roundRadius(selectedRadius);
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
GestureDetector(
|
||||||
height: 48,
|
onTap: _customRadiusOnTap,
|
||||||
width: 48,
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
height: 52,
|
||||||
color: colorScheme.fillFaint,
|
width: 52,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(2)),
|
decoration: BoxDecoration(
|
||||||
),
|
color: colorScheme.fillFaint,
|
||||||
padding: const EdgeInsets.all(4),
|
borderRadius: const BorderRadius.all(Radius.circular(2)),
|
||||||
child: Column(
|
border: Border.all(
|
||||||
mainAxisSize: MainAxisSize.min,
|
color: colorScheme.strokeFainter,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
width: 1,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
strokeAlign: BorderSide.strokeAlignInside,
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 6,
|
|
||||||
child: Text(
|
|
||||||
radiusValue.toString(),
|
|
||||||
style: radiusValue != 1200
|
|
||||||
? textTheme.largeBold
|
|
||||||
: textTheme.bodyBold,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
flex: 5,
|
padding: const EdgeInsets.all(4),
|
||||||
child: Text(
|
child: Column(
|
||||||
S.of(context).kiloMeterUnit,
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: textTheme.miniMuted,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 6,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
roundedRadius,
|
||||||
|
style: double.parse(roundedRadius) < 1000
|
||||||
|
? textTheme.largeBold
|
||||||
|
: textTheme.bodyBold,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
],
|
flex: 5,
|
||||||
|
child: Text(
|
||||||
|
S.of(context).kiloMeterUnit,
|
||||||
|
style: textTheme.miniMuted,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
|
@ -96,6 +110,7 @@ class _RadiusPickerWidgetState extends State<RadiusPickerWidget> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
const SizedBox(height: 4),
|
||||||
Text(S.of(context).radius, style: textTheme.body),
|
Text(S.of(context).radius, style: textTheme.body),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
@ -120,11 +135,11 @@ class _RadiusPickerWidgetState extends State<RadiusPickerWidget> {
|
||||||
),
|
),
|
||||||
child: RepaintBoundary(
|
child: RepaintBoundary(
|
||||||
child: Slider(
|
child: Slider(
|
||||||
value: selectedRadiusIndex.toDouble(),
|
value: radiusValues.indexOf(selectedRadius).toDouble(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.selectedRadiusIndexNotifier.value =
|
widget.selectedRadiusNotifier.value =
|
||||||
value.toInt();
|
radiusValues[value.toInt()];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
min: 0,
|
min: 0,
|
||||||
|
@ -141,4 +156,82 @@ class _RadiusPickerWidgetState extends State<RadiusPickerWidget> {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//9.99 -> 10, 9.0 -> 9, 5.02 -> 5, 5.09 -> 5.1
|
||||||
|
//12.3 -> 12, 121.65 -> 122, 999.9 -> 1000
|
||||||
|
String roundRadius(double radius) {
|
||||||
|
String result;
|
||||||
|
final roundedRadius = (radius * 10).round() / 10;
|
||||||
|
if (radius >= 10) {
|
||||||
|
result = roundedRadius.toStringAsFixed(0);
|
||||||
|
} else {
|
||||||
|
if (roundedRadius == roundedRadius.truncate()) {
|
||||||
|
result = roundedRadius.truncate().toString();
|
||||||
|
} else {
|
||||||
|
result = roundedRadius.toStringAsFixed(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _customRadiusOnTap() async {
|
||||||
|
final result = await showTextInputDialog(
|
||||||
|
context,
|
||||||
|
title: "Custom radius",
|
||||||
|
onSubmit: (customRadius) async {
|
||||||
|
final radius = double.tryParse(customRadius);
|
||||||
|
if (radius != null) {
|
||||||
|
final locationTagState = InheritedLocationTagData.of(context);
|
||||||
|
locationTagState.updateRadiusValues([radius]);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
widget.selectedRadiusNotifier.value = radius;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception("Radius is null");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitButtonLabel: "Done",
|
||||||
|
textInputFormatter: [NumberWithDecimalInputFormatter(maxValue: 10000)],
|
||||||
|
textInputType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
message: "km",
|
||||||
|
alignMessage: Alignment.centerRight,
|
||||||
|
);
|
||||||
|
if (result is Exception) {
|
||||||
|
await showGenericErrorDialog(context: context);
|
||||||
|
_logger.severe(
|
||||||
|
"Failed to create custom radius",
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumberWithDecimalInputFormatter extends TextInputFormatter {
|
||||||
|
final RegExp _pattern = RegExp(r'^(?:\d+(\.\d*)?)?$');
|
||||||
|
final double maxValue;
|
||||||
|
|
||||||
|
NumberWithDecimalInputFormatter({required this.maxValue});
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextEditingValue formatEditUpdate(
|
||||||
|
TextEditingValue oldValue,
|
||||||
|
TextEditingValue newValue,
|
||||||
|
) {
|
||||||
|
// Check if the new value matches the pattern
|
||||||
|
if (_pattern.hasMatch(newValue.text)) {
|
||||||
|
if (newValue.text.isEmpty) {
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
final newValueAsDouble = double.tryParse(newValue.text);
|
||||||
|
|
||||||
|
// Check if the new value is within the allowed range
|
||||||
|
if (newValueAsDouble != null && newValueAsDouble <= maxValue) {
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:math';
|
||||||
import 'package:confetti/confetti.dart';
|
import 'package:confetti/confetti.dart';
|
||||||
import "package:dio/dio.dart";
|
import "package:dio/dio.dart";
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import "package:flutter/services.dart";
|
||||||
import 'package:photos/core/constants.dart';
|
import 'package:photos/core/constants.dart';
|
||||||
import "package:photos/generated/l10n.dart";
|
import "package:photos/generated/l10n.dart";
|
||||||
import "package:photos/models/search/button_result.dart";
|
import "package:photos/models/search/button_result.dart";
|
||||||
|
@ -303,6 +304,9 @@ Future<dynamic> showTextInputDialog(
|
||||||
TextCapitalization textCapitalization = TextCapitalization.none,
|
TextCapitalization textCapitalization = TextCapitalization.none,
|
||||||
bool alwaysShowSuccessState = false,
|
bool alwaysShowSuccessState = false,
|
||||||
bool isPasswordInput = false,
|
bool isPasswordInput = false,
|
||||||
|
TextEditingController? textEditingController,
|
||||||
|
List<TextInputFormatter>? textInputFormatter,
|
||||||
|
TextInputType? textInputType,
|
||||||
}) {
|
}) {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
barrierColor: backdropFaintDark,
|
barrierColor: backdropFaintDark,
|
||||||
|
@ -330,6 +334,9 @@ Future<dynamic> showTextInputDialog(
|
||||||
textCapitalization: textCapitalization,
|
textCapitalization: textCapitalization,
|
||||||
alwaysShowSuccessState: alwaysShowSuccessState,
|
alwaysShowSuccessState: alwaysShowSuccessState,
|
||||||
isPasswordInput: isPasswordInput,
|
isPasswordInput: isPasswordInput,
|
||||||
|
textEditingController: textEditingController,
|
||||||
|
textInputFormatter: textInputFormatter,
|
||||||
|
textInputType: textInputType,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue