Send current user location (#1272)

* add new asset for location, add logic for sharing user current location

* work on default zoom and auth for location

* add attribution button

* fix attribution button position, fix isSharingUserLocation

* Add NSLocationWhenInUseUsageDescription back

* Fix initial animation

* change map logo and attribution position

* Add annotation just in the setup

* add errors and alert for map vies

* location unit test

* add unit test for location sharing

* add changelog

* Update ElementX/Sources/Other/MapLibre/MapLibreMapView.swift

Co-authored-by: Alfonso Grillo <alfogrillo@gmail.com>

* add comments and minor parameters refactor

* Update ElementX/Sources/Screens/LocationSharing/StaticLocationScreenCoordinator.swift

Co-authored-by: Mauro <34335419+Velin92@users.noreply.github.com>

* Update ElementX/Sources/Screens/LocationSharing/StaticLocationScreenViewModel.swift

Co-authored-by: Mauro <34335419+Velin92@users.noreply.github.com>

* Sort assets

* Cleanup

* Cleanup

* Fix error localizations

* Fix tests

---------

Co-authored-by: Alfonso Grillo <alfogrillo@gmail.com>
Co-authored-by: Mauro <34335419+Velin92@users.noreply.github.com>
This commit is contained in:
Flescio
2023-07-11 12:42:59 +02:00
committed by GitHub
parent a1b66c170f
commit e93af88ea5
23 changed files with 1139 additions and 129 deletions

View File

@@ -4,8 +4,7 @@
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -30,12 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<TestPlans>
<TestPlanReference
default = "YES"
reference = "container:UnitTests/SupportingFiles/UnitTests.xctestplan">
</TestPlanReference>
</TestPlans>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -45,10 +38,6 @@
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
@@ -58,6 +47,12 @@
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</CodeCoverageTargets>
<TestPlans>
<TestPlanReference
reference = "container:UnitTests/SupportingFiles/UnitTests.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -79,8 +74,6 @@
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "RUST_BACKTRACE"
@@ -120,8 +113,6 @@
ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "location-pointer-full.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "location-pointer-full-dark.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,189 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.062745 0.074510 0.090196 scn
0.000000 34.000000 m
0.000000 37.313709 2.686292 40.000000 6.000000 40.000000 c
34.000000 40.000000 l
37.313709 40.000000 40.000000 37.313709 40.000000 34.000000 c
40.000000 6.000000 l
40.000000 2.686291 37.313709 0.000000 34.000000 0.000000 c
5.999999 0.000000 l
2.686291 0.000000 0.000000 2.686291 0.000000 6.000000 c
0.000000 34.000000 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 9.000000 9.000000 cm
0.921569 0.933333 0.949020 scn
11.000000 15.000000 m
8.790000 15.000000 7.000000 13.210000 7.000000 11.000000 c
7.000000 8.790000 8.790000 7.000000 11.000000 7.000000 c
13.210000 7.000000 15.000000 8.790000 15.000000 11.000000 c
15.000000 13.210000 13.210000 15.000000 11.000000 15.000000 c
h
19.940001 12.000000 m
19.480001 16.170000 16.170000 19.480000 12.000000 19.940001 c
12.000000 21.000000 l
12.000000 21.549999 11.550000 22.000000 11.000000 22.000000 c
10.450000 22.000000 10.000000 21.549999 10.000000 21.000000 c
10.000000 19.940001 l
5.830000 19.480000 2.520000 16.170000 2.060000 12.000000 c
1.000000 12.000000 l
0.450000 12.000000 0.000000 11.550000 0.000000 11.000000 c
0.000000 10.450000 0.450000 10.000000 1.000000 10.000000 c
2.060000 10.000000 l
2.520000 5.830000 5.830000 2.519999 10.000000 2.059999 c
10.000000 1.000000 l
10.000000 0.450001 10.450000 0.000000 11.000000 0.000000 c
11.550000 0.000000 12.000000 0.450001 12.000000 1.000000 c
12.000000 2.059999 l
16.170000 2.519999 19.480001 5.830000 19.940001 10.000000 c
21.000000 10.000000 l
21.549999 10.000000 22.000000 10.450000 22.000000 11.000000 c
22.000000 11.550000 21.549999 12.000000 21.000000 12.000000 c
19.940001 12.000000 l
19.940001 12.000000 l
h
11.000000 4.000000 m
7.130000 4.000000 4.000000 7.130000 4.000000 11.000000 c
4.000000 14.870000 7.130000 18.000000 11.000000 18.000000 c
14.870000 18.000000 18.000000 14.870000 18.000000 11.000000 c
18.000000 7.130000 14.870000 4.000000 11.000000 4.000000 c
h
f
n
Q
endstream
endobj
2 0 obj
2028
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 34.000000 m
0.000000 37.313709 2.686292 40.000000 6.000000 40.000000 c
34.000000 40.000000 l
37.313709 40.000000 40.000000 37.313709 40.000000 34.000000 c
40.000000 6.000000 l
40.000000 2.686291 37.313709 0.000000 34.000000 0.000000 c
5.999999 0.000000 l
2.686291 0.000000 0.000000 2.686291 0.000000 6.000000 c
0.000000 34.000000 l
h
f
n
Q
endstream
endobj
4 0 obj
468
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 40.000000 40.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000002286 00000 n
0000002309 00000 n
0000003025 00000 n
0000003047 00000 n
0000003345 00000 n
0000003447 00000 n
0000003468 00000 n
0000003641 00000 n
0000003715 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
3775
%%EOF

View File

@@ -0,0 +1,189 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
1.000000 1.000000 1.000000 scn
0.000000 34.000000 m
0.000000 37.313709 2.686292 40.000000 6.000000 40.000000 c
34.000000 40.000000 l
37.313709 40.000000 40.000000 37.313709 40.000000 34.000000 c
40.000000 6.000000 l
40.000000 2.686291 37.313709 0.000000 34.000000 0.000000 c
5.999999 0.000000 l
2.686291 0.000000 0.000000 2.686291 0.000000 6.000000 c
0.000000 34.000000 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 9.000000 9.000000 cm
0.105882 0.113725 0.133333 scn
11.000000 15.000000 m
8.790000 15.000000 7.000000 13.210000 7.000000 11.000000 c
7.000000 8.790000 8.790000 7.000000 11.000000 7.000000 c
13.210000 7.000000 15.000000 8.790000 15.000000 11.000000 c
15.000000 13.210000 13.210000 15.000000 11.000000 15.000000 c
h
19.940001 12.000000 m
19.480001 16.170000 16.170000 19.480000 12.000000 19.940001 c
12.000000 21.000000 l
12.000000 21.549999 11.550000 22.000000 11.000000 22.000000 c
10.450000 22.000000 10.000000 21.549999 10.000000 21.000000 c
10.000000 19.940001 l
5.830000 19.480000 2.520000 16.170000 2.060000 12.000000 c
1.000000 12.000000 l
0.450000 12.000000 0.000000 11.550000 0.000000 11.000000 c
0.000000 10.450000 0.450000 10.000000 1.000000 10.000000 c
2.060000 10.000000 l
2.520000 5.830000 5.830000 2.519999 10.000000 2.059999 c
10.000000 1.000000 l
10.000000 0.450001 10.450000 0.000000 11.000000 0.000000 c
11.550000 0.000000 12.000000 0.450001 12.000000 1.000000 c
12.000000 2.059999 l
16.170000 2.519999 19.480001 5.830000 19.940001 10.000000 c
21.000000 10.000000 l
21.549999 10.000000 22.000000 10.450000 22.000000 11.000000 c
22.000000 11.550000 21.549999 12.000000 21.000000 12.000000 c
19.940001 12.000000 l
19.940001 12.000000 l
h
11.000000 4.000000 m
7.130000 4.000000 4.000000 7.130000 4.000000 11.000000 c
4.000000 14.870000 7.130000 18.000000 11.000000 18.000000 c
14.870000 18.000000 18.000000 14.870000 18.000000 11.000000 c
18.000000 7.130000 14.870000 4.000000 11.000000 4.000000 c
h
f
n
Q
endstream
endobj
2 0 obj
2028
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 34.000000 m
0.000000 37.313709 2.686292 40.000000 6.000000 40.000000 c
34.000000 40.000000 l
37.313709 40.000000 40.000000 37.313709 40.000000 34.000000 c
40.000000 6.000000 l
40.000000 2.686291 37.313709 0.000000 34.000000 0.000000 c
5.999999 0.000000 l
2.686291 0.000000 0.000000 2.686291 0.000000 6.000000 c
0.000000 34.000000 l
h
f
n
Q
endstream
endobj
4 0 obj
468
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 40.000000 40.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000002286 00000 n
0000002309 00000 n
0000003025 00000 n
0000003047 00000 n
0000003345 00000 n
0000003447 00000 n
0000003468 00000 n
0000003641 00000 n
0000003715 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
3775
%%EOF

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "location-pointer.pdf",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "location-pointer-dark.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,218 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.062745 0.074510 0.090196 scn
0.000000 34.000000 m
0.000000 37.313709 2.686292 40.000000 6.000000 40.000000 c
34.000000 40.000000 l
37.313709 40.000000 40.000000 37.313709 40.000000 34.000000 c
40.000000 6.000000 l
40.000000 2.686291 37.313709 0.000000 34.000000 0.000000 c
5.999999 0.000000 l
2.686291 0.000000 0.000000 2.686291 0.000000 6.000000 c
0.000000 34.000000 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 9.049805 9.049805 cm
0.921569 0.933333 0.949020 scn
10.950000 0.000391 m
10.666667 0.000391 10.429167 0.096224 10.237500 0.287889 c
10.045834 0.479557 9.950000 0.717056 9.950000 1.000391 c
9.950000 2.000391 l
7.866667 2.233725 6.079167 3.096224 4.587500 4.587891 c
3.095833 6.079557 2.233333 7.867057 2.000000 9.950391 c
1.000000 9.950391 l
0.716667 9.950391 0.479167 10.046224 0.287500 10.237890 c
0.095833 10.429557 0.000000 10.667057 0.000000 10.950391 c
0.000000 11.233724 0.095833 11.471224 0.287500 11.662890 c
0.479167 11.854557 0.716667 11.950391 1.000000 11.950391 c
2.000000 11.950391 l
2.233333 14.033724 3.095833 15.821224 4.587500 17.312891 c
6.079167 18.804558 7.866667 19.667057 9.950000 19.900391 c
9.950000 20.900391 l
9.950000 21.183723 10.045834 21.421225 10.237500 21.612890 c
10.429167 21.804558 10.666667 21.900391 10.950000 21.900391 c
11.233334 21.900391 11.470834 21.804558 11.662500 21.612890 c
11.854167 21.421225 11.950000 21.183723 11.950000 20.900391 c
11.950000 19.900391 l
14.033334 19.667057 15.820833 18.804558 17.312500 17.312891 c
18.804167 15.821224 19.666666 14.033724 19.900000 11.950391 c
20.900000 11.950391 l
21.183334 11.950391 21.420834 11.854557 21.612501 11.662890 c
21.804167 11.471224 21.900000 11.233724 21.900000 10.950391 c
21.900000 10.667057 21.804167 10.429557 21.612501 10.237890 c
21.420834 10.046224 21.183334 9.950391 20.900000 9.950391 c
19.900000 9.950391 l
19.666666 7.867057 18.804167 6.079557 17.312500 4.587891 c
15.820833 3.096224 14.033334 2.233725 11.950000 2.000391 c
11.950000 1.000391 l
11.950000 0.717056 11.854167 0.479557 11.662500 0.287889 c
11.470834 0.096224 11.233334 0.000391 10.950000 0.000391 c
h
10.950000 3.950390 m
12.883333 3.950390 14.533334 4.633724 15.900001 6.000390 c
17.266666 7.367057 17.950001 9.017057 17.950001 10.950391 c
17.950001 12.883724 17.266666 14.533724 15.900001 15.900391 c
14.533334 17.267057 12.883333 17.950390 10.950000 17.950390 c
9.016666 17.950390 7.366667 17.267057 6.000000 15.900391 c
4.633333 14.533724 3.950000 12.883724 3.950000 10.950391 c
3.950000 9.017057 4.633333 7.367057 6.000000 6.000390 c
7.366667 4.633724 9.016666 3.950390 10.950000 3.950390 c
h
10.950000 6.950391 m
9.850000 6.950391 8.908334 7.342057 8.125000 8.125390 c
7.341667 8.908724 6.950000 9.850390 6.950000 10.950391 c
6.950000 12.050390 7.341667 12.992057 8.125000 13.775391 c
8.908334 14.558723 9.850000 14.950390 10.950000 14.950390 c
12.050000 14.950390 12.991667 14.558723 13.775001 13.775391 c
14.558333 12.992057 14.950000 12.050390 14.950000 10.950391 c
14.950000 9.850390 14.558333 8.908724 13.775001 8.125390 c
12.991667 7.342057 12.050000 6.950391 10.950000 6.950391 c
h
10.950000 8.950391 m
11.500000 8.950391 11.970834 9.146224 12.362500 9.537890 c
12.754167 9.929557 12.950000 10.400391 12.950000 10.950391 c
12.950000 11.500390 12.754167 11.971224 12.362500 12.362890 c
11.970834 12.754558 11.500000 12.950391 10.950000 12.950391 c
10.400001 12.950391 9.929167 12.754558 9.537500 12.362890 c
9.145833 11.971224 8.950000 11.500390 8.950000 10.950391 c
8.950000 10.400391 9.145833 9.929557 9.537500 9.537890 c
9.929167 9.146224 10.400001 8.950391 10.950000 8.950391 c
h
f
n
Q
endstream
endobj
2 0 obj
3685
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 34.000000 m
0.000000 37.313709 2.686292 40.000000 6.000000 40.000000 c
34.000000 40.000000 l
37.313709 40.000000 40.000000 37.313709 40.000000 34.000000 c
40.000000 6.000000 l
40.000000 2.686291 37.313709 0.000000 34.000000 0.000000 c
5.999999 0.000000 l
2.686291 0.000000 0.000000 2.686291 0.000000 6.000000 c
0.000000 34.000000 l
h
f
n
Q
endstream
endobj
4 0 obj
468
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 40.000000 40.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000003943 00000 n
0000003966 00000 n
0000004682 00000 n
0000004704 00000 n
0000005002 00000 n
0000005104 00000 n
0000005125 00000 n
0000005298 00000 n
0000005372 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
5432
%%EOF

View File

@@ -0,0 +1,218 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
1.000000 1.000000 1.000000 scn
0.000000 34.000000 m
0.000000 37.313709 2.686292 40.000000 6.000000 40.000000 c
34.000000 40.000000 l
37.313709 40.000000 40.000000 37.313709 40.000000 34.000000 c
40.000000 6.000000 l
40.000000 2.686291 37.313709 0.000000 34.000000 0.000000 c
5.999999 0.000000 l
2.686291 0.000000 0.000000 2.686291 0.000000 6.000000 c
0.000000 34.000000 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 9.049805 9.049927 cm
0.105882 0.113725 0.133333 scn
10.950000 0.000025 m
10.666666 0.000025 10.429167 0.095856 10.237500 0.287523 c
10.045834 0.479191 9.950000 0.716692 9.950000 1.000025 c
9.950000 2.000025 l
7.866666 2.233358 6.079167 3.095858 4.587500 4.587524 c
3.095833 6.079191 2.233333 7.866692 2.000000 9.950025 c
1.000000 9.950025 l
0.716667 9.950025 0.479167 10.045857 0.287500 10.237524 c
0.095833 10.429191 0.000000 10.666691 0.000000 10.950025 c
0.000000 11.233358 0.095833 11.470858 0.287500 11.662524 c
0.479167 11.854191 0.716667 11.950025 1.000000 11.950025 c
2.000000 11.950025 l
2.233333 14.033358 3.095833 15.820858 4.587500 17.312525 c
6.079167 18.804192 7.866666 19.666691 9.950000 19.900024 c
9.950000 20.900024 l
9.950000 21.183357 10.045834 21.420858 10.237500 21.612524 c
10.429167 21.804192 10.666666 21.900024 10.950000 21.900024 c
11.233334 21.900024 11.470834 21.804192 11.662500 21.612524 c
11.854167 21.420858 11.950000 21.183357 11.950000 20.900024 c
11.950000 19.900024 l
14.033333 19.666691 15.820833 18.804192 17.312500 17.312525 c
18.804167 15.820858 19.666666 14.033358 19.900000 11.950025 c
20.900000 11.950025 l
21.183332 11.950025 21.420834 11.854191 21.612501 11.662524 c
21.804169 11.470858 21.900000 11.233358 21.900000 10.950025 c
21.900000 10.666691 21.804169 10.429191 21.612501 10.237524 c
21.420834 10.045857 21.183332 9.950025 20.900000 9.950025 c
19.900000 9.950025 l
19.666666 7.866692 18.804167 6.079191 17.312500 4.587524 c
15.820833 3.095858 14.033333 2.233358 11.950000 2.000025 c
11.950000 1.000025 l
11.950000 0.716692 11.854167 0.479191 11.662500 0.287523 c
11.470834 0.095856 11.233334 0.000025 10.950000 0.000025 c
h
10.950000 3.950024 m
12.883333 3.950024 14.533334 4.633358 15.900001 6.000024 c
17.266666 7.366691 17.950001 9.016691 17.950001 10.950025 c
17.950001 12.883358 17.266666 14.533358 15.900001 15.900024 c
14.533334 17.266691 12.883333 17.950024 10.950000 17.950024 c
9.016666 17.950024 7.366667 17.266691 6.000000 15.900024 c
4.633333 14.533358 3.950000 12.883358 3.950000 10.950025 c
3.950000 9.016691 4.633333 7.366691 6.000000 6.000024 c
7.366667 4.633358 9.016666 3.950024 10.950000 3.950024 c
h
10.950000 6.950025 m
9.849999 6.950025 8.908334 7.341690 8.125000 8.125024 c
7.341667 8.908358 6.950000 9.850024 6.950000 10.950025 c
6.950000 12.050025 7.341667 12.991691 8.125000 13.775024 c
8.908334 14.558357 9.849999 14.950024 10.950000 14.950024 c
12.050000 14.950024 12.991667 14.558357 13.775001 13.775024 c
14.558334 12.991691 14.950000 12.050025 14.950000 10.950025 c
14.950000 9.850024 14.558334 8.908358 13.775001 8.125024 c
12.991667 7.341690 12.050000 6.950025 10.950000 6.950025 c
h
10.950000 8.950025 m
11.500000 8.950025 11.970834 9.145858 12.362500 9.537524 c
12.754167 9.929191 12.950000 10.400024 12.950000 10.950025 c
12.950000 11.500025 12.754167 11.970858 12.362500 12.362524 c
11.970834 12.754190 11.500000 12.950025 10.950000 12.950025 c
10.400000 12.950025 9.929167 12.754190 9.537500 12.362524 c
9.145834 11.970858 8.950000 11.500025 8.950000 10.950025 c
8.950000 10.400024 9.145834 9.929191 9.537500 9.537524 c
9.929167 9.145858 10.400000 8.950025 10.950000 8.950025 c
h
f
n
Q
endstream
endobj
2 0 obj
3685
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 34.000000 m
0.000000 37.313709 2.686292 40.000000 6.000000 40.000000 c
34.000000 40.000000 l
37.313709 40.000000 40.000000 37.313709 40.000000 34.000000 c
40.000000 6.000000 l
40.000000 2.686291 37.313709 0.000000 34.000000 0.000000 c
5.999999 0.000000 l
2.686291 0.000000 0.000000 2.686291 0.000000 6.000000 c
0.000000 34.000000 l
h
f
n
Q
endstream
endobj
4 0 obj
468
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 40.000000 40.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000003943 00000 n
0000003966 00000 n
0000004682 00000 n
0000004704 00000 n
0000005002 00000 n
0000005104 00000 n
0000005125 00000 n
0000005298 00000 n
0000005372 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
5432
%%EOF

View File

@@ -139,10 +139,10 @@
"emoji_picker_category_places" = "Travel & Places";
"emoji_picker_category_symbols" = "Symbols";
"error_failed_creating_the_permalink" = "Failed creating the permalink";
"error_failed_loading_map" = "Element could not load the map. Please try again later.";
"error_failed_loading_map" = "%1$@ could not load the map. Please try again later.";
"error_failed_loading_messages" = "Failed loading messages";
"error_failed_locating_user" = "Element could not access your location. Please try again later.";
"error_missing_location_auth" = "Element does not have permission to access your location. You can enable access in Settings > Location";
"error_failed_locating_user" = "%1$@ could not access your location. Please try again later.";
"error_missing_location_auth" = "%1$@ does not have permission to access your location. You can enable access in Settings > Location";
"error_no_compatible_app_found" = "No compatible app was found to handle this action.";
"error_some_messages_have_not_been_sent" = "Some messages have not been sent";
"error_unknown" = "Sorry, an error occurred";
@@ -308,6 +308,8 @@
"screen_room_reactions_show_more" = "Show more";
"screen_room_retry_send_menu_send_again_action" = "Send again";
"screen_room_retry_send_menu_title" = "Your message failed to send";
"screen_room_timeline_add_reaction" = "Add emoji";
"screen_room_timeline_less_reactions" = "Show less";
"screen_roomlist_a11y_create_message" = "Create a new conversation or room";
"screen_roomlist_main_space_title" = "All Chats";
"screen_server_confirmation_change_server" = "Change account provider";

View File

@@ -146,5 +146,19 @@
<string>%1$d people</string>
</dict>
</dict>
<key>screen_room_timeline_more_reactions</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@COUNT@</string>
<key>COUNT</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%1$d more</string>
</dict>
</dict>
</dict>
</plist>

View File

@@ -515,7 +515,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
_ = await self.roomProxy?.sendLocation(body: geoURI.bodyMessage,
geoURI: geoURI,
description: nil,
zoomLevel: nil,
zoomLevel: 15,
assetType: isUserLocation ? .sender : .pin)
self.navigationSplitCoordinator.setSheetCoordinator(nil)
}

View File

@@ -39,6 +39,8 @@ internal enum Asset {
internal static let launchLogo = ImageAsset(name: "images/launch-logo")
internal static let locationMarker = ImageAsset(name: "images/location-marker")
internal static let locationPin = ImageAsset(name: "images/location-pin")
internal static let locationPointerFull = ImageAsset(name: "images/location-pointer-full")
internal static let locationPointer = ImageAsset(name: "images/location-pointer")
internal static let timelineComposerSendMessage = ImageAsset(name: "images/timeline-composer-send-message")
internal static let waitingGradient = ImageAsset(name: "images/waiting-gradient")
}

View File

@@ -308,14 +308,20 @@ public enum L10n {
public static var emojiPickerCategorySymbols: String { return L10n.tr("Localizable", "emoji_picker_category_symbols") }
/// Failed creating the permalink
public static var errorFailedCreatingThePermalink: String { return L10n.tr("Localizable", "error_failed_creating_the_permalink") }
/// Element could not load the map. Please try again later.
public static var errorFailedLoadingMap: String { return L10n.tr("Localizable", "error_failed_loading_map") }
/// %1$@ could not load the map. Please try again later.
public static func errorFailedLoadingMap(_ p1: Any) -> String {
return L10n.tr("Localizable", "error_failed_loading_map", String(describing: p1))
}
/// Failed loading messages
public static var errorFailedLoadingMessages: String { return L10n.tr("Localizable", "error_failed_loading_messages") }
/// Element could not access your location. Please try again later.
public static var errorFailedLocatingUser: String { return L10n.tr("Localizable", "error_failed_locating_user") }
/// Element does not have permission to access your location. You can enable access in Settings > Location
public static var errorMissingLocationAuth: String { return L10n.tr("Localizable", "error_missing_location_auth") }
/// %1$@ could not access your location. Please try again later.
public static func errorFailedLocatingUser(_ p1: Any) -> String {
return L10n.tr("Localizable", "error_failed_locating_user", String(describing: p1))
}
/// %1$@ does not have permission to access your location. You can enable access in Settings > Location
public static func errorMissingLocationAuth(_ p1: Any) -> String {
return L10n.tr("Localizable", "error_missing_location_auth", String(describing: p1))
}
/// No compatible app was found to handle this action.
public static var errorNoCompatibleAppFound: String { return L10n.tr("Localizable", "error_no_compatible_app_found") }
/// Some messages have not been sent
@@ -780,6 +786,14 @@ public enum L10n {
public static var screenRoomRetrySendMenuSendAgainAction: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_send_again_action") }
/// Your message failed to send
public static var screenRoomRetrySendMenuTitle: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_title") }
/// Add emoji
public static var screenRoomTimelineAddReaction: String { return L10n.tr("Localizable", "screen_room_timeline_add_reaction") }
/// Show less
public static var screenRoomTimelineLessReactions: String { return L10n.tr("Localizable", "screen_room_timeline_less_reactions") }
/// Plural format key: "%#@COUNT@"
public static func screenRoomTimelineMoreReactions(_ p1: Int) -> String {
return L10n.tr("Localizable", "screen_room_timeline_more_reactions", p1)
}
/// Create a new conversation or room
public static var screenRoomlistA11yCreateMessage: String { return L10n.tr("Localizable", "screen_roomlist_a11y_create_message") }
/// All Chats

View File

@@ -20,15 +20,20 @@ import SwiftUI
struct MapLibreMapView: UIViewRepresentable {
struct Options {
/// The initial zoom level
/// the final zoom level used when the first user location emit
let zoomLevel: Double
/// The initial zoom level used when the map it firstly loaded and the user location is not yet available, in case of annotations this property is not being used
let initialZoomLevel: Double
/// The initial map center
let mapCenter: CLLocationCoordinate2D?
let mapCenter: CLLocationCoordinate2D
/// Map annotations
let annotations: [LocationAnnotation]
init(zoomLevel: Double, mapCenter: CLLocationCoordinate2D? = nil, annotations: [LocationAnnotation] = []) {
init(zoomLevel: Double, initialZoomLevel: Double, mapCenter: CLLocationCoordinate2D, annotations: [LocationAnnotation] = []) {
self.zoomLevel = zoomLevel
self.initialZoomLevel = initialZoomLevel
self.mapCenter = mapCenter
self.annotations = annotations
}
@@ -43,14 +48,16 @@ struct MapLibreMapView: UIViewRepresentable {
let options: Options
/// Behavior mode of the current user's location, can be hidden, only shown and shown following the user
var showsUserLocationMode: ShowUserLocationMode = .hide
@Binding var showsUserLocationMode: ShowUserLocationMode
/// Bind view errors if any
let error: Binding<MapLibreError?>
@Binding var error: MapLibreError?
/// Coordinate of the center of the map
@Binding var mapCenterCoordinate: CLLocationCoordinate2D?
@Binding var isLocationAuthorized: Bool?
/// Called when the user pan on the map
var userDidPan: (() -> Void)?
@@ -59,23 +66,12 @@ struct MapLibreMapView: UIViewRepresentable {
func makeUIView(context: Context) -> MGLMapView {
let mapView = makeMapView()
mapView.delegate = context.coordinator
let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.didPan))
panGesture.delegate = context.coordinator
mapView.addGestureRecognizer(panGesture)
setupMap(mapView: mapView, with: options)
return mapView
}
func updateUIView(_ mapView: MGLMapView, context: Context) {
mapView.removeAllAnnotations()
mapView.addAnnotations(options.annotations)
if colorScheme == .dark {
mapView.styleURL = builder.dynamicMapURL(for: .dark)
} else {
mapView.styleURL = builder.dynamicMapURL(for: .light)
}
mapView.styleURL = builder.dynamicMapURL(for: .init(colorScheme))
showUserLocation(in: mapView)
}
@@ -86,32 +82,36 @@ struct MapLibreMapView: UIViewRepresentable {
// MARK: - Private
private func setupMap(mapView: MGLMapView, with options: Options) {
mapView.zoomLevel = options.zoomLevel
if let mapCenter = options.mapCenter {
mapView.centerCoordinate = mapCenter
}
mapView.addAnnotations(options.annotations)
mapView.zoomLevel = options.annotations.isEmpty ? options.initialZoomLevel : options.zoomLevel
mapView.centerCoordinate = options.mapCenter
}
private func makeMapView() -> MGLMapView {
let mapView = MGLMapView(frame: .zero, styleURL: colorScheme == .dark ? builder.dynamicMapURL(for: .dark) : builder.dynamicMapURL(for: .light))
showUserLocation(in: mapView)
mapView.attributionButton.isHidden = true
mapView.logoViewPosition = .topLeft
mapView.attributionButtonPosition = .topLeft
mapView.attributionButtonMargins = .init(x: mapView.logoView.frame.maxX + 8, y: mapView.logoView.center.y / 2)
return mapView
}
private func showUserLocation(in mapView: MGLMapView) {
switch showsUserLocationMode {
case .showAndFollow:
mapView.showsUserLocation = true
switch (showsUserLocationMode, options.annotations) {
case (.showAndFollow, _):
mapView.userTrackingMode = .follow
case .show:
case (.show, let annotations) where !annotations.isEmpty:
/** in the show mode, if there are annotations, we check the authorizationStatus,
if it's not determined, we wont prompt the user with a request for permissions,
because he should be able to see the annotations without sharing his location informations
**/
guard mapView.locationManager.authorizationStatus != .notDetermined else { return }
fallthrough
case (.show, _):
mapView.showsUserLocation = true
mapView.userTrackingMode = .none
case .hide:
mapView.setUserTrackingMode(.none, animated: false, completionHandler: nil)
case (.hide, _):
mapView.showsUserLocation = false
mapView.userTrackingMode = .none
mapView.setUserTrackingMode(.none, animated: false, completionHandler: nil)
}
}
}
@@ -119,11 +119,13 @@ struct MapLibreMapView: UIViewRepresentable {
// MARK: - Coordinator
extension MapLibreMapView {
class Coordinator: NSObject, MGLMapViewDelegate, UIGestureRecognizerDelegate {
class Coordinator: NSObject, MGLMapViewDelegate {
// MARK: - Properties
var mapLibreView: MapLibreMapView
private var previousUserLocation: MGLUserLocation?
// MARK: - Setup
init(_ mapLibreView: MapLibreMapView) {
@@ -140,20 +142,30 @@ extension MapLibreMapView {
}
func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {
mapLibreView.error.wrappedValue = .failedLoadingMap
mapLibreView.error = .failedLoadingMap
}
func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) { }
func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {
guard let userLocation else { return }
if previousUserLocation == nil, mapLibreView.options.annotations.isEmpty {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
mapView.setCenter(userLocation.coordinate, zoomLevel: self.mapLibreView.options.zoomLevel, animated: true)
}
}
previousUserLocation = userLocation
}
func mapView(_ mapView: MGLMapView, didChangeLocationManagerAuthorization manager: MGLLocationManager) {
guard mapView.showsUserLocation else {
return
}
switch manager.authorizationStatus {
case .denied, .restricted:
mapLibreView.error.wrappedValue = .invalidLocationAuthorization
default:
mapLibreView.isLocationAuthorized = false
case .authorizedAlways, .authorizedWhenInUse:
mapLibreView.isLocationAuthorized = true
case .notDetermined:
mapLibreView.isLocationAuthorized = nil
@unknown default:
break
}
}
@@ -171,15 +183,25 @@ extension MapLibreMapView {
false
}
// MARK: UIGestureRecognizer
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
gestureRecognizer is UIPanGestureRecognizer
}
@objc
func didPan() {
mapLibreView.userDidPan?()
func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera, reason: MGLCameraChangeReason) -> Bool {
// we send the userDidPan event only for the reasons that actually will change the map center, and not zoom only / rotations only events.
switch reason {
case .gesturePan,
.gesturePinch,
.gestureRotate:
mapLibreView.userDidPan?()
case .gestureOneFingerZoom,
.gestureTilt,
.gestureZoomIn,
.gestureZoomOut,
.programmatic,
.resetNorth,
.transitionCancelled:
break
default:
break
}
return true
}
}
}
@@ -194,3 +216,16 @@ private extension MGLMapView {
removeAnnotations(annotations)
}
}
private extension MapTilerStyle {
init(_ colorScheme: ColorScheme) {
switch colorScheme {
case .light:
self = .light
case .dark:
self = .dark
@unknown default:
fatalError()
}
}
}

View File

@@ -36,7 +36,6 @@ enum MapTilerStyle {
enum MapLibreError: Error {
case failedLoadingMap
case failedLocatingUser
case invalidLocationAuthorization
}
enum MapTilerAttributionPlacement: String {

View File

@@ -18,12 +18,13 @@ import CoreLocation
import Foundation
enum LocationSharingViewError: Error, Hashable {
case failedSharingLocation
case missingAuthorization
case mapError(MapLibreError)
}
enum StaticLocationScreenViewModelAction {
case close
case openSystemSettings
case sendLocation(GeoURI, isUserLocation: Bool)
}
@@ -33,47 +34,36 @@ enum StaticLocationInteractionMode: Hashable {
}
struct StaticLocationScreenViewState: BindableState {
init(interactionMode: StaticLocationInteractionMode, isSharingUserLocation: Bool = false, showsUserLocationMode: ShowUserLocationMode = .hide) {
init(interactionMode: StaticLocationInteractionMode) {
self.interactionMode = interactionMode
self.isSharingUserLocation = isSharingUserLocation
self.showsUserLocationMode = showsUserLocationMode
switch interactionMode {
case .picker:
bindings = .init()
case .viewOnly(let geoURI, _):
bindings = .init(mapCenterLocation: .init(latitude: geoURI.latitude, longitude: geoURI.longitude))
bindings.showsUserLocationMode = .showAndFollow
case .viewOnly:
bindings.showsUserLocationMode = .show
}
}
let interactionMode: StaticLocationInteractionMode
/// Indicates whether the user is sharing his current location
var isSharingUserLocation: Bool
/// Behavior mode of the current user's location, can be hidden, only shown and shown following the user
var showsUserLocationMode: ShowUserLocationMode
var bindings = StaticLocationScreenBindings()
var showBottomToolbar: Bool {
interactionMode == .picker
var isSharingUserLocation: Bool {
bindings.isLocationAuthorized == true && bindings.showsUserLocationMode == .showAndFollow
}
var mapAnnotationCoordinate: CLLocationCoordinate2D? {
var bindings = StaticLocationScreenBindings(showsUserLocationMode: .hide)
var initialMapCenter: CLLocationCoordinate2D {
switch interactionMode {
case .picker:
return nil
// middle point in Europe, to be used if the users location is not yet known
return .init(latitude: 49.843, longitude: 9.902056)
case .viewOnly(let geoURI, _):
return .init(latitude: geoURI.latitude, longitude: geoURI.longitude)
}
}
var isLocationPickerMode: Bool {
switch interactionMode {
case .picker:
return true
case .viewOnly:
return false
}
interactionMode == .picker
}
var navigationTitle: String {
@@ -95,9 +85,13 @@ struct StaticLocationScreenViewState: BindableState {
}
var zoomLevel: Double {
15.0
}
var initialZoomLevel: Double {
switch interactionMode {
case .picker:
return 5.0
return 2.7
case .viewOnly:
return 15.0
}
@@ -115,6 +109,10 @@ struct StaticLocationScreenViewState: BindableState {
struct StaticLocationScreenBindings {
var mapCenterLocation: CLLocationCoordinate2D?
var showsUserLocationMode: ShowUserLocationMode
var isLocationAuthorized: Bool?
/// Information describing the currently displayed alert.
var mapError: MapLibreError? {
@@ -125,7 +123,7 @@ struct StaticLocationScreenBindings {
return nil
}
set {
alertInfo = newValue.map { AlertInfo(id: .mapError($0)) }
alertInfo = newValue.map { AlertInfo(locationSharingViewError: .mapError($0)) }
}
}
@@ -138,5 +136,33 @@ struct StaticLocationScreenBindings {
enum StaticLocationScreenViewAction {
case close
case selectLocation
case centerToUser
case userDidPan
}
extension AlertInfo where T == LocationSharingViewError {
init(locationSharingViewError error: LocationSharingViewError,
primaryButton: AlertButton = AlertButton(title: L10n.actionOk, action: nil),
secondaryButton: AlertButton? = nil) {
switch error {
case .missingAuthorization:
self.init(id: error,
title: "",
message: L10n.errorMissingLocationAuth(InfoPlistReader.main.bundleDisplayName),
primaryButton: primaryButton,
secondaryButton: secondaryButton)
case .mapError(.failedLoadingMap):
self.init(id: error,
title: "",
message: L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName),
primaryButton: primaryButton,
secondaryButton: secondaryButton)
case .mapError(.failedLocatingUser):
self.init(id: error,
title: "",
message: L10n.errorFailedLocatingUser(InfoPlistReader.main.bundleDisplayName),
primaryButton: primaryButton,
secondaryButton: secondaryButton)
}
}
}

View File

@@ -51,6 +51,12 @@ final class StaticLocationScreenCoordinator: CoordinatorProtocol {
switch action {
case .close:
actionsSubject.send(.close)
case .openSystemSettings:
guard let url = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(url) else {
return
}
UIApplication.shared.open(url)
case .sendLocation(let geoURI, let isUserLocation):
actionsSubject.send(.selectedLocation(geoURI, isUserLocation: isUserLocation))
}

View File

@@ -38,8 +38,17 @@ class StaticLocationScreenViewModel: StaticLocationScreenViewModelType, StaticLo
guard let coordinate = state.bindings.mapCenterLocation else { return }
actionsSubject.send(.sendLocation(.init(coordinate: coordinate), isUserLocation: state.isSharingUserLocation))
case .userDidPan:
state.showsUserLocationMode = .hide
state.isSharingUserLocation = false
state.bindings.showsUserLocationMode = .show
case .centerToUser:
switch state.bindings.isLocationAuthorized {
case .some(true), .none:
state.bindings.showsUserLocationMode = .showAndFollow
case .some(false):
let action: () -> Void = { [weak self] in self?.actionsSubject.send(.openSystemSettings) }
state.bindings.alertInfo = .init(locationSharingViewError: .missingAuthorization,
primaryButton: .init(title: L10n.actionNotNow, role: .cancel, action: nil),
secondaryButton: .init(title: L10n.commonSettings, action: action))
}
}
}
}

View File

@@ -44,17 +44,20 @@ struct StaticLocationScreen: View {
ZStack(alignment: .center) {
MapLibreMapView(builder: builder,
options: mapOptions,
showsUserLocationMode: .hide,
showsUserLocationMode: $context.showsUserLocationMode,
error: $context.mapError,
mapCenterCoordinate: $context.mapCenterLocation,
userDidPan: {
context.send(viewAction: .userDidPan)
})
isLocationAuthorized: $context.isLocationAuthorized) {
context.send(viewAction: .userDidPan)
}
.ignoresSafeArea(.all, edges: mapSafeAreaEdges)
if context.viewState.isLocationPickerMode {
LocationMarkerView()
}
}
.ignoresSafeArea(.all, edges: mapSafeAreaEdges)
.overlay(alignment: .bottomTrailing) {
centerToUserLocationButton
}
}
// MARK: - Private
@@ -72,7 +75,7 @@ struct StaticLocationScreen: View {
}
}
if context.viewState.showBottomToolbar {
if context.viewState.isLocationPickerMode {
ToolbarItemGroup(placement: .bottomBar) {
selectLocationButton
Spacer()
@@ -81,19 +84,22 @@ struct StaticLocationScreen: View {
}
private var mapOptions: MapLibreMapView.Options {
guard let coordinate = context.viewState.mapAnnotationCoordinate else {
return .init(zoomLevel: context.viewState.zoomLevel)
var annotations: [LocationAnnotation] = []
if context.viewState.isLocationPickerMode == false {
let annotation = LocationAnnotation(coordinate: context.viewState.initialMapCenter, anchorPoint: .bottomCenter) {
LocationMarkerView()
}
annotations.append(annotation)
}
return .init(zoomLevel: context.viewState.zoomLevel,
mapCenter: coordinate,
annotations: [LocationAnnotation(coordinate: coordinate, anchorPoint: .bottomCenter) {
LocationMarkerView()
}])
initialZoomLevel: context.viewState.initialZoomLevel,
mapCenter: context.viewState.initialMapCenter,
annotations: annotations)
}
private var mapSafeAreaEdges: Edge.Set {
context.viewState.showBottomToolbar ? .horizontal : [.horizontal, .bottom]
context.viewState.isLocationPickerMode ? .horizontal : [.horizontal, .bottom]
}
@ScaledMetric private var shareMarkerSize: CGFloat = 28
@@ -111,6 +117,15 @@ struct StaticLocationScreen: View {
}
}
private var centerToUserLocationButton: some View {
Button {
context.send(viewAction: .centerToUser)
} label: {
Image(asset: context.viewState.isSharingUserLocation ? Asset.Images.locationPointerFull : Asset.Images.locationPointer)
}
.padding(16)
}
private var closeButton: some View {
Button(L10n.actionCancel) {
context.send(viewAction: .close)
@@ -127,14 +142,13 @@ struct StaticLocationScreen: View {
@ViewBuilder
private var shareSheet: some View {
if let location = context.viewState.mapAnnotationCoordinate {
let locationDescription = context.viewState.locationDescription
AppActivityView(activityItems: [ShareToMapsAppActivity.MapsAppType.apple.activityURL(for: location, locationDescription: locationDescription)],
applicationActivities: ShareToMapsAppActivity.MapsAppType.allCases.map { ShareToMapsAppActivity(type: $0, location: location, locationDescription: locationDescription) })
.edgesIgnoringSafeArea(.bottom)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.hidden)
}
let location = context.viewState.initialMapCenter
let locationDescription = context.viewState.locationDescription
AppActivityView(activityItems: [ShareToMapsAppActivity.MapsAppType.apple.activityURL(for: location, locationDescription: locationDescription)],
applicationActivities: ShareToMapsAppActivity.MapsAppType.allCases.map { ShareToMapsAppActivity(type: $0, location: location, locationDescription: locationDescription) })
.edgesIgnoringSafeArea(.bottom)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.hidden)
}
}

View File

@@ -238,7 +238,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
return .none
}
}
// swiftlint:disable:next cyclomatic_complexity function_body_length
private func updateTimelineItems() {
var newTimelineItems = [RoomTimelineItemProtocol]()
var canBackPaginate = true

View File

@@ -28,6 +28,8 @@
<false/>
<key>NSCameraUsageDescription</key>
<string>To take pictures or videos and send them as a message $(APP_DISPLAY_NAME) needs access to the camera.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>When you share your location to people, $(APP_DISPLAY_NAME) needs access to show them a map.</string>
<key>NSMicrophoneUsageDescription</key>
<string>To take videos with audio and send them as a message $(APP_DISPLAY_NAME) needs access to the microphone.</string>
<key>NSPhotoLibraryAddUsageDescription</key>

View File

@@ -69,6 +69,7 @@ targets:
NSCameraUsageDescription: To take pictures or videos and send them as a message $(APP_DISPLAY_NAME) needs access to the camera.
NSMicrophoneUsageDescription: To take videos with audio and send them as a message $(APP_DISPLAY_NAME) needs access to the microphone.
NSPhotoLibraryAddUsageDescription: Allows saving photos and videos to your library.
NSLocationWhenInUseUsageDescription: When you share your location to people, $(APP_DISPLAY_NAME) needs access to show them a map.
UIBackgroundModes: [
fetch
]

View File

@@ -32,13 +32,48 @@ class StaticLocationScreenViewModelTests: XCTestCase {
override func setUpWithError() throws {
let viewModel = StaticLocationScreenViewModel(interactionMode: .picker)
viewModel.state.isSharingUserLocation = true
viewModel.state.bindings.isLocationAuthorized = true
self.viewModel = viewModel
}
func testUserDidPan() async throws {
XCTAssertTrue(context.viewState.isSharingUserLocation)
XCTAssertEqual(context.showsUserLocationMode, .showAndFollow)
context.send(viewAction: .userDidPan)
XCTAssertFalse(context.viewState.isSharingUserLocation)
XCTAssertEqual(context.showsUserLocationMode, .show)
}
func testCenterOnUser() async throws {
XCTAssertTrue(context.viewState.isSharingUserLocation)
context.showsUserLocationMode = .show
XCTAssertFalse(context.viewState.isSharingUserLocation)
context.send(viewAction: .centerToUser)
XCTAssertTrue(context.viewState.isSharingUserLocation)
XCTAssertEqual(context.showsUserLocationMode, .showAndFollow)
}
func testCenterOnUserWithoutAuth() async throws {
context.showsUserLocationMode = .hide
context.isLocationAuthorized = nil
context.send(viewAction: .centerToUser)
XCTAssertEqual(context.showsUserLocationMode, .showAndFollow)
}
func testCenterOnUserWithDeniedAuth() async throws {
context.isLocationAuthorized = false
context.showsUserLocationMode = .hide
context.send(viewAction: .centerToUser)
XCTAssertNotEqual(context.showsUserLocationMode, .showAndFollow)
XCTAssertNotNil(context.alertInfo)
}
func testErrorMapping() async throws {
let mapError = AlertInfo(locationSharingViewError: .mapError(.failedLoadingMap))
XCTAssertEqual(mapError.message, L10n.errorFailedLoadingMap(InfoPlistReader.main.bundleDisplayName))
let locationError = AlertInfo(locationSharingViewError: .mapError(.failedLocatingUser))
XCTAssertEqual(locationError.message, L10n.errorFailedLocatingUser(InfoPlistReader.main.bundleDisplayName))
let authorizationError = AlertInfo(locationSharingViewError: .missingAuthorization)
XCTAssertEqual(authorizationError.message, L10n.errorMissingLocationAuth(InfoPlistReader.main.bundleDisplayName))
}
}

1
changelog.d/1272.feature Normal file
View File

@@ -0,0 +1 @@
Send current user location