Commit 7e128df2 authored by Ulrich Scheller's avatar Ulrich Scheller
Browse files

Release 1.11.1

parent 31fa1592
......@@ -10,8 +10,8 @@ android {
applicationId "de.culture4life.luca"
minSdkVersion 21
targetSdkVersion 30
versionCode 77
versionName "1.11.0"
versionCode 78
versionName "1.11.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
......@@ -94,6 +94,9 @@ android {
buildConfigField "String", "API_BASE_URL", '"https://app-preprod.luca-app.de"'
}
}
buildFeatures {
viewBinding = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
......
......@@ -62,6 +62,11 @@
</intent-filter>
</activity>
<activity
android:name=".ui.terms.UpdatedTermsActivity"
android:screenOrientation="portrait">
</activity>
<activity
android:name="de.culture4life.luca.ui.registration.RegistrationActivity"
android:screenOrientation="portrait">
......
......@@ -20,6 +20,7 @@ import de.culture4life.luca.checkin.CheckInManager;
import de.culture4life.luca.crypto.CryptoManager;
import de.culture4life.luca.dataaccess.DataAccessManager;
import de.culture4life.luca.document.DocumentManager;
import de.culture4life.luca.genuinity.GenuinityManager;
import de.culture4life.luca.history.HistoryManager;
import de.culture4life.luca.location.GeofenceManager;
import de.culture4life.luca.location.LocationManager;
......@@ -32,7 +33,6 @@ import de.culture4life.luca.registration.RegistrationManager;
import de.culture4life.luca.service.LucaService;
import de.culture4life.luca.ui.ViewError;
import de.culture4life.luca.ui.dialog.BaseDialogFragment;
import de.culture4life.luca.util.TimeUtil;
import java.util.ArrayList;
import java.util.HashSet;
......@@ -68,6 +68,7 @@ public class LucaApplication extends MultiDexApplication {
private final PreferencesManager preferencesManager;
private final CryptoManager cryptoManager;
private final GenuinityManager genuinityManager;
private final NetworkManager networkManager;
private final LucaNotificationManager notificationManager;
private final LocationManager locationManager;
......@@ -106,6 +107,7 @@ public class LucaApplication extends MultiDexApplication {
geofenceManager = new GeofenceManager();
historyManager = new HistoryManager(preferencesManager);
cryptoManager = new CryptoManager(preferencesManager, networkManager);
genuinityManager = new GenuinityManager(preferencesManager, networkManager);
registrationManager = new RegistrationManager(preferencesManager, networkManager, cryptoManager);
meetingManager = new MeetingManager(preferencesManager, networkManager, locationManager, historyManager, cryptoManager);
checkInManager = new CheckInManager(preferencesManager, networkManager, geofenceManager, locationManager, historyManager, cryptoManager, notificationManager);
......@@ -175,6 +177,7 @@ public class LucaApplication extends MultiDexApplication {
notificationManager.initialize(this).subscribeOn(Schedulers.io()),
networkManager.initialize(this).subscribeOn(Schedulers.io()),
cryptoManager.initialize(this).subscribeOn(Schedulers.io()),
genuinityManager.initialize(this).subscribeOn(Schedulers.io()),
locationManager.initialize(this).subscribeOn(Schedulers.io()),
registrationManager.initialize(this).subscribeOn(Schedulers.io()),
checkInManager.initialize(this).subscribeOn(Schedulers.io()),
......@@ -183,7 +186,7 @@ public class LucaApplication extends MultiDexApplication {
documentManager.initialize(this).subscribeOn(Schedulers.io()),
geofenceManager.initialize(this).subscribeOn(Schedulers.io())
).andThen(Completable.mergeArray(
invokeServerTimeCheck(),
invokeServerTimeCheck().delaySubscription(5, TimeUnit.SECONDS, Schedulers.io()),
invokeRotatingBackendPublicKeyUpdate(),
invokeAccessedDataUpdate(),
startKeepingDataUpdated()
......@@ -191,16 +194,10 @@ public class LucaApplication extends MultiDexApplication {
}
private Completable invokeServerTimeCheck() {
return Completable.fromAction(() -> applicationDisposable.add(networkManager.getLucaEndpointsV3()
.flatMap(LucaEndpointsV3::getServerTime)
.map(jsonObject -> jsonObject.get("unix").getAsInt())
.flatMap(TimeUtil::convertFromUnixTimestamp)
.map(serverTimestamp -> Math.abs(System.currentTimeMillis() - serverTimestamp))
return Completable.fromAction(() -> applicationDisposable.add(genuinityManager.isGenuineTime()
.subscribeOn(Schedulers.io())
.subscribe(
timestampOffset -> {
Timber.d("Timestamp offset: %d ms", timestampOffset);
if (timestampOffset > MAXIMUM_TIMESTAMP_OFFSET) {
.subscribe(isGenuineTime -> {
if (!isGenuineTime) {
showErrorAsDialog(new ViewError.Builder(this)
.withTitle(R.string.error_timestamp_offset_title)
.withDescription(R.string.error_timestamp_offset_description)
......@@ -213,8 +210,7 @@ public class LucaApplication extends MultiDexApplication {
.removeWhenShown()
.build());
}
},
throwable -> Timber.w("Unable to get server time offset: %s", throwable.toString())
}
)));
}
......@@ -241,7 +237,7 @@ public class LucaApplication extends MultiDexApplication {
return Completable.fromAction(() -> applicationDisposable.add(dataAccessManager.update()
.subscribeOn(Schedulers.io())
.subscribe(
() -> Timber.i("Updated accessed data"),
() -> Timber.d("Updated accessed data"),
throwable -> Timber.w("Unable to update accessed data: %s", throwable.toString())
)));
}
......@@ -463,6 +459,10 @@ public class LucaApplication extends MultiDexApplication {
return cryptoManager;
}
public GenuinityManager getGenuinityManager() {
return genuinityManager;
}
public NetworkManager getNetworkManager() {
return networkManager;
}
......
......@@ -169,7 +169,8 @@ public class CheckInManager extends Manager {
public Completable checkIn(@NonNull UUID scannerId, @NonNull QrCodeData qrCodeData) {
return assertNotCheckedIn()
.andThen(generateCheckInData(qrCodeData, scannerId)
.flatMapCompletable(checkInRequestData -> networkManager.getLucaEndpoints().checkIn(checkInRequestData)))
.flatMapCompletable(checkInRequestData -> networkManager.getLucaEndpointsV3()
.flatMapCompletable(lucaEndpointsV3 -> lucaEndpointsV3.checkIn(checkInRequestData))))
.andThen(getCheckInDataFromBackend()
.switchIfEmpty(Single.error(new IllegalStateException("No check-in data available at backend after checking in"))))
.flatMapCompletable(this::processCheckIn);
......@@ -201,7 +202,8 @@ public class CheckInManager extends Manager {
}
public Single<ECPublicKey> getLocationPublicKey(@NonNull UUID scannerId) {
return networkManager.getLucaEndpoints().getScanner(scannerId.toString())
return networkManager.getLucaEndpointsV3()
.flatMap(lucaEndpointsV3 -> lucaEndpointsV3.getScanner(scannerId.toString()))
.map(jsonObject -> jsonObject.get("publicKey").getAsString())
.flatMap(SerializationUtil::deserializeFromBase64)
.flatMap(AsymmetricCipherProvider::decodePublicKey);
......@@ -365,7 +367,8 @@ public class CheckInManager extends Manager {
.andThen(getCheckedInTraceId())
.toSingle()
.flatMap(traceId -> generateAdditionalCheckInProperties(properties, traceId, locationPublicKey))
.flatMapCompletable(requestData -> networkManager.getLucaEndpoints().addAdditionalCheckInProperties(requestData))
.flatMapCompletable(requestData -> networkManager.getLucaEndpointsV3()
.flatMapCompletable(lucaEndpointsV3 -> lucaEndpointsV3.addAdditionalCheckInProperties(requestData)))
.andThen(persistAdditionalCheckInProperties(properties));
}
......@@ -431,8 +434,8 @@ public class CheckInManager extends Manager {
.andThen(networkManager.assertNetworkConnected())
.andThen(generateCheckOutData()
.doOnSuccess(checkOutRequestData -> Timber.i("Generated checkout data: %s", checkOutRequestData))
.flatMapCompletable(checkOutRequestData -> networkManager.getLucaEndpoints()
.checkOut(checkOutRequestData)
.flatMapCompletable(checkOutRequestData -> networkManager.getLucaEndpointsV3()
.flatMapCompletable(lucaEndpointsV3 -> lucaEndpointsV3.checkOut(checkOutRequestData))
.onErrorResumeNext(throwable -> {
if (NetworkManager.isHttpException(throwable, HttpURLConnection.HTTP_NOT_FOUND)) {
// user is currently not checked-in
......@@ -716,7 +719,8 @@ public class CheckInManager extends Manager {
if (traceData.isCheckedOut()) {
return Maybe.empty();
}
return networkManager.getLucaEndpoints().getLocation(traceData.getLocationId())
return networkManager.getLucaEndpointsV3()
.flatMap(lucaEndpointsV3 -> lucaEndpointsV3.getLocation(traceData.getLocationId()))
.map(location -> {
Timber.d("Creating check-in data for location: %s", location);
CheckInData checkInData = new CheckInData();
......@@ -815,7 +819,8 @@ public class CheckInManager extends Manager {
jsonObject.add("traceIds", jsonArray);
return jsonObject;
})
.flatMap(jsonObject -> networkManager.getLucaEndpoints().getTraces(jsonObject))
.flatMap(jsonObject -> networkManager.getLucaEndpointsV3()
.flatMap(lucaEndpointsV3 -> lucaEndpointsV3.getTraces(jsonObject)))
.flatMapObservable(Observable::fromIterable)
.sorted((first, second) -> Long.compare(first.getCheckInTimestamp(), second.getCheckInTimestamp()));
}
......
......@@ -13,6 +13,7 @@ import de.culture4life.luca.crypto.CryptoManager;
import de.culture4life.luca.history.HistoryItem;
import de.culture4life.luca.history.HistoryManager;
import de.culture4life.luca.network.NetworkManager;
import de.culture4life.luca.network.endpoints.LucaEndpointsV3;
import de.culture4life.luca.network.pojo.AccessedHashedTraceIdsData;
import de.culture4life.luca.notification.LucaNotificationManager;
import de.culture4life.luca.preference.PreferencesManager;
......@@ -240,7 +241,8 @@ public class DataAccessManager extends Manager {
}
public Observable<AccessedHashedTraceIdsData> fetchAllRecentlyAccessedHashedTraceIdsData() {
return networkManager.getLucaEndpoints().getAccessedTraces()
return networkManager.getLucaEndpointsV3()
.flatMap(LucaEndpointsV3::getAccessedTraces)
.flatMapObservable(Observable::fromIterable);
}
......
package de.culture4life.luca.genuinity
import android.content.Context
import de.culture4life.luca.Manager
import de.culture4life.luca.network.NetworkManager
import de.culture4life.luca.preference.PreferencesManager
import de.culture4life.luca.util.TimeUtil
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.TimeUnit
import kotlin.math.abs
class GenuinityManager(
val preferencesManager: PreferencesManager,
val networkManager: NetworkManager
) : Manager() {
companion object {
const val KEY_SERVER_TIME_OFFSET = "server_time_offset"
val MAXIMUM_SERVER_TIME_OFFSET = TimeUnit.MINUTES.toMillis(5)
}
var timestampOffset: Long? = null
override fun doInitialize(context: Context): Completable {
return Completable.mergeArray(
preferencesManager.initialize(context),
networkManager.initialize(context)
).andThen(invokeServerTimeOffsetUpdateIfRequired().delaySubscription(3, TimeUnit.SECONDS))
}
private fun invokeServerTimeOffsetUpdateIfRequired(): Completable {
return Completable.fromAction {
if (timestampOffset == null) {
invokeServerTimeOffsetUpdate()
}
}
}
fun invokeServerTimeOffsetUpdate(): Completable {
return Completable.fromAction {
managerDisposable.add(fetchServerTimeOffset()
.subscribeOn(Schedulers.io())
.onErrorComplete()
.subscribe())
}
}
fun isGenuineTime(): Single<Boolean> {
return getOrFetchOrRestoreServerTimeOffset()
.map { it < MAXIMUM_SERVER_TIME_OFFSET }
.onErrorReturnItem(false)
}
fun getIsGenuineTimeChanges(): Observable<Boolean> {
return preferencesManager.getChanges(KEY_SERVER_TIME_OFFSET, Long::class.java)
.flatMapSingle { isGenuineTime() }
.distinctUntilChanged()
}
private fun getOrFetchOrRestoreServerTimeOffset(): Single<Long> {
return Single.defer {
if (timestampOffset != null) {
Single.just(timestampOffset!!)
} else {
fetchServerTimeOffset()
}
}.onErrorResumeWith(restoreLastKnownServerTimeOffset())
.onErrorResumeNext { Single.error(IllegalStateException("Unable to get server time offset", it)) }
}
private fun restoreLastKnownServerTimeOffset(): Single<Long> {
return preferencesManager.restore(KEY_SERVER_TIME_OFFSET, Long::class.java)
}
private fun fetchServerTimeOffset(): Single<Long> {
return networkManager.lucaEndpointsV3
.flatMap { it.serverTime }
.map { it["unix"].asLong }
.flatMap { TimeUtil.convertFromUnixTimestamp(it) }
.map { abs(System.currentTimeMillis() - it) }
.doOnSuccess {
timestampOffset = it
Timber.d("Server timestamp offset updated: %d", timestampOffset)
}
.doOnError { Timber.w("Unable to update server timestamp offset: %s", it.toString()) }
.flatMap { preferencesManager.persist(KEY_SERVER_TIME_OFFSET, it).andThen(Single.just(it)) }
}
}
\ No newline at end of file
......@@ -124,7 +124,8 @@ public class MeetingManager extends Manager {
jsonObject.addProperty("publicKey", serializedPubKey);
return jsonObject;
})
.flatMap(requestData -> networkManager.getLucaEndpoints().createPrivateLocation(requestData))
.flatMap(requestData -> networkManager.getLucaEndpointsV3()
.flatMap(lucaEndpointsV3 -> lucaEndpointsV3.createPrivateLocation(requestData)))
.map(MeetingData::new);
}
......@@ -134,8 +135,8 @@ public class MeetingManager extends Manager {
public Completable closePrivateLocation() {
return restoreCurrentMeetingDataIfAvailable()
.flatMapCompletable(meetingData -> networkManager.getLucaEndpoints()
.closePrivateLocation(meetingData.getAccessId().toString())
.flatMapCompletable(meetingData -> networkManager.getLucaEndpointsV3()
.flatMapCompletable(lucaEndpointsV3 -> lucaEndpointsV3.closePrivateLocation(meetingData.getAccessId().toString()))
.onErrorResumeNext(throwable -> {
if (NetworkManager.isHttpException(throwable, HttpURLConnection.HTTP_NOT_FOUND)) {
// meeting has already ended
......@@ -168,7 +169,8 @@ public class MeetingManager extends Manager {
public Observable<TracesResponseData> fetchGuestData() {
return restoreCurrentMeetingDataIfAvailable()
.map(MeetingData::getAccessId)
.flatMapSingle(accessUuid -> networkManager.getLucaEndpoints().fetchGuestList(accessUuid.toString()))
.flatMapSingle(accessUuid -> networkManager.getLucaEndpointsV3()
.flatMap(lucaEndpointsV3 -> lucaEndpointsV3.fetchGuestList(accessUuid.toString())))
.doOnSuccess(tracesResponseData -> Timber.d("Location traces: %s", tracesResponseData))
.flatMapObservable(Observable::fromIterable);
}
......
......@@ -112,16 +112,13 @@ public class NetworkManager extends Manager {
return builder.build();
}
@Deprecated
public LucaEndpointsV3 getLucaEndpoints() {
if (lucaEndpointsV3 == null) {
lucaEndpointsV3 = createEndpoints();
}
return lucaEndpointsV3;
}
public Single<LucaEndpointsV3> getLucaEndpointsV3() {
return Single.defer(() -> getInitializedField(getLucaEndpoints()));
return Single.fromCallable(() -> {
if (lucaEndpointsV3 == null) {
lucaEndpointsV3 = createEndpoints();
}
return lucaEndpointsV3;
});
}
public Completable assertNetworkConnected() {
......
......@@ -163,7 +163,8 @@ public class RegistrationManager extends Manager {
return Single.defer(() -> {
JsonObject message = new JsonObject();
message.addProperty("phone", formattedPhoneNumber);
return networkManager.getLucaEndpoints().requestPhoneNumberVerificationTan(message)
return networkManager.getLucaEndpointsV3()
.flatMap(lucaEndpointsV3 -> lucaEndpointsV3.requestPhoneNumberVerificationTan(message))
.doOnSubscribe(disposable -> Timber.i("Requesting TAN for %s", formattedPhoneNumber))
.map(jsonObject -> jsonObject.get("challengeId").getAsString());
});
......@@ -182,7 +183,8 @@ public class RegistrationManager extends Manager {
}
jsonObject.add("challengeIds", challengeIdArray);
jsonObject.addProperty("tan", verificationTan);
return networkManager.getLucaEndpoints().verifyPhoneNumberBulk(jsonObject);
return networkManager.getLucaEndpointsV3()
.flatMapCompletable(lucaEndpointsV3 -> lucaEndpointsV3.verifyPhoneNumberBulk(jsonObject));
});
}
......@@ -193,7 +195,8 @@ public class RegistrationManager extends Manager {
public Completable registerUser() {
return createUserRegistrationRequestData()
.doOnSuccess(data -> Timber.d("User registration request data: %s", data))
.flatMap(data -> networkManager.getLucaEndpoints().registerUser(data)
.flatMap(data -> networkManager.getLucaEndpointsV3()
.flatMap(lucaEndpointsV3 -> lucaEndpointsV3.registerUser(data))
.map(jsonObject -> jsonObject.get("userId").getAsString())
.map(UUID::fromString))
.doOnSuccess(userId -> Timber.i("Registered user for ID: %s", userId))
......@@ -222,7 +225,8 @@ public class RegistrationManager extends Manager {
.doOnSuccess(data -> data.setGuestKeyPairPublicKey(null)) // not part of update request
.doOnSuccess(data -> Timber.d("User update request data: %s", data))
.flatMapCompletable(data -> getUserIdIfAvailable()
.flatMapCompletable(userId -> networkManager.getLucaEndpoints().updateUser(userId.toString(), data)))
.flatMapCompletable(userId -> networkManager.getLucaEndpointsV3()
.flatMapCompletable(lucaEndpointsV3 -> lucaEndpointsV3.updateUser(userId.toString(), data))))
.doOnComplete(() -> Timber.i("Updated user"));
}
......@@ -340,7 +344,8 @@ public class RegistrationManager extends Manager {
// TODO: 24.03.21 This doesn't seem to belong to the registration process
return createDataTransferRequestData(days)
.doOnSuccess(data -> Timber.d("User data transfer request data: %s", data))
.flatMap(data -> networkManager.getLucaEndpoints().getTracingTan(data))
.flatMap(data -> networkManager.getLucaEndpointsV3()
.flatMap(lucaEndpointsV3 -> lucaEndpointsV3.getTracingTan(data)))
.map(jsonObject -> jsonObject.get("tan").getAsString());
}
......
......@@ -5,7 +5,6 @@ import com.google.android.material.snackbar.Snackbar;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.StrictMode;
import android.text.SpannedString;
......@@ -25,7 +24,6 @@ import de.culture4life.luca.BuildConfig;
import de.culture4life.luca.LucaApplication;
import de.culture4life.luca.R;
import de.culture4life.luca.ui.dialog.BaseDialogFragment;
import de.culture4life.luca.ui.registration.RegistrationActivity;
import java.util.Set;
import java.util.concurrent.TimeUnit;
......@@ -285,12 +283,12 @@ public abstract class BaseFragment<ViewModelType extends BaseViewModel> extends
errorSnackbar = Snackbar.make(getView(), error.getTitle(), duration);
errorSnackbar.addCallback(new Snackbar.Callback() {
@Override
public void onShown(Snackbar sb) {
public void onShown(Snackbar snackbar) {
viewModel.onErrorShown(error);
}
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
public void onDismissed(Snackbar snackbar, int event) {
viewModel.onErrorDismissed(error);
}
});
......@@ -361,15 +359,6 @@ public abstract class BaseFragment<ViewModelType extends BaseViewModel> extends
.show();
}
private void showDeleteAccountDialog() {
new BaseDialogFragment(new MaterialAlertDialogBuilder(getContext())
.setTitle(R.string.delete_account_dialog_title)
.setMessage(R.string.delete_account_dialog_message)
.setPositiveButton(R.string.delete_account_dialog_action, (dialog, which) -> viewModel.deleteAccount())
.setNegativeButton(R.string.action_cancel, (dialog, which) -> dialog.dismiss()))
.show();
}
protected Completable getCameraPermission() {
return rxPermissions.request(Manifest.permission.CAMERA)
.flatMapCompletable(granted -> {
......
......@@ -14,11 +14,12 @@ import net.yslibrary.android.keyboardvisibilityevent.KeyboardVisibilityEvent;
import androidx.annotation.Nullable;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import five.star.me.FiveStarMe;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Single;
import timber.log.Timber;
public class MainActivity extends BaseActivity {
......@@ -43,13 +44,31 @@ public class MainActivity extends BaseActivity {
private void initializeNavigation() {
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.qrCodeFragment, R.id.historyFragment
R.id.qrCodeFragment, R.id.myLucaFragment, R.id.historyFragment, R.id.accountFragment
).build();
navigationController = Navigation.findNavController(this, R.id.navigationHostFragment);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.navigationHostFragment);
navigationController = navHostFragment.getNavController();
navigationController.setGraph(R.navigation.mobile_navigation);
NavigationUI.setupActionBarWithNavController(this, navigationController, appBarConfiguration);
bottomNavigationView = findViewById(R.id.bottomNavigationView);
NavigationUI.setupWithNavController(bottomNavigationView, navigationController);
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
int currentDestinationId = navigationController.getCurrentDestination().getId();
if (currentDestinationId != item.getItemId()) {
if (item.getItemId() == R.id.qrCodeFragment) {
int destinationId = getCheckInScreenDestinationId().blockingGet();
if (currentDestinationId != destinationId) {
navigationController.navigate(destinationId);
}
} else {
NavigationUI.onNavDestinationSelected(item, navigationController);
}
}
return true;
});
if (application.isInDarkMode()) {
// workaround for removing the elevation color overlay
// https://github.com/material-components/material-components-android/issues/1148
......@@ -112,4 +131,24 @@ public class MainActivity extends BaseActivity {
}));
}
private Single<Integer> getCheckInScreenDestinationId() {
Single<Boolean> checkedIn = application.getCheckInManager()
.initialize(application)
.andThen(application.getCheckInManager().isCheckedIn());
Single<Boolean> hostingMeeting = application.getMeetingManager()
.initialize(application)
.andThen(application.getMeetingManager().isCurrentlyHostingMeeting());
return Single.zip(checkedIn, hostingMeeting, (isCheckedIn, isHostingMeeting) -> {
if (isCheckedIn) {
return R.id.venueDetailFragment;
} else if (isHostingMeeting) {
return R.id.meetingFragment;
} else {
return R.id.qrCodeFragment;
}
});
}
}
\ No newline at end of file
......@@ -5,6 +5,7 @@ import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.util.concurrent.ListenableFuture;
import android.content.Intent;
import android.util.Size;
import android.view.View;
import android.widget.ImageView;
......@@ -70,7 +71,8 @@ public class MyLucaFragment extends BaseFragment<MyLucaViewModel> implements MyL
super.onResume();
viewDisposable.add(Completable.mergeArray(
viewModel.updateUserName(),
viewModel.invokeListUpdate()
viewModel.invokeListUpdate(),
viewModel.invokeServerTimeOffsetUpdate()
).subscribe());
}
......@@ -81,6 +83,7 @@ public class MyLucaFragment extends BaseFragment<MyLucaViewModel> implements MyL
initializeMyLucaItemsViews();
initializeEmptyStateViews();
initializeImportViews();
initializeTimeSyncErrorViews();
}));
}
......@@ -149,6 +152,22 @@ public class MyLucaFragment extends BaseFragment<MyLucaViewModel> implements MyL
});
}
private void initializeTimeSyncErrorViews() {
View timeSyncErrorLayout = getView().findViewById(R.id.timeSyncErrorLayout);
TextView sheetDescriptionTextView = timeSyncErrorLayout.findViewById(R.id.sheetDescriptionTextView);
sheetDescriptionTextView.setText(R.string.time_error_description);
MaterialButton sheetActionButton = timeSyncErrorLayout.findViewById(R.id.sheetActionButton);
sheetActionButton.setText(R.string.time_error_action);
sheetActionButton.setOnClickListener(v -> {
Intent intent = new Intent(android.provider.Settings.ACTION_DATE_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
});
observe(viewModel.getIsGenuineTime(), isGenuineTime -> {
timeSyncErrorLayout.setVisibility(isGenuineTime ? View.GONE : View.VISIBLE);
});
}
private void toggleCameraPreview() {
if (cameraPreviewDisposable == null) {
viewModel.isCameraConsentGiven()
......