Merge branch 'feature/new-ui' of https://github.com/cryptomator/cryptomator into feature/new-ui

This commit is contained in:
infeo
2019-07-31 16:55:29 +02:00
11 changed files with 240 additions and 7 deletions

View File

@@ -19,7 +19,7 @@ import org.cryptomator.ui.unlock.UnlockComponent;
import java.util.ResourceBundle;
@Module(includes = {KeychainModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class})
@Module(includes = {KeychainModule.class, UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class})
abstract class FxApplicationModule {
@Binds
@@ -31,13 +31,13 @@ abstract class FxApplicationModule {
static ObjectProperty<Vault> provideSelectedVault() {
return new SimpleObjectProperty<>();
}
@Provides
@FxApplicationScoped
static ResourceBundle provideLocalization() {
return ResourceBundle.getBundle("i18n.strings");
}
@Provides
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
return builder.build();

View File

@@ -0,0 +1,66 @@
package org.cryptomator.ui.fxapp;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.WorkerStateEvent;
import javafx.util.Duration;
import org.cryptomator.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Comparator;
import java.util.Optional;
@FxApplicationScoped
public class UpdateChecker {
private static final Logger LOG = LoggerFactory.getLogger(UpdateChecker.class);
private final Settings settings;
private final Optional<String> applicationVersion;
private final StringProperty latestVersionProperty;
private final Comparator<String> semVerComparator;
private final ScheduledService<String> updateCheckerService;
@Inject
UpdateChecker(Settings settings, @Named("applicationVersion") Optional<String> applicationVersion, @Named("latestVersion") StringProperty latestVersionProperty, @Named("SemVer") Comparator<String> semVerComparator, ScheduledService<String> updateCheckerService) {
this.settings = settings;
this.applicationVersion = applicationVersion;
this.latestVersionProperty = latestVersionProperty;
this.semVerComparator = semVerComparator;
this.updateCheckerService = updateCheckerService;
}
public void startCheckingForUpdates(Duration initialDelay) {
updateCheckerService.setDelay(initialDelay);
updateCheckerService.setOnSucceeded(this::checkSucceeded);
updateCheckerService.setOnFailed(this::checkFailed);
updateCheckerService.restart();
}
public ReadOnlyStringProperty latestVersionProperty() {
return latestVersionProperty;
}
private void checkSucceeded(WorkerStateEvent event) {
String currentVersion = applicationVersion.orElse(null);
String latestVersion = updateCheckerService.getValue();
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
// TODO settings.lastVersionCheck = Instant.now()
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
// update is available!
latestVersionProperty.set(latestVersion);
} else {
latestVersionProperty.set(null);
}
}
private void checkFailed(WorkerStateEvent event) {
LOG.warn("Error checking for updates", event.getSource().getException());
}
}

View File

@@ -0,0 +1,63 @@
package org.cryptomator.ui.fxapp;
import dagger.Module;
import dagger.Provides;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import javafx.util.Duration;
import org.apache.commons.lang3.SystemUtils;
import javax.inject.Named;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
@Module
public abstract class UpdateCheckerModule {
private static final URI LATEST_VERSION_URI = URI.create("https://api.cryptomator.org/updates/latestVersion.json");
private static final Duration UPDATE_CHECK_INTERVAL = Duration.hours(3);
@Provides
@Named("latestVersion")
@FxApplicationScoped
static StringProperty provideLatestVersion() {
return new SimpleStringProperty();
}
@Provides
@FxApplicationScoped
static HttpClient providesHttpClient() {
return HttpClient.newHttpClient();
}
@Provides
@FxApplicationScoped
static HttpRequest providesCheckForUpdatesRequest(@Named("applicationVersion") Optional<String> applicationVersion) {
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
return HttpRequest.newBuilder() //
.uri(LATEST_VERSION_URI) //
.header("User-Agent", userAgent)
.build();
}
@Provides
@FxApplicationScoped
static ScheduledService<String> provideCheckForUpdatesService(ExecutorService executor, HttpClient httpClient, HttpRequest checkForUpdatesRequest) {
ScheduledService<String> service = new ScheduledService<>() {
@Override
protected Task<String> createTask() {
return new UpdateCheckerTask(httpClient, checkForUpdatesRequest);
}
};
service.setExecutor(executor);
service.setPeriod(UPDATE_CHECK_INTERVAL);
return service;
}
}

View File

@@ -0,0 +1,60 @@
package org.cryptomator.ui.fxapp;
import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import javafx.concurrent.Task;
import org.apache.commons.lang3.SystemUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class UpdateCheckerTask extends Task<String> {
private static final long MAX_RESPONSE_SIZE = 10 * 1024; // 10kb should be sufficient. protect against flooding
private static final Gson GSON = new GsonBuilder().setLenient().create();
private final HttpClient httpClient;
private final HttpRequest checkForUpdatesRequest;
UpdateCheckerTask(HttpClient httpClient, HttpRequest checkForUpdatesRequest) {
this.httpClient = httpClient;
this.checkForUpdatesRequest = checkForUpdatesRequest;
}
@Override
protected String call() throws Exception {
HttpResponse<InputStream> response = httpClient.send(checkForUpdatesRequest, HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() == 200) {
return processBody(response);
} else {
throw new IOException("Unexpected HTTP response code " + response.statusCode());
}
}
private String processBody(HttpResponse<InputStream> response) throws IOException {
try (InputStream in = response.body(); //
InputStream limitedIn = ByteStreams.limit(in, MAX_RESPONSE_SIZE); //
Reader reader = new InputStreamReader(limitedIn, StandardCharsets.UTF_8)) {
Map<String, String> map = GSON.fromJson(reader, new TypeToken<Map<String, String>>() {
}.getType());
if (SystemUtils.IS_OS_MAC_OSX) {
return map.get("mac");
} else if (SystemUtils.IS_OS_WINDOWS) {
return map.get("win");
} else if (SystemUtils.IS_OS_LINUX) {
return map.get("linux");
} else {
throw new IllegalStateException("Unsupported operating system");
}
}
}
}

View File

@@ -1,11 +1,14 @@
package org.cryptomator.ui.mainwindow;
import javafx.beans.binding.BooleanBinding;
import javafx.fxml.FXML;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.cryptomator.ui.fxapp.FxApplication;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,20 +20,25 @@ import java.util.concurrent.CountDownLatch;
public class MainWindowController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
private static final Duration CHECK_FOR_UPDATES_DELAY = Duration.seconds(5);
private final Stage window;
private final FxApplication application;
private final boolean minimizeToSysTray;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
public HBox titleBar;
public Region resizer;
private double xOffset;
private double yOffset;
@Inject
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray) {
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker) {
this.window = window;
this.application = application;
this.minimizeToSysTray = minimizeToSysTray;
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
}
@FXML
@@ -49,6 +57,7 @@ public class MainWindowController implements FxController {
window.setWidth(event.getSceneX());
window.setHeight(event.getSceneY());
});
updateChecker.startCheckingForUpdates(CHECK_FOR_UPDATES_DELAY);
}
@FXML
@@ -64,4 +73,14 @@ public class MainWindowController implements FxController {
public void showPreferences() {
application.showPreferencesWindow();
}
/* Getter/Setter */
public BooleanBinding updateAvailableProperty() {
return updateAvailable;
}
public boolean isUpdateAvailable() {
return updateAvailable.get();
}
}

View File

@@ -35,7 +35,6 @@ abstract class PreferencesModule {
stage.setTitle(resourceBundle.getString("preferences.title"));
stage.setMinWidth(400);
stage.setMinHeight(300);
stage.initModality(Modality.APPLICATION_MODAL);
return stage;
}

View File

@@ -48,6 +48,7 @@ class TrayMenuController {
// show window on start?
if (!settings.startHidden().get()) {
// TODO: schedule async to not delay tray menu initialization
showMainWindow(null);
}
}

View File

@@ -80,6 +80,16 @@
-fx-cursor: nw_resize;
}
/*******************************************************************************
* *
* Preferences Window *
* *
******************************************************************************/
.preferences-window {
}
/*******************************************************************************
* *
* SplitPane *

View File

@@ -80,6 +80,16 @@
-fx-cursor: nw_resize;
}
/*******************************************************************************
* *
* Preferences Window *
* *
******************************************************************************/
.preferences-window {
}
/*******************************************************************************
* *
* SplitPane *

View File

@@ -10,6 +10,7 @@
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
@@ -23,7 +24,11 @@
<Region HBox.hgrow="ALWAYS"/>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences">
<graphic>
<FontAwesomeIconView styleClass="fa-icon" glyphName="COGS"/>
<StackPane>
<FontAwesomeIconView styleClass="fa-icon" glyphName="COGS"/>
<!-- TODO style: -->
<Circle visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" styleClass="update-indicator" fill="red" radius="4"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.settingsBtn.tooltip"/>

View File

@@ -11,7 +11,7 @@
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.preferences.PreferencesController"
styleClass="main-window"
styleClass="preferences-window"
spacing="6">
<padding>
<Insets bottom="12" left="12" right="12" top="12"/>