parent
d2a6e31a1a
commit
cf47ebebed
25 changed files with 475 additions and 81 deletions
|
@ -42,6 +42,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
|
||||||
"canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context),
|
"canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context),
|
||||||
"canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT),
|
"canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT),
|
||||||
"canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.M),
|
"canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.M),
|
||||||
|
"canRenderSubdivisionFlagEmojis" to (sdkInt >= Build.VERSION_CODES.O),
|
||||||
"canRequestManageMedia" to (sdkInt >= Build.VERSION_CODES.S),
|
"canRequestManageMedia" to (sdkInt >= Build.VERSION_CODES.S),
|
||||||
"canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N),
|
"canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N),
|
||||||
"canUseCrypto" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
|
"canUseCrypto" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
|
||||||
|
|
87
lib/geo/states.dart
Normal file
87
lib/geo/states.dart
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import 'package:aves/utils/emoji_utils.dart';
|
||||||
|
import 'package:country_code/country_code.dart';
|
||||||
|
|
||||||
|
class GeoStates {
|
||||||
|
static final Set<String> stateCountryCodes = {
|
||||||
|
CountryCode.AU,
|
||||||
|
CountryCode.GB,
|
||||||
|
CountryCode.US,
|
||||||
|
}.map((v) => v.alpha2).toSet();
|
||||||
|
|
||||||
|
static const stateCodeByName = {
|
||||||
|
..._australiaEnglish,
|
||||||
|
..._unitedKingdomEnglish,
|
||||||
|
..._unitedStatesEnglish,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const _australiaEnglish = {
|
||||||
|
'Australian Capital Territory': StateCodes.auAustralianCapitalTerritory,
|
||||||
|
'New South Wales': StateCodes.auNewSouthWales,
|
||||||
|
'Northern Territory': StateCodes.auNorthernTerritory,
|
||||||
|
'Queensland': StateCodes.auQueensland,
|
||||||
|
'South Australia': StateCodes.auSouthAustralia,
|
||||||
|
'Tasmania': StateCodes.auTasmania,
|
||||||
|
'Victoria': StateCodes.auVictoria,
|
||||||
|
'Western Australia': StateCodes.auWesternAustralia,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const _unitedKingdomEnglish = {
|
||||||
|
'England': StateCodes.gbEngland,
|
||||||
|
'Northern Ireland': StateCodes.gbNorthernIreland,
|
||||||
|
'Scotland': StateCodes.gbScotland,
|
||||||
|
'Wales': StateCodes.gbWales,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const _unitedStatesEnglish = {
|
||||||
|
'Alabama': StateCodes.usAlabama,
|
||||||
|
'Alaska': StateCodes.usAlaska,
|
||||||
|
'Arizona': StateCodes.usArizona,
|
||||||
|
'Arkansas': StateCodes.usArkansas,
|
||||||
|
'California': StateCodes.usCalifornia,
|
||||||
|
'Colorado': StateCodes.usColorado,
|
||||||
|
'Connecticut': StateCodes.usConnecticut,
|
||||||
|
'Delaware': StateCodes.usDelaware,
|
||||||
|
'Florida': StateCodes.usFlorida,
|
||||||
|
'Georgia': StateCodes.usGeorgia,
|
||||||
|
'Hawaii': StateCodes.usHawaii,
|
||||||
|
'Idaho': StateCodes.usIdaho,
|
||||||
|
'Illinois': StateCodes.usIllinois,
|
||||||
|
'Indiana': StateCodes.usIndiana,
|
||||||
|
'Iowa': StateCodes.usIowa,
|
||||||
|
'Kansas': StateCodes.usKansas,
|
||||||
|
'Kentucky': StateCodes.usKentucky,
|
||||||
|
'Louisiana': StateCodes.usLouisiana,
|
||||||
|
'Maine': StateCodes.usMaine,
|
||||||
|
'Maryland': StateCodes.usMaryland,
|
||||||
|
'Massachusetts': StateCodes.usMassachusetts,
|
||||||
|
'Michigan': StateCodes.usMichigan,
|
||||||
|
'Minnesota': StateCodes.usMinnesota,
|
||||||
|
'Mississippi': StateCodes.usMississippi,
|
||||||
|
'Missouri': StateCodes.usMissouri,
|
||||||
|
'Montana': StateCodes.usMontana,
|
||||||
|
'Nebraska': StateCodes.usNebraska,
|
||||||
|
'Nevada': StateCodes.usNevada,
|
||||||
|
'New Hampshire': StateCodes.usNewHampshire,
|
||||||
|
'New Jersey': StateCodes.usNewJersey,
|
||||||
|
'New Mexico': StateCodes.usNewMexico,
|
||||||
|
'New York': StateCodes.usNewYork,
|
||||||
|
'North Carolina': StateCodes.usNorthCarolina,
|
||||||
|
'North Dakota': StateCodes.usNorthDakota,
|
||||||
|
'Ohio': StateCodes.usOhio,
|
||||||
|
'Oklahoma': StateCodes.usOklahoma,
|
||||||
|
'Oregon': StateCodes.usOregon,
|
||||||
|
'Pennsylvania': StateCodes.usPennsylvania,
|
||||||
|
'Rhode Island': StateCodes.usRhodeIsland,
|
||||||
|
'South Carolina': StateCodes.usSouthCarolina,
|
||||||
|
'South Dakota': StateCodes.usSouthDakota,
|
||||||
|
'Tennessee': StateCodes.usTennessee,
|
||||||
|
'Utah': StateCodes.usUtah,
|
||||||
|
'Vermont': StateCodes.usVermont,
|
||||||
|
'Virginia': StateCodes.usVirginia,
|
||||||
|
'Washington': StateCodes.usWashington,
|
||||||
|
'Washington DC': StateCodes.usWashingtonDC,
|
||||||
|
'West Virginia': StateCodes.usWestVirginia,
|
||||||
|
'Wisconsin': StateCodes.usWisconsin,
|
||||||
|
'Wyoming': StateCodes.usWyoming,
|
||||||
|
};
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ final Device device = Device._private();
|
||||||
class Device {
|
class Device {
|
||||||
late final String _userAgent;
|
late final String _userAgent;
|
||||||
late final bool _canAuthenticateUser, _canGrantDirectoryAccess, _canPinShortcut, _canPrint;
|
late final bool _canAuthenticateUser, _canGrantDirectoryAccess, _canPinShortcut, _canPrint;
|
||||||
late final bool _canRenderFlagEmojis, _canRequestManageMedia, _canSetLockScreenWallpaper, _canUseCrypto;
|
late final bool _canRenderFlagEmojis, _canRenderSubdivisionFlagEmojis, _canRequestManageMedia, _canSetLockScreenWallpaper, _canUseCrypto;
|
||||||
late final bool _hasGeocoder, _isDynamicColorAvailable, _isTelevision, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode, _supportPictureInPicture;
|
late final bool _hasGeocoder, _isDynamicColorAvailable, _isTelevision, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode, _supportPictureInPicture;
|
||||||
|
|
||||||
String get userAgent => _userAgent;
|
String get userAgent => _userAgent;
|
||||||
|
@ -25,6 +25,8 @@ class Device {
|
||||||
|
|
||||||
bool get canRenderFlagEmojis => _canRenderFlagEmojis;
|
bool get canRenderFlagEmojis => _canRenderFlagEmojis;
|
||||||
|
|
||||||
|
bool get canRenderSubdivisionFlagEmojis => _canRenderSubdivisionFlagEmojis;
|
||||||
|
|
||||||
bool get canRequestManageMedia => _canRequestManageMedia;
|
bool get canRequestManageMedia => _canRequestManageMedia;
|
||||||
|
|
||||||
bool get canSetLockScreenWallpaper => _canSetLockScreenWallpaper;
|
bool get canSetLockScreenWallpaper => _canSetLockScreenWallpaper;
|
||||||
|
@ -71,6 +73,7 @@ class Device {
|
||||||
_canPinShortcut = capabilities['canPinShortcut'] ?? false;
|
_canPinShortcut = capabilities['canPinShortcut'] ?? false;
|
||||||
_canPrint = capabilities['canPrint'] ?? false;
|
_canPrint = capabilities['canPrint'] ?? false;
|
||||||
_canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false;
|
_canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false;
|
||||||
|
_canRenderSubdivisionFlagEmojis = capabilities['canRenderSubdivisionFlagEmojis'] ?? false;
|
||||||
_canRequestManageMedia = capabilities['canRequestManageMedia'] ?? false;
|
_canRequestManageMedia = capabilities['canRequestManageMedia'] ?? false;
|
||||||
_canSetLockScreenWallpaper = capabilities['canSetLockScreenWallpaper'] ?? false;
|
_canSetLockScreenWallpaper = capabilities['canSetLockScreenWallpaper'] ?? false;
|
||||||
_canUseCrypto = capabilities['canUseCrypto'] ?? false;
|
_canUseCrypto = capabilities['canUseCrypto'] ?? false;
|
||||||
|
|
|
@ -57,6 +57,9 @@ extension ExtraAvesEntryLocation on AvesEntry {
|
||||||
final cc = address.countryCode?.toUpperCase();
|
final cc = address.countryCode?.toUpperCase();
|
||||||
final cn = address.countryName;
|
final cn = address.countryName;
|
||||||
final aa = address.adminArea;
|
final aa = address.adminArea;
|
||||||
|
final l = address.locality;
|
||||||
|
final sl = address.subLocality;
|
||||||
|
final saa = address.subAdminArea;
|
||||||
addressDetails = AddressDetails(
|
addressDetails = AddressDetails(
|
||||||
id: id,
|
id: id,
|
||||||
countryCode: cc,
|
countryCode: cc,
|
||||||
|
@ -64,7 +67,7 @@ extension ExtraAvesEntryLocation on AvesEntry {
|
||||||
adminArea: aa,
|
adminArea: aa,
|
||||||
// if country & admin fields are null, it is likely the ocean,
|
// if country & admin fields are null, it is likely the ocean,
|
||||||
// which is identified by `featureName` but we default to the address line anyway
|
// which is identified by `featureName` but we default to the address line anyway
|
||||||
locality: address.locality ?? (cc == null && cn == null && aa == null ? address.addressLine : null),
|
locality: l ?? sl ?? saa ?? (cc == null && cn == null && aa == null ? address.addressLine : null),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/utils/emoji_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
@ -11,23 +12,31 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
|
|
||||||
final LocationLevel level;
|
final LocationLevel level;
|
||||||
late final String _location;
|
late final String _location;
|
||||||
late final String? _countryCode;
|
late final String? _code;
|
||||||
late final EntryFilter _test;
|
late final EntryFilter _test;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [level, _location, _countryCode, reversed];
|
List<Object?> get props => [level, _location, _code, reversed];
|
||||||
|
|
||||||
LocationFilter(this.level, String location, {super.reversed = false}) {
|
LocationFilter(this.level, String location, {super.reversed = false}) {
|
||||||
final split = location.split(locationSeparator);
|
final split = location.split(locationSeparator);
|
||||||
_location = split.isNotEmpty ? split[0] : location;
|
_location = split.isNotEmpty ? split[0] : location;
|
||||||
_countryCode = split.length > 1 ? split[1] : null;
|
_code = split.length > 1 ? split[1] : null;
|
||||||
|
|
||||||
if (_location.isEmpty) {
|
if (_location.isEmpty) {
|
||||||
_test = (entry) => !entry.hasGps;
|
_test = (entry) => !entry.hasGps;
|
||||||
} else if (level == LocationLevel.country) {
|
} else {
|
||||||
_test = (entry) => entry.addressDetails?.countryCode == _countryCode;
|
switch (level) {
|
||||||
} else if (level == LocationLevel.place) {
|
case LocationLevel.country:
|
||||||
_test = (entry) => entry.addressDetails?.place == _location;
|
_test = (entry) => entry.addressDetails?.countryCode == _code;
|
||||||
|
break;
|
||||||
|
case LocationLevel.state:
|
||||||
|
_test = (entry) => entry.addressDetails?.stateCode == _code;
|
||||||
|
break;
|
||||||
|
case LocationLevel.place:
|
||||||
|
_test = (entry) => entry.addressDetails?.place == _location;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,16 +49,29 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() {
|
||||||
'type': type,
|
String location = _location;
|
||||||
'level': level.toString(),
|
switch (level) {
|
||||||
'location': _countryCode != null ? countryNameAndCode : _location,
|
case LocationLevel.country:
|
||||||
'reversed': reversed,
|
case LocationLevel.state:
|
||||||
};
|
if (_code != null) {
|
||||||
|
location = _nameAndCode;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LocationLevel.place:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'type': type,
|
||||||
|
'level': level.toString(),
|
||||||
|
'location': location,
|
||||||
|
'reversed': reversed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
String get countryNameAndCode => '$_location$locationSeparator$_countryCode';
|
String get _nameAndCode => '$_location$locationSeparator$_code';
|
||||||
|
|
||||||
String? get countryCode => _countryCode;
|
String? get code => _code;
|
||||||
|
|
||||||
String get place => _location;
|
String get place => _location;
|
||||||
|
|
||||||
|
@ -71,11 +93,9 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
return Icon(AIcons.locationUnlocated, size: size);
|
return Icon(AIcons.locationUnlocated, size: size);
|
||||||
}
|
}
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LocationLevel.place:
|
|
||||||
return Icon(AIcons.place, size: size);
|
|
||||||
case LocationLevel.country:
|
case LocationLevel.country:
|
||||||
if (_countryCode != null && device.canRenderFlagEmojis) {
|
if (_code != null && device.canRenderFlagEmojis) {
|
||||||
final flag = countryCodeToFlag(_countryCode);
|
final flag = EmojiUtils.countryCodeToFlag(_code);
|
||||||
if (flag != null) {
|
if (flag != null) {
|
||||||
return Text(
|
return Text(
|
||||||
flag,
|
flag,
|
||||||
|
@ -85,6 +105,20 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Icon(AIcons.country, size: size);
|
return Icon(AIcons.country, size: size);
|
||||||
|
case LocationLevel.state:
|
||||||
|
if (_code != null && device.canRenderSubdivisionFlagEmojis) {
|
||||||
|
final flag = EmojiUtils.stateCodeToFlag(_code);
|
||||||
|
if (flag != null) {
|
||||||
|
return Text(
|
||||||
|
flag,
|
||||||
|
style: TextStyle(fontSize: size),
|
||||||
|
textScaleFactor: 1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Icon(AIcons.state, size: size);
|
||||||
|
case LocationLevel.place:
|
||||||
|
return Icon(AIcons.place, size: size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,16 +126,7 @@ class LocationFilter extends CoveredCollectionFilter {
|
||||||
String get category => type;
|
String get category => type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get key => '$type-$reversed-$level-$_location';
|
String get key => '$type-$reversed-$level-$code-$place';
|
||||||
|
|
||||||
// U+0041 Latin Capital letter A
|
|
||||||
// U+1F1E6 🇦 REGIONAL INDICATOR SYMBOL LETTER A
|
|
||||||
static const _countryCodeToFlagDiff = 0x1F1E6 - 0x0041;
|
|
||||||
|
|
||||||
static String? countryCodeToFlag(String? code) {
|
|
||||||
if (code == null || code.length != 2) return null;
|
|
||||||
return String.fromCharCodes(code.toUpperCase().codeUnits.map((letter) => letter += _countryCodeToFlagDiff));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LocationLevel { place, country }
|
enum LocationLevel { place, state, country }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:aves/geo/states.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
@ -7,11 +8,15 @@ class AddressDetails extends Equatable {
|
||||||
final int id;
|
final int id;
|
||||||
final String? countryCode, countryName, adminArea, locality;
|
final String? countryCode, countryName, adminArea, locality;
|
||||||
|
|
||||||
String? get place => locality != null && locality!.isNotEmpty ? locality : adminArea;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, countryCode, countryName, adminArea, locality];
|
List<Object?> get props => [id, countryCode, countryName, adminArea, locality];
|
||||||
|
|
||||||
|
String? get place => locality != null && locality!.isNotEmpty ? locality : adminArea;
|
||||||
|
|
||||||
|
String? get stateCode => GeoStates.stateCodeByName[stateName];
|
||||||
|
|
||||||
|
String? get stateName => GeoStates.stateCountryCodes.contains(countryCode) ? adminArea : null;
|
||||||
|
|
||||||
const AddressDetails({
|
const AddressDetails({
|
||||||
required this.id,
|
required this.id,
|
||||||
this.countryCode,
|
this.countryCode,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import 'package:aves/model/source/events.dart';
|
||||||
import 'package:aves/model/source/location/country.dart';
|
import 'package:aves/model/source/location/country.dart';
|
||||||
import 'package:aves/model/source/location/location.dart';
|
import 'package:aves/model/source/location/location.dart';
|
||||||
import 'package:aves/model/source/location/place.dart';
|
import 'package:aves/model/source/location/place.dart';
|
||||||
|
import 'package:aves/model/source/location/state.dart';
|
||||||
import 'package:aves/model/source/tag.dart';
|
import 'package:aves/model/source/tag.dart';
|
||||||
import 'package:aves/model/source/trash.dart';
|
import 'package:aves/model/source/trash.dart';
|
||||||
import 'package:aves/model/vaults/vaults.dart';
|
import 'package:aves/model/vaults/vaults.dart';
|
||||||
|
@ -59,7 +60,7 @@ mixin SourceBase {
|
||||||
void invalidateEntries();
|
void invalidateEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, PlaceMixin, LocationMixin, TagMixin, TrashMixin {
|
abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, PlaceMixin, StateMixin, LocationMixin, TagMixin, TrashMixin {
|
||||||
CollectionSource() {
|
CollectionSource() {
|
||||||
settings.updateStream.where((event) => event.key == Settings.localeKey).listen((_) => invalidateAlbumDisplayNames());
|
settings.updateStream.where((event) => event.key == Settings.localeKey).listen((_) => invalidateAlbumDisplayNames());
|
||||||
settings.updateStream.where((event) => event.key == Settings.hiddenFiltersKey).listen((event) {
|
settings.updateStream.where((event) => event.key == Settings.hiddenFiltersKey).listen((event) {
|
||||||
|
@ -142,6 +143,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
invalidateAlbumFilterSummary(entries: entries, notify: notify);
|
invalidateAlbumFilterSummary(entries: entries, notify: notify);
|
||||||
invalidateCountryFilterSummary(entries: entries, notify: notify);
|
invalidateCountryFilterSummary(entries: entries, notify: notify);
|
||||||
invalidatePlaceFilterSummary(entries: entries, notify: notify);
|
invalidatePlaceFilterSummary(entries: entries, notify: notify);
|
||||||
|
invalidateStateFilterSummary(entries: entries, notify: notify);
|
||||||
invalidateTagFilterSummary(entries: entries, notify: notify);
|
invalidateTagFilterSummary(entries: entries, notify: notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,6 +513,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
switch (filter.level) {
|
switch (filter.level) {
|
||||||
case LocationLevel.country:
|
case LocationLevel.country:
|
||||||
return countryEntryCount(filter);
|
return countryEntryCount(filter);
|
||||||
|
case LocationLevel.state:
|
||||||
|
return stateEntryCount(filter);
|
||||||
case LocationLevel.place:
|
case LocationLevel.place:
|
||||||
return placeEntryCount(filter);
|
return placeEntryCount(filter);
|
||||||
}
|
}
|
||||||
|
@ -525,6 +529,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
switch (filter.level) {
|
switch (filter.level) {
|
||||||
case LocationLevel.country:
|
case LocationLevel.country:
|
||||||
return countrySize(filter);
|
return countrySize(filter);
|
||||||
|
case LocationLevel.state:
|
||||||
|
return stateSize(filter);
|
||||||
case LocationLevel.place:
|
case LocationLevel.place:
|
||||||
return placeSize(filter);
|
return placeSize(filter);
|
||||||
}
|
}
|
||||||
|
@ -539,6 +545,8 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
switch (filter.level) {
|
switch (filter.level) {
|
||||||
case LocationLevel.country:
|
case LocationLevel.country:
|
||||||
return countryRecentEntry(filter);
|
return countryRecentEntry(filter);
|
||||||
|
case LocationLevel.state:
|
||||||
|
return stateRecentEntry(filter);
|
||||||
case LocationLevel.place:
|
case LocationLevel.place:
|
||||||
return placeRecentEntry(filter);
|
return placeRecentEntry(filter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import 'package:aves/utils/collection_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
mixin CountryMixin on SourceBase {
|
mixin CountryMixin on SourceBase {
|
||||||
// filter summary
|
|
||||||
|
|
||||||
// by country code
|
// by country code
|
||||||
final Map<String, int> _filterEntryCountMap = {}, _filterSizeMap = {};
|
final Map<String, int> _filterEntryCountMap = {}, _filterSizeMap = {};
|
||||||
final Map<String, AvesEntry?> _filterRecentEntryMap = {};
|
final Map<String, AvesEntry?> _filterRecentEntryMap = {};
|
||||||
|
@ -39,19 +37,19 @@ mixin CountryMixin on SourceBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
int countryEntryCount(LocationFilter filter) {
|
int countryEntryCount(LocationFilter filter) {
|
||||||
final countryCode = filter.countryCode;
|
final countryCode = filter.code;
|
||||||
if (countryCode == null) return 0;
|
if (countryCode == null) return 0;
|
||||||
return _filterEntryCountMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).length);
|
return _filterEntryCountMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).length);
|
||||||
}
|
}
|
||||||
|
|
||||||
int countrySize(LocationFilter filter) {
|
int countrySize(LocationFilter filter) {
|
||||||
final countryCode = filter.countryCode;
|
final countryCode = filter.code;
|
||||||
if (countryCode == null) return 0;
|
if (countryCode == null) return 0;
|
||||||
return _filterSizeMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum);
|
return _filterSizeMap.putIfAbsent(countryCode, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
AvesEntry? countryRecentEntry(LocationFilter filter) {
|
AvesEntry? countryRecentEntry(LocationFilter filter) {
|
||||||
final countryCode = filter.countryCode;
|
final countryCode = filter.code;
|
||||||
if (countryCode == null) return null;
|
if (countryCode == null) return null;
|
||||||
return _filterRecentEntryMap.putIfAbsent(countryCode, () => sortedEntriesByDate.firstWhereOrNull(filter.test));
|
return _filterRecentEntryMap.putIfAbsent(countryCode, () => sortedEntriesByDate.firstWhereOrNull(filter.test));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,18 @@ import 'package:aves/model/source/analysis_controller.dart';
|
||||||
import 'package:aves/model/source/enums/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/location/country.dart';
|
import 'package:aves/model/source/location/country.dart';
|
||||||
import 'package:aves/model/source/location/place.dart';
|
import 'package:aves/model/source/location/place.dart';
|
||||||
|
import 'package:aves/model/source/location/state.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
mixin LocationMixin on CountryMixin, PlaceMixin {
|
mixin LocationMixin on CountryMixin, StateMixin {
|
||||||
static const commitCountThreshold = 200;
|
static const commitCountThreshold = 200;
|
||||||
static const _stopCheckCountThreshold = 50;
|
static const _stopCheckCountThreshold = 50;
|
||||||
|
|
||||||
List<String> sortedCountries = List.unmodifiable([]);
|
List<String> sortedCountries = List.unmodifiable([]);
|
||||||
|
List<String> sortedStates = List.unmodifiable([]);
|
||||||
List<String> sortedPlaces = List.unmodifiable([]);
|
List<String> sortedPlaces = List.unmodifiable([]);
|
||||||
|
|
||||||
Future<void> loadAddresses({Set<int>? ids}) async {
|
Future<void> loadAddresses({Set<int>? ids}) async {
|
||||||
|
@ -152,32 +154,56 @@ mixin LocationMixin on CountryMixin, PlaceMixin {
|
||||||
|
|
||||||
void updateLocations() {
|
void updateLocations() {
|
||||||
final locations = visibleEntries.map((entry) => entry.addressDetails).whereNotNull().toList();
|
final locations = visibleEntries.map((entry) => entry.addressDetails).whereNotNull().toList();
|
||||||
|
|
||||||
final updatedPlaces = locations.map((address) => address.place).whereNotNull().where((v) => v.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase);
|
final updatedPlaces = locations.map((address) => address.place).whereNotNull().where((v) => v.isNotEmpty).toSet().toList()..sort(compareAsciiUpperCase);
|
||||||
if (!listEquals(updatedPlaces, sortedPlaces)) {
|
if (!listEquals(updatedPlaces, sortedPlaces)) {
|
||||||
sortedPlaces = List.unmodifiable(updatedPlaces);
|
sortedPlaces = List.unmodifiable(updatedPlaces);
|
||||||
eventBus.fire(PlacesChangedEvent());
|
eventBus.fire(PlacesChangedEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
// the same country code could be found with different country names
|
final updatedStates = _getAreaByCode(
|
||||||
// e.g. if the locale changed between geocoding calls
|
locations: locations,
|
||||||
// so we merge countries by code, keeping only one name for each code
|
getCode: (v) => v.stateCode,
|
||||||
final countriesByCode = Map.fromEntries(locations.map((address) {
|
getName: (v) => v.stateName,
|
||||||
final code = address.countryCode;
|
);
|
||||||
if (code == null || code.isEmpty) return null;
|
if (!listEquals(updatedStates, sortedStates)) {
|
||||||
return MapEntry(code, address.countryName);
|
sortedStates = List.unmodifiable(updatedStates);
|
||||||
}).whereNotNull());
|
invalidateStateFilterSummary();
|
||||||
final updatedCountries = countriesByCode.entries.map((kv) {
|
eventBus.fire(StatesChangedEvent());
|
||||||
final code = kv.key;
|
}
|
||||||
final name = kv.value;
|
|
||||||
return '${name != null && name.isNotEmpty ? name : code}${LocationFilter.locationSeparator}$code';
|
final updatedCountries = _getAreaByCode(
|
||||||
}).toList()
|
locations: locations,
|
||||||
..sort(compareAsciiUpperCase);
|
getCode: (v) => v.countryCode,
|
||||||
|
getName: (v) => v.countryName,
|
||||||
|
);
|
||||||
if (!listEquals(updatedCountries, sortedCountries)) {
|
if (!listEquals(updatedCountries, sortedCountries)) {
|
||||||
sortedCountries = List.unmodifiable(updatedCountries);
|
sortedCountries = List.unmodifiable(updatedCountries);
|
||||||
invalidateCountryFilterSummary();
|
invalidateCountryFilterSummary();
|
||||||
eventBus.fire(CountriesChangedEvent());
|
eventBus.fire(CountriesChangedEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the same country/state code could be found with different country/state names
|
||||||
|
// e.g. if the locale changed between geocoding calls
|
||||||
|
// so we merge countries/states by code, keeping only one name for each code
|
||||||
|
List<String> _getAreaByCode({
|
||||||
|
required List<AddressDetails> locations,
|
||||||
|
required String? Function(AddressDetails address) getCode,
|
||||||
|
required String? Function(AddressDetails address) getName,
|
||||||
|
}) {
|
||||||
|
final namesByCode = Map.fromEntries(locations.map((address) {
|
||||||
|
final code = getCode(address);
|
||||||
|
if (code == null || code.isEmpty) return null;
|
||||||
|
return MapEntry(code, getName(address));
|
||||||
|
}).whereNotNull());
|
||||||
|
return namesByCode.entries.map((kv) {
|
||||||
|
final code = kv.key;
|
||||||
|
final name = kv.value;
|
||||||
|
return '${name != null && name.isNotEmpty ? name : code}${LocationFilter.locationSeparator}$code';
|
||||||
|
}).toList()
|
||||||
|
..sort(compareAsciiUpperCase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddressMetadataChangedEvent {}
|
class AddressMetadataChangedEvent {}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import 'package:aves/utils/collection_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
mixin PlaceMixin on SourceBase {
|
mixin PlaceMixin on SourceBase {
|
||||||
// filter summary
|
|
||||||
|
|
||||||
// by place
|
// by place
|
||||||
final Map<String, int> _filterEntryCountMap = {}, _filterSizeMap = {};
|
final Map<String, int> _filterEntryCountMap = {}, _filterSizeMap = {};
|
||||||
final Map<String, AvesEntry?> _filterRecentEntryMap = {};
|
final Map<String, AvesEntry?> _filterRecentEntryMap = {};
|
||||||
|
|
64
lib/model/source/location/state.dart
Normal file
64
lib/model/source/location/state.dart
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
import 'package:aves/model/filters/location.dart';
|
||||||
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
|
import 'package:aves/utils/collection_utils.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
mixin StateMixin on SourceBase {
|
||||||
|
// by state code
|
||||||
|
final Map<String, int> _filterEntryCountMap = {}, _filterSizeMap = {};
|
||||||
|
final Map<String, AvesEntry?> _filterRecentEntryMap = {};
|
||||||
|
|
||||||
|
void invalidateStateFilterSummary({
|
||||||
|
Set<AvesEntry>? entries,
|
||||||
|
Set<String>? stateCodes,
|
||||||
|
bool notify = true,
|
||||||
|
}) {
|
||||||
|
if (_filterEntryCountMap.isEmpty && _filterSizeMap.isEmpty && _filterRecentEntryMap.isEmpty) return;
|
||||||
|
|
||||||
|
if (entries == null && stateCodes == null) {
|
||||||
|
_filterEntryCountMap.clear();
|
||||||
|
_filterSizeMap.clear();
|
||||||
|
_filterRecentEntryMap.clear();
|
||||||
|
} else {
|
||||||
|
stateCodes ??= {};
|
||||||
|
if (entries != null) {
|
||||||
|
stateCodes.addAll(entries.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails?.stateCode).whereNotNull());
|
||||||
|
}
|
||||||
|
stateCodes.forEach((stateCode) {
|
||||||
|
_filterEntryCountMap.remove(stateCode);
|
||||||
|
_filterSizeMap.remove(stateCode);
|
||||||
|
_filterRecentEntryMap.remove(stateCode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (notify) {
|
||||||
|
eventBus.fire(StateSummaryInvalidatedEvent(stateCodes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int stateEntryCount(LocationFilter filter) {
|
||||||
|
final stateCode = filter.code;
|
||||||
|
if (stateCode == null) return 0;
|
||||||
|
return _filterEntryCountMap.putIfAbsent(stateCode, () => visibleEntries.where(filter.test).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int stateSize(LocationFilter filter) {
|
||||||
|
final stateCode = filter.code;
|
||||||
|
if (stateCode == null) return 0;
|
||||||
|
return _filterSizeMap.putIfAbsent(stateCode, () => visibleEntries.where(filter.test).map((v) => v.sizeBytes).sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
AvesEntry? stateRecentEntry(LocationFilter filter) {
|
||||||
|
final stateCode = filter.code;
|
||||||
|
if (stateCode == null) return null;
|
||||||
|
return _filterRecentEntryMap.putIfAbsent(stateCode, () => sortedEntriesByDate.firstWhereOrNull(filter.test));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatesChangedEvent {}
|
||||||
|
|
||||||
|
class StateSummaryInvalidatedEvent {
|
||||||
|
final Set<String>? stateCodes;
|
||||||
|
|
||||||
|
const StateSummaryInvalidatedEvent(this.stateCodes);
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
@ -35,9 +36,12 @@ class GeocodingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class Address {
|
class Address extends Equatable {
|
||||||
final String? addressLine, adminArea, countryCode, countryName, featureName, locality, postalCode, subAdminArea, subLocality, subThoroughfare, thoroughfare;
|
final String? addressLine, adminArea, countryCode, countryName, featureName, locality, postalCode, subAdminArea, subLocality, subThoroughfare, thoroughfare;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [addressLine, adminArea, countryCode, countryName, featureName, locality, postalCode, subAdminArea, subLocality, subThoroughfare, thoroughfare];
|
||||||
|
|
||||||
const Address({
|
const Address({
|
||||||
this.addressLine,
|
this.addressLine,
|
||||||
this.adminArea,
|
this.adminArea,
|
||||||
|
|
|
@ -37,6 +37,7 @@ class AIcons {
|
||||||
static const IconData location = Icons.place_outlined;
|
static const IconData location = Icons.place_outlined;
|
||||||
static const IconData locationUnlocated = Icons.location_off_outlined;
|
static const IconData locationUnlocated = Icons.location_off_outlined;
|
||||||
static const IconData country = Icons.flag_outlined;
|
static const IconData country = Icons.flag_outlined;
|
||||||
|
static const IconData state = Icons.flag_outlined;
|
||||||
static const IconData place = Icons.place_outlined;
|
static const IconData place = Icons.place_outlined;
|
||||||
static const IconData mainStorage = Icons.smartphone_outlined;
|
static const IconData mainStorage = Icons.smartphone_outlined;
|
||||||
static const IconData mimeType = Icons.code_outlined;
|
static const IconData mimeType = Icons.code_outlined;
|
||||||
|
|
93
lib/utils/emoji_utils.dart
Normal file
93
lib/utils/emoji_utils.dart
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
class EmojiUtils {
|
||||||
|
// U+0041 Latin Capital letter A
|
||||||
|
static const _capitalLetterA = 0x0041;
|
||||||
|
|
||||||
|
// U+1F1E6 Regional Indicator Symbol Letter A
|
||||||
|
static const _countryCodeToFlagDiff = 0x1F1E6 - _capitalLetterA;
|
||||||
|
|
||||||
|
// U+E0061 Tag Latin Small Letter a
|
||||||
|
static const _stateCodeToFlagDiff = 0xE0061 - _capitalLetterA;
|
||||||
|
|
||||||
|
static const _blackFlag = 0x1F3F4;
|
||||||
|
static const _cancel = 0xE007F;
|
||||||
|
|
||||||
|
static String? countryCodeToFlag(String? code) {
|
||||||
|
if (code == null || code.length != 2) return null;
|
||||||
|
return String.fromCharCodes(code.toUpperCase().codeUnits.map((letter) => letter += _countryCodeToFlagDiff));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? stateCodeToFlag(String? code) {
|
||||||
|
if (code == null) return null;
|
||||||
|
return String.fromCharCodes([_blackFlag, ...code.toUpperCase().codeUnits.map((letter) => letter += _stateCodeToFlagDiff), _cancel]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateCodes {
|
||||||
|
// AU
|
||||||
|
static const auAustralianCapitalTerritory = 'auact';
|
||||||
|
static const auNewSouthWales = 'aunsw';
|
||||||
|
static const auNorthernTerritory = 'aunt';
|
||||||
|
static const auQueensland = 'auqld';
|
||||||
|
static const auSouthAustralia = 'ausa';
|
||||||
|
static const auTasmania = 'autas';
|
||||||
|
static const auVictoria = 'auvic';
|
||||||
|
static const auWesternAustralia = 'auwa';
|
||||||
|
|
||||||
|
// GB
|
||||||
|
static const gbEngland = 'gbeng';
|
||||||
|
static const gbNorthernIreland = 'gbnir';
|
||||||
|
static const gbScotland = 'gbsct';
|
||||||
|
static const gbWales = 'gbwls';
|
||||||
|
|
||||||
|
// US
|
||||||
|
static const usAlabama = 'usal';
|
||||||
|
static const usAlaska = 'usak';
|
||||||
|
static const usArizona = 'usaz';
|
||||||
|
static const usArkansas = 'usar';
|
||||||
|
static const usCalifornia = 'usca';
|
||||||
|
static const usColorado = 'usco';
|
||||||
|
static const usConnecticut = 'usct';
|
||||||
|
static const usDelaware = 'usde';
|
||||||
|
static const usFlorida = 'usfl';
|
||||||
|
static const usGeorgia = 'usga';
|
||||||
|
static const usHawaii = 'ushi';
|
||||||
|
static const usIdaho = 'usid';
|
||||||
|
static const usIllinois = 'usil';
|
||||||
|
static const usIndiana = 'usin';
|
||||||
|
static const usIowa = 'usia';
|
||||||
|
static const usKansas = 'usks';
|
||||||
|
static const usKentucky = 'usky';
|
||||||
|
static const usLouisiana = 'usla';
|
||||||
|
static const usMaine = 'usme';
|
||||||
|
static const usMaryland = 'usmd';
|
||||||
|
static const usMassachusetts = 'usma';
|
||||||
|
static const usMichigan = 'usmi';
|
||||||
|
static const usMinnesota = 'usmn';
|
||||||
|
static const usMississippi = 'usms';
|
||||||
|
static const usMissouri = 'usmo';
|
||||||
|
static const usMontana = 'usmt';
|
||||||
|
static const usNebraska = 'usne';
|
||||||
|
static const usNevada = 'usnv';
|
||||||
|
static const usNewHampshire = 'usnh';
|
||||||
|
static const usNewJersey = 'usnj';
|
||||||
|
static const usNewMexico = 'usnm';
|
||||||
|
static const usNewYork = 'usny';
|
||||||
|
static const usNorthCarolina = 'usnc';
|
||||||
|
static const usNorthDakota = 'usnd';
|
||||||
|
static const usOhio = 'usoh';
|
||||||
|
static const usOklahoma = 'usok';
|
||||||
|
static const usOregon = 'usor';
|
||||||
|
static const usPennsylvania = 'uspa';
|
||||||
|
static const usRhodeIsland = 'usri';
|
||||||
|
static const usSouthCarolina = 'ussc';
|
||||||
|
static const usSouthDakota = 'ussd';
|
||||||
|
static const usTennessee = 'ustn';
|
||||||
|
static const usUtah = 'usut';
|
||||||
|
static const usVermont = 'usvt';
|
||||||
|
static const usVirginia = 'usva';
|
||||||
|
static const usWashington = 'uswa';
|
||||||
|
static const usWashingtonDC = 'usdc';
|
||||||
|
static const usWestVirginia = 'uswv';
|
||||||
|
static const usWisconsin = 'uswi';
|
||||||
|
static const usWyoming = 'uswy';
|
||||||
|
}
|
|
@ -18,7 +18,6 @@ import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/media_store_source.dart';
|
import 'package:aves/model/source/media_store_source.dart';
|
||||||
import 'package:aves/services/accessibility_service.dart';
|
import 'package:aves/services/accessibility_service.dart';
|
||||||
import 'package:aves_utils/aves_utils.dart';
|
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
|
@ -40,6 +39,7 @@ import 'package:aves/widgets/home_page.dart';
|
||||||
import 'package:aves/widgets/navigation/tv_page_transitions.dart';
|
import 'package:aves/widgets/navigation/tv_page_transitions.dart';
|
||||||
import 'package:aves/widgets/navigation/tv_rail.dart';
|
import 'package:aves/widgets/navigation/tv_rail.dart';
|
||||||
import 'package:aves/widgets/welcome_page.dart';
|
import 'package:aves/widgets/welcome_page.dart';
|
||||||
|
import 'package:aves_utils/aves_utils.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:fijkplayer/fijkplayer.dart';
|
import 'package:fijkplayer/fijkplayer.dart';
|
||||||
|
@ -74,8 +74,7 @@ class AvesApp extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
State<AvesApp> createState() => _AvesAppState();
|
State<AvesApp> createState() => _AvesAppState();
|
||||||
|
|
||||||
static void setSystemUIStyle(BuildContext context) {
|
static void setSystemUIStyle(ThemeData theme) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
final style = systemUIStyleForBrightness(theme.brightness, theme.scaffoldBackgroundColor);
|
final style = systemUIStyleForBrightness(theme.brightness, theme.scaffoldBackgroundColor);
|
||||||
SystemChrome.setSystemUIOverlayStyle(style);
|
SystemChrome.setSystemUIOverlayStyle(style);
|
||||||
}
|
}
|
||||||
|
@ -300,7 +299,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
required Widget? child,
|
required Widget? child,
|
||||||
}) {
|
}) {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => AvesApp.setSystemUIStyle(context));
|
WidgetsBinding.instance.addPostFrameCallback((_) => AvesApp.setSystemUIStyle(Theme.of(context)));
|
||||||
}
|
}
|
||||||
return Selector<Settings, bool>(
|
return Selector<Settings, bool>(
|
||||||
selector: (context, s) => s.initialized ? s.accessibilityAnimations.animate : true,
|
selector: (context, s) => s.initialized ? s.accessibilityAnimations.animate : true,
|
||||||
|
|
|
@ -417,6 +417,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
|
|
||||||
Set<String> obsoleteTags = todoItems.expand((entry) => entry.tags).toSet();
|
Set<String> obsoleteTags = todoItems.expand((entry) => entry.tags).toSet();
|
||||||
Set<String> obsoleteCountryCodes = todoItems.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails?.countryCode).whereNotNull().toSet();
|
Set<String> obsoleteCountryCodes = todoItems.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails?.countryCode).whereNotNull().toSet();
|
||||||
|
Set<String> obsoleteStateCodes = todoItems.where((entry) => entry.hasAddress).map((entry) => entry.addressDetails?.stateCode).whereNotNull().toSet();
|
||||||
|
|
||||||
final dataTypes = <EntryDataType>{};
|
final dataTypes = <EntryDataType>{};
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
|
@ -447,6 +448,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
if (obsoleteCountryCodes.isNotEmpty) {
|
if (obsoleteCountryCodes.isNotEmpty) {
|
||||||
source.invalidateCountryFilterSummary(countryCodes: obsoleteCountryCodes);
|
source.invalidateCountryFilterSummary(countryCodes: obsoleteCountryCodes);
|
||||||
}
|
}
|
||||||
|
if (obsoleteStateCodes.isNotEmpty) {
|
||||||
|
source.invalidateStateFilterSummary(stateCodes: obsoleteStateCodes);
|
||||||
|
}
|
||||||
if (obsoleteTags.isNotEmpty) {
|
if (obsoleteTags.isNotEmpty) {
|
||||||
source.invalidateTagFilterSummary(tags: obsoleteTags);
|
source.invalidateTagFilterSummary(tags: obsoleteTags);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
||||||
}
|
}
|
||||||
case LocationFilter:
|
case LocationFilter:
|
||||||
{
|
{
|
||||||
final countryCode = (filter as LocationFilter).countryCode;
|
final countryCode = (filter as LocationFilter).code;
|
||||||
return StreamBuilder<CountrySummaryInvalidatedEvent>(
|
return StreamBuilder<CountrySummaryInvalidatedEvent>(
|
||||||
stream: source.eventBus.on<CountrySummaryInvalidatedEvent>().where((event) => event.countryCodes == null || event.countryCodes!.contains(countryCode)),
|
stream: source.eventBus.on<CountrySummaryInvalidatedEvent>().where((event) => event.countryCodes == null || event.countryCodes!.contains(countryCode)),
|
||||||
builder: (context, snapshot) => _buildChip(context, source),
|
builder: (context, snapshot) => _buildChip(context, source),
|
||||||
|
|
|
@ -230,7 +230,10 @@ class CollectionSearchDelegate extends AvesSearchDelegate with FeedbackMixin, Va
|
||||||
return _buildFilterRow(
|
return _buildFilterRow(
|
||||||
context: context,
|
context: context,
|
||||||
title: context.l10n.searchPlacesSectionTitle,
|
title: context.l10n.searchPlacesSectionTitle,
|
||||||
filters: source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s)).toList(),
|
filters: [
|
||||||
|
...source.sortedStates.where(containQuery).map((s) => LocationFilter(LocationLevel.state, s)),
|
||||||
|
...source.sortedPlaces.where(containQuery).map((s) => LocationFilter(LocationLevel.place, s)),
|
||||||
|
].toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,6 +29,7 @@ import 'package:aves/widgets/stats/filter_table.dart';
|
||||||
import 'package:aves/widgets/stats/mime_donut.dart';
|
import 'package:aves/widgets/stats/mime_donut.dart';
|
||||||
import 'package:aves/widgets/stats/percent_text.dart';
|
import 'package:aves/widgets/stats/percent_text.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
|
@ -54,7 +55,8 @@ class StatsPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMixin {
|
class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMixin {
|
||||||
final Map<String, int> _entryCountPerCountry = {}, _entryCountPerPlace = {}, _entryCountPerTag = {}, _entryCountPerAlbum = {};
|
final Map<String, int> _entryCountPerCountry = {}, _entryCountPerTag = {}, _entryCountPerAlbum = {};
|
||||||
|
final Map<_PlaceFilterKey, int> _entryCountPerPlace = {};
|
||||||
final Map<int, int> _entryCountPerRating = Map.fromEntries(List.generate(7, (i) => MapEntry(5 - i, 0)));
|
final Map<int, int> _entryCountPerRating = Map.fromEntries(List.generate(7, (i) => MapEntry(5 - i, 0)));
|
||||||
late final ValueNotifier<bool> _isPageAnimatingNotifier;
|
late final ValueNotifier<bool> _isPageAnimatingNotifier;
|
||||||
|
|
||||||
|
@ -78,9 +80,16 @@ class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMix
|
||||||
country += '${LocationFilter.locationSeparator}${address.countryCode}';
|
country += '${LocationFilter.locationSeparator}${address.countryCode}';
|
||||||
_entryCountPerCountry[country] = (_entryCountPerCountry[country] ?? 0) + 1;
|
_entryCountPerCountry[country] = (_entryCountPerCountry[country] ?? 0) + 1;
|
||||||
}
|
}
|
||||||
|
var state = address.stateName;
|
||||||
|
if (state != null && state.isNotEmpty) {
|
||||||
|
state += '${LocationFilter.locationSeparator}${address.stateCode}';
|
||||||
|
final key = _PlaceFilterKey(LocationLevel.state, state);
|
||||||
|
_entryCountPerPlace[key] = (_entryCountPerPlace[key] ?? 0) + 1;
|
||||||
|
}
|
||||||
final place = address.place;
|
final place = address.place;
|
||||||
if (place != null && place.isNotEmpty) {
|
if (place != null && place.isNotEmpty) {
|
||||||
_entryCountPerPlace[place] = (_entryCountPerPlace[place] ?? 0) + 1;
|
final key = _PlaceFilterKey(LocationLevel.place, place);
|
||||||
|
_entryCountPerPlace[key] = (_entryCountPerPlace[key] ?? 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +219,7 @@ class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMix
|
||||||
),
|
),
|
||||||
locationIndicator,
|
locationIndicator,
|
||||||
..._buildFilterSection<String>(context, l10n.statsTopCountriesSectionTitle, _entryCountPerCountry, (v) => LocationFilter(LocationLevel.country, v)),
|
..._buildFilterSection<String>(context, l10n.statsTopCountriesSectionTitle, _entryCountPerCountry, (v) => LocationFilter(LocationLevel.country, v)),
|
||||||
..._buildFilterSection<String>(context, l10n.statsTopPlacesSectionTitle, _entryCountPerPlace, (v) => LocationFilter(LocationLevel.place, v)),
|
..._buildFilterSection<_PlaceFilterKey>(context, l10n.statsTopPlacesSectionTitle, _entryCountPerPlace, (v) => LocationFilter(v.level, v.location)),
|
||||||
..._buildFilterSection<String>(context, l10n.statsTopTagsSectionTitle, _entryCountPerTag, TagFilter.new),
|
..._buildFilterSection<String>(context, l10n.statsTopTagsSectionTitle, _entryCountPerTag, TagFilter.new),
|
||||||
..._buildFilterSection<String>(context, l10n.statsTopAlbumsSectionTitle, _entryCountPerAlbum, (v) => AlbumFilter(v, source.getAlbumDisplayName(context, v))),
|
..._buildFilterSection<String>(context, l10n.statsTopAlbumsSectionTitle, _entryCountPerAlbum, (v) => AlbumFilter(v, source.getAlbumDisplayName(context, v))),
|
||||||
if (showRatings) ..._buildFilterSection<int>(context, l10n.searchRatingSectionTitle, _entryCountPerRating, RatingFilter.new, sortByCount: false, maxRowCount: null),
|
if (showRatings) ..._buildFilterSection<int>(context, l10n.searchRatingSectionTitle, _entryCountPerRating, RatingFilter.new, sortByCount: false, maxRowCount: null),
|
||||||
|
@ -399,3 +408,27 @@ class StatsTopPage extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class _PlaceFilterKey extends Comparable<_PlaceFilterKey> with EquatableMixin {
|
||||||
|
final LocationLevel level;
|
||||||
|
final String location;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [level, location];
|
||||||
|
|
||||||
|
_PlaceFilterKey(this.level, this.location);
|
||||||
|
|
||||||
|
static const _levelOrder = [
|
||||||
|
LocationLevel.country,
|
||||||
|
LocationLevel.state,
|
||||||
|
LocationLevel.place,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
int compareTo(_PlaceFilterKey other) {
|
||||||
|
final c = _levelOrder.indexOf(level).compareTo(_levelOrder.indexOf(other.level));
|
||||||
|
if (c != 0) return c;
|
||||||
|
return location.compareTo(other.location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ mixin SingleEntryEditorMixin on FeedbackMixin, PermissionAwareMixin {
|
||||||
if (isMainMode && source != null) {
|
if (isMainMode && source != null) {
|
||||||
Set<String> obsoleteTags = targetEntry.tags;
|
Set<String> obsoleteTags = targetEntry.tags;
|
||||||
String? obsoleteCountryCode = targetEntry.addressDetails?.countryCode;
|
String? obsoleteCountryCode = targetEntry.addressDetails?.countryCode;
|
||||||
|
String? obsoleteStateCode = targetEntry.addressDetails?.stateCode;
|
||||||
|
|
||||||
await source.refreshEntries({targetEntry}, dataTypes);
|
await source.refreshEntries({targetEntry}, dataTypes);
|
||||||
|
|
||||||
|
@ -43,6 +44,9 @@ mixin SingleEntryEditorMixin on FeedbackMixin, PermissionAwareMixin {
|
||||||
if (obsoleteCountryCode != null) {
|
if (obsoleteCountryCode != null) {
|
||||||
source.invalidateCountryFilterSummary(countryCodes: {obsoleteCountryCode});
|
source.invalidateCountryFilterSummary(countryCodes: {obsoleteCountryCode});
|
||||||
}
|
}
|
||||||
|
if (obsoleteStateCode != null) {
|
||||||
|
source.invalidateStateFilterSummary(stateCodes: {obsoleteStateCode});
|
||||||
|
}
|
||||||
if (obsoleteTags.isNotEmpty) {
|
if (obsoleteTags.isNotEmpty) {
|
||||||
source.invalidateTagFilterSummary(tags: obsoleteTags);
|
source.invalidateTagFilterSummary(tags: obsoleteTags);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,11 @@ import 'dart:collection';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/location.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/android_debug_service.dart';
|
import 'package:aves/services/android_debug_service.dart';
|
||||||
|
import 'package:aves/services/geocoding_service.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||||
import 'package:aves/widgets/viewer/info/common.dart';
|
import 'package:aves/widgets/viewer/info/common.dart';
|
||||||
|
@ -23,7 +26,7 @@ class MetadataTab extends StatefulWidget {
|
||||||
|
|
||||||
class _MetadataTabState extends State<MetadataTab> {
|
class _MetadataTabState extends State<MetadataTab> {
|
||||||
late Future<Map> _bitmapFactoryLoader, _contentResolverMetadataLoader, _exifInterfaceMetadataLoader;
|
late Future<Map> _bitmapFactoryLoader, _contentResolverMetadataLoader, _exifInterfaceMetadataLoader;
|
||||||
late Future<Map> _mediaMetadataLoader, _metadataExtractorLoader, _pixyMetaLoader, _tiffStructureLoader;
|
late Future<Map> _mediaMetadataLoader, _metadataExtractorLoader, _pixyMetaLoader, _tiffStructureLoader, _addressLoader;
|
||||||
late Future<String?> _mp4ParserDumpLoader;
|
late Future<String?> _mp4ParserDumpLoader;
|
||||||
|
|
||||||
// MediaStore timestamp keys
|
// MediaStore timestamp keys
|
||||||
|
@ -47,6 +50,27 @@ class _MetadataTabState extends State<MetadataTab> {
|
||||||
_mp4ParserDumpLoader = AndroidDebugService.getMp4ParserDump(entry);
|
_mp4ParserDumpLoader = AndroidDebugService.getMp4ParserDump(entry);
|
||||||
_pixyMetaLoader = AndroidDebugService.getPixyMetadata(entry);
|
_pixyMetaLoader = AndroidDebugService.getPixyMetadata(entry);
|
||||||
_tiffStructureLoader = AndroidDebugService.getTiffStructure(entry);
|
_tiffStructureLoader = AndroidDebugService.getTiffStructure(entry);
|
||||||
|
_addressLoader = entry.hasGps
|
||||||
|
? GeocodingService.getAddress(entry.latLng!, settings.appliedLocale).then((addresses) {
|
||||||
|
if (addresses.isNotEmpty) {
|
||||||
|
final address = addresses.first;
|
||||||
|
return {
|
||||||
|
'addressLine': address.addressLine,
|
||||||
|
'adminArea': address.adminArea,
|
||||||
|
'countryCode': address.countryCode,
|
||||||
|
'countryName': address.countryName,
|
||||||
|
'featureName': address.featureName,
|
||||||
|
'locality': address.locality,
|
||||||
|
'postalCode': address.postalCode,
|
||||||
|
'subAdminArea': address.subAdminArea,
|
||||||
|
'subLocality': address.subLocality,
|
||||||
|
'subThoroughfare': address.subThoroughfare,
|
||||||
|
'thoroughfare': address.thoroughfare,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
})
|
||||||
|
: Future.value({});
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +176,10 @@ class _MetadataTabState extends State<MetadataTab> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
FutureBuilder<Map>(
|
||||||
|
future: _addressLoader,
|
||||||
|
builder: (context, snapshot) => builderFromSnapshot(context, snapshot, 'Address'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
|
@ -15,7 +16,6 @@ import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves_utils/aves_utils.dart';
|
|
||||||
import 'package:aves/widgets/aves_app.dart';
|
import 'package:aves/widgets/aves_app.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
|
@ -33,9 +33,10 @@ import 'package:aves/widgets/viewer/overlay/top.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/video/video.dart';
|
import 'package:aves/widgets/viewer/overlay/video/video.dart';
|
||||||
import 'package:aves/widgets/viewer/page_entry_builder.dart';
|
import 'package:aves/widgets/viewer/page_entry_builder.dart';
|
||||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||||
import 'package:aves_video/aves_video.dart';
|
|
||||||
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/controller_mixin.dart';
|
import 'package:aves/widgets/viewer/visual/controller_mixin.dart';
|
||||||
|
import 'package:aves_utils/aves_utils.dart';
|
||||||
|
import 'package:aves_video/aves_video.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:floating/floating.dart';
|
import 'package:floating/floating.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -544,16 +545,16 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
_verticalScrollNotifier.notify();
|
_verticalScrollNotifier.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToCollection(CollectionFilter filter) {
|
Future<void> _goToCollection(CollectionFilter filter) async {
|
||||||
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
|
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
|
||||||
if (!isMainMode) return;
|
if (!isMainMode) return;
|
||||||
|
|
||||||
final baseCollection = collection;
|
final baseCollection = collection;
|
||||||
if (baseCollection == null) return;
|
if (baseCollection == null) return;
|
||||||
|
|
||||||
_onLeave();
|
unawaited(_onLeave());
|
||||||
final uri = entryNotifier.value?.uri;
|
final uri = entryNotifier.value?.uri;
|
||||||
Navigator.maybeOf(context)?.pushAndRemoveUntil(
|
unawaited(Navigator.maybeOf(context)?.pushAndRemoveUntil(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
builder: (context) => CollectionPage(
|
builder: (context) => CollectionPage(
|
||||||
|
@ -563,7 +564,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(route) => false,
|
(route) => false,
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _goToVerticalPage(int page) async {
|
Future<void> _goToVerticalPage(int page) async {
|
||||||
|
@ -704,8 +705,8 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
|
|
||||||
void _popVisual() {
|
void _popVisual() {
|
||||||
if (Navigator.canPop(context)) {
|
if (Navigator.canPop(context)) {
|
||||||
void pop() {
|
Future<void> pop() async {
|
||||||
_onLeave();
|
unawaited(_onLeave());
|
||||||
Navigator.maybeOf(context)?.pop();
|
Navigator.maybeOf(context)?.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -747,13 +748,17 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLeave() async {
|
Future<void> _onLeave() async {
|
||||||
|
// get the theme first, as the context is likely
|
||||||
|
// to be unmounted after the other async steps
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
await ScreenBrightness().resetScreenBrightness();
|
await ScreenBrightness().resetScreenBrightness();
|
||||||
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
||||||
await windowService.keepScreenOn(false);
|
await windowService.keepScreenOn(false);
|
||||||
}
|
}
|
||||||
await mediaSessionService.release();
|
await mediaSessionService.release();
|
||||||
await AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
AvesApp.setSystemUIStyle(context);
|
AvesApp.setSystemUIStyle(theme);
|
||||||
if (!settings.useTvLayout) {
|
if (!settings.useTvLayout) {
|
||||||
await windowService.requestOrientation();
|
await windowService.requestOrientation();
|
||||||
}
|
}
|
||||||
|
@ -805,7 +810,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (_overlayVisible.value) {
|
if (_overlayVisible.value) {
|
||||||
await AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
AvesApp.setSystemUIStyle(context);
|
AvesApp.setSystemUIStyle(Theme.of(context));
|
||||||
if (animate) {
|
if (animate) {
|
||||||
await _overlayAnimationController.forward();
|
await _overlayAnimationController.forward();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -103,6 +103,8 @@ class _LocationSectionState extends State<LocationSection> {
|
||||||
final address = entry.addressDetails!;
|
final address = entry.addressDetails!;
|
||||||
final country = address.countryName;
|
final country = address.countryName;
|
||||||
if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country${LocationFilter.locationSeparator}${address.countryCode}'));
|
if (country != null && country.isNotEmpty) filters.add(LocationFilter(LocationLevel.country, '$country${LocationFilter.locationSeparator}${address.countryCode}'));
|
||||||
|
final state = address.stateName;
|
||||||
|
if (state != null && state.isNotEmpty) filters.add(LocationFilter(LocationLevel.state, '$state${LocationFilter.locationSeparator}${address.stateCode}'));
|
||||||
final place = address.place;
|
final place = address.place;
|
||||||
if (place != null && place.isNotEmpty) filters.add(LocationFilter(LocationLevel.place, place));
|
if (place != null && place.isNotEmpty) filters.add(LocationFilter(LocationLevel.place, place));
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@ class _PanoramaPageState extends State<PanoramaPage> {
|
||||||
|
|
||||||
Future<void> _onLeave() async {
|
Future<void> _onLeave() async {
|
||||||
await AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
AvesApp.setSystemUIStyle(context);
|
AvesApp.setSystemUIStyle(Theme.of(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
// system UI
|
// system UI
|
||||||
|
@ -183,7 +183,7 @@ class _PanoramaPageState extends State<PanoramaPage> {
|
||||||
Future<void> _onOverlayVisibleChanged() async {
|
Future<void> _onOverlayVisibleChanged() async {
|
||||||
if (_overlayVisible.value) {
|
if (_overlayVisible.value) {
|
||||||
await AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
AvesApp.setSystemUIStyle(context);
|
AvesApp.setSystemUIStyle(Theme.of(context));
|
||||||
} else {
|
} else {
|
||||||
await AvesApp.hideSystemUI();
|
await AvesApp.hideSystemUI();
|
||||||
}
|
}
|
||||||
|
|
|
@ -262,7 +262,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
|
||||||
Future<void> _onOverlayVisibleChanged({bool animate = true}) async {
|
Future<void> _onOverlayVisibleChanged({bool animate = true}) async {
|
||||||
if (_overlayVisible.value) {
|
if (_overlayVisible.value) {
|
||||||
await AvesApp.showSystemUI();
|
await AvesApp.showSystemUI();
|
||||||
AvesApp.setSystemUIStyle(context);
|
AvesApp.setSystemUIStyle(Theme.of(context));
|
||||||
if (animate) {
|
if (animate) {
|
||||||
await _overlayAnimationController.forward();
|
await _overlayAnimationController.forward();
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue