diff --git a/.github/workflows/appcenter_abnahme.yml b/.github/workflows/appcenter_abnahme.yml index 484e1b5b..83fa2e9a 100644 --- a/.github/workflows/appcenter_abnahme.yml +++ b/.github/workflows/appcenter_abnahme.yml @@ -3,6 +3,8 @@ name: Android Build on: push: branches: [ master, develop ] + pull_request: + branches: [ master ] jobs: build: diff --git a/.github/workflows/appcenter_dev.yml b/.github/workflows/appcenter_dev.yml index f51031b6..162892a6 100644 --- a/.github/workflows/appcenter_dev.yml +++ b/.github/workflows/appcenter_dev.yml @@ -3,6 +3,8 @@ name: Android Build on: push: branches: [ master, develop ] + pull_request: + branches: [ master ] jobs: build: diff --git a/.github/workflows/appcenter_prod.yml b/.github/workflows/appcenter_prod.yml index a0435bc0..6e51b29f 100644 --- a/.github/workflows/appcenter_prod.yml +++ b/.github/workflows/appcenter_prod.yml @@ -3,6 +3,8 @@ name: Android Build AppCenter Prod on: push: branches: [ master, develop ] + pull_request: + branches: [ master ] jobs: build: diff --git a/.github/workflows/appcenter_test.yml b/.github/workflows/appcenter_test.yml index cbee54c5..1bf3f23f 100644 --- a/.github/workflows/appcenter_test.yml +++ b/.github/workflows/appcenter_test.yml @@ -3,6 +3,8 @@ name: Android Build on: push: branches: [ master, develop ] + pull_request: + branches: [ master ] jobs: build: diff --git a/app/build.gradle b/app/build.gradle index b4a0e5bb..d991be12 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,8 +40,8 @@ android { applicationId "ch.admin.bag.dp3t" minSdkVersion 23 targetSdkVersion 30 - versionCode 11000 - versionName "1.1.0" + versionCode 11200 + versionName "1.1.2" resConfigs "en", "fr", "de", "it", "pt", "es", "sq", "bs", "hr", "sr", "rm", "tr", "ti" buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' diff --git a/app/src/log/java/ch/admin/bag/dp3t/debug/TracingStatusWrapper.java b/app/src/log/java/ch/admin/bag/dp3t/debug/TracingStatusWrapper.java index 5e470dfd..0cf8e435 100644 --- a/app/src/log/java/ch/admin/bag/dp3t/debug/TracingStatusWrapper.java +++ b/app/src/log/java/ch/admin/bag/dp3t/debug/TracingStatusWrapper.java @@ -9,7 +9,7 @@ */ package ch.admin.bag.dp3t.debug; -import ch.admin.bag.dp3t.main.model.DefaultTracingStatusWrapper; +import ch.admin.bag.dp3t.home.model.DefaultTracingStatusWrapper; public class TracingStatusWrapper extends DefaultTracingStatusWrapper { // default implementation diff --git a/app/src/main/java/ch/admin/bag/dp3t/MainApplication.java b/app/src/main/java/ch/admin/bag/dp3t/MainApplication.java index 3b5666bd..e2067b46 100644 --- a/app/src/main/java/ch/admin/bag/dp3t/MainApplication.java +++ b/app/src/main/java/ch/admin/bag/dp3t/MainApplication.java @@ -147,6 +147,7 @@ private static void createNewContactNotification(Context context, int contactId) .setPriority(NotificationCompat.PRIORITY_MAX) .setSmallIcon(R.drawable.ic_begegnungen) .setContentIntent(pendingIntent) + .setOngoing(true) .setAutoCancel(true) .build(); diff --git a/app/src/main/java/ch/admin/bag/dp3t/contacts/HistoryFragment.java b/app/src/main/java/ch/admin/bag/dp3t/contacts/HistoryFragment.java index af490ce8..352779f4 100644 --- a/app/src/main/java/ch/admin/bag/dp3t/contacts/HistoryFragment.java +++ b/app/src/main/java/ch/admin/bag/dp3t/contacts/HistoryFragment.java @@ -7,11 +7,11 @@ * * SPDX-License-Identifier: MPL-2.0 */ - package ch.admin.bag.dp3t.contacts; import android.os.Bundle; import android.view.View; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; @@ -20,7 +20,11 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import java.text.SimpleDateFormat; +import java.util.Locale; + import ch.admin.bag.dp3t.R; +import ch.admin.bag.dp3t.storage.SecureStorage; import ch.admin.bag.dp3t.viewmodel.TracingViewModel; public class HistoryFragment extends Fragment { @@ -51,6 +55,11 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); setupRecyclerView(view); + + SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss", Locale.GERMAN); + TextView nextFakeRequestView = view.findViewById(R.id.history_next_fake_request); + nextFakeRequestView.setText(getString(R.string.synchronizations_view_next_fake_request) + + sdf.format(SecureStorage.getInstance(view.getContext()).getTDummy())); } private void setupRecyclerView(View view) { diff --git a/app/src/main/java/ch/admin/bag/dp3t/networking/ConfigWorker.java b/app/src/main/java/ch/admin/bag/dp3t/networking/ConfigWorker.java index 748f6ea6..8032f402 100644 --- a/app/src/main/java/ch/admin/bag/dp3t/networking/ConfigWorker.java +++ b/app/src/main/java/ch/admin/bag/dp3t/networking/ConfigWorker.java @@ -94,6 +94,8 @@ private static void loadConfig(Context context) throws IOException, ResponseErro SecureStorage secureStorage = SecureStorage.getInstance(context); secureStorage.setDoForceUpdate(config.getDoForceUpdate()); + secureStorage.setWhatToDoPositiveTestTexts(config.getWhatToDoPositiveTestTexts()); + InfoBoxModel info = config.getInfoBox(context.getString(R.string.language_key)); if (info != null) { if (info.getInfoId() == null || !info.getInfoId().equals(secureStorage.getInfoboxId())) { diff --git a/app/src/main/java/ch/admin/bag/dp3t/networking/models/ConfigResponseModel.java b/app/src/main/java/ch/admin/bag/dp3t/networking/models/ConfigResponseModel.java index 2f30f283..ad5fdf5b 100644 --- a/app/src/main/java/ch/admin/bag/dp3t/networking/models/ConfigResponseModel.java +++ b/app/src/main/java/ch/admin/bag/dp3t/networking/models/ConfigResponseModel.java @@ -13,6 +13,7 @@ public class ConfigResponseModel { private boolean forceUpdate; private InfoBoxModelCollection infoBox; + private WhatToDoPositiveTestTextsCollection whatToDoPositiveTestTexts; private SdkConfigModel androidGaenSdkConfig; public boolean getDoForceUpdate() { @@ -32,4 +33,8 @@ public SdkConfigModel getSdkConfig() { return androidGaenSdkConfig; } + public WhatToDoPositiveTestTextsCollection getWhatToDoPositiveTestTexts() { + return whatToDoPositiveTestTexts; + } + } diff --git a/app/src/main/java/ch/admin/bag/dp3t/networking/models/FaqEntryModel.java b/app/src/main/java/ch/admin/bag/dp3t/networking/models/FaqEntryModel.java new file mode 100644 index 00000000..1c55f41e --- /dev/null +++ b/app/src/main/java/ch/admin/bag/dp3t/networking/models/FaqEntryModel.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ +package ch.admin.bag.dp3t.networking.models; + +public class FaqEntryModel { + + private String title; + private String text; + private String iconAndroid; + + /* optional */ + private String linkTitle; + private String linkUrl; + + public String getTitle() { + return title; + } + + public String getText() { + return text; + } + + public String getIconAndroid() { + return iconAndroid; + } + + public String getLinkTitle() { + return linkTitle; + } + + public String getLinkUrl() { + return linkUrl; + } + +} \ No newline at end of file diff --git a/app/src/main/java/ch/admin/bag/dp3t/networking/models/WhatToDoPositiveTestTextsCollection.java b/app/src/main/java/ch/admin/bag/dp3t/networking/models/WhatToDoPositiveTestTextsCollection.java new file mode 100644 index 00000000..330e6c7b --- /dev/null +++ b/app/src/main/java/ch/admin/bag/dp3t/networking/models/WhatToDoPositiveTestTextsCollection.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2020 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ +package ch.admin.bag.dp3t.networking.models; + +import java.util.HashMap; + +public class WhatToDoPositiveTestTextsCollection extends HashMap { +} diff --git a/app/src/main/java/ch/admin/bag/dp3t/networking/models/WhatToDoPositiveTestTextsModel.java b/app/src/main/java/ch/admin/bag/dp3t/networking/models/WhatToDoPositiveTestTextsModel.java new file mode 100644 index 00000000..426148d8 --- /dev/null +++ b/app/src/main/java/ch/admin/bag/dp3t/networking/models/WhatToDoPositiveTestTextsModel.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ +package ch.admin.bag.dp3t.networking.models; + +import java.util.List; + +public class WhatToDoPositiveTestTextsModel { + + private String enterCovidcodeBoxSupertitle; + private String enterCovidcodeBoxTitle; + private String enterCovidcodeBoxText; + private String enterCovidcodeBoxButtonTitle; + + //dismissible will be ignored by clients + private InfoBoxModel infoBox; + + private List faqEntries; + + public String getEnterCovidcodeBoxSupertitle() { + return enterCovidcodeBoxSupertitle; + } + + public String getEnterCovidcodeBoxTitle() { + return enterCovidcodeBoxTitle; + } + + public String getEnterCovidcodeBoxText() { + return enterCovidcodeBoxText; + } + + public String getEnterCovidcodeBoxButtonTitle() { + return enterCovidcodeBoxButtonTitle; + } + + public InfoBoxModel getInfoBox() { + return infoBox; + } + + public List getFaqEntries() { + return faqEntries; + } + +} diff --git a/app/src/main/java/ch/admin/bag/dp3t/stats/DiagramYAxisView.java b/app/src/main/java/ch/admin/bag/dp3t/stats/DiagramYAxisView.java index 075c2a36..b80be7f9 100644 --- a/app/src/main/java/ch/admin/bag/dp3t/stats/DiagramYAxisView.java +++ b/app/src/main/java/ch/admin/bag/dp3t/stats/DiagramYAxisView.java @@ -31,6 +31,7 @@ public class DiagramYAxisView extends View { private float dp; private int maxYValue; + private float paddingTopDp; private Paint labelPaint; Rect textRect = new Rect(); @@ -55,7 +56,9 @@ public DiagramYAxisView(Context context, @Nullable AttributeSet attrs, int defSt private void init(Context context) { dp = context.getResources().getDisplayMetrics().density; - int labelPaintColor = getResources().getColor(R.color.stats_diagram_labels, null); + paddingTopDp = context.getResources().getDimension(R.dimen.stats_diagram_padding_top); + + int labelPaintColor = context.getResources().getColor(R.color.stats_diagram_labels, null); float labelTextSize = context.getResources().getDimension(R.dimen.text_size_small); Typeface labelTypeface = ResourcesCompat.getFont(context, R.font.inter_regular); labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -89,12 +92,16 @@ protected void onDraw(Canvas canvas) { private void drawYAxisLabels(Canvas canvas) { float xAxisYCoord = getHeight() - DiagramView.OFFSET_BOTTOM_X_AXIS * dp; + float diagramHeight = xAxisYCoord - paddingTopDp; for (int yLabel : getYAxisLabelValues(maxYValue)) { String label = Integer.toString(yLabel); + // Note that the origin of the text is the BOTTOM LEFT coordinate (not the top left)!!! labelPaint.getTextBounds(label, 0, label.length(), textRect); - float bottom = xAxisYCoord + textRect.height() / 2F - xAxisYCoord * (yLabel / (float) maxYValue); + float bottom = xAxisYCoord // baseline + + textRect.height() / 2F // center text vertically + - (yLabel / (float) maxYValue) * diagramHeight; // move back up according to the y-label value canvas.drawText(label, PADDING_Y_AXIS_LEFT * dp, bottom, labelPaint); } diff --git a/app/src/main/java/ch/admin/bag/dp3t/storage/SecureStorage.java b/app/src/main/java/ch/admin/bag/dp3t/storage/SecureStorage.java index aee0fb47..3d2848a6 100644 --- a/app/src/main/java/ch/admin/bag/dp3t/storage/SecureStorage.java +++ b/app/src/main/java/ch/admin/bag/dp3t/storage/SecureStorage.java @@ -19,6 +19,12 @@ import java.io.IOException; import java.security.GeneralSecurityException; +import java.util.HashMap; + +import com.google.gson.Gson; + +import ch.admin.bag.dp3t.networking.models.WhatToDoPositiveTestTextsCollection; +import ch.admin.bag.dp3t.networking.models.WhatToDoPositiveTestTextsModel; public class SecureStorage { @@ -46,11 +52,14 @@ public class SecureStorage { private static final String KEY_LAST_CONFIG_LOAD_SUCCESS_APP_VERSION = "last_config_load_success_app_version"; private static final String KEY_LAST_CONFIG_LOAD_SUCCESS_SDK_INT = "last_config_load_success_sdk_int"; private static final String KEY_T_DUMMY = "KEY_T_DUMMY"; + private static final String KEY_WHAT_TO_DO_POSITIVE_TEST_TEXTS = "whatToDoPositiveTestTexts"; private static SecureStorage instance; private SharedPreferences prefs; + private Gson gson = new Gson(); + private final MutableLiveData forceUpdateLiveData; private final MutableLiveData hasInfoboxLiveData; @@ -265,4 +274,18 @@ public void setTDummy(long time) { prefs.edit().putLong(KEY_T_DUMMY, time).apply(); } + public void setWhatToDoPositiveTestTexts(WhatToDoPositiveTestTextsCollection whatToDoPositiveTestTexts) { + prefs.edit().putString(KEY_WHAT_TO_DO_POSITIVE_TEST_TEXTS, gson.toJson(whatToDoPositiveTestTexts)).apply(); + } + + public WhatToDoPositiveTestTextsModel getWhatToDoPositiveTestTexts(String language) { + HashMap map = + gson.fromJson(prefs.getString(KEY_WHAT_TO_DO_POSITIVE_TEST_TEXTS, "null"), + WhatToDoPositiveTestTextsCollection.class); + if (map == null) { + return null; + } + return map.get(language); + } + } \ No newline at end of file diff --git a/app/src/main/java/ch/admin/bag/dp3t/whattodo/WtdPositiveTestFragment.java b/app/src/main/java/ch/admin/bag/dp3t/whattodo/WtdPositiveTestFragment.java index b5ee3968..ac21f16a 100644 --- a/app/src/main/java/ch/admin/bag/dp3t/whattodo/WtdPositiveTestFragment.java +++ b/app/src/main/java/ch/admin/bag/dp3t/whattodo/WtdPositiveTestFragment.java @@ -9,10 +9,13 @@ */ package ch.admin.bag.dp3t.whattodo; +import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; @@ -20,6 +23,9 @@ import ch.admin.bag.dp3t.R; import ch.admin.bag.dp3t.inform.InformActivity; +import ch.admin.bag.dp3t.networking.models.FaqEntryModel; +import ch.admin.bag.dp3t.networking.models.WhatToDoPositiveTestTextsModel; +import ch.admin.bag.dp3t.storage.SecureStorage; import ch.admin.bag.dp3t.util.PhoneUtil; import ch.admin.bag.dp3t.util.UrlUtil; @@ -38,6 +44,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Toolbar toolbar = view.findViewById(R.id.wtd_test_toolbar); toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); + fillContentFromConfigServer(view); + view.findViewById(R.id.wtd_inform_button).setOnClickListener(v -> { Intent intent = new Intent(getActivity(), InformActivity.class); startActivity(intent); @@ -47,10 +55,73 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat UrlUtil.openUrl(getContext(), getString(R.string.faq_button_url)); }); - view.findViewById(R.id.wtd_inform_call_infoline_coronavirus).setOnClickListener(v->{ - PhoneUtil.callInfolineCoronavirus(v.getContext()); - }); + View oldCallButton = view.findViewById(R.id.wtd_inform_call_infoline_coronavirus); + if (oldCallButton != null) { + oldCallButton.setOnClickListener(v -> { + PhoneUtil.callInfolineCoronavirus(v.getContext()); + }); + } + } + + private void fillContentFromConfigServer(View view) { + Context context = view.getContext(); + SecureStorage secureStorage = SecureStorage.getInstance(context); + WhatToDoPositiveTestTextsModel textModel = + secureStorage.getWhatToDoPositiveTestTexts(context.getString(R.string.language_key)); + + if (textModel != null) { + ((TextView) view.findViewById(R.id.wtd_inform_box_supertitle)).setText(textModel.getEnterCovidcodeBoxSupertitle()); + ((TextView) view.findViewById(R.id.wtd_inform_box_title)).setText(textModel.getEnterCovidcodeBoxTitle()); + ((TextView) view.findViewById(R.id.wtd_inform_box_text)).setText(textModel.getEnterCovidcodeBoxText()); + ((TextView) view.findViewById(R.id.wtd_inform_button)).setText(textModel.getEnterCovidcodeBoxButtonTitle()); + + if (textModel.getInfoBox() != null) { + view.findViewById(R.id.wtd_inform_infobox).setVisibility(View.VISIBLE); + ((TextView) view.findViewById(R.id.wtd_inform_infobox_title)).setText(textModel.getInfoBox().getTitle()); + ((TextView) view.findViewById(R.id.wtd_inform_infobox_msg)).setText(textModel.getInfoBox().getMsg()); + + if (textModel.getInfoBox().getUrl() != null && textModel.getInfoBox().getUrlTitle() != null) { + ((TextView) view.findViewById(R.id.wtd_inform_infobox_link_text)).setText(textModel.getInfoBox().getUrlTitle()); + view.findViewById(R.id.wtd_inform_infobox_link_layout).setOnClickListener(v -> { + UrlUtil.openUrl(v.getContext(), textModel.getInfoBox().getUrl()); + }); + view.findViewById(R.id.wtd_inform_infobox_link_layout).setVisibility(View.VISIBLE); + } else { + view.findViewById(R.id.wtd_inform_infobox_link_layout).setVisibility(View.GONE); + } + } else { + view.findViewById(R.id.wtd_inform_infobox).setVisibility(View.GONE); + } + + LinearLayout faqLayout = view.findViewById(R.id.wtd_inform_faq_layout); + faqLayout.removeAllViews(); + + if (textModel.getFaqEntries() != null) { + for (FaqEntryModel faqEntry : textModel.getFaqEntries()) { + + View itemView = getLayoutInflater().inflate(R.layout.item_faq, faqLayout, false); + ((TextView) itemView.findViewById(R.id.item_faq_title)).setText(faqEntry.getTitle()); + ((TextView) itemView.findViewById(R.id.item_faq_text)).setText(faqEntry.getText()); + + int iconResource = + context.getResources().getIdentifier(faqEntry.getIconAndroid(), "drawable", context.getPackageName()); + if (iconResource != 0) { + ((ImageView) itemView.findViewById(R.id.item_faq_icon)).setImageResource(iconResource); + } + + if (faqEntry.getLinkUrl() != null && faqEntry.getLinkTitle() != null) { + ((TextView) itemView.findViewById(R.id.item_faq_link_text)).setText(faqEntry.getLinkTitle()); + itemView.findViewById(R.id.item_faq_link_layout).setOnClickListener(v -> { + UrlUtil.openUrl(v.getContext(), faqEntry.getLinkUrl()); + }); + } else { + itemView.findViewById(R.id.item_faq_link_layout).setVisibility(View.GONE); + } + faqLayout.addView(itemView); + } + } + } } } diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index 7c250c9c..30316ad7 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -69,6 +69,29 @@ android:text="@string/synchronizations_view_info_answer" /> + + + + + + + diff --git a/app/src/main/res/layout/fragment_what_to_do_test.xml b/app/src/main/res/layout/fragment_what_to_do_test.xml index c5588db1..e83071b5 100644 --- a/app/src/main/res/layout/fragment_what_to_do_test.xml +++ b/app/src/main/res/layout/fragment_what_to_do_test.xml @@ -10,6 +10,7 @@ - - - - - - -