Merge pull request #5467 from vector-im/doug/5160_ftue_use_case
Add the FTUE use case screen for new users.
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_use_case_community.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_use_case_community.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="white"/>
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="#368BD6" fill-opacity="0.15"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19 29C24.5228 29 29 24.5228 29 19C29 13.4772 24.5228 9 19 9C13.4772 9 9 13.4772 9 19C9 24.5228 13.4772 29 19 29ZM25.3518 22.3106L24.5865 24.6722C24.5747 24.7086 24.5546 24.7419 24.5279 24.7694L23.7731 25.5458L22.9776 26.364L22.4305 26.9267C22.3097 27.051 22.1022 27.0176 22.0264 26.8617L21.4043 25.5819C21.3927 25.558 21.3773 25.5361 21.3587 25.517L20.5913 24.7276L19.8694 23.9852C19.8224 23.9367 19.7577 23.9094 19.6902 23.9094H18.3613C18.2656 23.9094 18.1783 23.8548 18.1365 23.7687L17.4346 22.3248C17.418 22.2908 17.4094 22.2534 17.4094 22.2155V19.9711C17.4094 19.8774 17.3571 19.7916 17.2738 19.7487L15.8724 19.028C15.837 19.0098 15.7978 19.0003 15.758 19.0003H14.3333C14.2657 19.0003 14.2011 18.973 14.154 18.9246L12.713 17.4424C12.6644 17.3924 12.6388 17.3244 12.6426 17.2547L12.7331 15.5685C12.7445 15.3571 12.7842 15.1482 12.8511 14.9474L13.1309 14.108C13.6777 12.4677 14.9701 11.1839 16.614 10.6481L17.6003 10.3428C18.5075 10.062 19.4825 10.0931 20.3699 10.4312L21.335 10.7988C21.3691 10.8118 21.3999 10.8321 21.4253 10.8582L22.8082 12.2806C22.9025 12.3776 22.9025 12.5321 22.8082 12.6291L20.662 14.8367C20.6166 14.8833 20.5913 14.9458 20.5913 15.0109V16.2391C20.5913 16.3974 20.446 16.5159 20.2909 16.484L16.9144 15.7894C16.7593 15.7575 16.614 15.8759 16.614 16.0343V17.114C16.614 17.252 16.7259 17.364 16.864 17.364H18.7504C18.8884 17.364 19.0004 17.4759 19.0004 17.614V18.7503C19.0004 18.8884 19.1123 19.0003 19.2504 19.0003H22.0766C22.1441 19.0003 22.2087 19.0276 22.2558 19.0761L22.9491 19.7892C22.968 19.8086 22.9899 19.8248 23.014 19.8372L24.5321 20.618C24.5562 20.6304 24.5782 20.6466 24.5971 20.666L25.2932 21.3821C25.3386 21.4288 25.364 21.4913 25.364 21.5564V22.2336C25.364 22.2597 25.3599 22.2857 25.3518 22.3106Z" fill="#1E1E1E"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_use_case_community_dark.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_use_case_community_dark.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="white"/>
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="#368BD6" fill-opacity="0.3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19 29C24.5228 29 29 24.5228 29 19C29 13.4772 24.5228 9 19 9C13.4772 9 9 13.4772 9 19C9 24.5228 13.4772 29 19 29ZM25.3518 22.3106L24.5865 24.6722C24.5747 24.7086 24.5546 24.7419 24.5279 24.7694L23.7731 25.5458L22.9776 26.364L22.4305 26.9267C22.3097 27.051 22.1022 27.0176 22.0264 26.8617L21.4043 25.5819C21.3927 25.558 21.3773 25.5361 21.3587 25.517L20.5913 24.7276L19.8694 23.9852C19.8224 23.9367 19.7577 23.9094 19.6902 23.9094H18.3613C18.2656 23.9094 18.1783 23.8548 18.1365 23.7687L17.4346 22.3248C17.418 22.2908 17.4094 22.2534 17.4094 22.2155V19.9711C17.4094 19.8774 17.3571 19.7916 17.2738 19.7487L15.8724 19.028C15.837 19.0098 15.7978 19.0003 15.758 19.0003H14.3333C14.2657 19.0003 14.2011 18.973 14.154 18.9246L12.713 17.4424C12.6644 17.3924 12.6388 17.3244 12.6426 17.2547L12.7331 15.5685C12.7445 15.3571 12.7842 15.1482 12.8511 14.9474L13.1309 14.108C13.6777 12.4677 14.9701 11.1839 16.614 10.6481L17.6003 10.3428C18.5075 10.062 19.4825 10.0931 20.3699 10.4312L21.335 10.7988C21.3691 10.8118 21.3999 10.8321 21.4253 10.8582L22.8082 12.2806C22.9025 12.3776 22.9025 12.5321 22.8082 12.6291L20.662 14.8367C20.6166 14.8833 20.5913 14.9458 20.5913 15.0109V16.2391C20.5913 16.3974 20.446 16.5159 20.2909 16.484L16.9144 15.7894C16.7593 15.7575 16.614 15.8759 16.614 16.0343V17.114C16.614 17.252 16.7259 17.364 16.864 17.364H18.7504C18.8884 17.364 19.0004 17.4759 19.0004 17.614V18.7503C19.0004 18.8884 19.1123 19.0003 19.2504 19.0003H22.0766C22.1441 19.0003 22.2087 19.0276 22.2558 19.0761L22.9491 19.7892C22.968 19.8086 22.9899 19.8248 23.014 19.8372L24.5321 20.618C24.5562 20.6304 24.5782 20.6466 24.5971 20.666L25.2932 21.3821C25.3386 21.4288 25.364 21.4913 25.364 21.5564V22.2336C25.364 22.2597 25.3599 22.2857 25.3518 22.3106Z" fill="#1E1E1E"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_use_case_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_use_case_icon.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="71" height="70" viewBox="0 0 71 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M36 70C55.33 70 71 54.33 71 35C71 15.67 55.33 0 36 0C16.67 0 1 15.67 1 35C1 54.33 16.67 70 36 70Z" fill="#0DBD8B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.1655 26.3399C45.1655 34.8426 38.2529 41.7356 29.7259 41.7356C27.3129 41.7356 25.0291 41.1835 22.9947 40.1992L17.4268 41.9342C15.3689 42.5755 13.4328 40.6542 14.0666 38.6L15.7975 32.9908C14.8289 30.9776 14.2864 28.7219 14.2864 26.3399C14.2864 17.8372 21.1989 10.9443 29.7259 10.9443C38.2529 10.9443 45.1655 17.8372 45.1655 26.3399ZM33.1655 46.3665C31.1238 46.3665 29.175 45.9694 27.392 45.2478C29.984 50.0978 35.0976 53.3976 40.9822 53.3976C43.3876 53.3976 45.6641 52.8464 47.6923 51.8631L53.2379 53.5958C55.2953 54.2383 57.2329 52.3187 56.6003 50.264L54.8736 44.6536C55.8394 42.641 56.3804 40.3857 56.3804 38.0043C56.3804 32.1754 53.1396 27.1032 48.361 24.4902C49.4394 26.4605 50.062 28.6571 50.062 30.9731C50.062 39.4747 41.6697 46.3665 33.1655 46.3665Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_use_case_personal.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_use_case_personal.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="white"/>
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="#AC3BA8" fill-opacity="0.15"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.0668 19.0004C30.0668 25.0892 25.1308 30.0252 19.042 30.0252C17.026 30.0252 15.1365 29.4841 13.5106 28.5393L9.25692 29.852C8.47193 30.0942 7.73693 29.3572 7.98132 28.5729L9.33432 24.2306C8.49408 22.6744 8.01715 20.8931 8.01715 19.0004C8.01715 12.9116 12.9531 7.97559 19.042 7.97559C25.1308 7.97559 30.0668 12.9116 30.0668 19.0004ZM15.367 19.0004C15.367 19.6769 14.8186 20.2254 14.142 20.2254C13.4655 20.2254 12.9171 19.6769 12.9171 19.0004C12.9171 18.3239 13.4655 17.7754 14.142 17.7754C14.8186 17.7754 15.367 18.3239 15.367 19.0004ZM19.042 20.2254C19.7185 20.2254 20.2669 19.6769 20.2669 19.0004C20.2669 18.3239 19.7185 17.7754 19.042 17.7754C18.3654 17.7754 17.817 18.3239 17.817 19.0004C17.817 19.6769 18.3654 20.2254 19.042 20.2254ZM25.1668 19.0004C25.1668 19.6769 24.6184 20.2254 23.9419 20.2254C23.2653 20.2254 22.7169 19.6769 22.7169 19.0004C22.7169 18.3239 23.2653 17.7754 23.9419 17.7754C24.6184 17.7754 25.1668 18.3239 25.1668 19.0004Z" fill="#1E1E1E"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_use_case_personal_dark.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_use_case_personal_dark.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="white"/>
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="#AC3BA8" fill-opacity="0.3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.0666 19C30.0666 25.0889 25.1307 30.0248 19.0418 30.0248C17.0259 30.0248 15.1364 29.4838 13.5105 28.5389L9.2568 29.8516C8.47181 30.0938 7.73681 29.3568 7.9812 28.5725L9.3342 24.2302C8.49396 22.674 8.01702 20.8927 8.01702 19C8.01702 12.9112 12.953 7.97522 19.0418 7.97522C25.1307 7.97522 30.0666 12.9112 30.0666 19ZM15.3669 19C15.3669 19.6766 14.8185 20.225 14.1419 20.225C13.4654 20.225 12.9169 19.6766 12.9169 19C12.9169 18.3235 13.4654 17.775 14.1419 17.775C14.8185 17.775 15.3669 18.3235 15.3669 19ZM19.0418 20.225C19.7184 20.225 20.2668 19.6766 20.2668 19C20.2668 18.3235 19.7184 17.775 19.0418 17.775C18.3653 17.775 17.8169 18.3235 17.8169 19C17.8169 19.6766 18.3653 20.225 19.0418 20.225ZM25.1667 19C25.1667 19.6766 24.6183 20.225 23.9417 20.225C23.2652 20.225 22.7168 19.6766 22.7168 19C22.7168 18.3235 23.2652 17.775 23.9417 17.775C24.6183 17.775 25.1667 18.3235 25.1667 19Z" fill="#1E1E1E"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_use_case_work.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_use_case_work.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="white"/>
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="#0DBD8B" fill-opacity="0.15"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 19C30 25.0751 25.0751 30 19 30C12.9249 30 8 25.0751 8 19C8 12.9249 12.9249 8 19 8C25.0751 8 30 12.9249 30 19ZM18.0835 16.4792C18.0835 18.1245 16.8522 19.4583 15.3335 19.4583C13.8147 19.4583 12.5835 18.1245 12.5835 16.4792C12.5835 14.8338 13.8147 13.5 15.3335 13.5C16.8522 13.5 18.0835 14.8338 18.0835 16.4792ZM26.2277 17.9433C26.2277 19.3848 25.1491 20.5533 23.8185 20.5533C22.488 20.5533 21.4093 19.3848 21.4093 17.9433C21.4093 16.5019 22.488 15.3334 23.8185 15.3334C25.1491 15.3334 26.2277 16.5019 26.2277 17.9433ZM19.9167 25.3207C19.9167 23.1735 18.4086 21.3786 16.3938 20.9373C16.0507 20.8691 15.6961 20.8334 15.3334 20.8334C13.4811 20.8334 11.8427 21.7648 10.846 23.1924C12.368 26.1464 15.4481 28.1667 19 28.1667C19.3094 28.1667 19.6152 28.1513 19.9167 28.1214L19.9167 25.3207ZM27.1779 23.1456C26.0706 25.3255 24.1173 27.0034 21.7502 27.747L21.7502 25.3207C21.7502 24.3428 21.5281 23.4167 21.1316 22.5902C21.8992 22.0646 22.8237 21.7579 23.8186 21.7579C25.125 21.7579 26.31 22.2868 27.1779 23.1456Z" fill="#17191C"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_use_case_work_dark.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_use_case_work_dark.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="white"/>
|
||||
<path d="M0 14C0 6.26801 6.26801 0 14 0H24C31.732 0 38 6.26801 38 14V24C38 31.732 31.732 38 24 38H14C6.26801 38 0 31.732 0 24V14Z" fill="#0DBD8B" fill-opacity="0.3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 19C30 25.0751 25.0751 30 19 30C12.9249 30 8 25.0751 8 19C8 12.9249 12.9249 8 19 8C25.0751 8 30 12.9249 30 19ZM18.0835 16.4792C18.0835 18.1245 16.8522 19.4583 15.3335 19.4583C13.8147 19.4583 12.5835 18.1245 12.5835 16.4792C12.5835 14.8338 13.8147 13.5 15.3335 13.5C16.8522 13.5 18.0835 14.8338 18.0835 16.4792ZM26.2277 17.9433C26.2277 19.3848 25.1491 20.5533 23.8185 20.5533C22.488 20.5533 21.4093 19.3848 21.4093 17.9433C21.4093 16.5019 22.488 15.3334 23.8185 15.3334C25.1491 15.3334 26.2277 16.5019 26.2277 17.9433ZM19.9167 25.3207C19.9167 23.1735 18.4086 21.3786 16.3938 20.9373C16.0507 20.8691 15.6961 20.8334 15.3334 20.8334C13.4811 20.8334 11.8427 21.7648 10.846 23.1924C12.368 26.1464 15.4481 28.1667 19 28.1667C19.3094 28.1667 19.6152 28.1513 19.9167 28.1214L19.9167 25.3207ZM27.1779 23.1456C26.0706 25.3255 24.1173 27.0034 21.7502 27.747L21.7502 25.3207C21.7502 24.3428 21.5281 23.4167 21.1316 22.5902C21.8992 22.0646 22.8237 21.7579 23.8186 21.7579C25.125 21.7579 26.31 22.2868 27.1779 23.1456Z" fill="#17191C"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -93,6 +93,17 @@
|
|||
"onboarding_splash_page_4_title_no_pun" = "Messaging for your team.";
|
||||
"onboarding_splash_page_4_message" = "Element is also great for the workplace. It’s trusted by the world’s most secure organisations.";
|
||||
|
||||
"onboarding_use_case_title" = "Who will you chat to the most?";
|
||||
"onboarding_use_case_message" = "We’ll help you get connected.";
|
||||
"onboarding_use_case_personal_messaging" = "Friends and family";
|
||||
"onboarding_use_case_work_messaging" = "Teams";
|
||||
"onboarding_use_case_community_messaging" = "Communities";
|
||||
/* The placeholder string contains onboarding_use_case_skip_button as a tappable action */
|
||||
"onboarding_use_case_not_sure_yet" = "Not sure yet? You can %@";
|
||||
"onboarding_use_case_skip_button" = "skip this question";
|
||||
"onboarding_use_case_existing_server_message" = "Looking to join an existing server?";
|
||||
"onboarding_use_case_existing_server_button" = "Connect to server";
|
||||
|
||||
// Authentication
|
||||
"auth_login" = "Log in";
|
||||
"auth_register" = "Register";
|
||||
|
|
|
@ -118,6 +118,13 @@ internal enum Asset {
|
|||
internal static let onboardingSplashScreenPage3Dark = ImageAsset(name: "OnboardingSplashScreenPage3Dark")
|
||||
internal static let onboardingSplashScreenPage4 = ImageAsset(name: "OnboardingSplashScreenPage4")
|
||||
internal static let onboardingSplashScreenPage4Dark = ImageAsset(name: "OnboardingSplashScreenPage4Dark")
|
||||
internal static let onboardingUseCaseCommunity = ImageAsset(name: "onboarding_use_case_community")
|
||||
internal static let onboardingUseCaseCommunityDark = ImageAsset(name: "onboarding_use_case_community_dark")
|
||||
internal static let onboardingUseCaseIcon = ImageAsset(name: "onboarding_use_case_icon")
|
||||
internal static let onboardingUseCasePersonal = ImageAsset(name: "onboarding_use_case_personal")
|
||||
internal static let onboardingUseCasePersonalDark = ImageAsset(name: "onboarding_use_case_personal_dark")
|
||||
internal static let onboardingUseCaseWork = ImageAsset(name: "onboarding_use_case_work")
|
||||
internal static let onboardingUseCaseWorkDark = ImageAsset(name: "onboarding_use_case_work_dark")
|
||||
internal static let peopleEmptyScreenArtwork = ImageAsset(name: "people_empty_screen_artwork")
|
||||
internal static let peopleEmptyScreenArtworkDark = ImageAsset(name: "people_empty_screen_artwork_dark")
|
||||
internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action")
|
||||
|
|
|
@ -2443,6 +2443,42 @@ public class VectorL10n: NSObject {
|
|||
public static var onboardingSplashRegisterButtonTitle: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_splash_register_button_title")
|
||||
}
|
||||
/// Communities
|
||||
public static var onboardingUseCaseCommunityMessaging: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_community_messaging")
|
||||
}
|
||||
/// Connect to server
|
||||
public static var onboardingUseCaseExistingServerButton: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_existing_server_button")
|
||||
}
|
||||
/// Looking to join an existing server?
|
||||
public static var onboardingUseCaseExistingServerMessage: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_existing_server_message")
|
||||
}
|
||||
/// We’ll help you get connected.
|
||||
public static var onboardingUseCaseMessage: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_message")
|
||||
}
|
||||
/// Not sure yet? You can %@
|
||||
public static func onboardingUseCaseNotSureYet(_ p1: String) -> String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_not_sure_yet", p1)
|
||||
}
|
||||
/// Friends and family
|
||||
public static var onboardingUseCasePersonalMessaging: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_personal_messaging")
|
||||
}
|
||||
/// skip this question
|
||||
public static var onboardingUseCaseSkipButton: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_skip_button")
|
||||
}
|
||||
/// Who will you chat to the most?
|
||||
public static var onboardingUseCaseTitle: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_title")
|
||||
}
|
||||
/// Teams
|
||||
public static var onboardingUseCaseWorkMessaging: String {
|
||||
return VectorL10n.tr("Vector", "onboarding_use_case_work_messaging")
|
||||
}
|
||||
/// Open
|
||||
public static var `open`: String {
|
||||
return VectorL10n.tr("Vector", "open")
|
||||
|
|
|
@ -99,7 +99,7 @@ final class RiotSettings: NSObject {
|
|||
@UserDefault<String?>(key: "userInterfaceTheme", defaultValue: nil, storage: defaults)
|
||||
var userInterfaceTheme
|
||||
|
||||
// MARK: Other
|
||||
// MARK: Analytics & Rageshakes
|
||||
|
||||
/// Whether the user was previously shown the Matomo analytics prompt.
|
||||
var hasSeenAnalyticsPrompt: Bool {
|
||||
|
@ -130,6 +130,12 @@ final class RiotSettings: NSObject {
|
|||
@UserDefault(key: "enableRageShake", defaultValue: false, storage: defaults)
|
||||
var enableRageShake
|
||||
|
||||
// MARK: User
|
||||
|
||||
/// A dictionary of dictionaries keyed by user ID for storage of the `UserSessionProperties` from any active `UserSession`s.
|
||||
@UserDefault(key: "userSessionProperties", defaultValue: [:], storage: defaults)
|
||||
var userSessionProperties: [String: [String: Any]]
|
||||
|
||||
// MARK: Labs
|
||||
|
||||
/// Indicates if CallKit ringing is enabled for group calls. This setting does not disable the CallKit integration for group calls, only relates to ringing.
|
||||
|
|
|
@ -112,11 +112,18 @@ import DesignKit
|
|||
/// - Parameter tabBar: The tab bar to customise.
|
||||
func applyStyle(onTabBar tabBar: UITabBar)
|
||||
|
||||
/// Apply the theme on a navigation bar.
|
||||
/// Apply the theme on a navigation bar, without enabling the iOS 15's scroll edges appearance.
|
||||
///
|
||||
/// - Parameter navigationBar: the navigation bar to customise.
|
||||
func applyStyle(onNavigationBar navigationBar: UINavigationBar)
|
||||
|
||||
/// Apply the theme on a navigation bar.
|
||||
///
|
||||
/// - Parameter navigationBar: the navigation bar to customise.
|
||||
/// - Parameter modernScrollEdgesAppearance: whether or not to use the iOS 15 style scroll edges appearance
|
||||
func applyStyle(onNavigationBar navigationBar: UINavigationBar,
|
||||
withModernScrollEdgesAppearance modernScrollEdgesAppearance: Bool)
|
||||
|
||||
/// Apply the theme on a search bar.
|
||||
///
|
||||
/// - Parameter searchBar: the search bar to customise.
|
||||
|
|
|
@ -112,28 +112,36 @@ class DarkTheme: NSObject, Theme {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: We are not using UINavigationBarAppearance on iOS 13/14 because of UINavigationBar directly including UISearchBar on their titleView that cause crop issues with UINavigationController pop.
|
||||
// Protocols don't support default parameter values and a protocol extension won't work for @objc
|
||||
func applyStyle(onNavigationBar navigationBar: UINavigationBar) {
|
||||
navigationBar.tintColor = self.tintColor
|
||||
applyStyle(onNavigationBar: navigationBar, withModernScrollEdgesAppearance: false)
|
||||
}
|
||||
|
||||
// Note: We are not using UINavigationBarAppearance on iOS 13/14 because of UINavigationBar directly including UISearchBar on their titleView that cause crop issues with UINavigationController pop.
|
||||
func applyStyle(onNavigationBar navigationBar: UINavigationBar,
|
||||
withModernScrollEdgesAppearance modernScrollEdgesAppearance: Bool) {
|
||||
navigationBar.tintColor = tintColor
|
||||
|
||||
// On iOS 15 use UINavigationBarAppearance to fix visual issues with the scrollEdgeAppearance style.
|
||||
if #available(iOS 15.0, *) {
|
||||
let appearance = UINavigationBarAppearance()
|
||||
|
||||
appearance.configureWithOpaqueBackground()
|
||||
appearance.backgroundColor = self.baseColor
|
||||
appearance.shadowColor = nil
|
||||
appearance.backgroundColor = baseColor
|
||||
if !modernScrollEdgesAppearance {
|
||||
appearance.shadowColor = nil
|
||||
}
|
||||
appearance.titleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: self.textPrimaryColor
|
||||
NSAttributedString.Key.foregroundColor: textPrimaryColor
|
||||
]
|
||||
|
||||
navigationBar.standardAppearance = appearance
|
||||
navigationBar.scrollEdgeAppearance = appearance
|
||||
navigationBar.scrollEdgeAppearance = modernScrollEdgesAppearance ? nil : appearance
|
||||
} else {
|
||||
navigationBar.titleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: self.textPrimaryColor
|
||||
NSAttributedString.Key.foregroundColor: textPrimaryColor
|
||||
]
|
||||
navigationBar.barTintColor = self.baseColor
|
||||
navigationBar.barTintColor = baseColor
|
||||
navigationBar.shadowImage = UIImage() // Remove bottom shadow
|
||||
|
||||
// The navigation bar needs to be opaque so that its background color is the expected one
|
||||
|
|
|
@ -118,9 +118,15 @@ class DefaultTheme: NSObject, Theme {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: We are not using UINavigationBarAppearance on iOS 13/14 because of UINavigationBar directly including UISearchBar on their titleView that cause crop issues with UINavigationController pop.
|
||||
// Protocols don't support default parameter values and a protocol extension doesn't work for @objc
|
||||
func applyStyle(onNavigationBar navigationBar: UINavigationBar) {
|
||||
navigationBar.tintColor = self.tintColor
|
||||
applyStyle(onNavigationBar: navigationBar, withModernScrollEdgesAppearance: false)
|
||||
}
|
||||
|
||||
// Note: We are not using UINavigationBarAppearance on iOS 13/14 because of UINavigationBar directly including UISearchBar on their titleView that cause crop issues with UINavigationController pop.
|
||||
func applyStyle(onNavigationBar navigationBar: UINavigationBar,
|
||||
withModernScrollEdgesAppearance modernScrollEdgesAppearance: Bool) {
|
||||
navigationBar.tintColor = tintColor
|
||||
|
||||
// On iOS 15 use UINavigationBarAppearance to fix visual issues with the scrollEdgeAppearance style.
|
||||
if #available(iOS 15.0, *) {
|
||||
|
@ -128,13 +134,15 @@ class DefaultTheme: NSObject, Theme {
|
|||
|
||||
appearance.configureWithOpaqueBackground()
|
||||
appearance.backgroundColor = baseColor
|
||||
appearance.shadowColor = nil
|
||||
if !modernScrollEdgesAppearance {
|
||||
appearance.shadowColor = nil
|
||||
}
|
||||
appearance.titleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: textPrimaryColor
|
||||
]
|
||||
|
||||
navigationBar.standardAppearance = appearance
|
||||
navigationBar.scrollEdgeAppearance = appearance
|
||||
navigationBar.scrollEdgeAppearance = modernScrollEdgesAppearance ? nil : appearance
|
||||
} else {
|
||||
navigationBar.titleTextAttributes = [
|
||||
NSAttributedString.Key.foregroundColor: textPrimaryColor
|
||||
|
|
|
@ -33,19 +33,21 @@ class UserSession: NSObject, UserSessionProtocol {
|
|||
let account: MXKAccount
|
||||
// Keep strong reference to the MXSession because account.mxSession can become nil on logout or failure
|
||||
let matrixSession: MXSession
|
||||
|
||||
var userId: String {
|
||||
guard let userId = self.account.mxCredentials.userId else {
|
||||
fatalError("[UserSession] identifier: account.mxCredentials.userId should be defined")
|
||||
}
|
||||
return userId
|
||||
}
|
||||
let userId: String
|
||||
/// An object that contains user specific properties.
|
||||
let userProperties: UserSessionProperties
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(account: MXKAccount, matrixSession: MXSession) {
|
||||
guard let userId = account.mxCredentials.userId else {
|
||||
fatalError("[UserSession] identifier: account.mxCredentials.userId should be defined")
|
||||
}
|
||||
|
||||
self.account = account
|
||||
self.matrixSession = matrixSession
|
||||
self.userId = userId
|
||||
self.userProperties = UserSessionProperties(userId: userId)
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
|
86
Riot/Managers/UserSessions/UserSessionProperties.swift
Normal file
|
@ -0,0 +1,86 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// User properties that are tied to a particular user ID.
|
||||
class UserSessionProperties: NSObject {
|
||||
|
||||
// MARK: - Constants
|
||||
private enum Constants {
|
||||
static let useCaseKey = "useCase"
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
/// The user ID for these properties
|
||||
private let userId: String
|
||||
|
||||
/// The underlying dictionary for this userId from user defaults.
|
||||
private var dictionary: [String: Any] {
|
||||
get {
|
||||
RiotSettings.shared.userSessionProperties[userId] ?? [:]
|
||||
}
|
||||
set {
|
||||
var sharedProperties = RiotSettings.shared.userSessionProperties
|
||||
sharedProperties[userId] = newValue
|
||||
RiotSettings.shared.userSessionProperties = sharedProperties
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// The user's use case selection if this session was the one used to register the account.
|
||||
var useCase: UseCase? {
|
||||
get {
|
||||
guard let useCaseRawValue = dictionary[Constants.useCaseKey] as? String else { return nil }
|
||||
return UseCase(rawValue: useCaseRawValue)
|
||||
} set {
|
||||
dictionary[Constants.useCaseKey] = newValue?.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a selected use case for the app.
|
||||
/// Note: The raw string value is used for storage.
|
||||
enum UseCase: String {
|
||||
case personalMessaging
|
||||
case workMessaging
|
||||
case communityMessaging
|
||||
case skipped
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
/// Create new properties for the specified user ID.
|
||||
/// - Parameter userId: The user ID to load properties for.
|
||||
init(userId: String) {
|
||||
self.userId = userId
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
/// Clear all of the stored properties.
|
||||
func delete() {
|
||||
dictionary = [:]
|
||||
|
||||
var sharedProperties = RiotSettings.shared.userSessionProperties
|
||||
sharedProperties[userId] = nil
|
||||
RiotSettings.shared.userSessionProperties = sharedProperties
|
||||
}
|
||||
}
|
|
@ -131,6 +131,9 @@ class UserSessionsService: NSObject {
|
|||
NotificationCenter.default.post(name: UserSessionsService.willRemoveUserSession, object: self, userInfo: [NotificationUserInfoKey.userSession: userSession])
|
||||
}
|
||||
|
||||
// Clear any stored user properties from this session.
|
||||
userSession.userProperties.delete()
|
||||
|
||||
self.userSessions.removeAll { (userSession) -> Bool in
|
||||
return userId == userSession.userId
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
|||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: (() -> Void)?
|
||||
var completion: ((MXKAuthenticationType) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
|
@ -62,6 +62,10 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
|||
authenticationViewController.authType = authenticationType
|
||||
}
|
||||
|
||||
func showCustomServer() {
|
||||
authenticationViewController.setCustomServerFieldsVisible(true)
|
||||
}
|
||||
|
||||
func update(externalRegistrationParameters: [AnyHashable: Any]) {
|
||||
authenticationViewController.externalRegistrationParameters = externalRegistrationParameters
|
||||
}
|
||||
|
@ -82,6 +86,6 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc
|
|||
// MARK: - AuthenticationViewControllerDelegate
|
||||
extension AuthenticationCoordinator: AuthenticationViewControllerDelegate {
|
||||
func authenticationViewControllerDidDismiss(_ authenticationViewController: AuthenticationViewController!) {
|
||||
completion?()
|
||||
completion?(authenticationViewController.authType)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,14 @@ import Foundation
|
|||
|
||||
/// `AuthenticationCoordinatorProtocol` is a protocol describing a Coordinator that handle's the authentication navigation flow.
|
||||
protocol AuthenticationCoordinatorProtocol: Coordinator, Presentable {
|
||||
var completion: (() -> Void)? { get set }
|
||||
var completion: ((MXKAuthenticationType) -> Void)? { get set }
|
||||
|
||||
/// Update the screen to display registration or login.
|
||||
func update(authenticationType: MXKAuthenticationType)
|
||||
|
||||
/// Enable the custom server checkbox to allow the user to enter a homeserver URL.
|
||||
func showCustomServer()
|
||||
|
||||
/// Force a registration process based on a predefined set of parameters from a server provisioning link.
|
||||
/// For more information see `AuthenticationViewController.externalRegistrationParameters`.
|
||||
func update(externalRegistrationParameters: [AnyHashable: Any])
|
||||
|
|
|
@ -53,6 +53,10 @@
|
|||
/// returns YES if the SSO login can be continued.
|
||||
- (BOOL)continueSSOLoginWithToken:(NSString*)loginToken txnId:(NSString*)txnId;
|
||||
|
||||
/// Show or hide the custom server textfields.
|
||||
/// @param isVisible YES to show, NO to hide.
|
||||
- (void)setCustomServerFieldsVisible:(BOOL)isVisible;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
|||
}
|
||||
self.serverOptionsContainer.hidden = !BuildSettings.authScreenShowCustomServerOptions;
|
||||
|
||||
[self hideCustomServers:YES];
|
||||
[self setCustomServerFieldsVisible:NO];
|
||||
|
||||
// Soft logout section
|
||||
self.softLogoutClearDataButton.layer.cornerRadius = 5;
|
||||
|
@ -214,7 +214,8 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
|||
|
||||
- (void)userInterfaceThemeDidChange
|
||||
{
|
||||
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar];
|
||||
[ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar
|
||||
withModernScrollEdgesAppearance:YES];
|
||||
|
||||
self.view.backgroundColor = ThemeService.shared.theme.backgroundColor;
|
||||
|
||||
|
@ -887,7 +888,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
|||
{
|
||||
if (sender == self.customServersTickButton)
|
||||
{
|
||||
[self hideCustomServers:!self.customServersContainer.hidden];
|
||||
[self setCustomServerFieldsVisible:self.customServersContainer.hidden];
|
||||
}
|
||||
else if (sender == self.forgotPasswordButton)
|
||||
{
|
||||
|
@ -1235,14 +1236,14 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
|||
[self.view layoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)hideCustomServers:(BOOL)hidden
|
||||
- (void)setCustomServerFieldsVisible:(BOOL)isVisible
|
||||
{
|
||||
if (self.customServersContainer.isHidden == hidden)
|
||||
if (self.customServersContainer.isHidden != isVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (hidden)
|
||||
if (!isVisible)
|
||||
{
|
||||
[self.homeServerTextField resignFirstResponder];
|
||||
[self.identityServerTextField resignFirstResponder];
|
||||
|
@ -1360,7 +1361,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
|||
[self.authenticationActivityIndicator startAnimating];
|
||||
|
||||
// Hide the custom server details in order to save customized inputs
|
||||
[self hideCustomServers:YES];
|
||||
[self setCustomServerFieldsVisible:NO];
|
||||
|
||||
MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:userId];
|
||||
MXSession *session = account.mxSession;
|
||||
|
@ -1585,7 +1586,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
|||
{
|
||||
// wellKnown matches with application default servers
|
||||
// Hide custom servers
|
||||
[self hideCustomServers:YES];
|
||||
[self setCustomServerFieldsVisible:NO];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1617,7 +1618,7 @@ static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
|
|||
}
|
||||
|
||||
// And show custom servers
|
||||
[self hideCustomServers:NO];
|
||||
[self setCustomServerFieldsVisible:YES];
|
||||
}
|
||||
|
||||
#pragma mark - KeyVerificationCoordinatorBridgePresenterDelegate
|
||||
|
|
|
@ -28,6 +28,10 @@ class VectorHostingController: UIHostingController<AnyView> {
|
|||
|
||||
private var theme: Theme
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var enableNavigationBarScrollEdgesAppearance = false
|
||||
|
||||
init<Content>(rootView: Content) where Content: View {
|
||||
self.theme = ThemeService.shared().theme
|
||||
super.init(rootView: AnyView(rootView.vectorContent()))
|
||||
|
@ -67,7 +71,7 @@ class VectorHostingController: UIHostingController<AnyView> {
|
|||
|
||||
private func update(theme: Theme) {
|
||||
if let navigationBar = self.navigationController?.navigationBar {
|
||||
theme.applyStyle(onNavigationBar: navigationBar)
|
||||
theme.applyStyle(onNavigationBar: navigationBar, withModernScrollEdgesAppearance: enableNavigationBarScrollEdgesAppearance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
|
||||
// MARK: Screen results
|
||||
private var splashScreenResult: OnboardingSplashScreenViewModelResult?
|
||||
private var useCaseResult: OnboardingUseCaseViewModelResult?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
|
@ -126,9 +127,47 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
self.navigationRouter.setRootModule(coordinator, popCompletion: nil)
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// Displays the next view in the flow after the splash screen.
|
||||
private func splashScreenCoordinator(_ coordinator: OnboardingSplashScreenCoordinator, didCompleteWith result: OnboardingSplashScreenViewModelResult) {
|
||||
splashScreenResult = result
|
||||
|
||||
// Set the auth type early to allow network requests to finish during display of the use case screen.
|
||||
let mxkAuthenticationType = splashScreenResult == .register ? MXKAuthenticationTypeRegister : MXKAuthenticationTypeLogin
|
||||
authenticationCoordinator.update(authenticationType: mxkAuthenticationType)
|
||||
|
||||
switch result {
|
||||
case .register:
|
||||
showUseCaseSelectionScreen()
|
||||
case .login:
|
||||
showAuthenticationScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// Show the use case screen for new users.
|
||||
private func showUseCaseSelectionScreen() {
|
||||
let coordinator = OnboardingUseCaseCoordinator()
|
||||
coordinator.completion = { [weak self, weak coordinator] result in
|
||||
guard let self = self, let coordinator = coordinator else { return }
|
||||
self.useCaseCoordinator(coordinator, didCompleteWith: result)
|
||||
}
|
||||
|
||||
coordinator.start()
|
||||
add(childCoordinator: coordinator)
|
||||
|
||||
if self.navigationRouter.modules.isEmpty {
|
||||
self.navigationRouter.setRootModule(coordinator, popCompletion: nil)
|
||||
} else {
|
||||
self.navigationRouter.push(coordinator, animated: true) { [weak self] in
|
||||
self?.remove(childCoordinator: coordinator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays the next view in the flow after the use case screen.
|
||||
private func useCaseCoordinator(_ coordinator: OnboardingUseCaseCoordinator, didCompleteWith result: OnboardingUseCaseViewModelResult) {
|
||||
useCaseResult = result
|
||||
showAuthenticationScreen()
|
||||
}
|
||||
|
||||
|
@ -139,21 +178,22 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
MXLog.debug("[OnboardingCoordinator] showAuthenticationScreen")
|
||||
|
||||
let coordinator = authenticationCoordinator
|
||||
coordinator.completion = { [weak self, weak coordinator] in
|
||||
coordinator.completion = { [weak self, weak coordinator] authenticationType in
|
||||
guard let self = self, let coordinator = coordinator else { return }
|
||||
self.authenticationCoordinatorDidComplete(coordinator)
|
||||
self.authenticationCoordinator(coordinator, didCompleteWith: authenticationType)
|
||||
}
|
||||
|
||||
// Due to needing to preload the authVC, this breaks the Coordinator init/start pattern.
|
||||
// This can be re-assessed once we re-write a native flow for authentication.
|
||||
|
||||
// Set authType first as registration parameters or soft logout credentials will modify this.
|
||||
let mxkAuthenticationType = splashScreenResult == .register ? MXKAuthenticationTypeRegister : MXKAuthenticationTypeLogin
|
||||
coordinator.update(authenticationType: mxkAuthenticationType)
|
||||
|
||||
if let externalRegistrationParameters = externalRegistrationParameters {
|
||||
coordinator.update(externalRegistrationParameters: externalRegistrationParameters)
|
||||
}
|
||||
|
||||
if useCaseResult == .customServer {
|
||||
coordinator.showCustomServer()
|
||||
}
|
||||
|
||||
if let softLogoutCredentials = parameters.softLogoutCredentials {
|
||||
coordinator.update(softLogoutCredentials: softLogoutCredentials)
|
||||
}
|
||||
|
@ -178,8 +218,34 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
}
|
||||
|
||||
/// Displays the next view in the flow after the authentication screen.
|
||||
private func authenticationCoordinatorDidComplete(_ coordinator: AuthenticationCoordinatorProtocol) {
|
||||
private func authenticationCoordinator(_ coordinator: AuthenticationCoordinatorProtocol, didCompleteWith authenticationType: MXKAuthenticationType) {
|
||||
completion?()
|
||||
isShowingAuthentication = false
|
||||
|
||||
// Handle the chosen use case if appropriate
|
||||
if authenticationType == MXKAuthenticationTypeRegister,
|
||||
let useCaseResult = useCaseResult,
|
||||
let userSession = UserSessionsService.shared.mainUserSession {
|
||||
// Store the value in the user's session
|
||||
userSession.userProperties.useCase = useCaseResult.userSessionPropertyValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingUseCaseViewModelResult {
|
||||
/// The result converted into the type stored in the user session.
|
||||
var userSessionPropertyValue: UserSessionProperties.UseCase? {
|
||||
switch self {
|
||||
case .personalMessaging:
|
||||
return .personalMessaging
|
||||
case .workMessaging:
|
||||
return .workMessaging
|
||||
case .communityMessaging:
|
||||
return .communityMessaging
|
||||
case .skipped:
|
||||
return .skipped
|
||||
case .customServer:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ targets:
|
|||
- path: ../Riot/PropertyWrappers/UserDefaultsBackedPropertyWrapper.swift
|
||||
- path: ../Riot/Modules/MatrixKit
|
||||
- path: ../Riot/Modules/Analytics
|
||||
- path: ../Riot/Managers/UserSessions
|
||||
- path: ../Riot/Managers/AppInfo/
|
||||
excludes:
|
||||
- "**/*.md" # excludes all files with the .md extension
|
||||
|
|
|
@ -70,6 +70,7 @@ targets:
|
|||
buildPhase: resources
|
||||
- path: ../Riot/Modules/MatrixKit
|
||||
- path: ../Riot/Modules/Analytics
|
||||
- path: ../Riot/Managers/UserSessions
|
||||
excludes:
|
||||
- "**/*.md" # excludes all files with the .md extension
|
||||
- path: ../Riot/Generated/MatrixKitStrings.swift
|
||||
|
|
|
@ -42,18 +42,13 @@ struct AnalyticsPromptViewState: BindableState {
|
|||
/// A collection of strings for the UI that need to be created in
|
||||
/// the coordinator or mocked in the RiotSwiftUI target.
|
||||
protocol AnalyticsPromptStringsProtocol {
|
||||
var appDisplayName: String { get }
|
||||
|
||||
var point1: NSAttributedString { get }
|
||||
var point2: NSAttributedString { get }
|
||||
|
||||
var termsNewUser: NSAttributedString { get }
|
||||
var termsUpgrade: NSAttributedString { get }
|
||||
}
|
||||
|
||||
enum AnalyticsPromptType {
|
||||
case newUser(termsString: NSAttributedString)
|
||||
case upgrade(termsString: NSAttributedString)
|
||||
case newUser
|
||||
case upgrade
|
||||
}
|
||||
|
||||
extension AnalyticsPromptType {
|
||||
|
@ -67,11 +62,23 @@ extension AnalyticsPromptType {
|
|||
}
|
||||
}
|
||||
|
||||
/// The terms string that should be displayed.
|
||||
var termsStrings: NSAttributedString {
|
||||
/// The main part of the terms string that should be displayed.
|
||||
var mainTermsString: String {
|
||||
switch self {
|
||||
case .newUser(let termsString), .upgrade(let termsString):
|
||||
return termsString
|
||||
case .newUser:
|
||||
return VectorL10n.analyticsPromptTermsNewUser("%@")
|
||||
case .upgrade:
|
||||
return VectorL10n.analyticsPromptTermsUpgrade("%@")
|
||||
}
|
||||
}
|
||||
|
||||
/// The tappable part of the terms string that should be displayed.
|
||||
var termsLinkString: String {
|
||||
switch self {
|
||||
case .newUser:
|
||||
return VectorL10n.analyticsPromptTermsLinkNewUser
|
||||
case .upgrade:
|
||||
return VectorL10n.analyticsPromptTermsLinkUpgrade
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,15 +103,7 @@ extension AnalyticsPromptType {
|
|||
}
|
||||
}
|
||||
|
||||
extension AnalyticsPromptType: CaseIterable {
|
||||
static var allCases: [AnalyticsPromptType] {
|
||||
let strings = MockAnalyticsPromptStrings()
|
||||
return [
|
||||
.newUser(termsString: strings.termsNewUser),
|
||||
.upgrade(termsString: strings.termsUpgrade)
|
||||
]
|
||||
}
|
||||
}
|
||||
extension AnalyticsPromptType: CaseIterable { }
|
||||
|
||||
extension AnalyticsPromptType: Identifiable {
|
||||
var id: String {
|
||||
|
|
|
@ -52,9 +52,9 @@ final class AnalyticsPromptCoordinator: Coordinator, Presentable {
|
|||
let promptType: AnalyticsPromptType
|
||||
|
||||
if Analytics.shared.promptShouldDisplayUpgradeMessage {
|
||||
promptType = .upgrade(termsString: strings.termsUpgrade)
|
||||
promptType = .upgrade
|
||||
} else {
|
||||
promptType = .newUser(termsString: strings.termsNewUser)
|
||||
promptType = .newUser
|
||||
}
|
||||
|
||||
let viewModel = AnalyticsPromptViewModel(promptType: promptType, strings: strings, termsURL: BuildSettings.analyticsTermsURL)
|
||||
|
|
|
@ -18,16 +18,7 @@ import Foundation
|
|||
|
||||
@available(iOS 14.0, *)
|
||||
struct AnalyticsPromptStrings: AnalyticsPromptStringsProtocol {
|
||||
let appDisplayName = AppInfo.current.displayName
|
||||
|
||||
let point1 = HTMLFormatter().formatHTML(VectorL10n.analyticsPromptPoint1, withAllowedTags: ["b", "p"], fontSize: UIFont.systemFontSize)
|
||||
let point2 = HTMLFormatter().formatHTML(VectorL10n.analyticsPromptPoint2, withAllowedTags: ["b", "p"], fontSize: UIFont.systemFontSize)
|
||||
|
||||
let termsNewUser = HTMLFormatter().format(VectorL10n.analyticsPromptTermsNewUser("%@"),
|
||||
with: VectorL10n.analyticsPromptTermsLinkNewUser,
|
||||
using: BuildSettings.analyticsTermsURL)
|
||||
let termsUpgrade = HTMLFormatter().format(VectorL10n.analyticsPromptTermsUpgrade("%@"),
|
||||
with: VectorL10n.analyticsPromptTermsLinkUpgrade,
|
||||
using: BuildSettings.analyticsTermsURL)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,14 +17,9 @@
|
|||
import UIKit
|
||||
|
||||
struct MockAnalyticsPromptStrings: AnalyticsPromptStringsProtocol {
|
||||
var appDisplayName = "Element"
|
||||
|
||||
let point1: NSAttributedString
|
||||
let point2: NSAttributedString
|
||||
|
||||
let termsNewUser: NSAttributedString
|
||||
let termsUpgrade: NSAttributedString
|
||||
|
||||
let shortString = NSAttributedString(string: "This is a short string.")
|
||||
let longString = NSAttributedString(string: "This is a very long string that will be used to test the layout over multiple lines of text to ensure everything is correct.")
|
||||
|
||||
|
@ -38,15 +33,5 @@ struct MockAnalyticsPromptStrings: AnalyticsPromptStringsProtocol {
|
|||
point2.append(NSAttributedString(string: "don't", attributes: [.font: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize)]))
|
||||
point2.append(NSAttributedString(string: " share information with third parties"))
|
||||
self.point2 = point2
|
||||
|
||||
let termsNewUser = NSMutableAttributedString(string: "You can read all our terms ")
|
||||
termsNewUser.append(NSAttributedString(string: "here", attributes: [.link: URL(string: "https://element.io/cookie-policy")!]))
|
||||
termsNewUser.append(NSAttributedString(string: "."))
|
||||
self.termsNewUser = termsNewUser
|
||||
|
||||
let termsUpgrade = NSMutableAttributedString(string: "Read all our terms ")
|
||||
termsUpgrade.append(NSAttributedString(string: "here", attributes: [.link: URL(string: "https://element.io/cookie-policy")!]))
|
||||
termsUpgrade.append(NSAttributedString(string: ". Is that OK?"))
|
||||
self.termsUpgrade = termsUpgrade
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,12 +42,10 @@ struct AnalyticsPrompt: View {
|
|||
VStack {
|
||||
Text("\(viewModel.viewState.promptType.message)\n")
|
||||
|
||||
AnalyticsPromptTermsText(attributedString: viewModel.viewState.promptType.termsStrings)
|
||||
.accessibilityLabel(Text(viewModel.viewState.promptType.termsStrings.string))
|
||||
.accessibilityValue(Text(VectorL10n.accessibilityButtonLabel))
|
||||
.onTapGesture {
|
||||
viewModel.send(viewAction: .openTermsURL)
|
||||
}
|
||||
InlineTextButton(viewModel.viewState.promptType.mainTermsString,
|
||||
tappableText: viewModel.viewState.promptType.termsLinkString) {
|
||||
viewModel.send(viewAction: .openTermsURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +69,7 @@ struct AnalyticsPrompt: View {
|
|||
Image(uiImage: Asset.Images.analyticsLogo.image)
|
||||
.padding(.bottom, 25)
|
||||
|
||||
Text(VectorL10n.analyticsPromptTitle(viewModel.viewState.strings.appDisplayName))
|
||||
Text(VectorL10n.analyticsPromptTitle(AppInfo.current.displayName))
|
||||
.font(theme.fonts.title2B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
.padding(.bottom, 2)
|
||||
|
@ -125,6 +123,7 @@ struct AnalyticsPrompt: View {
|
|||
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
|
||||
}
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.accentColor(theme.colors.accent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// The last line of text in the description with highlighting on the link string.
|
||||
struct AnalyticsPromptTermsText: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
/// A string with a link attribute.
|
||||
private struct StringComponent {
|
||||
let string: String
|
||||
let isLink: Bool
|
||||
}
|
||||
|
||||
/// Internal representation of the string as composable parts.
|
||||
private let components: [StringComponent]
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(attributedString: NSAttributedString) {
|
||||
var components = [StringComponent]()
|
||||
let range = NSRange(location: 0, length: attributedString.length)
|
||||
let string = attributedString.string as NSString
|
||||
|
||||
attributedString.enumerateAttributes(in: range, options: []) { attributes, range, stop in
|
||||
let isLink = attributes.keys.contains(.link)
|
||||
components.append(StringComponent(string: string.substring(with: range), isLink: isLink))
|
||||
}
|
||||
|
||||
self.components = components
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
var body: some View {
|
||||
components.reduce(Text("")) {
|
||||
$0 + Text($1.string).foregroundColor($1.isLink ? theme.colors.accent : nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
@available(iOS 14.0, *)
|
||||
struct AnalyticsPromptTermsText_Previews: PreviewProvider {
|
||||
|
||||
static let strings = MockAnalyticsPromptStrings()
|
||||
|
||||
static var previews: some View {
|
||||
VStack(spacing: 8) {
|
||||
AnalyticsPromptTermsText(attributedString: strings.termsNewUser)
|
||||
AnalyticsPromptTermsText(attributedString: strings.termsUpgrade)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import Foundation
|
|||
@available(iOS 14.0, *)
|
||||
enum MockAppScreens {
|
||||
static let appScreens: [MockScreenState.Type] = [
|
||||
MockOnboardingUseCaseScreenState.self,
|
||||
MockOnboardingSplashScreenScreenState.self,
|
||||
MockLocationSharingScreenState.self,
|
||||
MockAnalyticsPromptScreenState.self,
|
||||
|
|
89
RiotSwiftUI/Modules/Common/Util/InlineTextButton.swift
Normal file
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS, introduced: 14.0, deprecated: 15.0, message: "Use Text with an AttributedString instead that includes a link and handle the tap by adding an OpenURLAction to the environment.")
|
||||
/// A `Button`, that fakes having a tappable string inside of a regular string.
|
||||
struct InlineTextButton: View {
|
||||
|
||||
private struct StringComponent {
|
||||
let string: Substring
|
||||
let isTinted: Bool
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
/// The individual components of the string.
|
||||
private let components: [StringComponent]
|
||||
private let action: () -> Void
|
||||
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
/// Creates a new `InlineTextButton`.
|
||||
/// - Parameters:
|
||||
/// - mainText: The main text that shouldn't appear tappable. This must contain a single `%@` placeholder somewhere within.
|
||||
/// - tappableText: The tappable text that will be substituted into the `%@` placeholder.
|
||||
/// - action: The action to perform when tapping the button.
|
||||
internal init(_ mainText: String, tappableText: String, action: @escaping () -> Void) {
|
||||
guard let range = mainText.range(of: "%@") else {
|
||||
self.components = [StringComponent(string: Substring(mainText), isTinted: false)]
|
||||
self.action = action
|
||||
return
|
||||
}
|
||||
|
||||
let firstComponent = StringComponent(string: mainText[..<range.lowerBound], isTinted: false)
|
||||
let middleComponent = StringComponent(string: Substring(tappableText), isTinted: true)
|
||||
let lastComponent = StringComponent(string: mainText[range.upperBound...], isTinted: false)
|
||||
|
||||
self.components = [firstComponent, middleComponent, lastComponent]
|
||||
self.action = action
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
EmptyView()
|
||||
}
|
||||
.buttonStyle(Style(components: components))
|
||||
.accessibilityLabel(components.map { $0.string }.joined())
|
||||
}
|
||||
|
||||
private struct Style: ButtonStyle {
|
||||
let components: [StringComponent]
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
components.reduce(Text("")) { lastValue, component in
|
||||
lastValue + Text(component.string)
|
||||
.foregroundColor(component.isTinted ? .accentColor.opacity(configuration.isPressed ? 0.2 : 1) : nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct Previews_InlineButtonText_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InlineTextButton("Hello there this is a sentence. %@.",
|
||||
tappableText: "And this is a button",
|
||||
action: { })
|
||||
.padding()
|
||||
}
|
||||
}
|
126
RiotSwiftUI/Modules/Common/Util/StyledText.swift
Normal file
|
@ -0,0 +1,126 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import DesignKit
|
||||
|
||||
@available(iOS, introduced: 14.0, deprecated: 15.0, message: "Use Text with an AttributedString instead.")
|
||||
/// A `Text` view that renders attributed strings with their `.font` and `.foregroundColor` attributes.
|
||||
/// This view is a workaround for iOS 13/14 not supporting `AttributedString`.
|
||||
struct StyledText: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
/// A string with a bold property.
|
||||
private struct StringComponent {
|
||||
let string: String
|
||||
var font: Font? = nil
|
||||
var color: Color? = nil
|
||||
}
|
||||
|
||||
/// Internal representation of the string as composable parts.
|
||||
private let components: [StringComponent]
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
/// Creates a `StyledText` using the supplied attributed string.
|
||||
/// - Parameter attributedString: The attributed string to display.
|
||||
init(_ attributedString: NSAttributedString) {
|
||||
var components = [StringComponent]()
|
||||
let range = NSRange(location: 0, length: attributedString.length)
|
||||
let string = attributedString.string as NSString
|
||||
|
||||
attributedString.enumerateAttributes(in: range, options: []) { attributes, range, stop in
|
||||
let font = attributes[.font] as? UIFont
|
||||
let color = attributes[.foregroundColor] as? UIColor
|
||||
|
||||
let component = StringComponent(
|
||||
string: string.substring(with: range),
|
||||
font: font.map { Font($0) },
|
||||
color: color.map { Color($0) }
|
||||
)
|
||||
|
||||
components.append(component)
|
||||
}
|
||||
|
||||
self.components = components
|
||||
}
|
||||
|
||||
/// Creates a `StyledText` using a plain string.
|
||||
/// - Parameter string: The plain string to display
|
||||
init(_ string: String) {
|
||||
self.components = [StringComponent(string: string, font: nil)]
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
var body: some View {
|
||||
components.reduce(Text("")) { lastValue, component in
|
||||
lastValue + Text(component.string)
|
||||
.font(component.font)
|
||||
.foregroundColor(component.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct StyledText_Previews: PreviewProvider {
|
||||
static func prettyText() -> NSAttributedString {
|
||||
let string = NSMutableAttributedString(string: "T", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 12),
|
||||
.foregroundColor: UIColor.red
|
||||
])
|
||||
string.append(NSAttributedString(string: "e", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 14),
|
||||
.foregroundColor: UIColor.orange
|
||||
]))
|
||||
string.append(NSAttributedString(string: "s", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 13),
|
||||
.foregroundColor: UIColor.yellow
|
||||
]))
|
||||
string.append(NSAttributedString(string: "t", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 15),
|
||||
.foregroundColor: UIColor.green
|
||||
]))
|
||||
string.append(NSAttributedString(string: "i", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 11),
|
||||
.foregroundColor: UIColor.cyan
|
||||
]))
|
||||
string.append(NSAttributedString(string: "n", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 16),
|
||||
.foregroundColor: UIColor.blue
|
||||
]))
|
||||
string.append(NSAttributedString(string: "g", attributes: [
|
||||
.font: UIFont.boldSystemFont(ofSize: 14),
|
||||
.foregroundColor: UIColor.purple
|
||||
]))
|
||||
return string
|
||||
}
|
||||
|
||||
static var previews: some View {
|
||||
VStack(spacing: 8) {
|
||||
StyledText("Hello, World!")
|
||||
StyledText(NSAttributedString(string: "Testing",
|
||||
attributes: [.font: UIFont.boldSystemFont(ofSize: 64)]))
|
||||
StyledText(prettyText())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct OnboardingButtonStyle: ButtonStyle {
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(configuration.isPressed ? theme.colors.accent : theme.colors.quinaryContent, lineWidth: configuration.isPressed ? 2 : 1.5)
|
||||
)
|
||||
.contentShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ struct OnboardingSplashScreenPage: View {
|
|||
.scaledToFit()
|
||||
.frame(maxWidth: 300)
|
||||
.padding(20)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
OnboardingSplashScreenTitleText(content.title)
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
final class OnboardingUseCaseCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let onboardingUseCaseHostingController: UIViewController
|
||||
private var onboardingUseCaseViewModel: OnboardingUseCaseViewModelProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: ((OnboardingUseCaseViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
init() {
|
||||
let viewModel = OnboardingUseCaseViewModel()
|
||||
let view = OnboardingUseCase(viewModel: viewModel.context)
|
||||
onboardingUseCaseViewModel = viewModel
|
||||
|
||||
let hostingController = VectorHostingController(rootView: view)
|
||||
hostingController.vc_removeBackTitle()
|
||||
hostingController.enableNavigationBarScrollEdgesAppearance = true
|
||||
onboardingUseCaseHostingController = hostingController
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
MXLog.debug("[OnboardingUseCaseCoordinator] did start.")
|
||||
onboardingUseCaseViewModel.completion = { [weak self] result in
|
||||
MXLog.debug("[OnboardingUseCaseCoordinator] OnboardingUseCaseViewModel did complete with result: \(result).")
|
||||
guard let self = self else { return }
|
||||
self.completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.onboardingUseCaseHostingController
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/// Using an enum for the screen allows you define the different state cases with
|
||||
/// the relevant associated data for each case.
|
||||
@available(iOS 14.0, *)
|
||||
enum MockOnboardingUseCaseScreenState: MockScreenState, CaseIterable {
|
||||
// A case for each state you want to represent
|
||||
// with specific, minimal associated data that will allow you
|
||||
// mock that screen.
|
||||
case `default`
|
||||
|
||||
/// The associated screen
|
||||
var screenType: Any.Type {
|
||||
OnboardingUseCase.self
|
||||
}
|
||||
|
||||
/// A list of screen state definitions
|
||||
static var allCases: [MockOnboardingUseCaseScreenState] {
|
||||
// Each of the presence statuses
|
||||
[.default]
|
||||
}
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
var screenView: ([Any], AnyView) {
|
||||
let viewModel = OnboardingUseCaseViewModel()
|
||||
|
||||
// can simulate service and viewModel actions here if needs be.
|
||||
|
||||
return (
|
||||
[self, viewModel],
|
||||
AnyView(OnboardingUseCase(viewModel: viewModel.context)
|
||||
.addDependency(MockAvatarService.example))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
// MARK: View model
|
||||
|
||||
enum OnboardingUseCaseStateAction {
|
||||
case viewAction(OnboardingUseCaseViewAction)
|
||||
}
|
||||
|
||||
enum OnboardingUseCaseViewModelResult {
|
||||
case personalMessaging
|
||||
case workMessaging
|
||||
case communityMessaging
|
||||
case skipped
|
||||
case customServer
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct OnboardingUseCaseViewState: BindableState { }
|
||||
|
||||
enum OnboardingUseCaseViewAction {
|
||||
case answer(OnboardingUseCaseViewModelResult)
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias OnboardingUseCaseViewModelType = StateStoreViewModel<OnboardingUseCaseViewState,
|
||||
OnboardingUseCaseStateAction,
|
||||
OnboardingUseCaseViewAction>
|
||||
@available(iOS 14, *)
|
||||
class OnboardingUseCaseViewModel: OnboardingUseCaseViewModelType, OnboardingUseCaseViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: ((OnboardingUseCaseViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init() {
|
||||
super.init(initialViewState: OnboardingUseCaseViewState())
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: OnboardingUseCaseViewAction) {
|
||||
switch viewAction {
|
||||
case .answer(let result):
|
||||
completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
override class func reducer(state: inout OnboardingUseCaseViewState, action: OnboardingUseCaseStateAction) {
|
||||
// There is no mutable state to reduce :)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol OnboardingUseCaseViewModelProtocol {
|
||||
|
||||
var completion: ((OnboardingUseCaseViewModelResult) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
var context: OnboardingUseCaseViewModelType.Context { get }
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingUseCaseUITests: MockScreenTest {
|
||||
// The view has no parameters or changing state to test.
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingUseCaseViewModelTests: XCTestCase {
|
||||
// The view model has nothing to test.
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// The screen shown to a new user to select their use case for the app.
|
||||
struct OnboardingUseCase: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@ObservedObject var viewModel: OnboardingUseCaseViewModel.Context
|
||||
|
||||
/// The screen's title and instructions.
|
||||
var titleContent: some View {
|
||||
VStack(spacing: 8) {
|
||||
Image(Asset.Images.onboardingUseCaseIcon.name)
|
||||
.padding(.bottom, 8)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(VectorL10n.onboardingUseCaseTitle)
|
||||
.font(theme.fonts.title2B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
|
||||
Text(VectorL10n.onboardingUseCaseMessage)
|
||||
.font(theme.fonts.body)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
}
|
||||
}
|
||||
|
||||
/// The buttons used to select a use case for the app.
|
||||
var useCaseButtons: some View {
|
||||
VStack(spacing: 8) {
|
||||
OnboardingUseCaseButton(title: VectorL10n.onboardingUseCasePersonalMessaging,
|
||||
image: theme.isDark ? Asset.Images.onboardingUseCasePersonalDark : Asset.Images.onboardingUseCasePersonal) {
|
||||
viewModel.send(viewAction: .answer(.personalMessaging))
|
||||
}
|
||||
|
||||
OnboardingUseCaseButton(title: VectorL10n.onboardingUseCaseWorkMessaging,
|
||||
image: theme.isDark ? Asset.Images.onboardingUseCaseWorkDark : Asset.Images.onboardingUseCaseWork) {
|
||||
viewModel.send(viewAction: .answer(.workMessaging))
|
||||
}
|
||||
|
||||
OnboardingUseCaseButton(title: VectorL10n.onboardingUseCaseCommunityMessaging,
|
||||
image: theme.isDark ? Asset.Images.onboardingUseCaseCommunityDark : Asset.Images.onboardingUseCaseCommunity) {
|
||||
viewModel.send(viewAction: .answer(.communityMessaging))
|
||||
}
|
||||
|
||||
InlineTextButton(VectorL10n.onboardingUseCaseNotSureYet("%@"),
|
||||
tappableText: VectorL10n.onboardingUseCaseSkipButton) {
|
||||
viewModel.send(viewAction: .answer(.skipped))
|
||||
}
|
||||
.font(theme.fonts.subheadline)
|
||||
.foregroundColor(theme.colors.tertiaryContent)
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
/// A footer showing a button to connect to a server.
|
||||
var serverFooter: some View {
|
||||
VStack(spacing: 14) {
|
||||
Text(VectorL10n.onboardingUseCaseExistingServerMessage)
|
||||
.font(theme.fonts.subheadline)
|
||||
.foregroundColor(theme.colors.tertiaryContent)
|
||||
|
||||
Button { viewModel.send(viewAction: .answer(.customServer)) } label: {
|
||||
Text(VectorL10n.onboardingUseCaseExistingServerButton)
|
||||
.font(theme.fonts.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
titleContent
|
||||
.padding(.bottom, 36)
|
||||
|
||||
useCaseButtons
|
||||
}
|
||||
.frame(maxWidth: OnboardingConstants.maxContentWidth,
|
||||
maxHeight: OnboardingConstants.maxContentHeight)
|
||||
.padding(16)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
serverFooter
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 20 : 36)
|
||||
}
|
||||
}
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.accentColor(theme.colors.accent)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct OnboardingUseCase_Previews: PreviewProvider {
|
||||
static let stateRenderer = MockOnboardingUseCaseScreenState.stateRenderer
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
stateRenderer.screenGroup()
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// A button used for the Use Case selection.
|
||||
struct OnboardingUseCaseButton: View {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// The button's title.
|
||||
let title: String
|
||||
/// The button's image.
|
||||
let image: ImageAsset
|
||||
|
||||
/// The button's action when tapped.
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 16) {
|
||||
Image(image.name)
|
||||
Text(title)
|
||||
.font(theme.fonts.bodySB)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
}
|
||||
.padding(16)
|
||||
}
|
||||
.buttonStyle(OnboardingButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct Previews_OnboardingUseCaseButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
OnboardingUseCaseButton(title: VectorL10n.onboardingUseCaseWorkMessaging,
|
||||
image: Asset.Images.onboardingUseCaseWork,
|
||||
action: { })
|
||||
.padding(16)
|
||||
}
|
||||
}
|
102
RiotTests/OnboardingTests.swift
Normal file
|
@ -0,0 +1,102 @@
|
|||
//
|
||||
// Copyright 2021 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Riot
|
||||
|
||||
class OnboardingTests: XCTestCase {
|
||||
|
||||
let userId = "@test:matrix.org"
|
||||
|
||||
override func setUp() {
|
||||
// Clear any properties for the test
|
||||
UserSessionProperties(userId: userId).delete()
|
||||
}
|
||||
|
||||
func testEmptyUseCase() {
|
||||
// Given an empty set of user properties
|
||||
let properties = UserSessionProperties(userId: userId)
|
||||
|
||||
// Then the use case property should return nil
|
||||
XCTAssertNil(properties.useCase, "A use case has not been set")
|
||||
}
|
||||
|
||||
func testPersonalMessagingUseCase() {
|
||||
// Given an empty set of user properties
|
||||
let properties = UserSessionProperties(userId: userId)
|
||||
|
||||
// When storing a use case result of personal messaging
|
||||
let result = OnboardingUseCaseViewModelResult.personalMessaging
|
||||
properties.useCase = result.userSessionPropertyValue
|
||||
|
||||
// Then the use case property should return personal messaging
|
||||
XCTAssertEqual(properties.useCase, .personalMessaging, "The use case should be Personal Messaging")
|
||||
}
|
||||
|
||||
func testSkippedUseCase() {
|
||||
// Given an empty set of user properties
|
||||
let properties = UserSessionProperties(userId: userId)
|
||||
|
||||
// When storing a skipped use case result
|
||||
let result = OnboardingUseCaseViewModelResult.skipped
|
||||
properties.useCase = result.userSessionPropertyValue
|
||||
|
||||
// Then the use case property should return skipped
|
||||
XCTAssertEqual(properties.useCase, .skipped)
|
||||
}
|
||||
|
||||
func testCustomServerUseCase() {
|
||||
// Given an empty set of user properties
|
||||
let properties = UserSessionProperties(userId: userId)
|
||||
|
||||
// When storing a custom server case result
|
||||
let result = OnboardingUseCaseViewModelResult.customServer
|
||||
properties.useCase = result.userSessionPropertyValue
|
||||
|
||||
// Then the use case property should return nil
|
||||
XCTAssertNil(properties.useCase)
|
||||
}
|
||||
|
||||
func testUseCaseAfterDeletingProperties() {
|
||||
// Given a set of user properties with the Work Messaging use case
|
||||
let properties = UserSessionProperties(userId: userId)
|
||||
let result = OnboardingUseCaseViewModelResult.workMessaging
|
||||
properties.useCase = result.userSessionPropertyValue
|
||||
XCTAssertEqual(properties.useCase, .workMessaging, "The use case should be Work Messaging")
|
||||
|
||||
// When deleting the user properties
|
||||
properties.delete()
|
||||
|
||||
// Then the use case property should return nil
|
||||
XCTAssertNil(properties.useCase)
|
||||
}
|
||||
|
||||
func testUseCasePersistence() {
|
||||
// Given a set of user properties with the Personal Messaging use case
|
||||
var properties: UserSessionProperties? = UserSessionProperties(userId: userId)
|
||||
let result = OnboardingUseCaseViewModelResult.personalMessaging
|
||||
properties?.useCase = result.userSessionPropertyValue
|
||||
XCTAssertEqual(properties?.useCase, .personalMessaging, "The use case should be Personal Messaging")
|
||||
|
||||
// When the app is relaunched and a new user properties instance is creates
|
||||
properties = nil
|
||||
let newProperties = UserSessionProperties(userId: userId)
|
||||
|
||||
// Then the use case property should still return Personal Messaging
|
||||
XCTAssertEqual(newProperties.useCase, .personalMessaging, "The use case should be Personal Messaging")
|
||||
}
|
||||
|
||||
}
|
|
@ -52,6 +52,7 @@ targets:
|
|||
- path: ../Riot/Managers/Locale/LocaleProvider.swift
|
||||
- path: ../Riot/Modules/MatrixKit
|
||||
- path: ../Riot/Modules/Analytics
|
||||
- path: ../Riot/Managers/UserSessions
|
||||
- path: ../Riot/Managers/AppInfo/
|
||||
- path: ../Riot/Managers/Locale/LocaleProviderType.swift
|
||||
- path: ../Riot/Generated/Strings.swift
|
||||
|
|
1
changelog.d/5160.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add Onboarding Use Case selection screen after the splash screen.
|