From 6fe156447678b32f7627f8668379b6fb8fcbd420 Mon Sep 17 00:00:00 2001 From: service-tip-git Date: Tue, 2 Jun 2020 10:37:53 +0000 Subject: [PATCH 1/9] [INTERNAL] Translation delivery: commit by LX Lab Change-Id: Ic0905f66e03544c549ee6bf0edde3707c528215c --- .../src/main/res/values-2Q/strings.xml | 371 ++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 Corona-Warn-App/src/main/res/values-2Q/strings.xml diff --git a/Corona-Warn-App/src/main/res/values-2Q/strings.xml b/Corona-Warn-App/src/main/res/values-2Q/strings.xml new file mode 100644 index 00000000000..856415e325e --- /dev/null +++ b/Corona-Warn-App/src/main/res/values-2Q/strings.xml @@ -0,0 +1,371 @@ + + + + + "[[[şĥąŗēƌ_ρŗēƒēŗēŋċēş_ċŵą∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ŏŋƃŏąŗƌįŋğ_ċŏɱρĺēţēƌ∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ŗēşēţ_ąρρ∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ŏŋĺŷ_ŵįƒį∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ţŗąċįŋğ∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ɱ_ţįɱēşţąɱρ_ƌįąğŋŏşįş_ķēŷş_ƒēţċĥ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ɱ_ţįɱēşţąɱρ_ɱąŋűąĺ_ƌįąğŋŏşįş_ķēŷş_ŗēţŗįēʋąĺ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ɱ_şţŗįŋğ_ğŏŏğĺē_ąρį_ţŏķēŋ∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ŋŏţįƒįċąţįŏŋş_ēŋąƃĺēƌ∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ŋŏţįƒįċąţįŏŋş_ŗįşķ_ēŋąƃĺēƌ∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ŋŏţįƒįċąţįŏŋş_ţēşţ_ēŋąƃĺēƌ∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ƃąċķğŗŏűŋƌ_ĵŏƃ_ēŋąƃĺēƌ∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ɱŏƃįĺē_ƌąţą_ēŋąƃĺēƌ∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ɱ_ŗēğįşţŗąţįŏŋ_ţŏķēŋ∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_įŋįţįąĺ_ţŗąċįŋğ_ąċţįʋąţįŏŋ_ţįɱē∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_įŋįţįąĺ_ŗēşűĺţ_ŗēċēįʋēƌ_ţįɱē∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ŗįşķ_ĺēʋēĺ_şċŏŗē∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ɱ_ţēşţ_ğűįƌ∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ɱ_įş_ąĺĺŏŵēƌ_ţŏ_şűƃɱįţ_ƌįąğŋŏşįş_ķēŷş∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ɱ_ąűţĥ_ċŏƌē∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ƌąţąƃąşē_ρąşşŵŏŗƌ∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ţŏţąĺ_ŋŏŋ_ąċţįʋē_ţŗąċįŋğ∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ĺąşţ_ŋŏŋ_ąċţįʋē_ţŗąċįŋğ_ţįɱēşţąɱρ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ŋűɱƃēŗ_şűċċēşşƒűĺ_şűƃɱįşşįŏŋş∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ρŗēƒēŗēŋċē_ţēĺēţąŋ∙∙∙∙∙∙]]]" + + + "[[[Üƃēŗƃĺįċķ∙∙∙∙∙]]]" + "[[[Āρρ Ĭŋƒŏŗɱąţįŏŋēŋ∙∙∙∙∙∙∙]]]" + "[[[Ĕįŋşţēĺĺűŋğēŋ∙∙∙∙∙∙]]]" + "[[[Ţēįĺēŋ Ŝįē ƌįē Āρρ∙∙∙∙∙∙]]]" + + + "[[[ƌē.ŗķį.ċŏŗŏŋąŵąŗŋąρρ.ŋŏţįƒįċąţįŏŋ.ēχρŏşűŗēŃŏţįƒįċąţįŏŋĈĥąŋŋēĺĬƌ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[ĔχρŏşűŗēŃŏţįƒįċąţįŏŋ∙∙∙∙]]]" + "[[[Ńŏţįƒįēş űşēŗ ŏƒ ρŏţēŋţįąĺ ēχρŏşűŗē.∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[1∙∙∙]]]" + "[[[Řįşķ ŋŏţįƒįċąţįŏŋ ţįţĺē∙∙∙∙∙∙]]]" + "[[[Řįşķ ŋŏţįƒįċąţįŏŋ ċŏŋţēŋţ∙∙∙∙∙∙∙]]]" + + + + + "[[[Ɓįşĥēŗ ķēįŋē Řįşįķŏƃēğēğŋűŋğ∙∙∙∙∙∙∙∙]]]" + "[[[%1$s Řįşįķŏƃēğēğŋűŋğēŋ]]]" + "[[[%1$s ʋŏŋ 14 Ţąğē ρŗŏţŏķŏĺĺįēŗţ]]]" + "[[[Ķŏŋţąķţē ŵűŗƌēŋ ŋŏċĥ ŋįċĥţ üƃēŗρŗüƒţ.∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Āķţűąĺįşįēŗţ: %1$s]]]" + "[[[Ńäċĥşţē Āķţűąĺįşįēŗűŋğ įŋ: %1$s]]]" + "[[[Āķţűąĺįşįēŗēŋ∙∙∙∙∙∙]]]" + "[[[Āķţűąĺįşįēŗűŋğ įŋ %1$s]]]" + "[[[Řįşįķŏēŗɱįţţĺűŋğ ēįŋşċĥąĺţēŋ∙∙∙∙∙∙∙∙]]]" + "[[[Ŝċĥąĺţēŋ Ŝįē ƌįē Řįşįķŏēŗɱįţţĺűŋğ ēįŋ, űɱ Ĭĥŗ Řįşįķŏ žű ąķţűąĺįşįēŗēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + + "[[[Ńįēƌŗįğēş Řįşįķŏ∙∙∙∙∙∙∙∙]]]" + + "[[[Ĕŗĥöĥţēş Řįşįķŏ∙∙∙∙]]]" + "[[[%1$s Ţąğē şēįţ ƌēŗ ĺēţžţēŋ Ɓēğēğŋűŋğ]]]" + + "[[[Ůŋƃēķąŋŋţēş Řįşįķŏ∙∙∙∙∙∙]]]" + "[[[Ďą Ŝįē ƌįē Řįşįķŏ-Ĕŗɱįţţĺűŋğ ŋŏċĥ ŋįċĥţ ĺąŋğē ğēŋűğ ąķţįʋįēŗţ ĥąƃēŋ, ķŏŋŋţēŋ ŵįŗ ƒüŗ Ŝįē ķēįŋ Ĭŋƒēķţįŏŋşŗįşįķŏ ƃēŗēċĥŋēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + + "[[[Řįşįķŏēŗɱįţţĺűŋğ ğēşţŏρρţ∙∙∙∙∙∙∙]]]" + "[[[Ļēţžţē Ĕŗɱįţţĺűŋğ:\n%1$s]]]" + + + "[[[Ʋēŗąĺţēţēş Řįşįķŏ∙∙∙∙∙∙∙]]]" + "[[[Ďą Ŝįē ƌįē Řįşįķŏ-Ĕŗɱįţţĺűŋğ žű ĺąŋğē ŋįċĥţ ąķţįʋįēŗţ ĥąţţēŋ, įşţ Ĭĥŗ Ĭŋƒēķţįŏŋşŗįşįķŏ ʋēŗąĺţēţ. Ɓįţţē ąķţűąĺįşįēŗēŋ Ŝįē Ĭĥŗ Řįşįķŏ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + "[[[Ƥŗüƒűŋğ ĺäűƒţ∙∙∙∙∙∙]]]" + "[[[Ĕş ŵēŗƌēŋ ąķţűēĺĺē Ďąţēŋ ĥēŗűŋţēŗğēĺąƌēŋ űŋƌ ğēρŗüƒţ. Ďįēş ķąŋŋ ɱēĥŗēŗē Μįŋűţēŋ ƌąűēŗŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + "[[[Ĭĥŗ Ɓēƒűŋƌ:∙∙∙∙∙∙∙∙]]]" + "[[[ŜĀŘŜ-ĈŏƲ-2∙∙∙∙]]]" + "[[[Řēğįşţŗįēŗţ ąɱ %tF]]]" + "[[[Ńēğąţįʋ∙∙∙∙∙∙∙]]]" + "[[[Ƥŏşįţįʋ∙∙∙∙∙∙∙]]]" + "[[[Āűşŵēŗţűŋğ ŋįċĥţ ɱöğĺįċĥ∙∙∙∙∙∙]]]" + + "[[[Ďąţēŋąƃŗűƒ…∙∙∙∙∙∙∙∙]]]" + "[[[Ŵűŗƌēŋ Ŝįē ğēţēşţēţ?∙∙∙∙]]]" + "[[[Ĕŗğēƃŋįş ĺįēğţ ŋŏċĥ ŋįċĥţ ʋŏŗ∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĭĥŗ Ĕŗğēƃŋįş ĺįēğţ ʋŏŗ∙∙∙∙∙]]]" + "[[[Ďąş Ĕŗğēƃŋįş ŵįŗƌ ąķţűąĺįşįēŗţ∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĥēĺƒēŋ Ŝįē ɱįţ, ƌįē Ĭŋƒēķţįŏŋşķēţţē žű ƌűŗċĥƃŗēċĥēŋ űŋƌ ŵąŗŋēŋ Ŝįē Āŋƌēŗē.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ďįē Āűşŵēŗţűŋğ ƌąűēŗţ žŵįşċĥēŋ 1–3 Ţąğēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĭĥŗ ţēşţ ķöŋŋţē ŋįċĥţ ąűşğēŵēŗţēţ ŵēŗƌēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ŝįē ŵűŗƌēŋ ρŏşįţįʋ ąűƒ ŜĀŘŜ-ĈŏƲ-2 ğēţēşţēţ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ŝįē ŵűŗƌēŋ ŋēğąţįʋ ąűƒ ŜĀŘŜ-ĈŏƲ-2 ğēţēşţēţ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĭŋƒŏŗɱįēŗēŋ & ɱįţɱąċĥēŋ∙∙∙∙∙∙]]]" + "[[[Ĕŗğēƃŋįş ąŋžēįğēŋ∙∙∙∙∙∙∙]]]" + "[[[Ďēţąįĺş ąŋžēįğēŋ∙∙∙∙∙∙∙∙]]]" + + + "[[[Üƃēŗşįċĥţ∙∙∙∙∙]]]" + + "[[[Ĥäűƒįğē Ƒŗąğēŋ∙∙∙∙∙]]]" + "[[[Ƒįŋƌēŋ Ŝįē ʋēŗĺäşşĺįċĥē Āŋţŵŏŗţēŋ űŋƌ ķŏŋķŗēţē Ĭŋƒŏŗɱąţįŏŋēŋ, ŵįē Ŝįē şįċĥ şċĥüţžēŋ űŋƌ ąŋƌēŗēŋ ĥēĺƒēŋ ķöŋŋēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + + "[[[Ĉŏŗŏŋą-Ŵąŗŋ-Āρρ ţēįĺēŋ∙∙∙∙∙]]]" + "[[[Ģēɱēįŋşąɱ Ĉŏŗŏŋą ƃēķäɱρƒēŋ∙∙∙∙∙∙∙]]]" + "[[[Ĵē ɱēĥŗ Μēŋşċĥēŋ ɱįţɱąċĥēŋ, ƌēşţŏ şċĥŋēĺĺēŗ ƌűŗċĥƃŗēċĥēŋ ŵįŗ ƌįŗ Ĭŋƒēķţįŏŋşķēţţēŋ. Ļąƌēŋ Ŝįē Ƒąɱįĺįē, Ƒŗēűŋƌē űŋƌ Ɓēķąŋŋţē žűɱ Ďŏŵŋĺŏąƌ ēįŋ!∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ďŏŵŋĺŏąƌ-Ļįŋķ ʋēŗşēŋƌēŋ∙∙∙∙∙∙]]]" + "[[[Ĕŗžäĥĺē ēş ĵēƌēɱ!""\n"" ""#""žűşąɱɱēŋĎįēĶűŗʋēĀƃƒĺąċĥēŋ""\n"" Ƥĺąŷ Ŝţŏŗē (Āŋƌŗŏįƌ): ""ĥţţρş://ρĺąŷ.ğŏŏğĺē.ċŏɱ/""\n"" Āρρ Ŝţŏŗē (įŎŜ): ""ĥţţρş://ŵŵŵ.ąρρĺē.ċŏɱ/įŏş/ąρρ-şţŏŗē/""\n" + "[[[ŎĶ∙∙]]]" + + + + "[[[Ʋēŗĥąĺţēŋ Ŝįē şįċĥ ŗįċĥţįğ∙∙∙∙∙∙∙]]]" + "[[[Ɓĺēįƃēŋ Ŝįē žű Ĥąűşē∙∙∙∙]]]" + "[[[Ʋēŗɱēįƌēŋ Ŝįē şŏžįąĺēŋ Ķŏŋţąķţ∙∙∙∙∙∙∙∙∙]]]" + "[[[Ƒüŗ Ƒŗąğēŋ žű ąűƒţŗēţēŋƌēŋ Ŝŷɱρţŏɱēŋ, Ţēşţɱöğĺįċĥķēįţēŋ űŋƌ ŵēįţēŗēŋ Āƃşŏŋƌēŗűŋğşɱąßŋąĥɱēŋ ŵēŋƌēŋ Ŝįē şįċĥ ƃįţţē ąŋ:∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ŵąşċĥēŋ Ŝįē Ĭĥŗē Ĥäŋƌē∙∙∙∙∙]]]" + "[[[Ţŗąğēŋ Ŝįē ēįŋē Ŝċĥűţžɱąşķē∙∙∙∙∙∙∙∙]]]" + "[[[Ĥąĺţēŋ Ŝįē 2 Μēţēŗ Āƃşţąŋƌ žű ąŋƌēŗēŋ Ƥēŗşŏŋēŋ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĥűşţēŋ űŋƌ Ńįēşēŋ Ŝįē įŋ Ĭĥŗē Āŗɱƃēűğē∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[- Ĭĥŗēŋ Ĥąűşąŗţžţ/Ĭĥŗē Ĥąűşäŗţžţįŋ∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[- Ďēŋ Ķąşşēŋäŗţžĺįċĥēŋ Ńŏţƌįēŋşţ (Ţēĺ: 116117)∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[- Ĭĥŗ Ģēşűŋƌĥēįţşąɱţ∙∙∙∙]]]" + "[[[Ĭŋƒēķţįŏŋşŗįşįķŏ∙∙∙∙∙∙∙∙]]]" + "[[[Ŝŏ ŵįŗƌ Ĭĥŗ Řįşįķŏ ēŗɱįţţēĺţ∙∙∙∙∙∙∙∙]]]" + @string/risk_card_button_update + @string/risk_card_button_enable_tracing + + "[[[Ģēşűŋƌĥēįţşąɱţ ąŋŗűƒēŋ∙∙∙∙∙]]]" + + + "[[[Ŏŋƃŏąŗƌįŋğ∙∙∙∙]]]" + "[[[ĬŜ ΜĬŜŜĬŃĢ∙∙∙∙]]]" + "[[[Żűşţįɱɱēŋ∙∙∙∙∙]]]" + "[[[Ńįċĥţ ąķţįʋįēŗēŋ∙∙∙∙∙∙∙∙]]]" + "[[[Ŵēįţēŗ∙∙∙∙∙∙∙∙]]]" + "[[[Ļŏş Ģēĥţ\'ş∙∙∙∙]]]" + "[[[Żűŗüċķ∙∙∙∙∙∙∙∙]]]" + + "[[[Żűşąɱɱēŋ ğēğēŋ Ĉŏŗŏŋą∙∙∙∙∙]]]" + @string/lorem_ipsum + @string/lorem_ipsum + @string/lorem_ipsum + + "[[[Ďąţēŋşċĥűţž∙∙∙∙∙∙∙∙]]]" + "[[[Ʋēŗąŋţŵŏŗţĺįċĥē Ŝţēĺĺē įɱ Ŝįŋŋē ƌēş Āŗţ. 4 Āƃş. 7 ĎŜĢƲŎ: \n \n Řŏƃēŗţ Ķŏċĥ-Ĭŋşţįţűţ\nŃŏŗƌűƒēŗ 20\n13353 Ɓēŗĺįŋ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + @string/lorem_ipsum + + "[[[Ŵįē Ŝįē ƌįē Řįşįķŏēŗķēŋŋűŋğ ēŗɱöğĺįċĥēŋ∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + @string/lorem_ipsum + @string/lorem_ipsum + "[[[Ɓēŗēċĥţįğűŋğ∙∙∙∙∙∙∙]]]" + "[[[Ĉŏŗŏŋą-Ŵŗąŋ-Āρρ ķąŋŋ ƌąƌűŗċĥ ķēįŋē Ɓēŋąċĥŗįċĥţįğűŋğēŋ žű ĈŎƲĬĎ-19-Řįşįķŏşţąţűş ʋēŗşēŋƌēŋ űŋƌ ēɱρƒąŋğēŋ. Ŝįē ķöŋŋēŋ ƌįē Ƒűŋķţįŏŋ ĵēƌēŗžēįţ ēįŋşċĥąĺţēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ţŗŏţžƌēɱ ŵēįţēŗ∙∙∙∙]]]" + "[[[Żűŗüċķ∙∙∙∙∙∙∙∙]]]" + + "[[[Ŝįē şįŋƌ ρŏşįţįʋ ğēţēşţēţ?∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + @string/lorem_ipsum + "[[[Ůɱƒēĺƌąűƒŋąĥɱē ąķţįʋįēŗēŋ∙∙∙∙∙∙∙]]]" + + "[[[Ŵąŗŋűŋğēŋ ēŗĥąĺţēŋ, Řįşįķēŋ ķēŋŋēŋ∙∙∙∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + @string/lorem_ipsum + @string/lorem_ipsum + "[[[Ĕŗĺąűƃŋįş∙∙∙∙∙]]]" + "[[[Ďįē Ɓēŋąċĥŗįċĥţįğűŋğēŋ ƒüŗ ƌįē Ĉŏŗŏŋą-Ŵąŗŋ-Āρρ şįŋƌ ąűşğēşţēĺĺţ. Ůɱ Ŝįē ƃēžüğĺįċĥ Ĭĥŗēş Ŝţąţűş įɱɱēŗ ąűƒ ƌēɱ ŋēűşţēŋ Ŝţąŋƌ žű ĥąĺţēŋ ŵäŗē ēş ĥįĺƒŗēįċĥ ŵēŋŋ şįē įĥŗē Ɓēŋąċĥŗįċĥţįğűŋğēŋ ąķţįʋįēŗēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Żű ƌēŋ Ĕįŋşţēĺĺűŋğēŋ∙∙∙∙]]]" + "[[[Ţŗŏţžƌēɱ ŵēįţēŗ∙∙∙∙]]]" + + + "[[[Ĕįŋşţēĺĺűŋğēŋ∙∙∙∙∙∙]]]" + "[[[Āŋ∙∙]]]" + "[[[Āűş∙]]]" + + "[[[Řįşįķŏ-Ĕŗɱįţţĺűŋğ∙∙∙∙∙∙∙]]]" + "[[[Āķţįʋįēŗēŋ Ŝįē ƌįē Āűƒžēįċĥŋűŋğ Ĭĥŗēŗ Ɓēğēğŋűŋğēŋ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĕŗĺąűƃēŋ Ŝįē Ĕŗƒąşşűŋğ űŋƌ Ŵēįţēŗğąƃē ʋŏŋ Ĉŏʋįƌ-19-Żűƒąĺĺş-ĬĎş.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Řįşįķŏ-Ĕŗɱįţţĺűŋğ Āķţįʋ∙∙∙∙∙∙]]]" + "[[[Řįşįķŏ-Ĕŗɱįţţĺűŋğ Ģēşţŏρρţ∙∙∙∙∙∙∙]]]" + "[[[Řįşįķŏ-Ĕŗɱįţţĺűŋğ įşţ ąķţįʋ. Ĕįŋē ʋēŗĺäşşĺįċĥē Řįşįķŏρŗüƒűŋğ ķąŋŋ ŋűŗ ɱįţ ƌąűēŗĥąƒţ ąķţįʋįēŗţēŗ Řįşįķŏ-Ĕŗɱįţţĺűŋğ ƌűŗċĥğēƒüĥŗţ ŵēŗƌēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Řįşįķŏ-Ĕŗɱįţţĺűŋğ įşţ ğēşţŏρρţ. Ĕįŋē ʋēŗĺäşşĺįċĥē Řįşįķŏρŗüƒűŋğ ķąŋŋ ŋűŗ ɱįţ ƌąűēŗĥąƒţ ąķţįʋįēŗţēŗ Řįşįķŏ-Ĕŗɱįţţĺűŋğ ƌűŗċĥğēƒüĥŗţ ŵēŗƌēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + + "[[[Μįţţēįĺűŋğēŋ∙∙∙∙∙∙∙]]]" + "[[[Μöċĥţēŋ Ŝįē Μįţţēįĺűŋğēŋ žű Ĭĥŗēɱ Ĉŏʋįƌ-19-Řįşįķŏşţąţűş ąķţįʋįēŗēŋ?∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Μįţţēįĺűŋğēŋ şįŋƌ ƌēąķţįʋįēŗţ∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĕŗĺąűƃēŋ Ŝįē ąűţŏɱąţįşċĥē Μįţţēįĺűŋğēŋ žű Ĉŏʋįƌ-19-Řįşįķŏşţąţűş.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ļēğēŋ Ŝįē ƒēşţ, žű ŵēĺċĥēŋ Ţĥēɱēŋ Ŝįē įŋƒŏŗɱįēŗţ ƃēįƃēŋ ɱöċĥţēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ůɱ Μįţţēįĺűŋğēŋ žű ąķţįʋįēŗēŋ, ɱüşşēŋ Ŝįē Μįţţēįĺűŋğēŋ ƒüŗ ƌįē Ĉŏŗŏŋą-Ŵąŗŋ-Āρρ įŋ Ĭĥŗēŋ Ţēĺēƒŏŋēįŋşţēĺĺűŋğēŋ žűĺąşşēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + @string/settings_notifications_title + "[[[Äŋƌēŗŋ Ĭĥŗēş Ĭŋƒēķţįŏŋşŗįşįķŏş∙∙∙∙∙∙∙∙∙]]]" + "[[[Ŝţąţűş Ĭĥŗēş ĈŎƲĬĎ-19-Ţēşţş∙∙∙∙∙∙∙∙]]]" + "[[[Ůρƌąţē žű ĈŎƲĬĎ-19∙∙∙∙∙∙]]]" + "[[[Ńēűē Āρρ Ƒűŋķţįŏŋēŋ∙∙∙∙∙]]]" + "[[[Ĕįŋşţēĺĺűŋğēŋ öƒƒŋēŋ∙∙∙∙]]]" + + "[[[Āŋŵēŋƌűŋğ žűŗüċķşēţžēŋ∙∙∙∙∙]]]" + "[[[Ŝįŋƌ Ŝįē şįċĥēŗ, ƌąşş Ŝįē ƌįē Āŋŵēŋƌűŋğ žűŗüċķşēţžēŋ ŵŏĺĺēŋ?∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + @string/settings_reset_title + "[[[Āƃƃŗēċĥēŋ∙∙∙∙∙]]]" + + + "[[[Ĭŋƒŏŗɱąţįŏŋ∙∙∙∙∙∙∙∙]]]" + "[[[Üƃēŗ ƌįē Āρρ∙∙∙∙∙∙∙]]]" + "[[[Ģēɱēįŋşąɱ Ĉŏŗŏŋą ƃēķäɱρƒēŋ∙∙∙∙∙∙∙]]]" + "[[[Ďąţēŋşċĥűţžįŋƒŏŗɱąţįŏŋ∙∙∙∙∙]]]" + @string/lorem_ipsum + "[[[Ńűţžűŋğşƃēƌįŋğűŋğēŋ∙∙∙∙∙]]]" + @string/lorem_ipsum + "[[[Ķŏŋţąķţ∙∙∙∙∙∙∙]]]" + "[[[Ŵįē ķöŋŋēŋ ŵįŗ Ĭĥŋēŋ ĥēĺƒēŋ?∙∙∙∙∙∙∙∙]]]" + "[[[Ĥäűƒįğē Ƒŗąğēŋ∙∙∙∙∙]]]" + "[[[Řēċĥţĺįċĥē Ĥįŋŵēįşē∙∙∙∙∙]]]" + "[[[ƑŢƤ/ŜƑŢƤ/ƑŢƤŜ ċĺįēŋţ űşēş ĺįƃş∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĭɱρŗēşşűɱ∙∙∙∙∙]]]" + "[[[Ĥēŗąűşğēƃēŗ∙∙∙∙∙∙∙∙]]]" + "[[[ŜĀƤ Ďēűţşċĥĺąŋƌ ŜĔ & Ĉŏ. ĶĢ \n \nĤąűρţşįţž: \nŜĀƤ Ďēűţşċĥĺąŋƌ ŜĔ & Ĉŏ. ĶĢ \nĤąşşŏ-Ƥĺąţţŋēŗ-Řįŋğ 7 \n69190 Ŵąĺĺƌŏŗƒ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + + + "[[[Ţēşţēŗğēƃŋįş∙∙∙∙∙∙∙]]]" + "[[[Ĭŋƒŏ žűɱ Āƃĺąűƒ:∙∙∙∙∙∙∙∙]]]" + "[[[Āƃşċĥĺįēßēŋ∙∙∙∙∙∙∙∙]]]" + "[[[Ţēşţ ēŗƒŏĺğŗēįċĥ ĥįŋžűğēƒüğţ∙∙∙∙∙∙∙∙]]]" + "[[[Ĭĥŗ Ţēşţ ŵűŗƌē įŋ ƌēŗ Ĉŏŗŏŋą-Ŵąŗŋ-Āρρ ĥįŋţēŗĺēğţ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ţēşţēŗğēƃŋįş ŋŏċĥ ŋįċĥţ ʋēŗƒüğƃąŗ∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĭĥŗ Ţēşţēŗğēƃŋįş şţēĥţ ŋŏċĥ ŋįċĥţ žűŗ Ʋēŗƒüğűŋğ. Ńŏŗɱąĺēŗŵēįşē ƌąűēŗţ ƌįē Āűşŵēŗţűŋğ 1–3 Ţąğē.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Āķţűąĺįşįēŗēŋ∙∙∙∙∙∙]]]" + "[[[Ţēşţ ēŋţƒēŗŋēŋ∙∙∙∙∙]]]" + "[[[Ţēşţēŗğēƃŋįş Ńēğąţįʋ∙∙∙∙]]]" + "[[[Ĭĥŗ Ţēşţēŗğēƃŋįş ŵűŗƌē ēŗƒŏĺğŗēįċĥ ąĺş “Ńēğąţįʋ” ʋēŗįƒįžįēŗţ. Ɓįţţē ēŋţƒēŗŋēŋ Ŝįē ƌēŋ Ţēşţ ŵįēƌēŗ ąűş ƌēŗ Ĉŏŗŏŋą-Ŵąŗŋ-Āρρ, ƌąɱįţ Ŝįē ƃēį Ɓēƌąŗƒ ēįŋēŋ ŋēűēŋ Ţēşţ ĥįŋţēŗĺēğēŋ ķöŋŋēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ţēşţ ēŋţƒēŗŋēŋ∙∙∙∙∙]]]" + "[[[Ţēşţēŗğēƃŋįş Ƥŏşįţįʋ∙∙∙∙]]]" + "[[[Ĭĥŗ Ţēşţēŗğēƃŋįş ŵűŗƌē ēŗƒŏĺğŗēįċĥ ąĺş “Ƥŏşįţįʋ” ʋēŗįƒįžįēŗţ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Āŋƌēŗē ŵąŗŋēŋ∙∙∙∙∙∙]]]" + "[[[Ţēįĺēŋ Ŝįē Ĭĥŗē Żűƒąĺĺşķēŋŋűŋğēŋ, ƌąɱįţ ąŋƌēŗē ğēŵąŗŋţ ŵēŗƌēŋ ķöŋŋēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ŵēįţēŗ∙∙∙∙∙∙∙∙]]]" + "[[[Ţēşţ ƒēĥĺēŗĥąƒţ∙∙∙∙]]]" + "[[[Ĕş ğąƃ ēįŋ Ƥŗŏƃĺēɱ ƃēį ƌēŗ Āűşŵēŗţűŋğ Ĭĥŗēş Ţēşţş. Ɓįţţē ķŏŋţąķţįēŗēŋ Ŝįē ƌąş Ģēşűŋƌĥēįţşąɱţ űɱ Ĭŋƒŏŗɱąţįŏŋ žűɱ ŵēįţēŗēŋ Ʋŏŗğēĥēŋ žű ēŗĥąĺţēŋ. Ɓįţţē ēŋţƒēŗŋēŋ Ŝįē ƌēŋ Ţēşţ ŵįēƌēŗ ąűş ƌēŗ Ĉŏŗŏŋą-Ŵąŗŋ-Āρρ, ƌąɱįţ Ŝįē ƃēį Ɓēƌąŗƒ ēįŋēŋ ŋēűēŋ Ţēşţ ĥįŋţēŗĺēğēŋ ķöŋŋēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ţēşţ ēŋţƒēŗŋēŋ∙∙∙∙∙]]]" + + "[[[ŢĀŃ Ĕįŋğąƃē∙∙∙∙∙∙∙∙]]]" + "[[[Ģēƃēŋ Ŝįē ƃįţţē ƌįē ŢĀŃ Ńűɱɱēŗ įĥŗēş Ɓēƒűŋƌēş ēįŋ. Ďįē ŢĀŃ įşţ 7-şţēĺĺįğ űŋƌ Ģŗŏß- űŋƌ Ķĺēįŋşċĥŗēįƃűŋğ ɱűşş ŋįċĥţ ƃēąċĥţēţ ŵēŗƌēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ŵēįţēŗ∙∙∙∙∙∙∙∙]]]" + + "[[[Ŵűŗƌēŋ Ŝįē ğēţēşţēţ?∙∙∙∙]]]" + "[[[Ŝŏ ƒűŋķţįŏŋēŗţ ƌąş Ĉŏŗŏŋą-Ŵąŗŋ-Ŝŷşţēɱ∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ďąɱįţ ƌįē Ĉŏŗŏŋą-Ŵąŗŋ-Āρρ ƒűŋķţįŏŋįēŗţ, ŵįŗƌ ƌįē ƒŗēįŵįĺĺįğē Μįţĥįĺƒē ʋŏŋ ąĺĺēŋ Ĉŏŗŏŋą ρŏşįţįʋēŋ Ƥēŗşŏŋēŋ ƃēŋöţįğţ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + "[[[Ŵēįţēŗ∙∙∙∙∙∙∙∙]]]" + + "[[[Ƥŏşįţįʋē Ďįąğŋŏşē∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + "[[[Āƃşċĥĺįēşşēŋ∙∙∙∙∙∙∙]]]" + + "[[[Ďąŋķē ƒüŗ ƌįē Μįţĥįĺƒē!∙∙∙∙∙∙]]]" + "[[[Ĭĥŗ ʋēŗįƒįžįēŗţēŗ Ɓēƒűŋƌ ŵűŗƌē ąŋŏŋŷɱ üƃēŗţŗąğēŋ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + "[[[Āűşŵąĥĺ∙∙∙∙∙∙∙]]]" + + "[[[Ŵēĺċĥē Ĭŋƒŏŗɱąţįŏŋēŋ ĺįēğēŋ Ĭĥŋēŋ ʋŏŗ?∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ďŏķűɱēŋţ ɱįţ ǬŘ-Ĉŏƌē∙∙∙∙]]]" + "[[[Ʋēŗįƒįžįēŗēŋ Ŝįē Ĭĥŗēŋ Ţēşţ, įŋƌēɱ Ŝįē ƌēŋ ǬŘ-Ĉŏƌē įĥŗēş Ţēşţ-Ďŏķűɱēŋţ şċąŋŋēŋ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + "[[[ŢĀŃ-Ĉŏƌē∙∙∙∙∙∙]]]" + "[[[Ʋēŗįƒįžįēŗēŋ Ŝįē Ĭĥŗēŋ Ţēşţ ρēŗ ɱąŋűēķķēŗ ŢĀŃ Ĕįŋğąƃē∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + "[[[ŢĀŃ ţēĺēƒŏŋįşċĥ ąŋƒŗąğēŋ∙∙∙∙∙∙]]]" + "[[[Ɓįţţē ŗűƒēŋ Ŝįē űŋş ąŋ, ƒąĺĺş Ŝįē Ƥŏşįţįʋ ğēţēşţēţ ŵűŗƌēŋ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + + "[[[Āŋƌēŗē ŵąŗŋēŋ∙∙∙∙∙∙]]]" + "[[[Ŝŏ ğēĥţ ēş ŵēįţēŗ:∙∙∙∙∙∙]]]" + "[[[Ļŏŗēɱ įρşűɱ ƌŏĺŏŗ şįţ ąɱēţ, ċŏŋşēċţēţűŗ ąƌįρįşċįŋğ ēĺįţ, şēƌ ƌŏ ēįűşɱŏƌ ţēɱρŏŗ įŋċįƌįƌűŋţ űţ ĺąƃŏŗē ēţ ƌŏĺŏŗē ɱąğŋą ąĺįƣűą. Ůţ ēŋįɱ ąƌ ɱįŋįɱ ʋēŋįąɱ, ƣűįş ŋŏşţŗűƌ ēχēŗċįţąţįŏŋ űĺĺąɱċŏ ĺąƃŏŗįş ŋįşį űţ ąĺįƣűįρ ēχ ēą ċŏɱɱŏƌŏ ċŏŋşēƣűąţ. \n\nĻŏŗēɱ įρşűɱ ƌŏĺŏŗ şįţ ąɱēţ, ċŏŋşēċţēţűŗ ąƌįρįşċįŋğ ēĺįţ, şēƌ ƌŏ ēįűşɱŏƌ ţēɱρŏŗ įŋċįƌįƌűŋţ űţ ĺąƃŏŗē ēţ ƌŏĺŏŗē ɱąğŋą ąĺįƣűą. Ůţ ēŋįɱ ąƌ ɱįŋįɱ ʋēŋįąɱ, ƣűįş ŋŏşţŗűƌ ēχēŗċįţąţįŏŋ űĺĺąɱċŏ ĺąƃŏŗįş ŋįşį űţ ąĺįƣűįρ ēχ ēą ċŏɱɱŏƌŏ ċŏŋşēƣűąţ. \n\nĎűįş ąűţē įŗűŗē ƌŏĺŏŗ įŋ ŗēρŗēĥēŋƌēŗįţ įŋ ʋŏĺűρţąţē ʋēĺįţ ēşşē ċįĺĺűɱ ƌŏĺŏŗē ēű ƒűğįąţ ŋűĺĺą ρąŗįąţűŗ. Ĕχċēρţēűŗ şįŋţ ŏċċąēċąţ ċűρįƌąţąţ ŋŏŋ ρŗŏįƌēŋţ, şűŋţ įŋ ċűĺρą ƣűį ŏƒƒįċįą ƌēşēŗűŋţ ɱŏĺĺįţ ąŋįɱ įƌ ēşţ ĺąƃŏŗűɱ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[+Ďąţą-Ŝēċűŗįţŷ Ţēχţ∙∙∙∙∙]]]" + "[[[Ŵēįţēŗ∙∙∙∙∙∙∙∙]]]" + "[[[Żűƒąĺĺş-ĬĎş ɱįţ ƌēŗ Āρρ Ĉŏŗŏŋą-Ŵąŗŋ ţēįĺēŋ?∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ļŏŗēɱ įρşűɱ ƌŏĺŏŗ şįţ ąɱēţ, ċŏŋşēċţēţűŗ ąƌįρįşċįŋğ ēĺįţ, şēƌ ƌŏ ēįűşɱŏƌ ţēɱρŏŗ įŋċįƌįƌűŋţ űţ ĺąƃŏŗē ēţ ƌŏĺŏŗē ɱąğŋą ąĺįƣűą. Ůţ ēŋįɱ ąƌ ɱįŋįɱ ʋēŋįąɱ, ƣűįş ŋŏşţŗűƌ ēχēŗċįţąţįŏŋ űĺĺąɱċŏ ĺąƃŏŗįş ŋįşį űţ ąĺįƣűįρ ēχ ēą ċŏɱɱŏƌŏ ċŏŋşēƣűąţ. Ďűįş ąűţē įŗűŗē ƌŏĺŏŗ įŋ ŗēρŗēĥēŋƌēŗįţ įŋ ʋŏĺűρţąţē ʋēĺįţ ēşşē ċįĺĺűɱ ƌŏĺŏŗē ēű ƒűğįąţ ŋűĺĺą ρąŗįąţűŗ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ĕŗĺąűƃēŋ∙∙∙∙∙∙]]]" + "[[[Ńįċĥţ ēŗĺąűƃēŋ∙∙∙∙∙]]]" + + + + "[[[Ļŏŗēɱ įρşűɱ ƌŏĺŏŗ şįţ ąɱēţ, ċŏŋşēċţēţűŗ ąƌįρįşċįŋğ ēĺįţ, şēƌ ƌŏ ēįűşɱŏƌ ţēɱρŏŗ įŋċįƌįƌűŋţ űţ ĺąƃŏŗē ēţ ƌŏĺŏŗē ɱąğŋą ąĺįƣűą. Ůţ ēŋįɱ ąƌ ɱįŋįɱ ʋēŋįąɱ, ƣűįş ŋŏşţŗűƌ ēχēŗċįţąţįŏŋ űĺĺąɱċŏ ĺąƃŏŗįş ŋįşį űţ ąĺįƣűįρ ēχ ēą ċŏɱɱŏƌŏ ċŏŋşēƣűąţ. Ďűįş ąűţē įŗűŗē ƌŏĺŏŗ įŋ ŗēρŗēĥēŋƌēŗįţ įŋ ʋŏĺűρţąţē ʋēĺįţ ēşşē ċįĺĺűɱ ƌŏĺŏŗē ēű ƒűğįąţ ŋűĺĺą ρąŗįąţűŗ. Ĕχċēρţēűŗ şįŋţ ŏċċąēċąţ ċűρįƌąţąţ ŋŏŋ ρŗŏįƌēŋţ, şűŋţ įŋ ċűĺρą ƣűį ŏƒƒįċįą ƌēşēŗűŋţ ɱŏĺĺįţ ąŋįɱ įƌ ēşţ ĺąƃŏŗűɱ.∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + "[[[Ţēşţ ĀƤĬ∙∙∙∙∙∙]]]" + "[[[Ţēşţ Ńŏţįƒįċąţįŏŋ∙∙∙∙∙∙∙]]]" + "[[[Āŋƌŗŏįƌ ĀƤĬ Ţēşţ(Μąŋűąĺ Ţēşţ)∙∙∙∙∙∙∙∙∙]]]" + "[[[Ŝţąŗţ (Ɓŗŏąƌċąşţ/Řēċēįʋē Ɓĺűēţŏŏţĥ)∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ģēţ Ĕχρŏşűŗē ķēŷş (ɱŷ ķēŷş ĥįşţŏŗŷ ƒŗŏɱ ąρį)∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + "[[[Ŝĥąŗē ɱŷ ķēŷş ʋįą Ĕɱąįĺ∙∙∙∙∙∙]]]" + "[[[Ŝűƃɱįţ Ĕχρŏşűŗē Ķēŷ∙∙∙∙∙]]]" + "[[[Ŝĥŏŵ ǬŘ Ĉŏƌē∙∙∙∙∙∙∙]]]" + "[[[Ŝűƃɱįţ ķēŷş ţŏ Ŝēŗʋēŗ∙∙∙∙∙]]]" + "[[[Ŝċąŋ Ĕχρŏşűŗē Ķēŷ∙∙∙∙∙∙∙]]]" + "[[[Ĉĥēċķ Ĕχρŏşűŗē Ŝűɱɱąŗŷ∙∙∙∙∙]]]" + "[[[Ĕχρŏşűŗē şűɱɱąŗŷ∙∙∙∙∙∙∙∙]]]" + "[[[Ďąŷş şįŋċē ĺąşţ ēχρŏşűŗē: %1$s]]]" + "[[[Āţţēŋűąţįŏŋ Ďűŗąţįŏŋş įŋ Μįŋűţēş: %1$s]]]" + "[[[Ŝűɱɱąţįŏŋ Řįşķ Ŝċŏŗē: %1$s]]]" + "[[[Μąţċĥēƌ ķēŷ ċŏűŋţ: %1$s]]]" + "[[[Μąχįɱűɱ ŗįşķ şċŏŗē %1$s]]]" + "[[[Μŷ ķēŷş (ċŏűŋţ: %1$d)]]]" + "[[[Ŏţĥēŗ ķēŷ∙∙∙∙∙]]]" + "[[[Ĉąĺċűĺąţē Řįşķ Ļēʋēĺ∙∙∙∙]]]" + + + + + "[[[Ķąɱēŗą Ɓēŗēċĥţįğűŋğ ƃēŋöţįğţ∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + "[[[Ĕŗĺąűƃēŋ∙∙∙∙∙∙]]]" + "[[[Ńįċĥţ ēŗĺąűƃēŋ∙∙∙∙∙]]]" + + "[[[Ķēįŋē Ķąɱēŗą Ɓēŗēċĥţįğűŋğ∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + "[[[Żűŗüċķ∙∙∙∙∙∙∙∙]]]" + + "[[[Ŝċąŋ ēŗƒŏĺğŗēįċĥ∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + "[[[Řēğįşţŗįēŗēŋ∙∙∙∙∙∙∙]]]" + "[[[Āƃƃŗēċĥēŋ∙∙∙∙∙]]]" + + "[[[Ƒąĺşċĥēŗ ǬŘ Ĉŏƌē∙∙∙∙∙∙∙∙]]]" + @string/lorem_ipsum + "[[[Ĕŗŋēűţ ʋēŗşűċĥēŋ∙∙∙∙∙∙∙∙]]]" + "[[[Āƃƃŗēċĥēŋ∙∙∙∙∙]]]" + + "[[[ǬŘ Ĉŏƌē Ŝċąŋŋēŋ∙∙∙∙]]]" + "[[[Ƥŏşįţįŏŋįēŗēŋ Ŝįē ƌēŋ ǬŘ Ĉŏƌē įŋ ƌēŋ Řąĥɱēŋ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙]]]" + + "[[[Ģēŗäţ Řēğįşţŗįēŗēŋ∙∙∙∙∙∙]]]" + @string/lorem_ipsum + + \ No newline at end of file From b648263134b0671b3a2460820e43ec1b9f3519c5 Mon Sep 17 00:00:00 2001 From: harambasicluka <64483219+harambasicluka@users.noreply.github.com> Date: Wed, 3 Jun 2020 12:16:53 +0200 Subject: [PATCH 2/9] Translation branch update (#127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement submission done fragment (#20) * Added submission done fragment (currently dangling in nav graph) * fixed leftover javadoc * Moved QR code scan and registration fragment to submission package (#21) * Dependency Updates and Gradle Fixes (#34) * Bump up Gradle to 4.0.0 / 6.1.1 * Bump up NDK * Bump Up SQLite * adapt circle config * Fix #23: Typo in onboarding screen (#31) Co-authored-by: Jakob Möller * Submission UI Additions (#25) * Moved camera permission check to dispatcher fragment * Added illustrations for test result screen * Updated result fragment to match new design * Added constraints to status card * Integrated submission result positive other warning fragment in ui flow * Added submission done fragment to nav graph Signed-off-by: Kolya Opahle * Added ability to dialog helper to create dialog without negative button * Switched from AlertDialog.Builder to DIalogHelper * Enable diagnosis key upload to backend (#35) * Moved camera permission check to dispatcher fragment * Added illustrations for test result screen * Updated result fragment to match new design * Added constraints to status card * Integrated submission result positive other warning fragment in ui flow * add TAN fetching to the key submission transaction * remove unneeded function * Fix log message for fetching TAN * Added submission done fragment to nav graph Signed-off-by: Kolya Opahle * Added ability to dialog helper to create dialog without negative button * Switched from AlertDialog.Builder to DIalogHelper * use actual TAN (authCode) for key submission set the TAN in the corresponding header field fixes #8 * request permission to retrieve diagnosis keys * permission needs to be requested again after dialog has been presented to the users * trigger transaction to retrieve TAN and upload keys Co-authored-by: Kolya Opahle * Removed ExposureNotificationIntentService, the functionality is handled by NotificationHelper (#40) * Fixing tele tan registration (#45) * Fixed teleTAN based device registration Signed-off-by: Kolya Opahle * allowed excluded tan chars for testing Signed-off-by: Kolya Opahle * Revert "allowed excluded tan chars for testing" This reverts commit 253ede4ea9c8ba2b5e6dd324680eed0f8f7e74c1. * Release/0.5.5 (#48) * Release/0.5.5 * Hotfix for resetting Gradle Version (internal pipeline confirmation) (#52) * Gradle Reset to 0.5.4 (#53) * Release/0.5.5 (#48) * Hotfix for resetting Gradle Version (internal pipeline confirmation) (#52) * Minify and Shrink Disabled (#56) * Release/0.5.5 * Gradle Reset to 0.5.4 (#53) * Release/0.5.5 (#48) * Hotfix for resetting Gradle Version (internal pipeline confirmation) (#52) * Disable minify / shrink * Dont obfuscate * 0.5.6 * Update known issues and fixing typo in readme (#44) * added strings wip to known issues * updated readme Co-authored-by: Muschko * Updated strings, introduced more details on app-information screens (#88) * updated strings for risk card and risk card details; plural strings are todo when finalized * updated more strings on main / details * added comments * added strings for information about, information technical hotline; changed technical hotline layout * removed hotline test icon * fixed build issues * Cleanup pull_request_template.md (#54) * Update pull_request_template.md * Removed Link to Contribution Guidelines (added by Github on the right hand side, anyhow) * Removed Link to issue #41 from title example (this has been linked quite a lot...) * 0.5.6 * Fix typo Co-authored-by: Jakob Möller Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> Co-authored-by: marcmuschko * Add Support for lower case characters in the teleTAN screen (#66) * added support for lower case input in teleTan fragment Signed-off-by: Kolya Opahle * Removed notice of case insensitivity from temporary strings.xml Signed-off-by: Kolya Opahle Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> * Correct typos in architecture overview (#67) Co-authored-by: marcmuschko * Bluetooth & connection card (#97) * introduced settings navigation helper * added connection and bluetooth stati to main and tracing settings * moved strings * removed unnecessary backgroundTint Co-authored-by: marcmuschko * Fix typo in class documentation (#71) * 0.5.6 * fix typo Co-authored-by: Jakob Möller Co-authored-by: marcmuschko * spelling fixes (#87) * 0.5.6 * spelling fixes Co-authored-by: Jakob Möller Co-authored-by: klemens Co-authored-by: marcmuschko * Moves creation of notification channel to app startup (#32) Co-authored-by: Jakob Möller * Tracing start/stop fix due to missing handling inside tracing (#99) Co-authored-by: Jakob Möller * Switch the DB Password to use the KeyStore MasterKey (#98) Signed-off-by: d067928 * Shared prefs ktx shorthand (#38) * closes #33 * Add Commit Flag to ensure synchronous consistent Updates to Disk Signed-off-by: d067928 * Add persistance of timestamp and whether user is allowed to submit keys (#74) * Store timestamp of receiving registration token * also reset this timestamp when deleting the registration token * use correct timestamp for display of test time * replace tracing activation time by test result reception time * rename TAN request to match context * set isAllowedToSubmitDiagnosisKeys properly Co-authored-by: Jakob Möller * set "enabled" for include_settings_switch_row in notifications settings, fixes wrong binding and removed clickable rows (#101) Co-authored-by: marcmuschko * fix #82 (#94) * fix #82 Co-authored-by: Jakob Möller * Add ShareHelper (#85) * Add ShareHelper * Use ShareHelper in MainShareFragment * Replace the current exposure status with a positive result card when test result is positive (#79) * Made submission status card style more cross device friendly Signed-off-by: Kolya Opahle * Added the positive result submission status card Signed-off-by: Kolya Opahle * lint fix * Updated main fragment documentation * Added icon for sharing risk status Signed-off-by: Kolya Opahle * Updated positive result home screen card text and icons Signed-off-by: Kolya Opahle * Added loading indicator to test result fragment (#100) Co-authored-by: marcmuschko Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> * Adjusted onboarding flow, adjusted contribution file, small fixes in settings (#106) * changed permission dialog logic for onboarding tracing fragment and onboarding notification fragment * adjusted contributing file * added settings notification handling to display value in settings overview * fixed app crash during bluetooth settings navigation * corrected wrong value from view model in tracing settings text * Submission Contact Fragment & StepView Custom View (#102) * - submission-contact fragment added - StepEntry custom view added to unify various step entry views - SimpleStepEntry as a simple implementation of a StepEntry with title and text added * - apply naming conventions for ids - use CallHelper to trigger a call - access views using kotlinx.android.synthetic Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> * Notifications when risk value has changed between low and high. (#76) * Notifications when risk value has changed between low and high. * Notifications when risk value has changed between low and high. Fixed wildcard imports. * Fixed formatting, ktlint issue. * app foreground/background checking. Notifications are not sent when app is in background * update comments * initialize foreground flag as false(valid for background threads before ui starts) * comments * included UNDERTERMINED as a low, for initialisation * fixed icon * fixed tests Co-authored-by: Jakob Möller * added onboarding strings (#109) * disable colored output for ktlint (#105) Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> Co-authored-by: Jakob Möller * Limit number of keys to be uploaded to server (#107) * limit the number of keys to be uploaded to 14 * fix spaces Co-authored-by: Jakob Möller * Fix white space, informal language and spelling of Docker in Readme (#78) * 0.5.6 * Fix spelling gonna and docker * Add missing "to" Co-authored-by: Jakob Möller Co-authored-by: marcmuschko * Update build.gradle (#111) * Build.Gradle Conflict Resolving (0.8.0) (#112) * Minify and proguard fix (#113) Commented out -dontobfuscate * Circle progress bar (#96) * circle progress bar impl * ktlint format * added new ui * added circle to contact tracing without binding * added binding and final icon * added circle progress to risk card * added comments * changed default value for disableText * adapted views order to designs * removed testing value Co-authored-by: Kirill * TracingStatusHelper unit test (#114) * TracingStatusHelper unit test * removed duplicate * Prevent screenshot of the app for all activities (#108) * Prevent screenshot of the app for all activities * fix ktlint * code smell fix Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> * Feature: Bluetooth callback (#115) * bluetooth callback added * bound bluetooth callback to the ui Co-authored-by: Kirill * Added main overview page to main menu (#120) * added overview fragment baseline * main overview draft version completed with build fixes * checked some final strings again * Fixed screen orientation to portrait (#121) * added restriction to portrait and reversed portrait orientation only * suppress SourceLockedOrientationActivity lint error * changed local config that a high match can be generated via QR code, integrated it with the UI (#125) * Feature: Translation preparation (#126) * renamed old strings and reordered * app information strings translation tags * updated translation tags for long texts * added translation comments to strings * fixed naming error * removed empty comment Co-authored-by: Muschko Co-authored-by: Kolya Opahle Co-authored-by: Jakob Möller Co-authored-by: Michael Keppler Co-authored-by: Thomas Klingbeil <64434904+tklingbeil@users.noreply.github.com> Co-authored-by: Hee Tatt Ooi <64406309+HeeTattSap@users.noreply.github.com> Co-authored-by: Muschko Co-authored-by: Thomas Kowark Co-authored-by: Robert Scheck Co-authored-by: Volkmar Vogel Co-authored-by: ka7 Co-authored-by: klemens Co-authored-by: Tim Brüggenthies Co-authored-by: mseele Co-authored-by: Janik Steegmüller Co-authored-by: Fabian-K Co-authored-by: Hee Tatt Ooi Co-authored-by: Matthias Küch Co-authored-by: oemerb <66002424+oemerb@users.noreply.github.com> Co-authored-by: AlexanderAlferov <64849422+AlexanderAlferov@users.noreply.github.com> Co-authored-by: Kirill Co-authored-by: Philipp Woessner <64482866+pwoessner@users.noreply.github.com> --- .circleci/config.yml | 7 +- .github/pull_request_template.md | 5 +- CONTRIBUTING.md | 2 +- Corona-Warn-App/build.gradle | 9 +- Corona-Warn-App/proguard-rules.pro | 7 +- .../coronawarnapp/ExampleInstrumentedTest.kt | 22 - Corona-Warn-App/src/main/AndroidManifest.xml | 7 - .../coronawarnapp/CoronaWarnApplication.kt | 79 +- .../rki/coronawarnapp/TestForAPIFragment.kt | 50 +- .../RiskLevelCalculationException.kt | 2 +- .../coronawarnapp/http/WebRequestBuilder.kt | 8 +- .../http/request/KeySubmissionRequest.kt | 2 +- .../http/request/RegistrationTokenRequest.kt | 14 +- .../ExposureNotificationIntentService.kt | 51 -- .../notification/NotificationConstants.kt | 2 +- .../notification/NotificationHelper.kt | 29 +- .../de/rki/coronawarnapp/risk/RiskLevel.kt | 36 + .../service/submission/SubmissionConstants.kt | 2 +- .../service/submission/SubmissionService.kt | 22 +- .../rki/coronawarnapp/storage/AppDatabase.kt | 7 +- .../de/rki/coronawarnapp/storage/LocalData.kt | 77 +- .../storage/SettingsRepository.kt | 4 +- .../storage/SubmissionRepository.kt | 6 +- .../tracing/TracingIntervalRepository.kt | 2 +- .../transaction/RiskLevelTransaction.kt | 11 + .../SubmitDiagnosisKeysTransaction.kt | 24 +- .../de/rki/coronawarnapp/ui/UiConstants.kt | 3 + .../information/InformationContactFragment.kt | 5 + .../rki/coronawarnapp/ui/main/MainActivity.kt | 42 +- .../rki/coronawarnapp/ui/main/MainFragment.kt | 26 +- .../ui/main/MainOverviewFragment.kt | 44 + .../ui/main/MainShareFragment.kt | 14 +- .../OnboardingNotificationsFragment.kt | 63 +- .../onboarding/OnboardingTracingFragment.kt | 4 +- .../ui/register/ApiRequestState.kt | 5 - .../ui/register/RegisterQRCodeScanFragment.kt | 222 ------ .../settings/SettingsNotificationFragment.kt | 28 +- .../ui/settings/SettingsTracingFragment.kt | 67 +- ...TestResultStatus.kt => ApiRequestState.kt} | 2 +- .../ui/{register => submission}/ScanStatus.kt | 2 +- .../submission/SubmissionContactFragment.kt | 51 ++ .../SubmissionDispatcherFragment.kt | 83 +- .../ui/submission/SubmissionDoneFragment.kt | 44 + .../SubmissionQRCodeScanFragment.kt | 128 +++ .../SubmissionRegisterDeviceFragment.kt} | 13 +- ...ssionResultPositiveOtherWarningFragment.kt | 75 +- .../SubmissionSuccessDialogFragment.kt | 2 +- .../ui/submission/SubmissionTanFragment.kt | 2 +- .../ui/submission/SubmissionTanViewModel.kt | 6 +- .../SubmissionTestResultFragment.kt | 5 +- .../coronawarnapp/ui/submission/TanInput.kt | 3 +- .../coronawarnapp/ui/view/CircleProgress.kt | 143 ++++ .../coronawarnapp/ui/view/SimpleStepEntry.kt | 29 + .../de/rki/coronawarnapp/ui/view/StepEntry.kt | 51 ++ .../ui/viewmodel/SettingsViewModel.kt | 8 +- .../ui/viewmodel/SubmissionViewModel.kt | 9 +- .../de/rki/coronawarnapp/util/CallHelper.kt | 20 + .../coronawarnapp/util/ConnectivityHelper.kt | 121 ++- .../coronawarnapp/util/DataRetentionHelper.kt | 2 - .../de/rki/coronawarnapp/util/DialogHelper.kt | 12 +- .../util/ProtoFormatConverterExtensions.kt | 4 + .../util/SettingsNavigationHelper.kt | 49 ++ .../de/rki/coronawarnapp/util/ShareHelper.kt | 14 + .../util/formatter/FormatterRiskHelper.kt | 36 +- .../util/formatter/FormatterSettingsHelper.kt | 243 +++++- .../formatter/FormatterSubmissionHelper.kt | 54 +- .../util/formatter/TracingStatusHelper.kt | 47 ++ .../util/security/SecurityHelper.kt | 22 + .../ic_main_illustration_overview.xml | 116 +++ .../main/res/drawable/ic_main_overview_1.xml | 18 + .../main/res/drawable/ic_main_overview_2.xml | 18 + .../main/res/drawable/ic_main_overview_3.xml | 18 + .../res/drawable/ic_risk_card_saved_days.xml | 12 + .../drawable/ic_risk_card_saved_days_14.xml | 2 +- .../res/drawable/ic_risk_details_share.xml | 12 + ...ic_settings_illustration_bluetooth_off.xml | 207 +++++ ...c_settings_illustration_connection_off.xml | 201 +++++ ..._on.xml => ic_settings_tracing_active.xml} | 0 .../ic_settings_tracing_bluetooth.xml | 12 + ...ic_settings_tracing_bluetooth_inactive.xml | 12 + .../ic_settings_tracing_connection.xml | 12 + ...c_settings_tracing_connection_inactive.xml | 12 + ...f.xml => ic_settings_tracing_inactive.xml} | 0 .../src/main/res/drawable/ic_step_1.xml | 6 + .../src/main/res/drawable/ic_step_2.xml | 6 + .../main/res/drawable/ic_step_background.xml | 19 + .../ic_submission_illustration_thanks.xml | 750 ++++++++++++++++++ .../ic_test_result_illustration_invalid.xml | 123 +++ .../ic_test_result_illustration_negative.xml | 132 +++ .../ic_test_result_illustration_pending.xml | 81 ++ .../ic_test_result_illustration_positive.xml | 120 +++ .../res/layout/fragment_information_about.xml | 23 +- .../layout/fragment_information_contact.xml | 48 +- .../src/main/res/layout/fragment_main.xml | 25 +- .../res/layout/fragment_main_overview.xml | 213 +++++ .../fragment_onboarding_notifications.xml | 18 +- .../res/layout/fragment_onboarding_test.xml | 1 - .../main/res/layout/fragment_risk_details.xml | 37 +- .../src/main/res/layout/fragment_settings.xml | 11 +- .../fragment_settings_notifications.xml | 14 +- .../res/layout/fragment_settings_tracing.xml | 97 ++- .../layout/fragment_submission_contact.xml | 72 ++ .../res/layout/fragment_submission_dialog.xml | 12 +- .../layout/fragment_submission_dispatcher.xml | 3 +- .../res/layout/fragment_submission_done.xml | 107 +++ ...ment_submission_positive_other_warning.xml | 2 +- ...l => fragment_submission_qr_code_scan.xml} | 58 +- ...> fragment_submission_register_device.xml} | 40 +- .../fragment_submission_test_result.xml | 176 ++-- .../res/layout/include_dispatcher_card.xml | 2 +- .../layout/include_information_details.xml | 2 +- .../layout/include_main_overview_glossary.xml | 62 ++ .../layout/include_main_overview_segment.xml | 75 ++ ...ion_row.xml => include_navigation_row.xml} | 37 +- .../main/res/layout/include_onboarding.xml | 5 +- .../src/main/res/layout/include_risk_card.xml | 52 +- .../layout/include_risk_details_behavior.xml | 8 +- ...e_risk_details_behavior_increased_risk.xml | 6 +- .../include_risk_details_behavior_row.xml | 8 +- .../src/main/res/layout/include_row.xml | 16 + .../main/res/layout/include_setting_row.xml | 12 +- .../layout/include_settings_switch_row.xml | 15 +- .../layout/include_step_entry_simple_body.xml | 28 + .../res/layout/include_submission_contact.xml | 109 +++ .../layout/include_submission_status_card.xml | 18 +- ...include_submission_status_card_content.xml | 35 +- ...nclude_submission_status_card_fetching.xml | 15 +- ...nclude_submission_status_card_positive.xml | 117 +++ ...de_submission_status_card_unregistered.xml | 21 +- .../res/layout/include_test_result_card.xml | 34 +- .../res/layout/include_test_result_step.xml | 4 +- .../layout/include_tracing_status_card.xml | 96 +++ .../main/res/layout/view_circle_progress.xml | 30 + .../src/main/res/layout/view_step_entry.xml | 49 ++ .../src/main/res/navigation/nav_graph.xml | 101 ++- Corona-Warn-App/src/main/res/values/attrs.xml | 26 +- .../src/main/res/values/colors.xml | 5 +- .../src/main/res/values/dimens.xml | 14 +- .../src/main/res/values/strings.xml | 415 ++++++---- .../src/main/res/values/styles.xml | 10 + .../de/rki/coronawarnapp/ExampleUnitTest.kt | 16 - .../rki/coronawarnapp/risk/RiskLevelTest.kt | 80 ++ .../transaction/RiskLevelTransactionTest.kt | 11 +- .../util/formatter/TracingStatusHelperTest.kt | 56 ++ README.md | 14 +- build.gradle | 1 + docs/architecture-overview.md | 10 +- docs/images/Architecture_Overview_v1.svg | 4 +- 148 files changed, 5481 insertions(+), 1274 deletions(-) delete mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ExampleInstrumentedTest.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ExposureNotificationIntentService.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/ApiRequestState.kt delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/RegisterQRCodeScanFragment.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/{TestResultStatus.kt => ApiRequestState.kt} (72%) rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/{register => submission}/ScanStatus.kt (57%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragment.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDoneFragment.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/{register/RegisterDeviceFragment.kt => submission/SubmissionRegisterDeviceFragment.kt} (67%) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/SimpleStepEntry.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CallHelper.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/SettingsNavigationHelper.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ShareHelper.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/TracingStatusHelper.kt create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_main_illustration_overview.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_main_overview_1.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_main_overview_2.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_main_overview_3.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_risk_details_share.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_settings_illustration_bluetooth_off.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_settings_illustration_connection_off.xml rename Corona-Warn-App/src/main/res/drawable/{ic_tracing_on.xml => ic_settings_tracing_active.xml} (100%) create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_bluetooth.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_bluetooth_inactive.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_connection.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_connection_inactive.xml rename Corona-Warn-App/src/main/res/drawable/{ic_tracing_off.xml => ic_settings_tracing_inactive.xml} (100%) create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_step_1.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_step_2.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_step_background.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_submission_illustration_thanks.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_invalid.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_negative.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_pending.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_positive.xml create mode 100644 Corona-Warn-App/src/main/res/layout/fragment_main_overview.xml create mode 100644 Corona-Warn-App/src/main/res/layout/fragment_submission_contact.xml create mode 100644 Corona-Warn-App/src/main/res/layout/fragment_submission_done.xml rename Corona-Warn-App/src/main/res/layout/{fragment_register_qr_code_scan.xml => fragment_submission_qr_code_scan.xml} (52%) rename Corona-Warn-App/src/main/res/layout/{fragment_register_device.xml => fragment_submission_register_device.xml} (56%) create mode 100644 Corona-Warn-App/src/main/res/layout/include_main_overview_glossary.xml create mode 100644 Corona-Warn-App/src/main/res/layout/include_main_overview_segment.xml rename Corona-Warn-App/src/main/res/layout/{include_settings_navigation_row.xml => include_navigation_row.xml} (50%) create mode 100644 Corona-Warn-App/src/main/res/layout/include_step_entry_simple_body.xml create mode 100644 Corona-Warn-App/src/main/res/layout/include_submission_contact.xml create mode 100644 Corona-Warn-App/src/main/res/layout/include_submission_status_card_positive.xml create mode 100644 Corona-Warn-App/src/main/res/layout/include_tracing_status_card.xml create mode 100644 Corona-Warn-App/src/main/res/layout/view_circle_progress.xml create mode 100644 Corona-Warn-App/src/main/res/layout/view_step_entry.xml delete mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ExampleUnitTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/TracingStatusHelperTest.kt diff --git a/.circleci/config.yml b/.circleci/config.yml index df6f775ede8..61e9a9dbb61 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,10 @@ jobs: ndk-version: "android-ndk-r21b" - restore-build-cache - restore_cache: - key: jars-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }} + key: jars-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }}-{{ checksum "Server-Protocol-Buffer/build.gradle" }} + - run: + name: Sudo GradleWrapper + command: chmod +x ./gradlew - run: command: ./gradlew quickBuild environment: @@ -26,7 +29,7 @@ jobs: - save_cache: paths: - ~/.gradle - key: jars-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }} + key: jars-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }}-{{ checksum "Server-Protocol-Buffer/build.gradle" }} - store_artifacts: path: Corona-Warn-App/build/reports destination: reports diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d291bdb330f..fc9b825df03 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -7,11 +7,10 @@ Before submitting, please take the time to check the points below and provide so * [ ] Test your changes as thoroughly as possible before you commit them. Preferably, automate your test by unit/integration tests. * [ ] If this PR comes from a fork, please [Allow edits from maintainers](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) -* [ ] Set a speaking title. Format: {task_name} (closes #{issue_number}). For example: Use logger (closes #41) +* [ ] Set a speaking title. Format: {task_name} (closes #{issue_number}). For example: Use logger (closes # 41) * [ ] [Link your Pull Request to an issue](https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) (if applicable) * [ ] Create Work In Progress [WIP] pull requests only if you need clarification or an explicit review before you can continue your work item. -* [ ] Make sure that your PR is not introducing _unncessary_ reformatting (e.g., introduced by on-save hooks in your IDE) -* [ ] Check our [Contribution Guidelines](https://github.com/corona-warn-app/cwa-app-android/blob/master/CONTRIBUTING.md) +* [ ] Make sure that your PR is not introducing _unnecessary_ reformatting (e.g., introduced by on-save hooks in your IDE) ## Description diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4e5bb23367d..210103484f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,7 +46,7 @@ The following rule governs documentation contributions: ## Pull Request Checklist -* Branch from the master branch and, if needed, rebase to the current master branch before submitting your pull request. If it doesn't merge cleanly with master, you may be asked to rebase your changes. +* Branch from the dev branch and ensure it is up to date with the current dev branch before submitting your pull request. If it doesn't merge cleanly with dev, you may be asked to resolve the conflicts. Pull requests to master will be closed. * Commits should be as small as possible while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests). diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index a7c764f2dca..318578a81d5 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -32,8 +32,8 @@ android { applicationId 'de.rki.coronawarnapp' minSdkVersion 23 targetSdkVersion 29 - versionCode 4 - versionName "0.5.4" + versionCode 7 + versionName "0.8.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "DOWNLOAD_CDN_URL", "\"$DOWNLOAD_CDN_URL\"" @@ -135,11 +135,6 @@ android { } -configurations.all { - resolutionStrategy.force "org.antlr:antlr4-runtime:4.7.1" - resolutionStrategy.force "org.antlr:antlr4-tool:4.7.1" -} - dependencies { api fileTree(dir: 'libs', include: ['play-services-nearby-18.0.2-eap.aar']) implementation project(":Server-Protocol-Buffer") diff --git a/Corona-Warn-App/proguard-rules.pro b/Corona-Warn-App/proguard-rules.pro index fef7ed68569..d7f94bd58f3 100644 --- a/Corona-Warn-App/proguard-rules.pro +++ b/Corona-Warn-App/proguard-rules.pro @@ -16,10 +16,15 @@ private public protected *; } +-keep class * extends com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite { *; } +-keep class net.sqlcipher.** { *; } +-dontwarn net.sqlcipher.** + # Uncomment this to preserve the line number information for # debugging stack traces. -#-keepattributes SourceFile,LineNumberTable +-keepattributes SourceFile,LineNumberTable +#-dontobfuscate # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ExampleInstrumentedTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ExampleInstrumentedTest.kt deleted file mode 100644 index 722ed8fbcca..00000000000 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package de.rki.coronawarnapp - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("de.rki.coronawarnapp", appContext.packageName) - } -} diff --git a/Corona-Warn-App/src/main/AndroidManifest.xml b/Corona-Warn-App/src/main/AndroidManifest.xml index 45df464cdfa..bc150fdde9c 100644 --- a/Corona-Warn-App/src/main/AndroidManifest.xml +++ b/Corona-Warn-App/src/main/AndroidManifest.xml @@ -65,13 +65,6 @@ android:exported="false" android:screenOrientation="fullSensor" tools:replace="screenOrientation" /> - - - \ No newline at end of file diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt index 40a4b67b30f..3e151d6899f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt @@ -1,18 +1,95 @@ package de.rki.coronawarnapp +import android.annotation.SuppressLint +import android.app.Activity import android.app.Application import android.content.Context +import android.content.pm.ActivityInfo +import android.os.Bundle +import android.util.Log +import android.view.WindowManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.ProcessLifecycleOwner +import de.rki.coronawarnapp.notification.NotificationHelper -class CoronaWarnApplication : Application() { +class CoronaWarnApplication : Application(), LifecycleObserver, + Application.ActivityLifecycleCallbacks { companion object { + val TAG: String? = CoronaWarnApplication::class.simpleName private lateinit var instance: CoronaWarnApplication + + /* describes if the app is in foreground + * Initialized to false, because app could also be started by a background job. + * For the cases where the app is started via the launcher icon, the onAppForegrounded + * event will be called, setting it to true + */ + var isAppInForeground = false + fun getAppContext(): Context = instance.applicationContext } override fun onCreate() { instance = this + NotificationHelper.createNotificationChannel() super.onCreate() + ProcessLifecycleOwner.get().lifecycle.addObserver(this) + registerActivityLifecycleCallbacks(this) + } + + /** + * Callback when the app is open but backgrounded + */ + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onAppBackgrounded() { + isAppInForeground = false + Log.v(TAG, "App backgrounded") + } + + /** + * Callback when the app is foregrounded + */ + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun onAppForegrounded() { + isAppInForeground = true + Log.v(TAG, "App foregrounded") + } + + override fun onActivityPaused(activity: Activity) { + // does not override function. Empty on intention + } + + override fun onActivityStarted(activity: Activity) { + // does not override function. Empty on intention + } + + override fun onActivityDestroyed(activity: Activity) { + // does not override function. Empty on intention + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { + // does not override function. Empty on intention + } + + override fun onActivityStopped(activity: Activity) { + // does not override function. Empty on intention + } + + @SuppressLint("SourceLockedOrientationActivity") + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + // prevents screenshot of the app for all activities + activity.window.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) + // set screen orientation to portrait + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT + } + + override fun onActivityResumed(activity: Activity) { + // does not override function. Empty on intention } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/TestForAPIFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/TestForAPIFragment.kt index 935fd3a7723..907d9c8eea6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/TestForAPIFragment.kt @@ -35,6 +35,7 @@ import de.rki.coronawarnapp.server.protocols.AppleLegacyKeyExchange import de.rki.coronawarnapp.sharing.ExposureSharingService import de.rki.coronawarnapp.storage.AppDatabase import de.rki.coronawarnapp.storage.ExposureSummaryRepository +import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository import de.rki.coronawarnapp.transaction.RiskLevelTransaction import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction @@ -76,6 +77,8 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel companion object { val TAG: String? = TestForAPIFragment::class.simpleName + const val CONFIG_SCORE = 8 + fun keysToJson(keys: List): String { return Gson().toJson(keys).toString() } @@ -308,6 +311,7 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel showToast("No other keys provided. Please fill the EditText with the JSON containing keys") } else { token = UUID.randomUUID().toString() + LocalData.googleApiToken(token) val appleKeyList = mutableListOf() @@ -344,7 +348,7 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API InternalExposureNotificationClient.asyncProvideDiagnosisKeys( googleFileList, - ExposureConfiguration.ExposureConfigurationBuilder().build(), + getCustomConfig(), token!! ) showToast("Provided ${appleKeyList.size} keys to Google API with token $token") @@ -442,4 +446,48 @@ class TestForAPIFragment : Fragment(), InternalExposureNotificationPermissionHel keysToJson(keys) updateKeysDisplay() } + + private fun getCustomConfig(): ExposureConfiguration = ExposureConfiguration + .ExposureConfigurationBuilder() + .setAttenuationScores( + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE + ) + .setDaysSinceLastExposureScores( + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE + ) + .setDurationScores( + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE + ) + .setTransmissionRiskScores( + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE, + CONFIG_SCORE + ) + .build() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/RiskLevelCalculationException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/RiskLevelCalculationException.kt index 53aa3ab6135..6f6216f1cde 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/RiskLevelCalculationException.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/exception/RiskLevelCalculationException.kt @@ -1,4 +1,4 @@ package de.rki.coronawarnapp.exception class RiskLevelCalculationException(cause: Throwable) : - Exception("an exception occured during risk level calculation", cause) + Exception("an exception occurred during risk level calculation", cause) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/WebRequestBuilder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/WebRequestBuilder.kt index 558f09e2d24..68167454aaf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/WebRequestBuilder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/WebRequestBuilder.kt @@ -192,7 +192,7 @@ object WebRequestBuilder { ) = suspendCoroutine { cont -> val requestID = UUID.randomUUID() - val getTestResultRequest = + val getTANRequest = TanRequest( url, requestID, @@ -203,13 +203,13 @@ object WebRequestBuilder { Response.Listener { response -> Log.d( TAG, - "$requestID: Test Result Request successful" + "$requestID: TAN Request successful" ) cont.resume(response) }, RequestErrorListener(requestID, cont) ) - RequestQueueHolder.addToRequestQueue(getTestResultRequest) + RequestQueueHolder.addToRequestQueue(getTANRequest) Log.d(TAG, "$requestID: Added $url to queue.") } @@ -254,7 +254,7 @@ object WebRequestBuilder { Response.ErrorListener { override fun onErrorResponse(error: VolleyError?) { if (error != null) { - val webRequestException = WebRequestException("an error occured during a webrequest", error) + val webRequestException = WebRequestException("an error occurred during a webrequest", error) webRequestException.report(de.rki.coronawarnapp.exception.ExceptionCategory.HTTP) cont.resumeWithException(webRequestException) } else { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/KeySubmissionRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/KeySubmissionRequest.kt index 1299f9eae4e..e1fd16f017f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/KeySubmissionRequest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/KeySubmissionRequest.kt @@ -44,7 +44,7 @@ class KeySubmissionRequest( val headers = HashMap(super.getHeaders()) if (faked) headers["cwa-fake"] = Math.random().toInt().toString() else headers["cwa-fake"] = "0" - headers["cwa-authorization"] = "TAN 123456" + headers["cwa-authorization"] = authCode this.addMarker("headers:$headers") return headers } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/RegistrationTokenRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/RegistrationTokenRequest.kt index 9291670c4bf..1a76aac8ca8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/RegistrationTokenRequest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/http/request/RegistrationTokenRequest.kt @@ -10,6 +10,7 @@ import com.android.volley.RetryPolicy import com.android.volley.toolbox.HttpHeaderParser import de.rki.coronawarnapp.exception.ExceptionCategory.HTTP import de.rki.coronawarnapp.exception.report +import de.rki.coronawarnapp.service.submission.SubmissionConstants import org.json.JSONException import org.json.JSONObject import java.io.UnsupportedEncodingException @@ -37,12 +38,17 @@ class RegistrationTokenRequest( override fun getBody(): ByteArray { val body = JSONObject() - val md = MessageDigest.getInstance("SHA-256") - val keyDigest = md.digest(key.toByteArray()) var keyStr = "" - for (b in keyDigest) { - keyStr += String.format("%02x", b) + if (keyType == SubmissionConstants.QR_CODE_KEY_TYPE) { + val md = MessageDigest.getInstance("SHA-256") + val keyDigest = md.digest(key.toByteArray()) + + for (b in keyDigest) { + keyStr += String.format("%02x", b) + } + } else { + keyStr = key } body.put("keyType", keyType) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ExposureNotificationIntentService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ExposureNotificationIntentService.kt deleted file mode 100644 index a2f02f20099..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ExposureNotificationIntentService.kt +++ /dev/null @@ -1,51 +0,0 @@ -package de.rki.coronawarnapp.notification - -import android.app.IntentService -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.util.Log -import androidx.core.app.NotificationCompat -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.ui.main.MainActivity - -class ExposureNotificationIntentService : IntentService("ExposureNotificationIntentService") { - - companion object { - private val TAG: String? = ExposureNotificationIntentService::class.simpleName - } - - override fun onHandleIntent(p0: Intent?) { - Log.i(TAG, "onHandleIntent") - fireNotification() - } - - private fun fireNotification() { - val notificationManager = - this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val takeBreakIntent = Intent(this, MainActivity::class.java) - val pendingIntent = - PendingIntent.getActivity( - this, - 0, - takeBreakIntent, - PendingIntent.FLAG_UPDATE_CURRENT - ) - - val notificationChannelId = getString(R.string.notification_channel_id) - val notificationId = getString(R.string.notification_id) - - val notificationBuilder = - NotificationCompat.Builder(this, notificationChannelId).apply { - setContentTitle(getString(R.string.notification_headline)) - setContentText(getString(R.string.notification_body)) - setSmallIcon(R.drawable.ic_splash_logo) - priority = NotificationCompat.PRIORITY_HIGH - setContentIntent(pendingIntent) - } - val notification = notificationBuilder.build() - - notificationManager.notify(notificationId.toInt(), notification) - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt index 4ed48094bd8..1aba38cf9b0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt @@ -17,7 +17,7 @@ object NotificationConstants { /** * Notification small icon String.xml path */ - const val NOTIFICATION_SMALL_ICON = R.drawable.ic_app_launch_icon + const val NOTIFICATION_SMALL_ICON = R.drawable.ic_splash_logo /** * Notification channel name String.xml path diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationHelper.kt index c41ff76fa88..4a1b47a284f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationHelper.kt @@ -19,6 +19,7 @@ import kotlin.random.Random /** * Singleton class for notification handling + * Notifications should only be sent when the app is not in foreground. * The helper uses externalised constants for readability. * * @see NotificationConstants @@ -91,14 +92,23 @@ object NotificationHelper { private fun buildNotification(title: String, content: String, visibility: Int): Notification? { val builder = NotificationCompat.Builder(CoronaWarnApplication.getAppContext(), channelId) .setSmallIcon(NotificationConstants.NOTIFICATION_SMALL_ICON) - .setContentTitle(title) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVisibility(visibility) .setContentIntent(createPendingIntentToMainActivity()) .setAutoCancel(true) + if (title.isNotEmpty()) { + builder.setContentTitle(title) + } + if (visibility == NotificationCompat.VISIBILITY_PRIVATE) { - builder.setPublicVersion(buildNotification(title, content, NotificationCompat.VISIBILITY_PUBLIC)) + builder.setPublicVersion( + buildNotification( + title, + content, + NotificationCompat.VISIBILITY_PUBLIC + ) + ) } else if (visibility == NotificationCompat.VISIBILITY_PUBLIC) { builder.setContentText(content) } @@ -128,7 +138,6 @@ object NotificationHelper { * @param visibility: Int */ fun sendNotification(title: String, content: String, visibility: Int) { - createNotificationChannel() val notification = buildNotification(title, content, visibility) ?: return with(NotificationManagerCompat.from(CoronaWarnApplication.getAppContext())) { notify(Random.nextInt(), notification) @@ -137,16 +146,16 @@ object NotificationHelper { /** * Send notification - * Build and send notification with predefined title and content. - * Visibility is auto set to NotificationCompat.VISIBILITY_PRIVATE + * Build and send notification with content and visibility. + * Notification is only sent if app is not in foreground. * - * @param title: String * @param content: String - * - * @see NotificationCompat.VISIBILITY_PRIVATE + * @param visibility: Int */ - fun sendNotification(title: String, content: String) { - sendNotification(title, content, NotificationCompat.VISIBILITY_PRIVATE) + fun sendNotification(content: String, visibility: Int) { + if (!CoronaWarnApplication.isAppInForeground) { + sendNotification("", content, visibility) + } } /** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevel.kt index 9a912d19f5a..0f521a9d188 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevel.kt @@ -40,5 +40,41 @@ enum class RiskLevel(val raw: Int) { else -> UNDETERMINED } } + + // risk level categories + private val HIGH_RISK_LEVELS = arrayOf(INCREASED_RISK) + private val LOW_RISK_LEVELS = arrayOf( + UNKNOWN_RISK_INITIAL, + NO_CALCULATION_POSSIBLE_TRACING_OFF, + LOW_LEVEL_RISK, + UNKNOWN_RISK_OUTDATED_RESULTS, + UNDETERMINED + ) + + /** + * Checks if the RiskLevel has change from a high to low or from low to high + * + * @param previousRiskLevel previously persisted RiskLevel + * @param currentRiskLevel newly calculated RiskLevel + * @return + */ + fun riskLevelChangedBetweenLowAndHigh( + previousRiskLevel: RiskLevel, + currentRiskLevel: RiskLevel + ): Boolean { + var riskLevelChangedBetweenLowAndHigh = false + if (HIGH_RISK_LEVELS.contains(previousRiskLevel) && LOW_RISK_LEVELS.contains( + currentRiskLevel + ) + ) { + riskLevelChangedBetweenLowAndHigh = true + } else if (LOW_RISK_LEVELS.contains(previousRiskLevel) && HIGH_RISK_LEVELS.contains( + currentRiskLevel + ) + ) { + riskLevelChangedBetweenLowAndHigh = true + } + return riskLevelChangedBetweenLowAndHigh + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionConstants.kt index c497246aa52..7798f280359 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionConstants.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionConstants.kt @@ -14,7 +14,7 @@ object SubmissionConstants { private val VERSIONED_VERIFICATION_CDN_URL = "$VERIFICATION_CDN_URL/$VERSION/$CURRENT_VERSION" const val QR_CODE_KEY_TYPE = "GUID" - const val TELE_TAN__KEY_TYPE = "teleTAN" + const val TELE_TAN_KEY_TYPE = "TELETAN" val REGISTRATION_TOKEN_URL = "$VERSIONED_VERIFICATION_CDN_URL/$REGISTRATION_TOKEN" val TEST_RESULT_URL = "$VERSIONED_VERIFICATION_CDN_URL/$TEST_RESULT" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt index 59a27c5a6a7..77f4d57c139 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt @@ -1,7 +1,6 @@ package de.rki.coronawarnapp.service.submission import de.rki.coronawarnapp.exception.InvalidQRCodeExcpetion -import de.rki.coronawarnapp.exception.NoAuthCodeSetException import de.rki.coronawarnapp.exception.NoGUIDOrTANSetException import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException import de.rki.coronawarnapp.http.WebRequestBuilder @@ -9,7 +8,7 @@ import de.rki.coronawarnapp.service.submission.SubmissionConstants.QR_CODE_KEY_T import de.rki.coronawarnapp.service.submission.SubmissionConstants.QR_CODE_VALIDATION_REGEX import de.rki.coronawarnapp.service.submission.SubmissionConstants.REGISTRATION_TOKEN_URL import de.rki.coronawarnapp.service.submission.SubmissionConstants.TAN_REQUEST_URL -import de.rki.coronawarnapp.service.submission.SubmissionConstants.TELE_TAN__KEY_TYPE +import de.rki.coronawarnapp.service.submission.SubmissionConstants.TELE_TAN_KEY_TYPE import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction @@ -23,6 +22,7 @@ object SubmissionService { testTAN != null -> asyncRegisterDeviceViaTAN(testTAN) else -> throw NoGUIDOrTANSetException() } + LocalData.devicePairingSuccessfulTimestamp(System.currentTimeMillis()) } private suspend fun asyncRegisterDeviceViaGUID(guid: String) { @@ -42,30 +42,25 @@ object SubmissionService { WebRequestBuilder.asyncGetRegistrationToken( REGISTRATION_TOKEN_URL, tan, - TELE_TAN__KEY_TYPE + TELE_TAN_KEY_TYPE ) LocalData.registrationToken(registrationToken) deleteTeleTAN() } - suspend fun asyncRequestAuthCode() { + suspend fun asyncRequestAuthCode(): String { val registrationToken = LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() val authCode = WebRequestBuilder.asyncGetTan(TAN_REQUEST_URL, registrationToken) - - LocalData.authCode(authCode) - deleteRegistrationToken() + return authCode } suspend fun asyncSubmitExposureKeys() { - val authCode = - LocalData.authCode() ?: throw NoAuthCodeSetException() - - SubmitDiagnosisKeysTransaction.start(authCode) - - deleteAuthCode() + val registrationToken = + LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() + SubmitDiagnosisKeysTransaction.start(registrationToken) } fun validateAndStoreTestGUID(testGUID: String) { @@ -79,6 +74,7 @@ object SubmissionService { fun deleteRegistrationToken() { LocalData.registrationToken(null) + LocalData.devicePairingSuccessfulTimestamp(0L) } private fun deleteAuthCode() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt index f6ec3ec633d..15e12326fef 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/AppDatabase.kt @@ -10,9 +10,9 @@ import de.rki.coronawarnapp.storage.keycache.KeyCacheEntity import de.rki.coronawarnapp.storage.tracing.TracingIntervalDao import de.rki.coronawarnapp.storage.tracing.TracingIntervalEntity import de.rki.coronawarnapp.util.Converters +import de.rki.coronawarnapp.util.security.SecurityHelper import net.sqlcipher.database.SQLiteDatabase import net.sqlcipher.database.SupportFactory -import java.util.UUID @Database( entities = [ExposureSummaryEntity::class, KeyCacheEntity::class, TracingIntervalEntity::class], @@ -39,11 +39,8 @@ abstract class AppDatabase : RoomDatabase() { fun resetInstance(context: Context) = { instance = null }.also { getInstance(context) } private fun buildDatabase(context: Context): AppDatabase { - if (LocalData.databasePassword() == null) { - LocalData.databasePassword(UUID.randomUUID().toString().toCharArray()) - } return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) - .openHelperFactory(SupportFactory(SQLiteDatabase.getBytes(LocalData.databasePassword()))) + .openHelperFactory(SupportFactory(SQLiteDatabase.getBytes(SecurityHelper.getDBPassword()))) .build() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt index 4d7ce3c8bce..7725698b6db 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/LocalData.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.storage import android.content.SharedPreferences +import androidx.core.content.edit import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R import de.rki.coronawarnapp.util.security.SecurityHelper.globalEncryptedSharedPreferencesInstance @@ -37,12 +38,11 @@ object LocalData { * * @param value boolean if onboarding was completed */ - fun isOnboarded(value: Boolean) = with(getSharedPreferenceInstance().edit()) { + fun isOnboarded(value: Boolean) = getSharedPreferenceInstance().edit(true) { putBoolean( CoronaWarnApplication.getAppContext() .getString(R.string.preference_onboarding_completed), value ) - commit() } /**************************************************** @@ -73,7 +73,7 @@ object LocalData { * @param value timestamp in ms */ fun initialTracingActivationTimestamp(value: Long) = - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putLong( CoronaWarnApplication.getAppContext() .getString(R.string.preference_initial_tracing_activation_time), @@ -106,7 +106,7 @@ object LocalData { * * @param value timestamp in ms */ - fun lastNonActiveTracingTimestamp(value: Long?) = with(getSharedPreferenceInstance().edit()) { + fun lastNonActiveTracingTimestamp(value: Long?) = getSharedPreferenceInstance().edit(true) { // TODO need this for nullable ref, shout not be goto for nullable storage putLong( CoronaWarnApplication.getAppContext().getString( @@ -139,7 +139,7 @@ object LocalData { */ fun totalNonActiveTracing(value: Long?) { // TODO need this for nullable ref, shout not be goto for nullable storage - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putLong( CoronaWarnApplication.getAppContext() .getString(R.string.preference_total_non_active_tracing), @@ -180,7 +180,7 @@ object LocalData { */ fun lastTimeDiagnosisKeysFromServerFetch(value: Date?) { // TODO need this for nullable ref, shout not be goto for nullable storage - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putLong( CoronaWarnApplication.getAppContext() .getString(R.string.preference_m_timestamp_diagnosis_keys_fetch), @@ -205,7 +205,7 @@ object LocalData { * Sets the last timestamp the user manually triggered the key retrieval process */ fun lastTimeManualDiagnosisKeysRetrieved(value: Long) = - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putLong( CoronaWarnApplication.getAppContext() .getString(R.string.preference_m_timestamp_manual_diagnosis_keys_retrieval), @@ -234,7 +234,7 @@ object LocalData { * * @param value UUID as string */ - fun googleApiToken(value: String?) = with(getSharedPreferenceInstance().edit()) { + fun googleApiToken(value: String?) = getSharedPreferenceInstance().edit(true) { putString( CoronaWarnApplication.getAppContext() .getString(R.string.preference_m_string_google_api_token), @@ -262,7 +262,7 @@ object LocalData { * Toggles the user decision if notification should be enabled for a risk change * */ - fun toggleNotificationsRiskEnabled() = with(getSharedPreferenceInstance().edit()) { + fun toggleNotificationsRiskEnabled() = getSharedPreferenceInstance().edit(true) { putBoolean( CoronaWarnApplication.getAppContext() .getString(R.string.preference_notifications_risk_enabled), @@ -277,7 +277,7 @@ object LocalData { true ) - fun toggleNotificationsTestEnabled() = with(getSharedPreferenceInstance().edit()) { + fun toggleNotificationsTestEnabled() = getSharedPreferenceInstance().edit(true) { putBoolean( CoronaWarnApplication.getAppContext() .getString(R.string.preference_notifications_test_enabled), @@ -300,7 +300,7 @@ object LocalData { * Toggles the decision if background jobs are enabled * */ - fun toggleBackgroundJobEnabled() = with(getSharedPreferenceInstance().edit()) { + fun toggleBackgroundJobEnabled() = getSharedPreferenceInstance().edit(true) { putBoolean( CoronaWarnApplication.getAppContext() .getString(R.string.preference_background_job_allowed), @@ -323,7 +323,7 @@ object LocalData { * Toggles the boolean if the user has mobile data enabled * */ - fun toggleMobileDataEnabled() = with(getSharedPreferenceInstance().edit()) { + fun toggleMobileDataEnabled() = getSharedPreferenceInstance().edit(true) { putBoolean( CoronaWarnApplication.getAppContext() .getString(R.string.preference_mobile_data_allowed), @@ -353,7 +353,7 @@ object LocalData { * @param value registration token as string */ fun registrationToken(value: String?) { - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putString( CoronaWarnApplication.getAppContext() .getString(R.string.preference_m_registration_token), @@ -364,7 +364,7 @@ object LocalData { } fun inititalTestResultReceivedTimestamp(value: Long) = - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putLong( CoronaWarnApplication.getAppContext() .getString(R.string.preference_initial_result_received_time), @@ -386,8 +386,26 @@ object LocalData { return timestamp } - fun numberOfSuccessfulSubmissions(value: Int) = + fun devicePairingSuccessfulTimestamp(value: Long) = with(getSharedPreferenceInstance().edit()) { + putLong( + CoronaWarnApplication.getAppContext() + .getString(R.string.preference_device_pairing_successful_time), + value + ) + commit() + } + + fun devicePairingSuccessfulTimestamp(): Long? { + return getSharedPreferenceInstance().getLong( + CoronaWarnApplication.getAppContext() + .getString(R.string.preference_device_pairing_successful_time), + 0L + ) + } + + fun numberOfSuccessfulSubmissions(value: Int) = + getSharedPreferenceInstance().edit(true) { putInt( CoronaWarnApplication.getAppContext() .getString(R.string.preference_number_successful_submissions), @@ -411,7 +429,7 @@ object LocalData { ) fun testGUID(value: String?) { - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putString( CoronaWarnApplication.getAppContext() .getString(R.string.preference_m_test_guid), @@ -428,7 +446,7 @@ object LocalData { ) fun authCode(value: String?) { - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putString( CoronaWarnApplication.getAppContext() .getString(R.string.preference_m_auth_code), @@ -439,7 +457,7 @@ object LocalData { } fun isAllowedToSubmitDiagnosisKeys(isAllowedToSubmitDiagnosisKeys: Boolean) { - with(getSharedPreferenceInstance().edit()) { + getSharedPreferenceInstance().edit(true) { putBoolean( CoronaWarnApplication.getAppContext() .getString(R.string.preference_m_is_allowed_to_submit_diagnosis_keys), @@ -457,7 +475,7 @@ object LocalData { ) } - fun teletan(value: String?) = with(getSharedPreferenceInstance().edit()) { + fun teletan(value: String?) = getSharedPreferenceInstance().edit(true) { putString( CoronaWarnApplication.getAppContext().getString(R.string.preference_teletan), value @@ -469,27 +487,6 @@ object LocalData { CoronaWarnApplication.getAppContext().getString(R.string.preference_teletan), null ) - /**************************************************** - * DATABASE PASSWORD - ****************************************************/ - - fun databasePassword(): CharArray? = getSharedPreferenceInstance().getString( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_database_password), - null - )?.toCharArray() - - fun databasePassword(password: CharArray) { - with(getSharedPreferenceInstance().edit()) { - putString( - CoronaWarnApplication.getAppContext() - .getString(R.string.preference_database_password), - password.toString() - ) - commit() - } - } - /**************************************************** * ENCRYPTED SHARED PREFERENCES HANDLING ****************************************************/ diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt index db4794e2387..918f0ad3a4b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SettingsRepository.kt @@ -92,8 +92,8 @@ object SettingsRepository { * * @see ConnectivityHelper */ - fun refreshBluetoothEnabled() { - isBluetoothEnabled.value = ConnectivityHelper.isBluetoothEnabled() + fun updateBluetoothEnabled(value: Boolean) { + isBluetoothEnabled.postValue(value) } /** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt index 839ab07154f..5d3546d6034 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt @@ -19,12 +19,14 @@ object SubmissionRepository { val testResultValue = WebRequestBuilder.asyncGetTestResult(TEST_RESULT_URL, registrationToken) testResult.value = TestResult.fromInt(testResultValue) - + if (testResult == TestResult.POSITIVE) { + LocalData.isAllowedToSubmitDiagnosisKeys(true) + } val initialTestResultReceivedTimestamp = LocalData.inititalTestResultReceivedTimestamp() if (initialTestResultReceivedTimestamp == null) { val currentTime = System.currentTimeMillis() - LocalData.initialTracingActivationTimestamp(currentTime) + LocalData.inititalTestResultReceivedTimestamp(currentTime) testResultReceivedDate.value = Date(currentTime) } else { testResultReceivedDate.value = Date(initialTestResultReceivedTimestamp) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/tracing/TracingIntervalRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/tracing/TracingIntervalRepository.kt index 3c24bf0a0e1..43f434f3c17 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/tracing/TracingIntervalRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/tracing/TracingIntervalRepository.kt @@ -49,7 +49,7 @@ class TracingIntervalRepository(private val tracingIntervalDao: TracingIntervalD suspend fun createInterval(from: Long, to: Long) { Log.v(TAG, "Insert Tracing Interval $from, $to") - if (to < from) throw IllegalArgumentException("to cannot be after from") + if (to < from) throw IllegalArgumentException("to cannot be after or equal from") tracingIntervalDao.insertInterval(TracingIntervalEntity().apply { this.from = from this.to = to diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RiskLevelTransaction.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RiskLevelTransaction.kt index bcea122a2c9..48c521cae4c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RiskLevelTransaction.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RiskLevelTransaction.kt @@ -1,9 +1,13 @@ package de.rki.coronawarnapp.transaction import android.util.Log +import androidx.core.app.NotificationCompat import com.google.android.gms.nearby.exposurenotification.ExposureSummary +import de.rki.coronawarnapp.CoronaWarnApplication +import de.rki.coronawarnapp.R import de.rki.coronawarnapp.exception.RiskLevelCalculationException import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient +import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.risk.RiskLevel import de.rki.coronawarnapp.risk.RiskLevel.INCREASED_RISK import de.rki.coronawarnapp.risk.RiskLevel.LOW_LEVEL_RISK @@ -444,6 +448,13 @@ object RiskLevelTransaction : Transaction() { * @param riskLevel */ private fun updateRiskLevelScore(riskLevel: RiskLevel) { + val lastCalculatedScore = RiskLevelRepository.getLastCalculatedScore() + if (RiskLevel.riskLevelChangedBetweenLowAndHigh(lastCalculatedScore, riskLevel)) { + NotificationHelper.sendNotification( + CoronaWarnApplication.getAppContext().getString(R.string.notification_body), + NotificationCompat.PRIORITY_HIGH + ) + } RiskLevelRepository.setRiskLevelScore(riskLevel) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransaction.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransaction.kt index 39984d65b6d..f998b199a8d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransaction.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/SubmitDiagnosisKeysTransaction.kt @@ -2,13 +2,16 @@ package de.rki.coronawarnapp.transaction import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.service.diagnosiskey.DiagnosisKeyService +import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.CLOSE +import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TAN import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction.SubmitDiagnosisKeysTransactionState.SUBMIT_KEYS +import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.limitKeyCount import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHistoryToExternalFormat /** - * The ReportDiagnosisKeysTransaction is used to define an atomic Transaction for Key Reports. Its states allow an + * The SubmitDiagnosisKeysTransaction is used to define an atomic Transaction for Key Reports. Its states allow an * isolated work area that can recover from failures and keep a consistent key state even through an * unclear, potentially dangerous state within the transaction itself. It is guaranteed that the Key Files * that are used in the transaction will be generated, submitted and accepted from the Google API once the transaction @@ -17,11 +20,10 @@ import de.rki.coronawarnapp.util.ProtoFormatConverterExtensions.transformKeyHist * There is currently a simple rollback behavior needed / identified. * * The Transaction undergoes multiple States: - * 1. INIT - Initial Setup of the Transaction and Transaction ID Generation - * 2. TOKEN - Initialisation of the identifying token used during the entire transaction - * 3. RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY - Retrieval of the Exposure Key History from the API - * 5. SUBMIT_KEYS - Submission of Key retrieved to the Server - * 8. CLOSE - Transaction Closure + * 1. RETRIEVE_TAN - Fetch the TAN with the provided Registration Token + * 2. RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY - Get the TEKs from the exposure notification framework + * 3. SUBMIT_KEYS - Submission of the diagnosis keys to the Server + * 4. CLOSE - Transaction Closure * * This transaction is special in terms of concurrent entry-calls (e.g. calling the transaction again before it closes and * releases its internal mutex. The transaction will not queue up like a normal mutex, but instead completely omit the last @@ -38,18 +40,26 @@ object SubmitDiagnosisKeysTransaction : Transaction() { /** possible transaction states */ private enum class SubmitDiagnosisKeysTransactionState : TransactionState { + RETRIEVE_TAN, RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY, SUBMIT_KEYS, CLOSE } /** initiates the transaction. This suspend function guarantees a successful transaction once completed. */ - suspend fun start(authCode: String) = lockAndExecuteUnique { + suspend fun start(registrationToken: String) = lockAndExecuteUnique { + /**************************************************** + * RETRIEVE TAN + ****************************************************/ + val authCode = executeState(RETRIEVE_TAN) { + SubmissionService.asyncRequestAuthCode() + } /**************************************************** * RETRIEVE TEMPORARY EXPOSURE KEY HISTORY ****************************************************/ val temporaryExposureKeyList = executeState(RETRIEVE_TEMPORARY_EXPOSURE_KEY_HISTORY) { InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory() + .limitKeyCount() .transformKeyHistoryToExternalFormat() } /**************************************************** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UiConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UiConstants.kt index 13f360e19ba..4179af0c143 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UiConstants.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UiConstants.kt @@ -2,4 +2,7 @@ package de.rki.coronawarnapp.ui object UiConstants { const val INFORMATION_URI = "https://www.bundesregierung.de/c19app-intern" + + // todo move to strings if translatable is needed? if yes include regex in CallHelper to filter non-numerical chars excluding '+' + const val TECHNICAL_HOTLINE = "tel:+49 800 7540001" } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt index d3bc89ba198..e05286d770d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationContactFragment.kt @@ -6,7 +6,9 @@ import android.view.View import android.view.ViewGroup import de.rki.coronawarnapp.databinding.FragmentInformationContactBinding import de.rki.coronawarnapp.ui.BaseFragment +import de.rki.coronawarnapp.ui.UiConstants import de.rki.coronawarnapp.ui.main.MainActivity +import de.rki.coronawarnapp.util.CallHelper /** * Basic Fragment which only displays static content. @@ -35,5 +37,8 @@ class InformationContactFragment : BaseFragment() { binding.informationContactHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { (activity as MainActivity).goBack() } + binding.informationContactNavigationRowPhone.navigationRow.setOnClickListener { + CallHelper.call(this, UiConstants.TECHNICAL_HOTLINE) + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt index f71a30643c7..595f3580cbd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt @@ -12,7 +12,8 @@ import de.rki.coronawarnapp.util.ConnectivityHelper import de.rki.coronawarnapp.worker.BackgroundWorkScheduler /** - * This activity holds all the fragments (except onboarding) and also registers a listener for the connectivity to update ui regarding. + * This activity holds all the fragments (except onboarding) and also registers a listener for + * connectivity and bluetooth to update the ui. * * @see SettingsViewModel * @see ConnectivityHelper @@ -28,16 +29,31 @@ class MainActivity : AppCompatActivity() { private lateinit var settingsViewModel: SettingsViewModel - private val callback = object : ConnectivityHelper.NetworkCallback() { + /** + * Register connection callback. + */ + private val callbackNetwork = object : ConnectivityHelper.NetworkCallback() { override fun onNetworkAvailable() { settingsViewModel.updateConnectionEnabled(true) } - override fun onNetworkUnavailable() { settingsViewModel.updateConnectionEnabled(false) } } + /** + * Register bluetooth callback. + */ + private val callbackBluetooth = object : ConnectivityHelper.BluetoothCallback() { + override fun onBluetoothAvailable() { + settingsViewModel.updateBluetoothEnabled(true) + } + + override fun onBluetoothUnavailable() { + settingsViewModel.updateBluetoothEnabled(false) + } + } + init { scheduleWork() } @@ -48,14 +64,22 @@ class MainActivity : AppCompatActivity() { settingsViewModel = ViewModelProviders.of(this).get(SettingsViewModel::class.java) } + /** + * Register network and bluetooth callback. + */ override fun onResume() { super.onResume() - ConnectivityHelper.registerNetworkStatusCallback(this, callback) + ConnectivityHelper.registerNetworkStatusCallback(this, callbackNetwork) + ConnectivityHelper.registerBluetoothStatusCallback(this, callbackBluetooth) } + /** + * Unregister network and bluetooth callback. + */ override fun onPause() { - ConnectivityHelper.unregisterNetworkStatusCallback(this, callback) super.onPause() + ConnectivityHelper.unregisterNetworkStatusCallback(this, callbackNetwork) + ConnectivityHelper.unregisterBluetoothStatusCallback(this, callbackBluetooth) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -67,11 +91,15 @@ class MainActivity : AppCompatActivity() { ) } + /** + * Function is called from back buttons in fragments. + */ fun goBack() { onBackPressed() } - // Scheduling for a Download of Keys every Hour. - // private fun scheduleDiagnosisKeysDownload() = DiagnosisKeyWorkerScheduler.startWork() + /** + * Scheduling for a download of keys every hour. + */ private fun scheduleWork() = BackgroundWorkScheduler.startWorkScheduler() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt index d786f81d378..5d2606f2096 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainFragment.kt @@ -1,18 +1,16 @@ package de.rki.coronawarnapp.ui.main -import android.content.Intent import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.PopupMenu -import android.widget.Toast +import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.fragment.app.activityViewModels import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentMainBinding -import de.rki.coronawarnapp.notification.ExposureNotificationIntentService import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.timer.TimerHelper import de.rki.coronawarnapp.ui.BaseFragment @@ -24,11 +22,12 @@ import de.rki.coronawarnapp.util.OpenUrlHelper /** * After the user has finished the onboarding this fragment will be the heart of the application. - * Two VieModels are needed that this fragment shows all relevant information to the user. + * Three ViewModels are needed that this fragment shows all relevant information to the user. * Also the Menu is set here. * * @see tracingViewModel * @see settingsViewModel + * @see submissionViewModel * @see PopupMenu */ class MainFragment : BaseFragment() { @@ -69,7 +68,6 @@ class MainFragment : BaseFragment() { tracingViewModel.refreshIsTracingEnabled() tracingViewModel.refreshActiveTracingDaysInRetentionPeriod() settingsViewModel.refreshBackgroundJobEnabled() - settingsViewModel.refreshBluetoothEnabled() TimerHelper.checkManualKeyRetrievalTimer() if (submissionViewModel.deviceRegistered) { submissionViewModel.refreshTestResult() @@ -87,6 +85,11 @@ class MainFragment : BaseFragment() { MainFragmentDirections.actionMainFragmentToSubmissionResultFragment() ) } + binding.mainTestPositive.submissionStatusCardPositiveResultShowButton.setOnClickListener { + doNavigate( + MainFragmentDirections.actionMainFragmentToSubmissionResultFragment() + ) + } binding.mainTest.submissionStatusCardUnregistered.submissionStatusCardUnregisteredButton.setOnClickListener { doNavigate( MainFragmentDirections.actionMainFragmentToSubmissionIntroFragment() @@ -123,11 +126,7 @@ class MainFragment : BaseFragment() { popup.setOnMenuItemClickListener { return@setOnMenuItemClickListener when (it.itemId) { R.id.menu_help -> { - Toast.makeText( - requireContext(), - "Help Navigation isn't implemented", - Toast.LENGTH_LONG - ).show() + doNavigate(MainFragmentDirections.actionMainFragmentToMainOverviewFragment()) true } R.id.menu_information -> { @@ -151,9 +150,10 @@ class MainFragment : BaseFragment() { NotificationManagerCompat.from(requireContext()).areNotificationsEnabled() .toString() ) - NotificationHelper.createNotificationChannel() - val intent = Intent(context, ExposureNotificationIntentService::class.java) - activity?.startService(intent) + NotificationHelper.sendNotification( + getString(R.string.notification_body), + NotificationCompat.PRIORITY_HIGH + ) true } else -> super.onOptionsItemSelected(it) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt new file mode 100644 index 00000000000..5a007838f7b --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainOverviewFragment.kt @@ -0,0 +1,44 @@ +package de.rki.coronawarnapp.ui.main + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import de.rki.coronawarnapp.databinding.FragmentMainOverviewBinding +import de.rki.coronawarnapp.ui.BaseFragment + +/** + * The fragment displays static informative content to the user + * and represents one way to gain more detailed understanding of the + * app and its content. + * + */ + +class MainOverviewFragment : BaseFragment() { + + companion object { + private val TAG: String? = MainOverviewFragment::class.simpleName + } + + private lateinit var binding: FragmentMainOverviewBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentMainOverviewBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setButtonOnClickListener() + } + + private fun setButtonOnClickListener() { + binding.mainOverviewHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { + (activity as MainActivity).goBack() + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt index fe7b897c5b9..9bff3a7ade9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainShareFragment.kt @@ -1,6 +1,5 @@ package de.rki.coronawarnapp.ui.main -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -10,6 +9,7 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentMainShareBinding import de.rki.coronawarnapp.ui.BaseFragment import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel +import de.rki.coronawarnapp.util.ShareHelper /** * This fragment informs the user about what he is going to share and how he is going to help everybody with this :) @@ -43,20 +43,10 @@ class MainShareFragment : BaseFragment() { private fun setButtonOnClickListener() { binding.mainShareButton.setOnClickListener { - share() + ShareHelper.shareText(this, getString(R.string.main_share_message), null) } binding.mainShareHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { (activity as MainActivity).goBack() } } - - // TODO move to helper - private fun share() { - val share = Intent.createChooser(Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, getString(R.string.main_share_message)) - }, null) - startActivity(share) - } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt index 4b95d319a41..11f6fb9ba9e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragment.kt @@ -1,20 +1,13 @@ package de.rki.coronawarnapp.ui.onboarding -import android.content.Intent -import android.os.Build import android.os.Bundle -import android.provider.Settings -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.core.app.NotificationManagerCompat -import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentOnboardingNotificationsBinding -import de.rki.coronawarnapp.storage.SettingsRepository import de.rki.coronawarnapp.ui.BaseFragment -import de.rki.coronawarnapp.util.DialogHelper /** * This fragment ask the user if he wants to get notifications and finishes the onboarding afterwards. @@ -44,13 +37,7 @@ class OnboardingNotificationsFragment : BaseFragment() { } private fun setButtonOnClickListener() { - binding.onboardingButtonFinish.setOnClickListener { - finishOrSettings() - } - binding.onboardingButtonDisable.setOnClickListener { - // Set default value for notifications to false - SettingsRepository.toggleNotificationsRiskEnabled() - SettingsRepository.toggleNotificationsTestEnabled() + binding.onboardingButtonNext.setOnClickListener { navigateToMain() } binding.onboardingButtonBack.buttonIcon.setOnClickListener { @@ -58,55 +45,7 @@ class OnboardingNotificationsFragment : BaseFragment() { } } - private fun finishOrSettings() { - // If the os notifications settings are enabled the user can finish the onboarding. - // If the user want to get notifications but they are disabled on the os level he can go - // to the notification settings to activate them. - val areNotificationsEnabled = - NotificationManagerCompat.from(requireContext()).areNotificationsEnabled() - if (areNotificationsEnabled) { - navigateToMain() - } else { - showNotificationsDisabledDialog() - } - } - private fun navigateToMain() { (requireActivity() as OnboardingActivity).completeOnboarding() } - - private fun showNotificationsDisabledDialog() { - val dialog = DialogHelper.DialogInstance( - requireActivity(), - R.string.onboarding_notifications_headline, - R.string.onboarding_notifications_dialog_body, - R.string.onboarding_notifications_dialog_button_positive, - R.string.onboarding_notifications_dialog_button_negative, - { - navigateToNotificationSettings() - }, { - navigateToMain() - }) - DialogHelper.showDialog(dialog) - } - - private fun navigateToNotificationSettings() { - val intent = Intent() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Log.i(TAG, "current") - intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS - intent.putExtra( - Settings.EXTRA_APP_PACKAGE, - requireContext().packageName - ) - } else { - Log.i(TAG, "old") - intent.putExtra( - "app_package", - requireContext().packageName - ) - intent.putExtra("app_uid", requireContext().applicationInfo.uid) - } - startActivity(intent) - } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt index fe791014e5c..341a61b48ea 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragment.kt @@ -60,7 +60,7 @@ class OnboardingTracingFragment : BaseFragment(), internalExposureNotificationPermissionHelper.requestPermissionToStartTracing() } binding.onboardingButtonDisable.setOnClickListener { - navigate() + showCancelDialog() } binding.onboardingButtonBack.buttonIcon.setOnClickListener { (activity as OnboardingActivity).goBack() @@ -72,7 +72,7 @@ class OnboardingTracingFragment : BaseFragment(), } override fun onFailure(exception: Exception?) { - showCancelDialog() + navigate() } private fun showCancelDialog() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/ApiRequestState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/ApiRequestState.kt deleted file mode 100644 index edd4dde71b1..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/ApiRequestState.kt +++ /dev/null @@ -1,5 +0,0 @@ -package de.rki.coronawarnapp.ui.register - -enum class ApiRequestState { - IDLE, STARTED, FAILED, SUCCESS -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/RegisterQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/RegisterQRCodeScanFragment.kt deleted file mode 100644 index de5ad319851..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/RegisterQRCodeScanFragment.kt +++ /dev/null @@ -1,222 +0,0 @@ -package de.rki.coronawarnapp.ui.register - -import android.Manifest -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog -import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer -import com.google.zxing.BarcodeFormat -import com.journeyapps.barcodescanner.BarcodeResult -import com.journeyapps.barcodescanner.DefaultDecoderFactory -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.databinding.FragmentRegisterQrCodeScanBinding -import de.rki.coronawarnapp.ui.BaseFragment -import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel -import de.rki.coronawarnapp.util.CameraPermissionHelper - -/** - * A simple [BaseFragment] subclass. - */ -class RegisterQRCodeScanFragment : BaseFragment() { - - companion object { - private val TAG: String? = RegisterQRCodeScanFragment::class.simpleName - private const val REQUEST_CAMERA_PERMISSION_CODE = 1 - } - - private val viewModel: SubmissionViewModel by viewModels() - private lateinit var binding: FragmentRegisterQrCodeScanBinding - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - binding = FragmentRegisterQrCodeScanBinding.inflate(inflater) - binding.lifecycleOwner = this - return binding.root - } - - private fun decodeCallback(result: BarcodeResult) { - viewModel.validateAndStoreTestGUID(result.text) - } - - private fun startDecode() { - binding.registerQrCodeScanPreview.decodeSingle { decodeCallback(it) } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - checkForCameraPermission() - - binding.registerQrCodeScanTorch.setOnCheckedChangeListener { _, isChecked -> - binding.registerQrCodeScanPreview.setTorch( - isChecked - ) - } - - binding.registerQrCodeScanClose.buttonIcon.setOnClickListener { - doNavigate( - RegisterQRCodeScanFragmentDirections.actionRegisterQRCodeFragmentToSubmissionDispatcherFragment() - ) - } - - binding.registerQrCodeScanPreview.decoderFactory = - DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE)) - binding.registerQrCodeScanViewfinderView.setCameraPreview(binding.registerQrCodeScanPreview) - - viewModel.scanStatus.observe(viewLifecycleOwner, Observer { - if (ScanStatus.SUCCESS == it) { - showSuccessfulScanDialog() - } - - if (ScanStatus.INVALID == it) { - showInvalidScanDialog() - } - }) - - startDecode() - } - - private fun navigateToDispatchScreen() = - doNavigate( - RegisterQRCodeScanFragmentDirections.actionRegisterQRCodeFragmentToSubmissionDispatcherFragment() - ) - - private fun showSuccessfulScanDialog() { - val alertDialog: AlertDialog = requireActivity().let { - val builder = AlertDialog.Builder(it) - builder.apply { - setTitle(R.string.register_qr_code_scan_successful_dialog_headline) - setMessage(R.string.register_qr_code_scan_successful_dialog_body) - setPositiveButton( - R.string.register_qr_code_scan_successful_dialog_button_positive - ) { _, _ -> - doNavigate( - RegisterQRCodeScanFragmentDirections.actionRegisterQRCodeFragmentToDeviceRegistrationFragment() - ) - } - setNegativeButton( - R.string.register_qr_code_scan_successful_dialog_button_negative - ) { _, _ -> - viewModel.deleteTestGUID() - navigateToDispatchScreen() - } - } - builder.create() - } - alertDialog.show() - } - - private fun showInvalidScanDialog() { - val alertDialog: AlertDialog = requireActivity().let { - val builder = AlertDialog.Builder(it) - builder.apply { - setTitle(R.string.register_qr_code_scan_invalid_dialog_headline) - setMessage(R.string.register_qr_code_scan_invalid_dialog_body) - setPositiveButton( - R.string.register_qr_code_scan_invalid_dialog_button_positive - ) { _, _ -> - startDecode() - } - setNegativeButton( - R.string.register_qr_code_scan_invalid_dialog_button_negative - ) { _, _ -> - navigateToDispatchScreen() - } - } - builder.create() - } - alertDialog.show() - } - - private fun checkForCameraPermission() { - if (!CameraPermissionHelper.hasCameraPermission(requireActivity())) { - if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { - showCameraPermissionRationaleDialog() - } else { - requestPermissions( - arrayOf(Manifest.permission.CAMERA), - REQUEST_CAMERA_PERMISSION_CODE - ) - } - } else { - cameraPermissionIsGranted() - } - } - - private fun cameraPermissionIsGranted() { - binding.registerQrCodeScanPreview.resume() - startDecode() - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - cameraPermissionIsGranted() - } else { - showCameraPermissionDeniedDialog() - } - } - - private fun showCameraPermissionRationaleDialog() { - val alertDialog: AlertDialog = requireActivity().let { - val builder = AlertDialog.Builder(it) - builder.apply { - setTitle(R.string.register_qr_code_scan_permission_rationale_dialog_headline) - setMessage(R.string.register_qr_code_scan_permission_rationale_dialog_body) - setPositiveButton( - R.string.register_qr_code_scan_permission_rationale_dialog_button_positive - ) { _, _ -> - requestPermissions( - arrayOf(Manifest.permission.CAMERA), - REQUEST_CAMERA_PERMISSION_CODE - ) - } - setNegativeButton( - R.string.register_qr_code_scan_permission_rationale_dialog_button_negative - ) { _, _ -> - navigateToDispatchScreen() - } - } - builder.create() - } - alertDialog.show() - } - - private fun showCameraPermissionDeniedDialog() { - val alertDialog: AlertDialog = requireActivity().let { - val builder = AlertDialog.Builder(it) - builder.apply { - setTitle(R.string.register_qr_code_scan_permission_denied_dialog_headline) - setMessage(R.string.register_qr_code_scan_permission_denied_dialog_body) - setPositiveButton( - R.string.register_qr_code_scan_permission_denied_dialog_button_positive - ) { _, _ -> - navigateToDispatchScreen() - } - } - builder.create() - } - alertDialog.show() - } - - override fun onResume() { - super.onResume() - binding.registerQrCodeScanPreview.resume() - } - - override fun onPause() { - super.onPause() - binding.registerQrCodeScanPreview.pause() - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt index 30aaf81fa62..570453ad966 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsNotificationFragment.kt @@ -1,8 +1,6 @@ package de.rki.coronawarnapp.ui.settings -import android.content.Intent import android.os.Bundle -import android.provider.Settings import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -12,6 +10,7 @@ import de.rki.coronawarnapp.databinding.FragmentSettingsNotificationsBinding import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel +import de.rki.coronawarnapp.util.SettingsNavigationHelper /** * This is the setting notification page. Here the user sees his os notifications settings status. @@ -57,15 +56,11 @@ class SettingsNotificationFragment : Fragment() { // Notifications about risk status val updateRiskNotificationSwitch = binding.settingsSwitchRowNotificationsRisk.settingsSwitchRowSwitch - val updateRiskNotificationRow = - binding.settingsSwitchRowNotificationsRisk.settingsSwitchRow // Notifications about test status val updateTestNotificationSwitch = binding.settingsSwitchRowNotificationsTest.settingsSwitchRowSwitch - val updateTestNotificationRow = - binding.settingsSwitchRowNotificationsTest.settingsSwitchRow // Settings - val settingsRow = binding.settingsNavigationRowSystem.settingsNavigationRow + val settingsRow = binding.settingsNavigationRowSystem.navigationRow val goBack = binding.settingsDetailsHeaderNotifications.settingsDetailsHeaderButtonBack.buttonIcon // Update Risk @@ -75,9 +70,6 @@ class SettingsNotificationFragment : Fragment() { settingsViewModel.toggleNotificationsRiskEnabled() } } - updateRiskNotificationRow.setOnClickListener { - settingsViewModel.toggleNotificationsRiskEnabled() - } // Update Test updateTestNotificationSwitch.setOnCheckedChangeListener { _, _ -> // android calls this listener also on start, so it has to be verified if the user pressed the switch @@ -85,26 +77,12 @@ class SettingsNotificationFragment : Fragment() { settingsViewModel.toggleNotificationsTestEnabled() } } - updateTestNotificationRow.setOnClickListener { - settingsViewModel.toggleNotificationsTestEnabled() - } goBack.setOnClickListener { (activity as MainActivity).goBack() } // System Settings settingsRow.setOnClickListener { - navigateToSettings() + SettingsNavigationHelper.toNotifications(requireContext()) } } - - private fun navigateToSettings() { - // Todo change to api level 23 - val intent = Intent() - intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS - intent.putExtra( - Settings.EXTRA_APP_PACKAGE, - requireContext().packageName - ) - startActivity(intent) - } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt index e39eaf2a4cc..8e78bc76be4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsTracingFragment.kt @@ -9,6 +9,7 @@ import android.widget.Toast import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import de.rki.coronawarnapp.databinding.FragmentSettingsTracingBinding +import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.report import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper @@ -17,6 +18,7 @@ import de.rki.coronawarnapp.ui.ViewBlocker import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.ui.viewmodel.SettingsViewModel import de.rki.coronawarnapp.ui.viewmodel.TracingViewModel +import de.rki.coronawarnapp.util.SettingsNavigationHelper import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import kotlinx.coroutines.launch @@ -57,34 +59,10 @@ class SettingsTracingFragment : BaseFragment(), setButtonOnClickListener() } - private fun setButtonOnClickListener() { - val switch = binding.settingsSwitchRowTracing.settingsSwitchRowSwitch - val row = binding.settingsSwitchRowTracing.settingsSwitchRow - internalExposureNotificationPermissionHelper = - InternalExposureNotificationPermissionHelper(this, this) - switch.setOnCheckedChangeListener { _, _ -> - // android calls this listener also on start, so it has to be verified if the user pressed the switch - if (switch.isPressed) { - ViewBlocker.runAndBlockInteraction(arrayOf(row, switch)) { - startStopTracing() - } - } - } - row.setOnClickListener { - ViewBlocker.runAndBlockInteraction(arrayOf(row, switch)) { - startStopTracing() - } - } - binding.settingsDetailsHeaderTracing.settingsDetailsHeaderButtonBack.buttonIcon.setOnClickListener { - (activity as MainActivity).goBack() - } - } - override fun onResume() { super.onResume() // refresh required data tracingViewModel.refreshIsTracingEnabled() - settingsViewModel.refreshBluetoothEnabled() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -103,21 +81,42 @@ class SettingsTracingFragment : BaseFragment(), override fun onFailure(exception: Exception?) { tracingViewModel.refreshIsTracingEnabled() - exception?.report(de.rki.coronawarnapp.exception.ExceptionCategory.EXPOSURENOTIFICATION) + exception?.report(ExceptionCategory.EXPOSURENOTIFICATION) // TODO Toast.makeText( requireContext(), exception?.localizedMessage ?: "Unknown Error", Toast.LENGTH_SHORT ).show() - tracingViewModel.refreshIsTracingEnabled() + } + + private fun setButtonOnClickListener() { + val switch = binding.settingsTracingSwitchRow.settingsSwitchRowSwitch + internalExposureNotificationPermissionHelper = + InternalExposureNotificationPermissionHelper(this, this) + switch.setOnCheckedChangeListener { _, _ -> + // android calls this listener also on start, so it has to be verified if the user pressed the switch + if (switch.isPressed) { + ViewBlocker.runAndBlockInteraction(arrayOf(switch)) { + startStopTracing() + } + } + } + binding.settingsTracingHeader.settingsDetailsHeaderButtonBack.buttonIcon.setOnClickListener { + (activity as MainActivity).goBack() + } + binding.settingsTracingStatusBluetooth.tracingStatusCardButton.setOnClickListener { + SettingsNavigationHelper.toConnections(requireContext()) + } + binding.settingsTracingStatusConnection.tracingStatusCardButton.setOnClickListener { + SettingsNavigationHelper.toConnections(requireContext()) + } } private fun startStopTracing() { - if (tracingViewModel.isTracingEnabled.value != null) { - // if tracing is enabled when listener is activated it should be disabled - if (tracingViewModel.isTracingEnabled.value!!) { - lifecycleScope.launch { + // if tracing is enabled when listener is activated it should be disabled + lifecycleScope.launch { + if (InternalExposureNotificationClient.asyncIsEnabled()) { try { Toast.makeText( requireContext(), @@ -125,19 +124,17 @@ class SettingsTracingFragment : BaseFragment(), Toast.LENGTH_SHORT ) .show() - tracingViewModel.refreshIsTracingEnabled() + + InternalExposureNotificationClient.asyncStop() } catch (exception: Exception) { exception.report( - de.rki.coronawarnapp.exception.ExceptionCategory.EXPOSURENOTIFICATION, + ExceptionCategory.EXPOSURENOTIFICATION, TAG, null ) - tracingViewModel.refreshIsTracingEnabled() } - InternalExposureNotificationClient.asyncStop() tracingViewModel.refreshIsTracingEnabled() BackgroundWorkScheduler.stopWorkScheduler() - } } else { internalExposureNotificationPermissionHelper.requestPermissionToStartTracing() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TestResultStatus.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/ApiRequestState.kt similarity index 72% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TestResultStatus.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/ApiRequestState.kt index 231b2a52268..3043dcacf37 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TestResultStatus.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/ApiRequestState.kt @@ -1,5 +1,5 @@ package de.rki.coronawarnapp.ui.submission -enum class TestResultStatus { +enum class ApiRequestState { IDLE, STARTED, FAILED, SUCCESS } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/ScanStatus.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/ScanStatus.kt similarity index 57% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/ScanStatus.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/ScanStatus.kt index 1c0a0ca350f..b3bd73f8a46 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/ScanStatus.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/ScanStatus.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.ui.register +package de.rki.coronawarnapp.ui.submission enum class ScanStatus { STARTED, INVALID, SUCCESS diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragment.kt new file mode 100644 index 00000000000..14607969a60 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragment.kt @@ -0,0 +1,51 @@ +package de.rki.coronawarnapp.ui.submission + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentSubmissionContactBinding +import de.rki.coronawarnapp.ui.BaseFragment +import de.rki.coronawarnapp.ui.main.MainActivity +import de.rki.coronawarnapp.util.CallHelper + +/** + * The [SubmissionContactFragment] allows requesting a teletan via phone + */ +class SubmissionContactFragment : BaseFragment() { + + private lateinit var binding: FragmentSubmissionContactBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // get the binding reference by inflating it with the current layout + binding = FragmentSubmissionContactBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setButtonOnClickListener() + } + + private fun setButtonOnClickListener() { + binding.submissionContactHeader.headerButtonBack.buttonIcon.setOnClickListener { + (activity as MainActivity).goBack() + } + binding.submissionContactButtonCall.setOnClickListener { + dial() + } + binding.submissionContactButtonEnter.setOnClickListener { + doNavigate(SubmissionContactFragmentDirections.actionSubmissionContactFragmentToSubmissionTanFragment()) + } + } + + private fun dial() = context?.let { + val number = getString(R.string.submission_contact_number) + CallHelper.call(this, "tel:$number") + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragment.kt index 5f5916104e8..f362685bee6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragment.kt @@ -1,16 +1,21 @@ package de.rki.coronawarnapp.ui.submission +import android.Manifest +import android.content.pm.PackageManager import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionDispatcherBinding import de.rki.coronawarnapp.ui.BaseFragment +import de.rki.coronawarnapp.util.CameraPermissionHelper +import de.rki.coronawarnapp.util.DialogHelper class SubmissionDispatcherFragment : BaseFragment() { companion object { + private const val REQUEST_CAMERA_PERMISSION_CODE = 1 private val TAG: String? = SubmissionDispatcherFragment::class.simpleName } @@ -33,17 +38,83 @@ class SubmissionDispatcherFragment : BaseFragment() { private fun setButtonOnClickListener() { binding.submissionDispatcherQr.dispatcherCard.setOnClickListener { - doNavigate( - SubmissionDispatcherFragmentDirections.actionSubmissionDispatcherFragmentToRegisterQRCodeFragment() - ) + checkForCameraPermission() } binding.submissionDispatcherTanCode.dispatcherCard.setOnClickListener { doNavigate( - SubmissionDispatcherFragmentDirections.actionSubmissionDispatcherFragmentToSubmissionTanFragment() + SubmissionDispatcherFragmentDirections + .actionSubmissionDispatcherFragmentToSubmissionTanFragment() ) } binding.submissionDispatcherTanTele.dispatcherCard.setOnClickListener { - Log.i(TAG, "TAN tele pressed") + doNavigate( + SubmissionDispatcherFragmentDirections + .actionSubmissionDispatcherFragmentToSubmissionContactFragment() + ) + } + } + + private fun checkForCameraPermission() { + if (!CameraPermissionHelper.hasCameraPermission(requireActivity())) { + if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { + showCameraPermissionRationaleDialog() + } else { + requestPermissions( + arrayOf(Manifest.permission.CAMERA), + REQUEST_CAMERA_PERMISSION_CODE + ) + } + } else { + cameraPermissionIsGranted() + } + } + + private fun showCameraPermissionRationaleDialog() { + val cameraPermissionRationaleDialogInstance = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_qr_code_scan_permission_rationale_dialog_headline, + R.string.submission_qr_code_scan_permission_rationale_dialog_body, + R.string.submission_qr_code_scan_permission_rationale_dialog_button_positive, + R.string.submission_qr_code_scan_permission_rationale_dialog_button_negative, + { + requestPermissions( + arrayOf(Manifest.permission.CAMERA), + REQUEST_CAMERA_PERMISSION_CODE + ) + } + ) + + DialogHelper.showDialog(cameraPermissionRationaleDialogInstance) + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + cameraPermissionIsGranted() + } else { + showCameraPermissionDeniedDialog() } } + + private fun showCameraPermissionDeniedDialog() { + val cameraPermissionDeniedDialogInstance = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_qr_code_scan_permission_denied_dialog_headline, + R.string.submission_qr_code_scan_permission_denied_dialog_body, + R.string.submission_qr_code_scan_permission_denied_dialog_button_positive + ) + + DialogHelper.showDialog(cameraPermissionDeniedDialogInstance) + } + + private fun cameraPermissionIsGranted() { + doNavigate( + SubmissionDispatcherFragmentDirections + .actionSubmissionDispatcherFragmentToSubmissionQRCodeScanFragment() + ) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDoneFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDoneFragment.kt new file mode 100644 index 00000000000..a3841c8f6d5 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionDoneFragment.kt @@ -0,0 +1,44 @@ +package de.rki.coronawarnapp.ui.submission + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import de.rki.coronawarnapp.databinding.FragmentSubmissionDoneBinding +import de.rki.coronawarnapp.ui.BaseFragment + +/** + * The [SubmissionDoneFragment] displays information to a user that submitted his exposure keys + */ +class SubmissionDoneFragment : BaseFragment() { + + private lateinit var binding: FragmentSubmissionDoneBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // get the binding reference by inflating it with the current layout + binding = FragmentSubmissionDoneBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setButtonOnClickListener() + } + + private fun setButtonOnClickListener() { + binding.submissionDoneHeader.informationHeader.headerButtonBack.buttonIcon.setOnClickListener { + doNavigate( + SubmissionDoneFragmentDirections.actionSubmissionDoneFragmentToMainFragment() + ) + } + binding.submissionDoneButtonDone.setOnClickListener { + doNavigate( + SubmissionDoneFragmentDirections.actionSubmissionDoneFragmentToMainFragment() + ) + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt new file mode 100644 index 00000000000..7f22372c6ed --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionQRCodeScanFragment.kt @@ -0,0 +1,128 @@ +package de.rki.coronawarnapp.ui.submission + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import com.google.zxing.BarcodeFormat +import com.journeyapps.barcodescanner.BarcodeResult +import com.journeyapps.barcodescanner.DefaultDecoderFactory +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeScanBinding +import de.rki.coronawarnapp.ui.BaseFragment +import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel +import de.rki.coronawarnapp.util.DialogHelper + +/** + * A simple [BaseFragment] subclass. + */ +class SubmissionQRCodeScanFragment : BaseFragment() { + + companion object { + private val TAG: String? = SubmissionQRCodeScanFragment::class.simpleName + } + + private val viewModel: SubmissionViewModel by viewModels() + private lateinit var binding: FragmentSubmissionQrCodeScanBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentSubmissionQrCodeScanBinding.inflate(inflater) + binding.lifecycleOwner = this + return binding.root + } + + private fun decodeCallback(result: BarcodeResult) { + viewModel.validateAndStoreTestGUID(result.text) + } + + private fun startDecode() { + binding.submissionQrCodeScanPreview.decodeSingle { decodeCallback(it) } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.submissionQrCodeScanTorch.setOnCheckedChangeListener { _, isChecked -> + binding.submissionQrCodeScanPreview.setTorch( + isChecked + ) + } + + binding.submissionQrCodeScanClose.buttonIcon.setOnClickListener { + navigateToDispatchScreen() + } + + binding.submissionQrCodeScanPreview.decoderFactory = + DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE)) + binding.submissionQrCodeScanViewfinderView.setCameraPreview(binding.submissionQrCodeScanPreview) + + viewModel.scanStatus.observe(viewLifecycleOwner, Observer { + if (ScanStatus.SUCCESS == it) { + showSuccessfulScanDialog() + } + + if (ScanStatus.INVALID == it) { + showInvalidScanDialog() + } + }) + } + + private fun navigateToDispatchScreen() = + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionDispatcherFragment() + ) + + private fun showSuccessfulScanDialog() { + val successfulScanDialogInstance = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_qr_code_scan_successful_dialog_headline, + R.string.submission_qr_code_scan_successful_dialog_body, + R.string.submission_qr_code_scan_successful_dialog_button_positive, + R.string.submission_qr_code_scan_successful_dialog_button_negative, + { + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionRegisterDeviceFragment() + ) + }, + { + viewModel.deleteTestGUID() + navigateToDispatchScreen() + } + ) + + DialogHelper.showDialog(successfulScanDialogInstance) + } + + private fun showInvalidScanDialog() { + val invalidScanDialogInstance = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_qr_code_scan_invalid_dialog_headline, + R.string.submission_qr_code_scan_invalid_dialog_body, + R.string.submission_qr_code_scan_invalid_dialog_button_positive, + R.string.submission_qr_code_scan_invalid_dialog_button_negative, + ::startDecode, + ::navigateToDispatchScreen + ) + + DialogHelper.showDialog(invalidScanDialogInstance) + } + + override fun onResume() { + super.onResume() + binding.submissionQrCodeScanPreview.resume() + startDecode() + } + + override fun onPause() { + super.onPause() + binding.submissionQrCodeScanPreview.pause() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/RegisterDeviceFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionRegisterDeviceFragment.kt similarity index 67% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/RegisterDeviceFragment.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionRegisterDeviceFragment.kt index 997a5e6778e..853c16b6e8b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/register/RegisterDeviceFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionRegisterDeviceFragment.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.ui.register +package de.rki.coronawarnapp.ui.submission import android.os.Bundle import android.view.LayoutInflater @@ -6,20 +6,20 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer -import de.rki.coronawarnapp.databinding.FragmentRegisterDeviceBinding +import de.rki.coronawarnapp.databinding.FragmentSubmissionRegisterDeviceBinding import de.rki.coronawarnapp.ui.BaseFragment import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel -class RegisterDeviceFragment : BaseFragment() { +class SubmissionRegisterDeviceFragment : BaseFragment() { private val viewModel: SubmissionViewModel by activityViewModels() - private lateinit var binding: FragmentRegisterDeviceBinding + private lateinit var binding: FragmentSubmissionRegisterDeviceBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - binding = FragmentRegisterDeviceBinding.inflate(inflater) + binding = FragmentSubmissionRegisterDeviceBinding.inflate(inflater) binding.lifecycleOwner = this return binding.root } @@ -30,7 +30,8 @@ class RegisterDeviceFragment : BaseFragment() { viewModel.registrationState.observe(viewLifecycleOwner, Observer { if (ApiRequestState.SUCCESS == it) { doNavigate( - RegisterDeviceFragmentDirections.actionDeviceRegistrationFragmentToSubmissionResultFragment() + SubmissionRegisterDeviceFragmentDirections + .actionSubmissionRegisterDeviceFragmentToSubmissionResultFragment() ) } }) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionResultPositiveOtherWarningFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionResultPositiveOtherWarningFragment.kt index dc292822cca..14f0a914b67 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionResultPositiveOtherWarningFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionResultPositiveOtherWarningFragment.kt @@ -1,28 +1,54 @@ package de.rki.coronawarnapp.ui.submission import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog -import de.rki.coronawarnapp.R +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.databinding.FragmentSubmissionPositiveOtherWarningBinding +import de.rki.coronawarnapp.nearby.InternalExposureNotificationPermissionHelper import de.rki.coronawarnapp.ui.BaseFragment +import de.rki.coronawarnapp.ui.viewmodel.SubmissionViewModel -class SubmissionResultPositiveOtherWarningFragment : BaseFragment() { +class SubmissionResultPositiveOtherWarningFragment : BaseFragment(), + InternalExposureNotificationPermissionHelper.Callback { companion object { private val TAG: String? = SubmissionResultPositiveOtherWarningFragment::class.simpleName } + private val viewModel: SubmissionViewModel by activityViewModels() private lateinit var binding: FragmentSubmissionPositiveOtherWarningBinding + private var submissionRequested = false + private var submissionFailed = false + private lateinit var internalExposureNotificationPermissionHelper: + InternalExposureNotificationPermissionHelper + + override fun onResume() { + super.onResume() + if (submissionRequested && !submissionFailed) { + internalExposureNotificationPermissionHelper.requestPermissionToShareKeys() + } + } + + override fun onKeySharePermissionGranted(keys: List) { + super.onKeySharePermissionGranted(keys) + viewModel.submitDiagnosisKeys() + } + + override fun onFailure(exception: Exception?) { + submissionFailed = true + } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { + internalExposureNotificationPermissionHelper = + InternalExposureNotificationPermissionHelper(this, this) binding = FragmentSubmissionPositiveOtherWarningBinding.inflate(inflater) binding.lifecycleOwner = this return binding.root @@ -31,39 +57,28 @@ class SubmissionResultPositiveOtherWarningFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setButtonOnClickListener() + + viewModel.submissionState.observe(viewLifecycleOwner, Observer { + if (it == ApiRequestState.SUCCESS) { + doNavigate( + SubmissionResultPositiveOtherWarningFragmentDirections + .actionSubmissionResultPositiveOtherWarningFragmentToSubmissionDoneFragment() + ) + } + }) } private fun setButtonOnClickListener() { binding.submissionPositiveOtherWarningButton.setOnClickListener { - Log.i(TAG, "Weiter pressed") - showShareIDConfirmationDialog() + submissionRequested = true + internalExposureNotificationPermissionHelper.requestPermissionToShareKeys() } binding.submissionPositiveOtherWarningHeader .informationHeader.headerButtonBack.buttonIcon.setOnClickListener { - Log.i(TAG, "Back button pressed") - } - } - - private fun showShareIDConfirmationDialog() { - val alertDialog: AlertDialog = requireActivity().let { - val builder = AlertDialog.Builder(it) - builder.apply { - setTitle(R.string.submission_positive_dialog_confirmation_title) - setMessage(R.string.submission_positive_dialog_confirmation_body) - setPositiveButton( - R.string.submission_positive_dialog_confirmation_positive - ) { _, _ -> - Log.i(TAG, "Agreed") - // Navigate to next screen - } - setNegativeButton( - R.string.submission_positive_dialog_confirmation_negative - ) { _, _ -> - Log.i(TAG, "Declined") - } + doNavigate( + SubmissionResultPositiveOtherWarningFragmentDirections + .actionSubmissionResultPositiveOtherWarningFragmentToSubmissionResultFragment() + ) } - builder.create() - } - alertDialog.show() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSuccessDialogFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSuccessDialogFragment.kt index 2047a329d10..de42246623d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSuccessDialogFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionSuccessDialogFragment.kt @@ -33,7 +33,7 @@ class SubmissionSuccessDialogFragment : DialogFragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - binding.smVerificationSuccessButton.setOnClickListener { + binding.submissionVerificationSuccessButton.setOnClickListener { Log.i(TAG, "button OK clicked") dismiss() findNavController().navigate( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragment.kt index 18e7d982010..4f05de4597f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragment.kt @@ -48,6 +48,6 @@ class SubmissionTanFragment : BaseFragment() { // store locally viewModel.storeTeletan() - doNavigate(SubmissionTanFragmentDirections.actionSubmissionTanFragmentToDeviceRegistrationFragment()) + doNavigate(SubmissionTanFragmentDirections.actionSubmissionTanFragmentToSubmissionRegisterDeviceFragment()) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanViewModel.kt index 50f093824f5..22b63d2d9bb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTanViewModel.kt @@ -13,7 +13,11 @@ class SubmissionTanViewModel : ViewModel() { private const val TAN_LENGTH = 7 private val EXCLUDED_TAN_CHARS = listOf('0', 'O', 'I', '1') - private val VALID_TAN_CHARS = ('A'..'Z').plus('0'..'9').minus(EXCLUDED_TAN_CHARS) + private val VALID_TAN_CHARS = + ('a'..'z') + .plus('A'..'Z') + .plus('0'..'9') + .minus(EXCLUDED_TAN_CHARS) } val tan = MutableLiveData(null) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragment.kt index 4580017d4eb..aed4b341efc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragment.kt @@ -63,7 +63,10 @@ class SubmissionTestResultFragment : BaseFragment() { } binding.submissionTestResultButtonPositiveContinue.setOnClickListener { - viewModel.submitDiagnosisKeys() + doNavigate( + SubmissionTestResultFragmentDirections + .actionSubmissionResultFragmentToSubmissionResultPositiveOtherWarningFragment() + ) } binding.submissionTestResultButtonInvalidRemoveTest.setOnClickListener { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TanInput.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TanInput.kt index 04c323b62d1..d1a25184653 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TanInput.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/TanInput.kt @@ -15,7 +15,6 @@ import kotlinx.android.synthetic.main.view_tan_input.view.tan_input_textview_4 import kotlinx.android.synthetic.main.view_tan_input.view.tan_input_textview_5 import kotlinx.android.synthetic.main.view_tan_input.view.tan_input_textview_6 import kotlinx.android.synthetic.main.view_tan_input.view.tan_input_textview_7 -import java.util.Locale class TanInput(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) { @@ -46,7 +45,7 @@ class TanInput(context: Context, attrs: AttributeSet) : FrameLayout(context, att } private fun updateTan(text: CharSequence?) { - this.tan = text?.toString()?.toUpperCase(Locale.getDefault()) + this.tan = text?.toString() updateDigits() notifyListener() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt new file mode 100644 index 00000000000..6ca8fca2c8d --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/CircleProgress.kt @@ -0,0 +1,143 @@ +package de.rki.coronawarnapp.ui.view + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import androidx.core.content.ContextCompat +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.ViewCircleProgressBinding +import de.rki.coronawarnapp.risk.TimeVariables + +/** + * Used on the tracing details fragment without text and also on the risk card with the progress + * number in the circle. + * + * @param context + * @param attrs + * @param defStyleAttr + */ +class CircleProgress @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + companion object { + private const val START_ANGLE = 270f + private const val FULL_CIRCLE = 360f + private const val DEFAULT_WIDTH = 10f + private val DEFAULT_MAX_PROGRESS = TimeVariables.getDefaultRetentionPeriodInDays() + } + + private val circlePaint: Paint + private val progressPaint: Paint + private val rect = RectF() + + private var binding: ViewCircleProgressBinding + private var centerX: Float = 0f + private var centerY: Float = 0f + private var radius: Float = 0f + private var progressWidth: Float = 0f + private var disableText: Boolean = false + + /** + * Setter for progress. Text and icon depend on the progress value. + * The visibility is also influenced by the disableText attribute. + */ + var progress: Int = 0 + set(value) { + field = value + val body = binding.circleProgressBody + val icon = binding.circleProgressIcon + if (value == DEFAULT_MAX_PROGRESS) { + body.visibility = View.GONE + icon.visibility = View.VISIBLE + } else { + body.visibility = View.VISIBLE + icon.visibility = View.GONE + } + if (disableText) { + body.visibility = View.GONE + } else { + body.visibility = View.VISIBLE + body.text = value.toString() + } + invalidate() + } + + /** + * Initialise the view with the following attributes or some default values: + * - circleColor + * - textColor + * - disableText + * - progressWidth + */ + init { + setWillNotDraw(false) + binding = ViewCircleProgressBinding.inflate(LayoutInflater.from(context), this) + val styleAttrs = context.obtainStyledAttributes(attrs, R.styleable.CircleProgress) + // attribute circleColor; default = colorGreyLight + val circleColor = styleAttrs.getColor( + R.styleable.CircleProgress_secondaryColor, + ContextCompat.getColor(context, R.color.colorGreyLight) + ) + // attribute progressColor; default = colorPrimary + val progressColor = styleAttrs.getColor(R.styleable.CircleProgress_progressColor, + ContextCompat.getColor(context, R.color.colorPrimary)) + // attribute textColor; default = colorGrey + val textColor = styleAttrs.getColor(R.styleable.CircleProgress_textColor, + ContextCompat.getColor(context, R.color.colorGrey)) + // attribute disableText; default = true + disableText = styleAttrs.getBoolean(R.styleable.CircleProgress_disableText, false) + // attribute progressWidth; default = DEFAULT_WIDTH + progressWidth = styleAttrs.getFloat(R.styleable.CircleProgress_circleWidth, DEFAULT_WIDTH) + // attribute progress; default = 0 + progress = styleAttrs.getInt(R.styleable.CircleProgress_progress, 0) + // set textColor + val body = binding.circleProgressBody + body.setTextColor(textColor) + // circlePaint based on the attributes and default value + circlePaint = Paint().apply { + color = circleColor + style = Paint.Style.STROKE + strokeWidth = progressWidth + isAntiAlias = true + } + // progressPaint based on the attributes and default value + progressPaint = Paint().apply { + color = progressColor + style = Paint.Style.STROKE + strokeWidth = progressWidth + isAntiAlias = true + strokeCap = Paint.Cap.ROUND + } + styleAttrs.recycle() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + centerX = w.toFloat().div(2) + centerY = h.toFloat().div(2) + radius = w.toFloat().div(2).minus(progressWidth) + rect.set( + centerX.minus(radius), + centerY.minus(radius), + centerX.plus(radius), + centerY.plus(radius)) + super.onSizeChanged(w, h, oldw, oldh) + } + + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + canvas?.drawCircle(centerX, centerY, radius, circlePaint) + canvas?.drawArc( + rect, + START_ANGLE, + FULL_CIRCLE.times(progress).div(DEFAULT_MAX_PROGRESS), + false, + progressPaint) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/SimpleStepEntry.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/SimpleStepEntry.kt new file mode 100644 index 00000000000..6194d713af1 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/SimpleStepEntry.kt @@ -0,0 +1,29 @@ +package de.rki.coronawarnapp.ui.view + +import android.content.Context +import android.util.AttributeSet +import androidx.core.content.withStyledAttributes +import de.rki.coronawarnapp.R +import kotlinx.android.synthetic.main.include_step_entry_simple_body.view.* + +/** + * The [SimpleStepEntry] is a step entry with title and text as content. + */ +class SimpleStepEntry @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : StepEntry(context, attrs, defStyleAttr) { + + init { + inflate(context, R.layout.include_step_entry_simple_body, this) + + context.withStyledAttributes(attrs, R.styleable.SimpleStepEntry) { + simple_step_entry_title.text = + getText(R.styleable.SimpleStepEntry_simple_step_entry_title) + + simple_step_entry_body.text = + getText(R.styleable.SimpleStepEntry_simple_step_entry_text) + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt new file mode 100644 index 00000000000..0b1f13a3d64 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/StepEntry.kt @@ -0,0 +1,51 @@ +package de.rki.coronawarnapp.ui.view + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.content.withStyledAttributes +import de.rki.coronawarnapp.R +import kotlinx.android.synthetic.main.view_step_entry.view.* + +/** + * The [StepEntry] displays one step in a sequence. Children can be added to define the content. + */ +open class StepEntry @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + val body: FrameLayout? + + init { + inflate(context, R.layout.view_step_entry, this) + + body = findViewById(R.id.step_entry_wrapper_children) + + context.withStyledAttributes(attrs, R.styleable.StepEntry) { + val icon = getDrawable(R.styleable.StepEntry_step_entry_icon) + step_entry_icon.setImageDrawable(icon) + + val isFinal = getBoolean(R.styleable.StepEntry_step_entry_final, false) + step_entry_line.visibility = if (isFinal) { + View.INVISIBLE + } else { + View.VISIBLE + } + } + } + + override fun addView( + child: View?, + index: Int, + params: ViewGroup.LayoutParams? + ) { + if (body == null) + super.addView(child, index, params) + else + body.addView(child, index, params) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt index ca35fe3a3fe..ada13e7039f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SettingsViewModel.kt @@ -93,10 +93,12 @@ class SettingsViewModel : ViewModel() { } /** - * Update connection enabled + * Update bluetooth enabled + * + * @param value */ - fun refreshBluetoothEnabled() { - SettingsRepository.refreshBluetoothEnabled() + fun updateBluetoothEnabled(value: Boolean) { + SettingsRepository.updateBluetoothEnabled(value) } /** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt index e470de14a82..e1fda8bb4e7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt @@ -10,8 +10,8 @@ import de.rki.coronawarnapp.exception.report import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.SubmissionRepository -import de.rki.coronawarnapp.ui.register.ApiRequestState -import de.rki.coronawarnapp.ui.register.ScanStatus +import de.rki.coronawarnapp.ui.submission.ApiRequestState +import de.rki.coronawarnapp.ui.submission.ScanStatus import de.rki.coronawarnapp.util.formatter.TestResult import kotlinx.coroutines.launch import java.util.Date @@ -38,9 +38,6 @@ class SubmissionViewModel : ViewModel() { fun submitDiagnosisKeys() = executeRequestWithState(SubmissionService::asyncSubmitExposureKeys, _submissionState) - fun requestAuthCode() = - executeRequestWithState(SubmissionService::asyncRequestAuthCode, _authCodeState) - fun doDeviceRegistration() = executeRequestWithState(SubmissionService::asyncRegisterDevice, _registrationState) @@ -63,6 +60,8 @@ class SubmissionViewModel : ViewModel() { fun deregisterTestFromDevice() { deleteTestGUID() SubmissionService.deleteRegistrationToken() + LocalData.isAllowedToSubmitDiagnosisKeys(false) + LocalData.inititalTestResultReceivedTimestamp(0L) } private fun executeRequestWithState(apiRequest: suspend () -> Unit, state: MutableLiveData) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CallHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CallHelper.kt new file mode 100644 index 00000000000..783c1e7f1d1 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/CallHelper.kt @@ -0,0 +1,20 @@ +package de.rki.coronawarnapp.util + +import android.content.Intent +import android.net.Uri +import de.rki.coronawarnapp.ui.BaseFragment + +/** + * Helper object for intents triggering a phone call + * todo unify once necessary intents are final with share, external url and others + */ +object CallHelper { + fun call(fragment: BaseFragment, uri: String) { + fragment.startActivity( + Intent( + Intent.ACTION_DIAL, + Uri.parse(uri) + ) + ) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ConnectivityHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ConnectivityHelper.kt index cd3fa001e22..6d2bbcf58a8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ConnectivityHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ConnectivityHelper.kt @@ -1,21 +1,79 @@ package de.rki.coronawarnapp.util import android.bluetooth.BluetoothAdapter +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest -import android.provider.Settings import android.util.Log import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.report -// TODO integrate the ConnectivityHelper into the UI logic (e.g. RiskLevelTransaction) +/** + * Helper for connectivity statuses. + */ object ConnectivityHelper { private val TAG: String? = ConnectivityHelper::class.simpleName + /** + * Register bluetooth state change listener. + * + * @param context the context + * @param callback the bluetooth state callback + * + * @see [BluetoothAdapter.ACTION_STATE_CHANGED] + * @see [BluetoothCallback] + */ + fun registerBluetoothStatusCallback(context: Context, callback: BluetoothCallback) { + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent) { + val action = intent.action + if (BluetoothAdapter.ACTION_STATE_CHANGED == action) { + when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) { + BluetoothAdapter.STATE_OFF -> { + callback.onBluetoothUnavailable() + } + BluetoothAdapter.STATE_ON -> { + callback.onBluetoothAvailable() + } + } + } + } + } + callback.recevier = receiver + context.registerReceiver(callback.recevier, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) + // bluetooth state doesn't change when you register + if (isBluetoothEnabled()) + callback.onBluetoothAvailable() + else + callback.onBluetoothUnavailable() + } + + /** + * Register bluetooth state change listener. + * + * @param context the context + * @param callback the bluetooth state callback + * + * @see [BluetoothCallback] + */ + fun unregisterBluetoothStatusCallback(context: Context, callback: BluetoothCallback) { + context.unregisterReceiver(callback.recevier) + callback.recevier = null + } + + /** + * Unregister network state change callback. + * + * @param context the context + * @param callback the network state callback + * + * @see [ConnectivityManager] + */ fun unregisterNetworkStatusCallback(context: Context, callback: NetworkCallback) { try { val manager = @@ -30,6 +88,16 @@ object ConnectivityHelper { } } + /** + * Register network state change callback. + * + * @param context the context + * @param callback the network state callback + * + * @see [ConnectivityManager] + * @see [NetworkCapabilities] + * @see [NetworkRequest] + */ fun registerNetworkStatusCallback(context: Context, callback: NetworkCallback) { try { // If there are no Wi-Fi or mobile data presented when callback is registered @@ -52,6 +120,13 @@ object ConnectivityHelper { } } + /** + * Get bluetooth enabled status. + * + * @return current bluetooth status + * + * @see [BluetoothAdapter] + */ fun isBluetoothEnabled(): Boolean { val bAdapter = BluetoothAdapter.getDefaultAdapter() if (bAdapter == null) { @@ -61,24 +136,40 @@ object ConnectivityHelper { return bAdapter.isEnabled } - fun navigateToBluetoothSettings(context: Context, onFailure: () -> Unit? = {}) { - val intent = Intent() - intent.action = Settings.ACTION_BLUETOOTH_SETTINGS - try { - context.startActivity(intent) - } catch (e: Exception) { - e.report( - ExceptionCategory.CONNECTIVITY, - TAG, - null - ) - onFailure() - } + /** + * Abstract bluetooth state change callback. + * + * @see BroadcastReceiver + */ + abstract class BluetoothCallback { + var recevier: BroadcastReceiver? = null + + /** + * Called when bluetooth is turned on. + */ + abstract fun onBluetoothAvailable() + + /** + * Called when bluetooth is turned off. + */ + abstract fun onBluetoothUnavailable() } + /** + * Abstract network state change callback. + * + * @see [ConnectivityManager.NetworkCallback] + */ abstract class NetworkCallback : ConnectivityManager.NetworkCallback() { + + /** + * Called when network is available. + */ abstract fun onNetworkAvailable() + /** + * Called when network is unavailable or lost. + */ abstract fun onNetworkUnavailable() override fun onAvailable(network: Network?) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataRetentionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataRetentionHelper.kt index a7a878fa2ec..a6891315cfb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataRetentionHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataRetentionHelper.kt @@ -24,7 +24,6 @@ import android.util.Log import de.rki.coronawarnapp.storage.AppDatabase import de.rki.coronawarnapp.storage.FileStorageHelper import de.rki.coronawarnapp.storage.LocalData -import java.util.UUID /** * Helper for supplying functionality regarding Data Retention @@ -44,7 +43,6 @@ object DataRetentionHelper { LocalData.getSharedPreferenceInstance().edit().clear().apply() // Delete Database Instance AppDatabase.resetInstance(context) - LocalData.databasePassword(UUID.randomUUID().toString().toCharArray()) // Export File Reset FileStorageHelper.getAllFilesInKeyExportDirectory().forEach { it.delete() } Log.w(TAG, "CWA LOCAL DATA DELETION COMPLETED.") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt index f9f3fad38f5..6eacc39b5c2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DialogHelper.kt @@ -10,7 +10,7 @@ object DialogHelper { val title: Int, val message: Int, val positiveButton: Int, - val negativeButton: Int, + val negativeButton: Int? = null, val positiveButtonFunction: () -> Unit? = {}, val negativeButtonFunction: () -> Unit? = {} ) @@ -28,10 +28,12 @@ object DialogHelper { ) { _, _ -> dialogInstance.positiveButtonFunction() } - setNegativeButton( - dialogInstance.negativeButton - ) { _, _ -> - dialogInstance.negativeButtonFunction() + if (dialogInstance.negativeButton != null) { + setNegativeButton( + dialogInstance.negativeButton + ) { _, _ -> + dialogInstance.negativeButtonFunction() + } } } builder.create() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensions.kt index c4fe29eeaa6..80325f9529a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ProtoFormatConverterExtensions.kt @@ -9,6 +9,10 @@ object ProtoFormatConverterExtensions { private const val ROLLING_PERIOD = 144 private const val DEFAULT_TRANSMISSION_RISK_LEVEL = 1 + private const val MAXIMUM_KEYS = 14 + + fun List.limitKeyCount() = + this.sortedWith(compareBy({ it.rollingStartIntervalNumber })).asReversed().take(MAXIMUM_KEYS) fun List.transformKeyHistoryToExternalFormat() = this.map { KeyExportFormat.TemporaryExposureKey.newBuilder() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/SettingsNavigationHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/SettingsNavigationHelper.kt new file mode 100644 index 00000000000..d172d0ecd28 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/SettingsNavigationHelper.kt @@ -0,0 +1,49 @@ +package de.rki.coronawarnapp.util + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.Settings + +/** + * A helper to navigate to the os settings, used in different places in the application, + * e.g. settings, notification settings, tracing settings... + */ +object SettingsNavigationHelper { + + /** + * Navigate the user to the os connection settings. + * + * @param context + */ + fun toConnections(context: Context) { + val intent = Intent(Settings.ACTION_WIRELESS_SETTINGS) + context.startActivity(intent) + } + + /** + * Navigate the user to the os notification settings. + * + * @param context + */ + // todo has to be tested on API23 on a device + fun toNotifications(context: Context) { + val intent = Intent() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + intent.putExtra( + Settings.EXTRA_APP_PACKAGE, + context.packageName + ) + } else { + intent.putExtra( + "app_package", + context.packageName + ) + intent.putExtra("app_uid", context.applicationInfo.uid) + } + context.startActivity(intent) + } + + // todo navigate storage settings +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ShareHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ShareHelper.kt new file mode 100644 index 00000000000..c2b07480683 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ShareHelper.kt @@ -0,0 +1,14 @@ +package de.rki.coronawarnapp.util + +import android.content.Intent +import de.rki.coronawarnapp.ui.BaseFragment + +object ShareHelper { + fun shareText(fragment: BaseFragment, text: String, title: String?) { + fragment.startActivity(Intent.createChooser(Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, text) + }, title)) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt index 0ad6e41b509..20392db037e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterRiskHelper.kt @@ -264,21 +264,45 @@ fun formatNextUpdate( } } +/** + * Formats the risk details subtitle text display depending on risk level + * + * @param riskLevelScore + * @return + */ +fun formatRiskDetailsRiskLevelSubtitle(riskLevelScore: Int?): String { + val appContext = CoronaWarnApplication.getAppContext() + return when (riskLevelScore) { + RiskLevelConstants.LOW_LEVEL_RISK, + RiskLevelConstants.INCREASED_RISK -> appContext.getString(R.string.risk_details_subtitle_infection_risk_past) + else -> appContext.getString(R.string.risk_details_subtitle_infection_risk) + } +} + /** * Formats the risk details text display for each risk level * * @param riskLevelScore + * @param daysSinceLastExposure * @return */ -fun formatRiskDetailsRiskLevelBody(riskLevelScore: Int?): String { +fun formatRiskDetailsRiskLevelBody(riskLevelScore: Int?, daysSinceLastExposure: Int?): String { // TODO replace lorem ipsum by text from rki val appContext = CoronaWarnApplication.getAppContext() + val daysArg = daysSinceLastExposure.toString() + return when (riskLevelScore) { - RiskLevelConstants.INCREASED_RISK -> appContext.getString(R.string.lorem_ipsum) - RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS -> appContext.getString(R.string.lorem_ipsum) - RiskLevelConstants.NO_CALCULATION_POSSIBLE_TRACING_OFF -> appContext.getString(R.string.lorem_ipsum) - RiskLevelConstants.LOW_LEVEL_RISK -> appContext.getString(R.string.lorem_ipsum) - else -> appContext.getString(R.string.lorem_ipsum) + RiskLevelConstants.INCREASED_RISK -> { + appContext.getString(R.string.risk_details_information_body_increased_risk) + .format(daysArg) + } + RiskLevelConstants.UNKNOWN_RISK_OUTDATED_RESULTS -> + appContext.getString(R.string.risk_details_information_body_outdated_risk) + RiskLevelConstants.LOW_LEVEL_RISK -> + appContext.getString(R.string.risk_details_information_body_low_risk) + RiskLevelConstants.UNKNOWN_RISK_INITIAL -> + appContext.getString(R.string.risk_details_information_body_unknown_risk) + else -> "" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelper.kt index 5bdb444f9c1..41def653e3c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSettingsHelper.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.util.formatter import android.graphics.drawable.Drawable +import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R /*Texter*/ @@ -10,7 +11,7 @@ import de.rki.coronawarnapp.R * Formats the text display of settings item status depending on flag provided * * @param value - * @return + * @return String */ fun formatStatus(value: Boolean): String = formatText( value, @@ -19,28 +20,63 @@ fun formatStatus(value: Boolean): String = formatText( ) /** - * Formats the main text display of tracing status depending on tracing status + * Formats the text display of settings notification status depending on notification values * - * @param tracing + * @param notifications + * @param notificationsRisk + * @param notificationsTest * @return */ -fun formatTracingText(tracing: Boolean): String = formatText( - tracing, - R.string.settings_tracing_body_active, - R.string.settings_tracing_body_inactive -) +fun formatNotificationsStatusText( + notifications: Boolean, + notificationsRisk: Boolean, + notificationsTest: Boolean +): String = + formatStatus((notifications && (notificationsRisk || notificationsTest))) + +/** + * Change the tracing text in the row based on the tracing status. + * + * @param tracing + * @param bluetooth + * @param connection + * @return String + */ +fun formatTracingStatusText(tracing: Boolean, bluetooth: Boolean, connection: Boolean): String { + val appContext = CoronaWarnApplication.getAppContext() + return when (tracingStatusHelper(tracing, bluetooth, connection)) { + TracingStatusHelper.CONNECTION, TracingStatusHelper.BLUETOOTH -> + appContext.getString(R.string.settings_tracing_status_restricted) + TracingStatusHelper.TRACING_ACTIVE -> + appContext.getString(R.string.settings_tracing_status_active) + TracingStatusHelper.TRACING_INACTIVE -> + appContext.getString(R.string.settings_tracing_status_inactive) + else -> "" + } +} /** * Format the settings tracing description text display depending on tracing status * * @param tracing - * @return + * @param bluetooth + * @param connection + * @return String */ -fun formatTracingDescription(tracing: Boolean): String = formatText( - tracing, - R.string.settings_tracing_body_active_description, - R.string.settings_tracing_body_inactive_description -) +fun formatTracingDescription(tracing: Boolean, bluetooth: Boolean, connection: Boolean): String { + val appContext = CoronaWarnApplication.getAppContext() + return when (tracingStatusHelper(tracing, bluetooth, connection)) { + TracingStatusHelper.CONNECTION -> + appContext.getString(R.string.settings_tracing_body_connection_inactive) + TracingStatusHelper.BLUETOOTH -> + appContext.getString(R.string.settings_tracing_body_bluetooth_inactive) + TracingStatusHelper.TRACING_ACTIVE -> + appContext.getString(R.string.settings_tracing_body_active) + TracingStatusHelper.TRACING_INACTIVE -> + appContext.getString(R.string.settings_tracing_body_inactive) + else -> "" + } +} /** * Formats the settings notifications title display depending on notifications status @@ -66,46 +102,199 @@ fun formatNotificationsDescription(notifications: Boolean): String = formatText( R.string.settings_notifications_body_inactive ) +/** + * Formats the tracing body depending on the tracing status and the days since last exposure. + * + * @param tracing + * @param activeTracingDaysInRetentionPeriod + * @return String + */ +fun formatTracingStatusBody(tracing: Boolean, activeTracingDaysInRetentionPeriod: Long): String { + val appContext = CoronaWarnApplication.getAppContext() + val daysArg = activeTracingDaysInRetentionPeriod.toString() + return if (tracing) { + appContext.getString(R.string.settings_tracing_status_body_active) + .format(daysArg) + } else { + appContext.getString(R.string.settings_tracing_status_body_inactive) + .format(daysArg) + } +} + /*Styler*/ /** * Formats the settings icon color depending on flag provided * * @param active - * @return + * @return Int */ fun formatIconColor(active: Boolean): Int = formatColor(active, R.color.settingsIconActive, R.color.settingsIconInactive) +/** + * Formats the settings icon color for notifications depending on notification values + * + * @param notifications + * @param notificationsRisk + * @param notificationsTest + * @return Int + */ +fun formatIconColor( + notifications: Boolean, + notificationsRisk: Boolean, + notificationsTest: Boolean +): Int = + formatIconColor((notifications && (notificationsRisk || notificationsTest))) + +/** + * Formats the tracing switch status based on the tracing status + * + * @param tracing + * @param bluetooth + * @param connection + * @return Boolean + */ +fun formatTracingSwitch(tracing: Boolean, bluetooth: Boolean, connection: Boolean): Boolean { + return when (tracingStatusHelper(tracing, bluetooth, connection)) { + TracingStatusHelper.TRACING_ACTIVE -> true + else -> false + } +} + +/** + * Formats the tracing switch enabled status based on the tracing status + * + * @param tracing + * @param bluetooth + * @param connection + * @return Boolean + */ +fun formatTracingSwitchEnabled(tracing: Boolean, bluetooth: Boolean, connection: Boolean): Boolean { + return when (tracingStatusHelper(tracing, bluetooth, connection)) { + TracingStatusHelper.TRACING_ACTIVE, TracingStatusHelper.TRACING_INACTIVE -> true + else -> false + } +} + /** * Formats the main tracing icon depending on tracing status * * @param tracing - * @return + * @param bluetooth + * @param connection + * @return Drawable */ -fun formatTracingIcon(tracing: Boolean): Drawable? = - formatDrawable(tracing, R.drawable.ic_tracing_on, R.drawable.ic_tracing_off) +fun formatTracingIcon(tracing: Boolean, bluetooth: Boolean, connection: Boolean): Drawable? { + val appContext = CoronaWarnApplication.getAppContext() + return when (tracingStatusHelper(tracing, bluetooth, connection)) { + TracingStatusHelper.BLUETOOTH -> + appContext.getDrawable(R.drawable.ic_settings_tracing_bluetooth_inactive) + TracingStatusHelper.CONNECTION -> + appContext.getDrawable(R.drawable.ic_settings_tracing_connection_inactive) + TracingStatusHelper.TRACING_ACTIVE -> + appContext.getDrawable(R.drawable.ic_settings_tracing_active) + else -> + appContext.getDrawable(R.drawable.ic_settings_tracing_inactive) + } +} /** * Formats the main tracing icon color depending on tracing status * * @param tracing - * @return + * @param bluetooth + * @param connection + * @return Int */ -fun formatTracingIconColor(tracing: Boolean): Int = - formatColor(tracing, R.color.tracingIconActive, R.color.tracingIconInactive) +fun formatTracingIconColor(tracing: Boolean, bluetooth: Boolean, connection: Boolean): Int { + val appContext = CoronaWarnApplication.getAppContext() + return when (tracingStatusHelper(tracing, bluetooth, connection)) { + TracingStatusHelper.TRACING_ACTIVE -> + appContext.getColor(R.color.tracingIconActive) + else -> + appContext.getColor(R.color.tracingIconInactive) + } +} /** * Formats the settings tracing details illustration depending on tracing status * * @param tracing - * @return + * @param bluetooth + * @param connection + * @return Drawable */ -fun formatTracingImage(tracing: Boolean): Drawable? = - formatDrawable( - tracing, - R.drawable.ic_settings_illustration_tracing_on, - R.drawable.ic_settings_illustration_tracing_off +fun formatTracingStatusImage(tracing: Boolean, bluetooth: Boolean, connection: Boolean): Drawable? { + val appContext = CoronaWarnApplication.getAppContext() + return when (tracingStatusHelper(tracing, bluetooth, connection)) { + TracingStatusHelper.BLUETOOTH -> + appContext.getDrawable(R.drawable.ic_settings_illustration_bluetooth_off) + TracingStatusHelper.CONNECTION -> + appContext.getDrawable(R.drawable.ic_settings_illustration_connection_off) + TracingStatusHelper.TRACING_ACTIVE -> + appContext.getDrawable(R.drawable.ic_settings_illustration_tracing_on) + else -> + appContext.getDrawable(R.drawable.ic_settings_illustration_tracing_off) + } +} + +/** + * Change the visibility of the connection card based on the tracing status. + * + * @param tracing + * @param bluetooth + * @param connection + * @return Int + */ +fun formatTracingStatusConnection(tracing: Boolean, bluetooth: Boolean, connection: Boolean): Int = + formatVisibility( + tracingStatusHelper( + tracing, + bluetooth, + connection + ) == TracingStatusHelper.CONNECTION + ) + +/** + * Change the visibility of the bluetooth card based on the tracing status. + * + * @param tracing + * @param bluetooth + * @param connection + * @return Int + */ +fun formatTracingStatusVisibilityBluetooth( + tracing: Boolean, + bluetooth: Boolean, + connection: Boolean +): Int = + formatVisibility( + tracingStatusHelper( + tracing, + bluetooth, + connection + ) == TracingStatusHelper.BLUETOOTH + ) + +/** + * Change the visibility of the tracing text based on the tracing status. + * + * @param tracing + * @param bluetooth + * @param connection + * @return Int + */ +fun formatTracingStatusVisibilityTracing( + tracing: Boolean, + bluetooth: Boolean, + connection: Boolean +): Int { + val tracingStatus = tracingStatusHelper(tracing, bluetooth, connection) + return formatVisibility( + tracingStatus == TracingStatusHelper.TRACING_ACTIVE || + tracingStatus == TracingStatusHelper.TRACING_INACTIVE ) +} /** * Formats the settings notifications details illustration depending on notifications status diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt index 53ad88c011d..5d19352eef0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt @@ -6,19 +6,18 @@ import android.graphics.drawable.Drawable import android.view.View import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.ui.register.ApiRequestState -import de.rki.coronawarnapp.ui.submission.TestResultStatus +import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.util.formatter.TestResult.INVALID import de.rki.coronawarnapp.util.formatter.TestResult.NEGATIVE import de.rki.coronawarnapp.util.formatter.TestResult.PENDING import de.rki.coronawarnapp.util.formatter.TestResult.POSITIVE import java.util.Date -fun formatTestResultStatusVisibility(testResultStatus: TestResultStatus?): Int = - formatVisibility(testResultStatus != TestResultStatus.SUCCESS) +fun formatTestResultSpinnerVisible(testResultStatus: ApiRequestState?): Int = + formatVisibility(testResultStatus != ApiRequestState.SUCCESS) -fun formatTestResultHeadingTextVisible(testResult: TestResult?): Int = - formatVisibility(testResult != PENDING) +fun formatTestResultVisible(testResultStatus: ApiRequestState?): Int = + formatVisibility(testResultStatus == ApiRequestState.SUCCESS) fun formatTestResultVirusNameTextVisible(testResult: TestResult?): Int { return when (testResult) { @@ -56,23 +55,19 @@ fun formatTestStatusIcon(testResult: TestResult?): Drawable? { val appContext = CoronaWarnApplication.getAppContext() // TODO Replace with real drawables when design is finished return when (testResult) { - PENDING -> appContext.getDrawable(R.drawable.ic_risk_details_stethoscope) - POSITIVE -> appContext.getDrawable(R.drawable.rectangle) - NEGATIVE -> appContext.getDrawable(R.drawable.circle) - INVALID -> appContext.getDrawable(R.drawable.button) - else -> appContext.getDrawable(R.drawable.button) + PENDING -> appContext.getDrawable(R.drawable.ic_test_result_illustration_pending) + POSITIVE -> appContext.getDrawable(R.drawable.ic_test_result_illustration_positive) + NEGATIVE -> appContext.getDrawable(R.drawable.ic_main_illustration_negative) + INVALID -> appContext.getDrawable(R.drawable.ic_test_result_illustration_invalid) + else -> appContext.getDrawable(R.drawable.ic_test_result_illustration_invalid) } } fun formatTestResultInvalidStatusTextVisible(testResult: TestResult?): Int = formatVisibility(testResult == INVALID) -fun formatTestResultRegisteredAtVisible(testResult: TestResult?): Int { - return when (testResult) { - POSITIVE, NEGATIVE, INVALID -> View.VISIBLE - else -> View.GONE - } -} +fun formatTestResultPendingStatusTextVisible(testResult: TestResult?): Int = + formatVisibility(testResult == PENDING) fun formatTestResultRegisteredAtText(registeredAt: Date?): String { val appContext = CoronaWarnApplication.getAppContext() @@ -119,22 +114,6 @@ fun formatSubmissionStatusCardContentButtonText(testResult: TestResult?): String } } -fun formatSubmissionStatusCardContentButtonColor(testResult: TestResult?): Int { - val appContext = CoronaWarnApplication.getAppContext() - return when (testResult) { - INVALID, NEGATIVE, POSITIVE -> appContext.getColor(R.color.tracingIconActive) - else -> appContext.getColor(R.color.colorLight) - } -} - -fun formatSubmissionStatusCardContentButtonTextColor(testResult: TestResult?): Int { - val appContext = CoronaWarnApplication.getAppContext() - return when (testResult) { - INVALID, NEGATIVE, POSITIVE -> appContext.getColor(R.color.colorLight) - else -> appContext.getColor(R.color.tracingIconActive) - } -} - fun formatSubmissionStatusCardContentStatusTextVisible(testResult: TestResult?): Int { return when (testResult) { POSITIVE, NEGATIVE, INVALID -> View.VISIBLE @@ -179,3 +158,12 @@ fun formatSubmissionTanButtonTextColor(isValidTanFormat: Boolean) = formatColor( R.color.textColorLight, R.color.colorGreyDisabled ) + +fun formatShowSubmissionStatusCard(testResult: TestResult?): Int = + formatVisibility(testResult != POSITIVE) + +fun formatShowSubmissionStatusPositiveCard(testResult: TestResult?): Int = + formatVisibility(testResult == POSITIVE) + +fun formatShowRiskStatusCard(testResult: TestResult?): Int = + formatVisibility(testResult != POSITIVE) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/TracingStatusHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/TracingStatusHelper.kt new file mode 100644 index 00000000000..f7328e1aa89 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/TracingStatusHelper.kt @@ -0,0 +1,47 @@ +package de.rki.coronawarnapp.util.formatter + +/** + * This stati are used for the first row in the main fragment and in the tracing settings, + * to change the ui depending on the different system stati. + */ +object TracingStatusHelper { + const val TRACING_ACTIVE = 0 + const val TRACING_INACTIVE = 1 + const val BLUETOOTH = 2 + const val CONNECTION = 3 +} + +/** + * The following table explains the different stati which can appear in the ui. + * This follows this prioritization: Tracing, Bluetooth, Connection + * Connection will only be relevant in one exact case, Bluetooth is relevant in two different cases, + * but independently from the Connection status. And in every other case Tracing will be shown. + * + * | Tracing | Bluetooth | Connection | Result | + * |---------|-----------|------------|------------| + * | OFF | OFF | ON | TRACING* | + * | OFF | ON | OFF | TRACING* | + * | OFF | ON | ON | TRACING* | + * | OFF | OFF | OFF | TRACING* | + * | ON | ON | ON | TRACING | + * | ON | OFF | ON | BLUETOOTH | + * | ON | OFF | OFF | BLUETOOTH | + * | ON | ON | OFF | CONNECTION | + * *circle has to be disabled via another formatter + * + * @param tracing + * @param bluetooth + * @param connection + * @return Int + */ +fun tracingStatusHelper(tracing: Boolean, bluetooth: Boolean, connection: Boolean): Int { + return if (tracing && bluetooth && !connection) { + TracingStatusHelper.CONNECTION + } else if (tracing && !bluetooth) { + TracingStatusHelper.BLUETOOTH + } else if (tracing) { + TracingStatusHelper.TRACING_ACTIVE + } else { + TracingStatusHelper.TRACING_INACTIVE + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt index ebfba90caa4..c4ab75ad0ab 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt @@ -24,16 +24,30 @@ import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKeys import de.rki.coronawarnapp.CoronaWarnApplication +import java.security.KeyStore +/** + * Key Store and Password Access + */ object SecurityHelper { private const val SHARED_PREF_NAME = "shared_preferences_cwa" private val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC private val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec) + private const val AndroidKeyStore = "AndroidKeyStore" + + private val keyStore: KeyStore by lazy { + KeyStore.getInstance(AndroidKeyStore).also { + it.load(null) + } + } val globalEncryptedSharedPreferencesInstance: SharedPreferences by lazy { CoronaWarnApplication.getAppContext().getEncryptedSharedPrefs(SHARED_PREF_NAME) } + /** + * Initializes the private encrypted key store + */ private fun Context.getEncryptedSharedPrefs(fileName: String) = EncryptedSharedPreferences .create( fileName, @@ -42,4 +56,12 @@ object SecurityHelper { EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) + + /** + * Retrieves the Master Key from the Android KeyStore to use in SQLCipher + */ + fun getDBPassword() = keyStore + .getKey(masterKeyAlias, null) + .toString() + .toCharArray() } diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_illustration_overview.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_illustration_overview.xml new file mode 100644 index 00000000000..44126e7a717 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_illustration_overview.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_overview_1.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_1.xml new file mode 100644 index 00000000000..3201d47f539 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_1.xml @@ -0,0 +1,18 @@ + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_overview_2.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_2.xml new file mode 100644 index 00000000000..d4be2c81cc2 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_2.xml @@ -0,0 +1,18 @@ + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_main_overview_3.xml b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_3.xml new file mode 100644 index 00000000000..cf2dfa5efe1 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_main_overview_3.xml @@ -0,0 +1,18 @@ + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days.xml new file mode 100644 index 00000000000..4b9a19681a7 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days.xml @@ -0,0 +1,12 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_14.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_14.xml index 7c205a9e13a..1d6799296b4 100644 --- a/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_14.xml +++ b/Corona-Warn-App/src/main/res/drawable/ic_risk_card_saved_days_14.xml @@ -8,5 +8,5 @@ android:fillType="nonZero" android:pathData="M20.7489,10C15.2379,10 10.7489,14.489 10.7489,20C10.7489,25.511 15.2379,30 20.7489,30C26.2599,30 30.7489,25.511 30.7489,20C30.7489,14.489 26.2599,10 20.7489,10ZM20.7489,12C25.179,12 28.7489,15.5699 28.7489,20C28.7489,24.4301 25.179,28 20.7489,28C16.3188,28 12.7489,24.4301 12.7489,20C12.7489,15.5699 16.3188,12 20.7489,12ZM25.0419,16.293L18.7489,22.5859L16.4559,20.293L15.0419,21.707L18.7489,25.4141L26.4559,17.707L25.0419,16.293Z" android:strokeWidth="1" - android:strokeColor="#00000000" /> + android:strokeColor="#ff0000" /> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_risk_details_share.xml b/Corona-Warn-App/src/main/res/drawable/ic_risk_details_share.xml new file mode 100644 index 00000000000..1a8cf443f49 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_risk_details_share.xml @@ -0,0 +1,12 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_settings_illustration_bluetooth_off.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_illustration_bluetooth_off.xml new file mode 100644 index 00000000000..326e296d851 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_settings_illustration_bluetooth_off.xml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_settings_illustration_connection_off.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_illustration_connection_off.xml new file mode 100644 index 00000000000..01661cf05c6 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_settings_illustration_connection_off.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_tracing_on.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_active.xml similarity index 100% rename from Corona-Warn-App/src/main/res/drawable/ic_tracing_on.xml rename to Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_active.xml diff --git a/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_bluetooth.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_bluetooth.xml new file mode 100644 index 00000000000..66c42c5e858 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_bluetooth.xml @@ -0,0 +1,12 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_bluetooth_inactive.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_bluetooth_inactive.xml new file mode 100644 index 00000000000..c241107eb76 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_bluetooth_inactive.xml @@ -0,0 +1,12 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_connection.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_connection.xml new file mode 100644 index 00000000000..734f6ebf890 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_connection.xml @@ -0,0 +1,12 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_connection_inactive.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_connection_inactive.xml new file mode 100644 index 00000000000..42e277ea0e2 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_connection_inactive.xml @@ -0,0 +1,12 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_tracing_off.xml b/Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_inactive.xml similarity index 100% rename from Corona-Warn-App/src/main/res/drawable/ic_tracing_off.xml rename to Corona-Warn-App/src/main/res/drawable/ic_settings_tracing_inactive.xml diff --git a/Corona-Warn-App/src/main/res/drawable/ic_step_1.xml b/Corona-Warn-App/src/main/res/drawable/ic_step_1.xml new file mode 100644 index 00000000000..45b76198137 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_step_1.xml @@ -0,0 +1,6 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_step_2.xml b/Corona-Warn-App/src/main/res/drawable/ic_step_2.xml new file mode 100644 index 00000000000..64729728a7d --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_step_2.xml @@ -0,0 +1,6 @@ + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_step_background.xml b/Corona-Warn-App/src/main/res/drawable/ic_step_background.xml new file mode 100644 index 00000000000..b1270b99fb0 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_step_background.xml @@ -0,0 +1,19 @@ + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_submission_illustration_thanks.xml b/Corona-Warn-App/src/main/res/drawable/ic_submission_illustration_thanks.xml new file mode 100644 index 00000000000..911a77ad58b --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_submission_illustration_thanks.xml @@ -0,0 +1,750 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_invalid.xml b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_invalid.xml new file mode 100644 index 00000000000..16d54eb8eb7 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_invalid.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_negative.xml b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_negative.xml new file mode 100644 index 00000000000..358b6f85c4e --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_negative.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_pending.xml b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_pending.xml new file mode 100644 index 00000000000..8feb68e65b6 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_pending.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_positive.xml b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_positive.xml new file mode 100644 index 00000000000..d817b79c812 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_test_result_illustration_positive.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Corona-Warn-App/src/main/res/layout/fragment_information_about.xml b/Corona-Warn-App/src/main/res/layout/fragment_information_about.xml index 22505136a4e..e222be48e28 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_information_about.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_information_about.xml @@ -25,31 +25,26 @@ app:title="@{@string/information_about_title}" /> - - + app:layout_constraintTop_toBottomOf="@+id/information_about_body_emphasized" /> + + + android:text="@string/information_contact_body_phone" + app:layout_constraintEnd_toEndOf="@+id/guideline_end" + app:layout_constraintStart_toStartOf="@+id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/information_contact_navigation_row_phone" /> + android:layout_marginTop="@dimen/spacing_normal" + android:text="@string/information_contact_body_open" + app:layout_constraintEnd_toEndOf="@+id/guideline_end" + app:layout_constraintStart_toStartOf="@+id/guideline_start" + app:layout_constraintTop_toBottomOf="@+id/information_contact_body_phone" /> + + + - + @@ -94,7 +95,7 @@ style="@style/textLargeEmphasized" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@{FormatterSettingsHelper.formatTracingText(tracingViewModel.isTracingEnabled())}" + android:text="@{FormatterSettingsHelper.formatTracingDescription(tracingViewModel.isTracingEnabled(), settingsViewModel.isBluetoothEnabled(), settingsViewModel.isConnectionEnabled())}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" @@ -105,8 +106,8 @@ android:layout_width="@dimen/icon_size_main_card" android:layout_height="@dimen/icon_size_main_card" android:importantForAccessibility="no" - android:src="@{FormatterSettingsHelper.formatTracingIcon(tracingViewModel.isTracingEnabled())}" - android:tint="@{FormatterSettingsHelper.formatTracingIconColor(tracingViewModel.isTracingEnabled())}" + android:src="@{FormatterSettingsHelper.formatTracingIcon(tracingViewModel.isTracingEnabled(), settingsViewModel.isBluetoothEnabled(), settingsViewModel.isConnectionEnabled())}" + android:tint="@{FormatterSettingsHelper.formatTracingIconColor(tracingViewModel.isTracingEnabled(), settingsViewModel.isBluetoothEnabled(), settingsViewModel.isConnectionEnabled())}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -125,6 +126,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/fragment_onboarding_notifications.xml b/Corona-Warn-App/src/main/res/layout/fragment_onboarding_notifications.xml index b28518783ed..1a52491df69 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_onboarding_notifications.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_onboarding_notifications.xml @@ -21,7 +21,6 @@ android:layout_width="0dp" android:layout_height="0dp" app:body="@{@string/onboarding_notifications_body}" - app:bodyEmphasized="@{@string/onboarding_notifications_body}" app:headline="@{@string/onboarding_notifications_headline}" app:illustration="@{@drawable/ic_onboarding_illustration_notification}" app:layout_constraintBottom_toTopOf="@+id/guideline_bottom" @@ -31,26 +30,15 @@ app:subtitle="@{@string/onboarding_notifications_subtitle}" />