Skip to content

Commit

Permalink
#61 draft album manager, on hold until album deletion is supported by…
Browse files Browse the repository at this point in the history
… GP API
  • Loading branch information
ylexus committed Dec 25, 2020
1 parent 3f1ac3d commit c526e60
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.yudichev.googlephotosupload.ui;

// TODO remove if empty
interface AlbumEditorController {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package net.yudichev.googlephotosupload.ui;

import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.ObservableValueBase;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import net.yudichev.jiotty.common.async.ExecutorFactory;
import net.yudichev.jiotty.common.async.SchedulingExecutor;
import net.yudichev.jiotty.common.inject.BaseLifecycleComponent;
import net.yudichev.jiotty.connector.google.photos.GooglePhotosAlbum;
import net.yudichev.jiotty.connector.google.photos.GooglePhotosClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Provider;
import java.time.Duration;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.util.Collections.emptyList;

// TODO rebrand Album Editor -> Album Managwr
public final class AlbumEditorControllerImpl extends BaseLifecycleComponent implements AlbumEditorController {
private static final Logger logger = LoggerFactory.getLogger(AlbumEditorControllerImpl.class);
private final ExecutorFactory executorFactory;
private final Provider<JavafxApplicationResources> javafxApplicationResourcesProvider;
private final GooglePhotosClient googlePhotosClient;
public TableView<SelectableAlbum> tableView;
public TableColumn<SelectableAlbum, Boolean> selectColumn;
public TableColumn<SelectableAlbum, SelectableAlbum> titleColumn;
public TableColumn<SelectableAlbum, Long> itemCountColumn;
public ProgressIndicator loadingIndicator;
public CheckBox selectAllCheckBox;
public Button deleteButton;
private int selectedCount;
private boolean updatingSelectionBoxes;
private SchedulingExecutor executor;

@Inject
AlbumEditorControllerImpl(ExecutorFactory executorFactory,
Provider<JavafxApplicationResources> javafxApplicationResourcesProvider,
GooglePhotosClient googlePhotosClient) {
this.executorFactory = checkNotNull(executorFactory);
this.javafxApplicationResourcesProvider = checkNotNull(javafxApplicationResourcesProvider);
this.googlePhotosClient = checkNotNull(googlePhotosClient);
}

public void initialize() {
executor = executorFactory.createSingleThreadedSchedulingExecutor("empty-albums");
executor.scheduleAtFixedRate(Duration.ZERO, Duration.ofMinutes(1), this::refreshAlbums);

titleColumn.setCellValueFactory(param -> new ObservableValueBase<>() {
@Override
public SelectableAlbum getValue() {
return param.getValue();
}
});
titleColumn.setCellFactory(param -> new TableCell<>() {
@Override
protected void updateItem(SelectableAlbum album, boolean empty) {
super.updateItem(album, empty);
if (album == null || empty) {
setText(null);
} else {
var hyperlink = new Hyperlink(album.googlePhotosAlbum.getTitle());
hyperlink.setOnAction(event -> javafxApplicationResourcesProvider.get().hostServices().showDocument(album.googlePhotosAlbum.getAlbumUrl()));
setGraphic(hyperlink);
}
}
});

itemCountColumn.setCellValueFactory(param -> new ObservableValueBase<>() {
@Override
public Long getValue() {
return param.getValue().googlePhotosAlbum.getMediaItemCount();
}
});


selectAllCheckBox = new CheckBox();
selectAllCheckBox.selectedProperty().addListener(this::onSelectAllChanged);
selectColumn.setCellValueFactory(data -> data.getValue().selectedProperty);
selectColumn.setGraphic(selectAllCheckBox);
selectColumn.setCellFactory(CheckBoxTableCell.forTableColumn(selectColumn));
selectColumn.setEditable(true);

tableView.setEditable(true);
}

@SuppressWarnings("TypeParameterExtendsFinalClass")
private void onSelectAllChanged(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
runSelectionUpdateListener(() -> {
var items = tableView.getItems();
items.forEach(selectableAlbum -> selectableAlbum.selectedProperty.set(newValue));
selectedCount = newValue ? items.size() : 0;
logger.debug("selectedCount={}", selectedCount);
});
}

private void refreshAlbums() {
Platform.runLater(() -> loadingIndicator.setVisible(true));
googlePhotosClient.listAlbums(executor)
.exceptionally(e -> {
// TODO reflect in the UI
logger.error("Failed to load albums", e);
return emptyList();
})
.thenAccept(albums -> {
var newAlbumIds = albums.stream()
.filter(GooglePhotosAlbum::isWriteable)
.map(GooglePhotosAlbum::getId).collect(toImmutableSet());
Platform.runLater(() -> {
loadingIndicator.setVisible(false);
var tableItems = tableView.getItems();
// remove deleted
tableItems.removeIf(selectableAlbum -> !newAlbumIds.contains(selectableAlbum.googlePhotosAlbum.getId()));
// add new
var existingAlbumIds = tableItems.stream().map(selectableAlbum -> selectableAlbum.googlePhotosAlbum.getId()).collect(toImmutableSet());
albums.stream()
.filter(GooglePhotosAlbum::isWriteable)
.filter(googlePhotosAlbum -> !existingAlbumIds.contains(googlePhotosAlbum.getId()))
.forEach(googlePhotosAlbum -> tableItems.add(new SelectableAlbum(googlePhotosAlbum)));
// re-index
selectedCount = 0;
for (var selectableAlbum : tableItems) {
if (selectableAlbum.selectedProperty.get()) {
selectedCount++;
}
}
runSelectionUpdateListener(() -> {
updateSelectAllCheckbox();
updateButtons();
});
});
});
}

private void updateButtons() {
deleteButton.setDisable(selectedCount == 0);
}

private void updateSelectAllCheckbox() {
logger.debug("selectedCount={}, table items={}", selectedCount, tableView.getItems().size());
if (selectedCount == tableView.getItems().size()) {
selectAllCheckBox.setSelected(true);
selectAllCheckBox.setIndeterminate(false);
} else if (selectedCount == 0) {
selectAllCheckBox.setSelected(false);
selectAllCheckBox.setIndeterminate(false);
} else {
selectAllCheckBox.setIndeterminate(true);
}
}

private void runSelectionUpdateListener(Runnable action) {
if (!updatingSelectionBoxes) {
updatingSelectionBoxes = true;
try {
action.run();
} finally {
updatingSelectionBoxes = false;
}
}
}

@Override
protected void doStop() {
// TODO stop all activity, clear table contents
executor.close();
}

public void onDeleteButtonAction(ActionEvent actionEvent) {

actionEvent.consume();
}

private final class SelectableAlbum {
private final GooglePhotosAlbum googlePhotosAlbum;
private final BooleanProperty selectedProperty = new SimpleBooleanProperty(false);

private SelectableAlbum(GooglePhotosAlbum googlePhotosAlbum) {
this.googlePhotosAlbum = checkNotNull(googlePhotosAlbum);
selectedProperty.addListener((observable, oldValue, newValue) -> runSelectionUpdateListener(() -> {
if (newValue) {
selectedCount++;
} else {
selectedCount--;
}
updateSelectAllCheckbox();
updateButtons();
}));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import net.yudichev.jiotty.common.inject.BaseLifecycleComponent;

import javax.inject.Inject;
import javax.inject.Provider;
import java.io.File;
import java.nio.file.Path;
import java.util.ResourceBundle;
Expand All @@ -25,6 +26,7 @@

public final class FolderSelectorControllerImpl extends BaseLifecycleComponent implements FolderSelectorController {
private final Uploader uploader;
private final Provider<MainScreenController> mainScreenControllerProvider;
private final ResourceBundle resourceBundle;
public VBox folderSelector;
public CheckBox resumeCheckbox;
Expand All @@ -35,8 +37,10 @@ public final class FolderSelectorControllerImpl extends BaseLifecycleComponent i

@Inject
FolderSelectorControllerImpl(Uploader uploader,
Provider<MainScreenController> mainScreenControllerProvider,
ResourceBundle resourceBundle) {
this.uploader = checkNotNull(uploader);
this.mainScreenControllerProvider = checkNotNull(mainScreenControllerProvider);
this.resourceBundle = checkNotNull(resourceBundle);
}

Expand Down Expand Up @@ -121,4 +125,9 @@ public void onBrowseButtonClick(ActionEvent actionEvent) {
}
actionEvent.consume();
}

public void onAlbumManagerButtonClick(ActionEvent actionEvent) {
mainScreenControllerProvider.get().launchAlbumManager();
actionEvent.consume();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

public interface MainScreenController {
void toFolderSelectionMode();

void launchAlbumManager();
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public final class MainScreenControllerImpl implements MainScreenController {
public VBox root;
private Dialog preferencesDialog;
private Dialog aboutDialog;
private Dialog albumEditorDialog;

@Inject
public MainScreenControllerImpl(ApplicationLifecycleControl applicationLifecycleControl,
Expand Down Expand Up @@ -122,12 +123,28 @@ public void toFolderSelectionMode() {
});
}

@Override
public void launchAlbumManager() {
if (albumEditorDialog == null) {
albumEditorDialog = dialogFactory.create(
resourceBundle.getString("albumEditorDialogTitle"),
"AlbumEditor.fxml",
stage -> {});
}
albumEditorDialog.show();
}

public void onStopUpload(ActionEvent actionEvent) {
menuItemStopUpload.setDisable(true);
uploadPaneController.stopUpload();
actionEvent.consume();
}

public void onLaunchAlbumEditor(ActionEvent actionEvent) {
launchAlbumManager();
actionEvent.consume();
}

private void onAbout(ActionEvent actionEvent) {
if (aboutDialog == null) {
aboutDialog = dialogFactory.create(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ private void bindControllers() {
bind(UploaderStrategyChoicePanelControllerImpl.class).in(Singleton.class);
bind(UploaderStrategyChoicePanelController.class).toProvider(UploaderStrategyChoicePanelControllerProvider.class).in(Singleton.class);

bind(AlbumEditorController.class).to(boundLifecycleComponent(AlbumEditorControllerImpl.class));

// needed for FxmlLoader to find them
expose(MainScreenControllerImpl.class);
expose(LoginDialogControllerImpl.class);
Expand All @@ -87,5 +89,7 @@ private void bindControllers() {
expose(UploaderStrategyChoicePanelController.class);
expose(UploaderStrategyChoicePanelControllerImpl.class);
expose(uploadPaneControllerKey);
expose(AlbumEditorControllerImpl.class);
expose(AlbumEditorController.class);
}
}
24 changes: 24 additions & 0 deletions src/main/resources/AlbumEditor.fxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml/1" prefHeight="800.0" prefWidth="800.0" stylesheets="@style.css" xmlns="http://javafx.com/javafx/11.0.1"
fx:controller="net.yudichev.googlephotosupload.ui.AlbumEditorControllerImpl">
<HBox>
<Button fx:id="deleteButton" onAction="#onDeleteButtonAction" text="%albumEditorDeleteButtonText" disable="true"/>
<Label HBox.hgrow="ALWAYS" text="%albumEditorFilterNotice"/>
<ProgressIndicator fx:id="loadingIndicator" maxHeight="${deleteButton.height}" visible="false"/>
<padding>
<Insets bottom="4.0" left="4.0" right="4.0" top="4.0"/>
</padding>
</HBox>
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="selectColumn" sortable="false"/>
<TableColumn fx:id="titleColumn" prefWidth="500" text="%albumEditorColumnAlbumTitle"/>
<TableColumn fx:id="itemCountColumn" text="%albumEditorColumnAlbumItemCount"/>
</columns>
</TableView>
</VBox>
4 changes: 3 additions & 1 deletion src/main/resources/FolderSelector.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:id="folderSelector" alignment="CENTER" onDragDropped="#folderSelectorOnDragDropped"
onDragEntered="#folderSelectorOnDragEnter" onDragExited="#folderSelectorOnDragExit" onDragOver="#folderSelectorOnDragOver" spacing="10.0"
stylesheets="@style.css" VBox.vgrow="ALWAYS" xmlns="http://javafx.com/javafx/10.0.2-internal"
stylesheets="@style.css"
VBox.vgrow="ALWAYS" xmlns="http://javafx.com/javafx/11.0.1"
fx:controller="net.yudichev.googlephotosupload.ui.FolderSelectorControllerImpl">
<VBox alignment="CENTER" spacing="10.0" VBox.vgrow="ALWAYS">
<ImageView fitWidth="250.0" preserveRatio="true" VBox.vgrow="NEVER">
Expand All @@ -19,6 +20,7 @@
</ImageView>
<Label alignment="CENTER" contentDisplay="CENTER" text="%folderSelectorDragHereLabel" textAlignment="CENTER" wrapText="true"/>
<Button alignment="CENTER" mnemonicParsing="false" onAction="#onBrowseButtonClick" text="%folderSelectorBrowseButtonLabel"/>
<Button alignment="CENTER" mnemonicParsing="false" onAction="#onAlbumManagerButtonClick" text="%folderSelectorAlbumManagerButtonLabel"/>
</VBox>
<FlowPane fx:id="resumePane" alignment="BOTTOM_CENTER" hgap="4.0" visible="false">
<CheckBox fx:id="resumeCheckbox" mnemonicParsing="false" selected="true" text="%folderSelectorResumeCheckboxLabel"/>
Expand Down
7 changes: 4 additions & 3 deletions src/main/resources/MainScreen.fxml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:id="root" prefHeight="700.0" prefWidth="600.0" stylesheets="@style.css"
xmlns="http://javafx.com/javafx/10.0.2-internal" fx:controller="net.yudichev.googlephotosupload.ui.MainScreenControllerImpl">
Expand All @@ -15,5 +13,8 @@
<MenuItem fx:id="menuItemStopUpload" disable="true" mnemonicParsing="false" onAction="#onStopUpload" text="%mainScreenMenuActionsStopUploadText"/>
<MenuItem fx:id="menuItemLogout" disable="true" mnemonicParsing="false" onAction="#onMenuActionLogout" text="%mainScreenMenuActionsLogoutText"/>
</Menu>
<Menu mnemonicParsing="false" text="%mainScreenMenuToolsText">
<MenuItem mnemonicParsing="false" onAction="#onLaunchAlbumEditor" text="%mainScreenMenuToolsLaunchAlbumEditor"/>
</Menu>
</MenuBar>
</VBox>
12 changes: 11 additions & 1 deletion src/main/resources/i18n/Resources.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ menuItemDefaultSettings=Settings...
menuItemDefaultExit=Exit
folderSelectorAlreadyUploadedLabelPrefix=already uploaded:
folderSelectorDirectoryChooserTitle=Select folder with media
folderSelectorAlbumManagerButtonLabel=Album Editor...
mainScreenAboutDialogTitle=About
mainScreenInitialUploadMethodDialogTitle=You've got a choice!
preferencesDialogTitle=Preferences
Expand Down Expand Up @@ -53,6 +54,9 @@ folderSelectorResumeCheckboxLabel=Resume from last attempt
mainScreenMenuActionsText=Actions
mainScreenMenuActionsStopUploadText=Stop Upload
mainScreenMenuActionsLogoutText=Logout
#TODO add to all
mainScreenMenuToolsText=Tools
mainScreenMenuToolsLaunchAlbumEditor=Album Manager
supportPanePaymentSentence=I develop this app in my free time.
supportPanePaymentLinkSentence=Please support the project!
supportPaneReportIssueSentence=Problems? Suggestions?
Expand Down Expand Up @@ -80,4 +84,10 @@ singleInstanceCheckDialogMessage=Another copy of the app is already running
albumManagerPleaseDeleteManually=Album '%s' may now be empty and will require manual deletion (Google Photos API does not allow me to delete it for you)
diagnosticsHeapDumpFailureMessage=Failed to write heap dump
diagnosticsHeapDumpSuccessMessage=Heap dump written to %s
fatalUserCorrectableRemoteApiException.maybeEmptyFile=Empty file?
fatalUserCorrectableRemoteApiException.maybeEmptyFile=Empty file?
#TODO add to others
albumEditorDialogTitle=Album Editor
albumEditorDeleteButtonText=Delete
albumEditorColumnAlbumTitle=Title
albumEditorColumnAlbumItemCount=Items
albumEditorFilterNotice=Only showing albums Jiotty is allowed to edit

0 comments on commit c526e60

Please sign in to comment.