Compare commits

...

186 Commits

Author SHA1 Message Date
Armin Schrenk
1fb987607c use single method to initialize JavaFX 2025-01-21 17:03:42 +01:00
Armin Schrenk
5d4066d29a fix test 2025-01-21 16:01:49 +01:00
Armin Schrenk
d3909134d5 actually use authentication 2025-01-21 15:55:47 +01:00
Armin Schrenk
fd9d1bf0cf fix unit test 2025-01-16 10:49:57 +01:00
Armin Schrenk
2f812377c3 bump integrations-win to 1.4.1 to fix jvm crashes 2025-01-16 10:31:38 +01:00
Armin Schrenk
ebce90eb74 weakValues are too agressive 2025-01-15 10:02:42 +01:00
Armin Schrenk
c888b52ebb rely on the cache directly after unlock and inject in the cache on keychain changes 2025-01-15 10:02:21 +01:00
Armin Schrenk
e422c7ce4b use caffeine for building a cache
(cherry picked from commit fc709eb700)
2025-01-15 09:38:50 +01:00
Armin Schrenk
41ad8d5dcc response in th ui to keychain impl change 2025-01-14 18:42:08 +01:00
Armin Schrenk
526c3973d6 bump dependencies to add biometric auth providers
* integrations-win to 1.4.0
* integrations-mac to 1.3.0
2025-01-14 15:42:10 +01:00
Armin Schrenk
dc16c961af only store password, if it is not already stored 2025-01-14 15:41:17 +01:00
Ralph Plawetzki
c54a603078 Merge branch 'develop' into 2ndfactor 2025-01-11 17:59:54 +01:00
mindmonk
de3b2715c7 Merge pull request #3618 from cryptomator/feature/dialog-builder
Feature: SimpleDialogBuilder
2025-01-10 15:17:24 +01:00
Jan-Peter Klein
f3d8bd359d Merge branch 'develop' into feature/dialog-builder 2025-01-10 15:05:07 +01:00
Armin Schrenk
d7d6e46b2a Ensure JDK 23 is used in building mac dmg x64 2025-01-10 14:33:34 +01:00
Jan-Peter Klein
501f682d68 annotate Dialogs with @Singleton
removed redundant providers
2025-01-10 11:58:19 +01:00
Jan-Peter Klein
7cbf1e10e5 refactor dialogs with injected resourceBundle for builder creation 2025-01-09 17:39:02 +01:00
Jan-Peter Klein
fb00972bc1 improve naming 2025-01-09 15:31:07 +01:00
Jan-Peter Klein
387fb28c65 remove unused LOGs and improved var naming 2025-01-09 15:22:25 +01:00
Jan-Peter Klein
40fbfd10d1 moved all simple dialogs to Dialogs 2025-01-08 19:17:20 +01:00
Jan-Peter Klein
e5e4695cd6 replaced setters and extended the constructor of SimpleDialogController 2025-01-08 17:50:54 +01:00
Armin Schrenk
4a0c8ae80e fix check-jdk-update workflow [ci skip] 2025-01-08 17:09:53 +01:00
Armin Schrenk
384ca8eb2a for testing 2025-01-08 16:02:00 +01:00
Jan-Peter Klein
59f360bde7 improve formatting, rename and simplify vars 2025-01-08 15:07:04 +01:00
Jan-Peter Klein
8830e0f5fb Move repeated 'remove vault' dialog to Dialogs class 2025-01-07 15:19:06 +01:00
Jan-Peter Klein
7a391a2825 reformat code 2025-01-07 14:47:20 +01:00
Jan-Peter Klein
a1941911ef renamed to simple Dialog 2025-01-07 14:45:09 +01:00
Jan-Peter Klein
a0667ff361 add validation for required parameters and changed exception message 2025-01-07 14:39:22 +01:00
Ralph Plawetzki
be59097a53 Merge branch 'develop' into 2ndfactor 2024-12-14 07:37:30 +01:00
Armin Schrenk
cbc41d535d Merge pull request #3641 from cryptomator/feature/jdk23
Feature: JDK 23
2024-12-11 15:42:09 +01:00
Armin Schrenk
f51780dcb5 also update deb rules file [ci skip] 2024-12-10 18:03:07 +01:00
Armin Schrenk
c055488df7 update github workflows 2024-12-10 17:47:48 +01:00
Armin Schrenk
b15c376e59 fix and update JDK check workflow 2024-12-10 17:42:55 +01:00
Armin Schrenk
fdf03d2fdc enable JDK23 for IDE 2024-12-10 17:15:34 +01:00
Armin Schrenk
437350af9f reduce api diff 2024-12-10 17:14:09 +01:00
Julian Raufelder
5114e2ad22 Merge pull request #3640 from lamtrinhdev/develop
Correct the link of issues list for both iOS and Android.
2024-12-09 14:09:03 +01:00
LamTrinh.Dev
b3b4f61c42 Correct the link of issues list for both iOS and Android. 2024-12-09 19:13:44 +07:00
Armin Schrenk
fa16556af1 do not allow empty display name for vault
Fixes #3633
2024-12-02 11:02:11 +01:00
Armin Schrenk
de766c2653 Update suppression.xml 2024-12-02 09:40:40 +01:00
Ralph Plawetzki
ccc4842839 Use widened integrations-api 2024-12-02 08:16:28 +01:00
Ralph Plawetzki
d95625faa1 Revert "Add CheckBox to enable Touch ID per vault"
This reverts commit cfa3093dd0.
2024-12-02 08:12:03 +01:00
Ralph Plawetzki
c5bfdf32a9 Revert "Save Touch ID setting per vault"
This reverts commit 10bce1fb06.
2024-12-02 08:12:03 +01:00
Ralph Plawetzki
0598681a23 Revert "Decide on Touch ID setting whether user needs"
This reverts commit 2194360c8a.
2024-12-02 08:12:03 +01:00
Ralph Plawetzki
3b8bff0d47 Revert "loadPassphraseForAuthenticatedUser is not needed to"
This reverts commit 2378227756.
2024-12-02 08:12:02 +01:00
Ralph Plawetzki
55051072cb Revert "Actually save preference on checkbox change"
This reverts commit 62827b69cc.
2024-12-02 08:12:02 +01:00
Ralph Plawetzki
6cb409eeaf Revert "Grab new API"
This reverts commit 9516928529.
2024-12-02 08:12:02 +01:00
Ralph Plawetzki
b38a350c48 Revert "Remove log statement used for testing"
This reverts commit 14ba852351.
2024-12-02 08:12:02 +01:00
Ralph Plawetzki
0e5857015a Revert "Change existing keychain entry"
This reverts commit 9cc863ae79.
2024-12-02 08:12:02 +01:00
Ralph Plawetzki
9e9aa20692 Revert "Disable feature for other platforms than Mac"
This reverts commit d8b798ff0f.
2024-12-02 08:12:01 +01:00
Ralph Plawetzki
ad49ca2cb4 Revert "Generalize naming"
This reverts commit 953aee560f.
2024-12-02 08:12:01 +01:00
Ralph Plawetzki
ba84da71f3 Revert "Code cleanups following coderabbitai recommendations"
This reverts commit af3779ba2e.
2024-12-02 08:12:01 +01:00
Ralph Plawetzki
5c34f59d09 Revert "Don't use SNAPSHOTS"
This reverts commit 175ed500a2.
2024-12-02 08:12:01 +01:00
Ralph Plawetzki
7499867665 Revert "Use overloaded method storePassword instead of a new one"
This reverts commit aa34ad52e6.
2024-12-02 08:12:00 +01:00
Ralph Plawetzki
7b293b8082 Revert "New, widened integrations-api"
This reverts commit 2f1b5109d6.
2024-12-02 08:12:00 +01:00
Ralph Plawetzki
46e8a13555 Revert "Improve thread handling"
This reverts commit c8075dbc19.
2024-12-02 08:11:01 +01:00
Ralph Plawetzki
63a3150ec3 Revert "Remove unneeded saving a keychain entry right after"
This reverts commit e8126e68ce.
2024-12-02 08:11:01 +01:00
Ralph Plawetzki
0a78987d30 Revert "Fix typo"
This reverts commit 78675c9638.
2024-12-02 08:11:00 +01:00
Ralph Plawetzki
929782c318 Revert "Fix doubled method due to merge error"
This reverts commit b5dbfd3209.
2024-12-02 08:11:00 +01:00
Ralph Plawetzki
97a0f9c435 Migrate keychain entries on Mac on provider change 2024-12-02 07:56:23 +01:00
Ralph Plawetzki
f7e65f4eec Add cryptomator.integrationsWin.windowsHelloKeychainPaths to Environment 2024-11-30 14:39:56 +01:00
Ralph Plawetzki
b3c56f3aab Add Windows Hello provider 2024-11-30 12:25:36 +01:00
Armin Schrenk
dcc27efe5c remove unused option in app-image creation [skip ci]
references 84732337ca
2024-11-28 17:26:10 +01:00
Armin Schrenk
78ceb604f9 only use macos intel runner on releases 2024-11-27 16:47:42 +01:00
Armin Schrenk
7bdcdcba3d Update suppression.xml 2024-11-26 15:49:43 +01:00
Jan-Peter Klein
e929d41d67 reordered and renamed to org.ui.dialogs.SimpleDialog... 2024-11-25 19:23:48 +01:00
Jan-Peter Klein
600aca083c refactor CustomDialog.Builder provisioning to use Dagger's Provider 2024-11-25 15:09:29 +01:00
Ralph Plawetzki
b5dbfd3209 Fix doubled method due to merge error 2024-11-24 18:14:26 +01:00
Ralph Plawetzki
78675c9638 Fix typo 2024-11-24 15:02:06 +01:00
Ralph Plawetzki
e8126e68ce Remove unneeded saving a keychain entry right after
it was loaded
2024-11-24 15:02:06 +01:00
Ralph Plawetzki
c8075dbc19 Improve thread handling 2024-11-24 15:02:05 +01:00
Ralph Plawetzki
2f1b5109d6 New, widened integrations-api 2024-11-24 15:01:44 +01:00
Ralph Plawetzki
aa34ad52e6 Use overloaded method storePassword instead of a new one 2024-11-24 15:00:54 +01:00
Ralph Plawetzki
175ed500a2 Don't use SNAPSHOTS 2024-11-24 14:57:16 +01:00
Ralph Plawetzki
af3779ba2e Code cleanups following coderabbitai recommendations 2024-11-24 14:56:38 +01:00
Ralph Plawetzki
953aee560f Generalize naming 2024-11-24 14:56:38 +01:00
Ralph Plawetzki
d8b798ff0f Disable feature for other platforms than Mac 2024-11-24 14:56:38 +01:00
Ralph Plawetzki
9cc863ae79 Change existing keychain entry 2024-11-24 14:56:38 +01:00
Ralph Plawetzki
14ba852351 Remove log statement used for testing 2024-11-24 14:56:37 +01:00
Ralph Plawetzki
9516928529 Grab new API 2024-11-24 14:56:30 +01:00
Ralph Plawetzki
62827b69cc Actually save preference on checkbox change 2024-11-24 14:48:32 +01:00
Ralph Plawetzki
2378227756 loadPassphraseForAuthenticatedUser is not needed to
load as an authenticated user; loadPassphrase is
sufficient
2024-11-24 14:48:32 +01:00
Ralph Plawetzki
2194360c8a Decide on Touch ID setting whether user needs
to authenticate on loading and storing a passphrase
2024-11-24 14:48:32 +01:00
Ralph Plawetzki
10bce1fb06 Save Touch ID setting per vault 2024-11-24 14:48:32 +01:00
Ralph Plawetzki
cfa3093dd0 Add CheckBox to enable Touch ID per vault 2024-11-24 14:48:31 +01:00
Jan-Peter Klein
02c20c01ee improve exception handling 2024-11-21 16:55:03 +01:00
mindmonk
50cfdbbc0b Merge pull request #3617 from cryptomator/feature/ui-cleanup
Enhance UI Components, Fix Issues, and Improve Visual Consistency
2024-11-20 12:21:31 +01:00
Gutyina Gergő
38c556af0b Update jdk version to 23 2024-11-19 23:31:24 +01:00
Gutyina Gergő
0b9d777109 Remove string templates 2024-11-19 23:31:24 +01:00
Armin Schrenk
eb3c1f3c7a Merge branch 'main' into develop
# Conflicts:
#	pom.xml
2024-11-19 10:01:00 +01:00
Jan-Peter Klein
bba5d11bdc small fixes 2024-11-18 18:07:36 +01:00
mindmonk
36ef191874 Merge branch 'develop' into feature/dialog-builder 2024-11-18 17:58:59 +01:00
Jan-Peter Klein
3e4c6c1b61 del old logo file 2024-11-18 16:59:13 +01:00
Jan-Peter Klein
c9d0224814 fix logo scaling 2024-11-18 16:48:41 +01:00
Armin Schrenk
8aed6045a0 Feature: Show update reminder every 14 days (#3597)
fixes #3596

only show update reminder every 14 days
2024-11-15 18:14:57 +01:00
Jan-Peter Klein
8440705436 set resizable false 2024-11-15 15:49:11 +01:00
Jan-Peter Klein
af667b10cf code cleanup 2024-11-14 18:09:23 +01:00
Jan-Peter Klein
1b522fe9a0 undo add uiDesigner 2024-11-14 17:20:08 +01:00
Jan-Peter Klein
cb3c46744e removed replaced files 2024-11-14 16:38:44 +01:00
Jan-Peter Klein
c471289fb5 changed resolveText() to throw Exceptions
removed fxApplication scope
2024-11-14 16:19:50 +01:00
Jan-Peter Klein
18ca563865 custom dialog init 2024-11-14 11:35:23 +01:00
Jan-Peter Klein
2430526ee7 changed to class Builder
used FxmlLoaderFactory.forController
set title and other string values by key
2024-11-06 16:35:19 +01:00
Armin Schrenk
c9bb0235e2 use macos 15 for building [ci skip] 2024-11-05 16:18:32 +01:00
Armin Schrenk
2f8236e9db Update release template [ci skip] 2024-11-05 12:10:57 +01:00
dependabot[bot]
68c206a4fa Bump the java-test-dependencies group across 1 directory with 2 updates (#3591) 2024-11-04 15:49:48 +00:00
dependabot[bot]
d258171131 Bump the maven-build-plugins group across 1 directory with 3 updates (#3598) 2024-11-04 15:47:08 +00:00
dependabot[bot]
adaf9ef5ce Bump the java-production-dependencies group across 1 directory with 8 updates (#3603) 2024-11-04 15:43:21 +00:00
Tobias Hagemann
22f299f67c Update README.md
[ci skip]
2024-11-04 15:13:40 +01:00
Cryptobot
0fb0bc8e1d New Crowdin updates (#3559)
New translations strings.properties

Afrikaans; Arabic; Bashkir; Belarusian; Bengali; Bosnian; Bulgarian; Catalan; Chinese Simplified; Chinese Traditional; Chinese Traditional, Hong Kong; Croatian; Czech; Danish; Dutch; English; Filipino; Finnish; French; Galician; German; Greek; Hebrew; Hindi; Hungarian; Indonesian; Italian; Japanese; Korean; Latvian; Macedonian; Marathi; Norwegian; Norwegian Bokmal; Norwegian Nynorsk; Persian; Polish; Portuguese; Portuguese, Brazilian; Punjabi; Romanian; Russian; Serbian (Cyrillic); Serbian (Latin); Sinhala; Slovak; Slovenian; Spanish; Swahili, Tanzania; Swedish; Tamil; Telugu; Thai; Turkish; Ukrainian; Urdu (Pakistan); Uyghur; Vietnamese; 

[ci skip]
2024-11-04 15:05:37 +01:00
Armin Schrenk
da666410fc update dependabot config 2024-11-04 14:56:02 +01:00
Armin Schrenk
ea007f3cfe fix IDE issues 2024-10-29 09:46:56 +01:00
Armin Schrenk
1c868a644f remove unused/obsolete static variable 2024-10-29 09:44:50 +01:00
Jan-Peter Klein
5ff6a8c258 used dialogBuilder for dokankySupportEndDialog 2024-10-23 19:48:55 +02:00
Jan-Peter Klein
62d7b7a0c0 replaced RemoveVaultComponent.Builder with CustomDialogBuilder in all places 2024-10-22 18:53:06 +02:00
Jan-Peter Klein
7fcbb57ab1 used builder pattern 2024-10-22 17:07:42 +02:00
Jan-Peter Klein
8e25dcd396 implement CustomDialogBuilder 2024-10-22 14:56:56 +02:00
mindmonk
a9f1e5b761 Merge pull request #3550 from cryptomator/feature/gdrive-presets
Feature: Refined GoogleDrive Presets - List Only "My Drive" Root Folders
2024-10-18 13:05:50 +02:00
Jan-Peter Klein
f642149723 used List.of() for FALLBACK_LOCATIONS 2024-10-16 18:00:47 +02:00
Jan-Peter Klein
4b359d98cb removed unnecessary stream support 2024-10-10 17:39:53 +02:00
Jan-Peter Klein
764fccf0b5 pr mentioned changes 2024-10-10 17:34:23 +02:00
Jan-Peter Klein
81d1059489 added generic 'Remove' string property 2024-10-09 17:35:33 +02:00
Jan-Peter Klein
a2574a5883 unified spacing 2024-10-09 16:59:13 +02:00
Jan-Peter Klein
118a1411d8 fixed empty vaultList contextMenu 2024-10-09 16:55:56 +02:00
Jan-Peter Klein
44310cbd0e set missing min width and height 2024-10-09 16:50:26 +02:00
Jan-Peter Klein
2dce7b6f71 removed drivePath param 2024-10-09 14:50:05 +02:00
Jan-Peter Klein
98db82d137 contains instead of stream 2024-10-09 14:48:40 +02:00
Jan-Peter Klein
fd55656261 filtered by 'My Drive' and translations 2024-10-08 20:08:37 +02:00
mindmonk
978dec64ee Merge pull request #3549 from cryptomator/feature/improve-preferences-contribute-tab
Feature: Optimize 'Support us' Tab in Preferences - Add Supporter Certificate Removal & Improved Layout
2024-10-04 12:27:13 +02:00
mindmonk
8d7bf3a370 Merge pull request #3467 from cryptomator/feature/redesign-mainwindow
Feature: Redesigned MainWindow
2024-10-04 12:13:09 +02:00
Jan-Peter Klein
1cea7bcffc set bigger minHeight 2024-10-02 17:03:52 +02:00
Jan-Peter Klein
16e77d4b9b add clear to setStyleClass 2024-10-02 16:56:39 +02:00
Jan-Peter Klein
f22111e36f revert unrelated changes 2024-10-02 16:46:41 +02:00
Jan-Peter Klein
c5d4c9a9c1 removed duplicate code 2024-10-02 14:20:09 +02:00
mindmonk
4bc9b1d60c Merge pull request #3474 from cryptomator/feature/notificationbar
Feature: Introduce NotificationBars for Update is available, Support Cryptomator, and DEBUG MODE
2024-10-02 11:05:58 +02:00
mindmonk
7a40a3cb0c Merge branch 'feature/redesign-mainwindow' into feature/notificationbar 2024-10-02 11:05:39 +02:00
mindmonk
ad555ece8e Merge pull request #3554 from cryptomator/feature/condensed-vault-list
Feature: Add Option to Enable Compact Vault List View
2024-10-02 10:55:51 +02:00
Jan-Peter Klein
2d96d2e5c6 optimised cellSize binding and removed unused imports 2024-10-01 18:56:21 +02:00
Armin Schrenk
0a968b60aa Remove Guava dependency and update Dagger 2024-10-01 17:19:31 +02:00
Jan-Peter Klein
3b4f82092d undo string removal 2024-10-01 13:31:43 +02:00
Jan-Peter Klein
de16647d00 reformat code 2024-09-26 11:24:57 +02:00
Ahmatjan
03403e53bc translation: Add Uyghur Language Translation (#3557) 2024-09-26 11:12:26 +02:00
Cryptobot
2939702842 New Crowdin updates (#3553)
New translations strings.properties

Afrikaans; English; Norwegian; Thai; Uyghur;

[ci skip]
2024-09-26 11:10:21 +02:00
Jan-Peter Klein
b3d09a4cae changed apostroph 2024-09-26 10:41:17 +02:00
Jan-Peter Klein
73c0af9749 changed wording and removed unnecessary method calls 2024-09-25 16:20:19 +02:00
Jan-Peter Klein
53c7e031a3 changes mentioned by infeo 2024-09-24 16:14:52 +02:00
Jan-Peter Klein
1e280f2c97 removed FXML loader, elements created programmatically in NotificationBar 2024-09-24 16:05:31 +02:00
Jan-Peter Klein
ce82593fc6 simplified bindings 2024-09-24 15:48:15 +02:00
Jan-Peter Klein
c2c3d778d1 added @CheckAvailability
changed exception handling
2024-09-24 15:29:13 +02:00
Jan-Peter Klein
7771181e15 changed wording to compact 2024-09-23 14:21:55 +02:00
Jan-Peter Klein
1a39333b4c cr mentioned changes 2024-09-23 14:02:42 +02:00
Armin Schrenk
85472db00a Merge branch 'main' into develop 2024-09-18 13:25:15 +02:00
Jan-Peter Klein
31e9f3a025 Merge branch 'feature/redesign-mainwindow' into feature/condensed-vault-list 2024-09-17 18:06:14 +02:00
Jan-Peter Klein
a03d5af144 add tilter to display only folders with write permissions 2024-09-17 16:49:31 +02:00
Armin Schrenk
50a654d5af [ci skip] use recommended winget-releaser version 2024-09-17 11:37:27 +02:00
Armin Schrenk
1954f31910 [ci skip] Merge branch 'main' into develop 2024-09-17 10:04:32 +02:00
Jan-Peter Klein
c5cfe4d1b3 removed unnecessary properties 2024-09-16 15:40:52 +02:00
Jan-Peter Klein
a09b55c81b triggered updatechecker in constructor 2024-09-16 15:39:14 +02:00
Jan-Peter Klein
32f266c721 removed unused vbox in controller 2024-09-16 15:26:45 +02:00
Jan-Peter Klein
3adffad087 removed ALT+F4 shortcut, it is already registered by default 2024-09-16 14:55:51 +02:00
Jan-Peter Klein
84e3cf0762 removed green border and fixed margin for windows 2024-09-16 14:48:06 +02:00
Jan-Peter Klein
1292936904 clean up 2024-09-13 11:49:05 +02:00
Jan-Peter Klein
7a913c89c9 condensed vault list implemented 2024-09-12 11:36:14 +02:00
Jan-Peter Klein
4c4816bdab fixed missing import 2024-09-11 15:10:23 +02:00
Jan-Peter Klein
f9bfd5d1b1 set stage owner 2024-09-11 15:06:27 +02:00
Jan-Peter Klein
00e1e3654e implemented remove cert dialog 2024-09-10 18:01:09 +02:00
Jan-Peter Klein
1946fc6c0e filter hidden directories 2024-09-04 15:05:24 +02:00
Jan-Peter Klein
8fb95b67d9 changes mentioned in pull #3270 2024-09-04 14:28:42 +02:00
Jan-Peter Klein
e6890e8fa5 Merge branch 'develop' of https://github.com/siard-y/cryptomator into feature/gdrive-presets 2024-09-04 13:56:47 +02:00
Jan-Peter Klein
7cdb2025dc improved supporter cert ui with cert stemp, donate/sponsors link buttons and remove button with dialog 2024-09-04 13:23:36 +02:00
Jan-Peter Klein
958c22bed8 Merge branch 'feature/redesign-mainwindow' in feature/notificationbar 2024-07-16 14:52:50 +02:00
Jan-Peter Klein
6ff640648f Merge branch 'develop' into feature/redesign-mainwindow 2024-07-16 14:48:50 +02:00
Jan-Peter Klein
7b132adfe2 adjust add vault button position for improved UI 2024-07-16 14:31:11 +02:00
Jan-Peter Klein
3497144034 introduced NotificationBar control 2024-07-08 16:42:49 +02:00
Jan-Peter Klein
edbeea5502 fixed update check on start and bindings 2024-07-02 15:31:28 +02:00
Jan-Peter Klein
a88e08147d changed background color of pref button 2024-07-02 15:08:07 +02:00
Jan-Peter Klein
8ff06a3efd implement hideable notification bars 2024-07-02 12:43:27 +02:00
Jan-Peter Klein
902c66cf1e removed vbox id and renamed style 2024-07-01 18:19:30 +02:00
Jan-Peter Klein
d58307d1d6 optimize vaultList height update to avoid repeated bindings 2024-07-01 13:09:53 +02:00
Jan-Peter Klein
3e6204a657 remove showMinimizeButton setting 2024-07-01 12:32:50 +02:00
Jan-Peter Klein
e677a0beaa removed MainWindowTitleController and ResizeController and the corresponding fxml 2024-07-01 12:25:48 +02:00
Jan-Peter Klein
d379ada100 addded debug, update and support notification bar 2024-06-26 15:13:28 +02:00
Jan-Peter Klein
cb7d0ade47 fixed some SonarCloud mentioned issues 2024-06-26 13:43:21 +02:00
Jan-Peter Klein
4d9dc4312d Merge branch 'develop' into feature/redesign-mainwindow 2024-06-26 13:07:53 +02:00
Jan-Peter Klein
92fad41b96 Merge branch 'develop' into feature/redesign-mainwindow 2024-06-13 12:49:59 +02:00
Jan-Peter Klein
e038348dca changed mainWindow StageStyle to UNDECORATED
removed main_window_title area
moved main_window_resize functions to MainWindowController
put add vault button under the vault list and wrapped both in a ScrollPane
bound vault list height properties to amount of entries and width to scroll pane width
add listener to scroll down after adding a vault
moved preferences button under vault list
new style classes for new elements
changed wording
2024-05-28 21:56:03 +02:00
Siard
d85c6c8f41 Create separate files for GoogleDrive presets providers. cryptomator/2921
- Change imports accordingly in module-info.java
2024-01-03 16:42:46 +01:00
Siard
17057e8f8d Add Google Drive presets improvement. cryptomator/2921
- The code now defaults to searching through '~/Library/CloudStorage/GoogleDrive-*/*' before using the fallback options.
2024-01-03 16:41:11 +01:00
102 changed files with 1600 additions and 1331 deletions

View File

@@ -3,7 +3,7 @@
## Did you find a bug?
- Ensure you're running the latest version of Cryptomator.
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively.
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/android/issues) respectively.
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://community.cryptomator.org/c/kb/faq).
- If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new/choose).

View File

@@ -10,6 +10,10 @@ updates:
ignore:
- dependency-name: "org.cryptomator:integrations-api"
versions: ["2.0.0-alpha1"]
- dependency-name: "jakarta.inject:jakarta.inject-api"
versions: ["2.0.1.MR"]
- dependency-name: "org.openjfx:*"
update-types: ["version-update:semver-major"]
groups:
java-test-dependencies:
patterns:

View File

@@ -11,7 +11,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '22.0.2+9'
JAVA_VERSION: '23.0.1+11'
jobs:
get-version:
@@ -116,7 +116,6 @@ jobs:
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\""
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
--resource-dir dist/linux/resources
- name: Patch Cryptomator.AppDir
run: |

View File

@@ -7,7 +7,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: 22
JAVA_VERSION: 23
defaults:
run:
@@ -53,10 +53,25 @@ jobs:
generate_release_notes: true
body: |-
:construction: Work in Progress
### What's New 🎉
⏳ Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. ⏳
### Bugfixes 🐛
As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
### Other Changes 📎
---
<!-- Don't forget to include the 💾 SHA-256 checksums of release artifacts: -->
TODO FULL CHANGELOG
📜 List of closed issues is available [here](TODO)
---
⏳ Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. ⏳
<!-- Don't forget to include the
💾 SHA-256 checksums of release artifacts:
```
```
-->
As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).

View File

@@ -1,56 +1,75 @@
name: Checks JDK version for minor updates
name: Check JDK for non-major updates
on:
schedule:
- cron: '0 0 1 * *' # run once a month at the first day of month
workflow_dispatch:
env:
JDK_VERSION: '22.0.1+8'
JDK_VERSION: '23.0.1+11'
JDK_VENDOR: zulu
RUNTIME_VERSION_HELPER: >
public class Test {
public static void main(String[] args) {
System.out.println(Runtime.version());
}
}
jobs:
jdk-current:
name: Check out current version
runs-on: ubuntu-latest
outputs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
steps:
- uses: actions/setup-java@v4
with:
java-version: ${{ env.JDK_VERSION }}
distribution: ${{ env.JDK_VENDOR }}
check-latest: false
- name: Read JAVA_VERSION_DATE and store in env variable
id: get-data
run: |
date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-date=${date}" >> "$GITHUB_OUTPUT"
jdk-latest:
check-version:
name: Checkout latest jdk version
runs-on: ubuntu-latest
outputs:
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
jdk-version: ${{ steps.get-data.outputs.jdk-version}}
env:
JDK_MAJOR_VERSION: 'toBeFilled'
steps:
- uses: actions/setup-java@v4
- name: Determine current major version
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,20) >> "$env:GITHUB_ENV"
shell: pwsh
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
uses: actions/setup-java@v4
with:
java-version: 21
java-version: ${{ env.JDK_MAJOR_VERSION}}
distribution: ${{ env.JDK_VENDOR }}
check-latest: true
- name: Read JAVA_VERSION_DATE and store in env variable
id: get-data
- name: Determine if update is available
id: determine
shell: pwsh
run: |
date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-date=${date}" >> "$GITHUB_OUTPUT"
version=$(cat ${JAVA_HOME}/release | grep "JAVA_RUNTIME_VERSION=\"" | awk -F'=' '{print $2}' | tr -d '"')
echo "jdk-version=${version}" >> "$GITHUB_OUTPUT"
notify:
name: Notifies for jdk update
runs-on: ubuntu-latest
needs: [jdk-current, jdk-latest]
if: ${{ needs.jdk-latest.outputs.jdk-date }} > ${{ needs.jdk-current.outputs.jdk-date }}
steps:
- name: Slack Notification
$latestVersion = 0,0,0,0 #INTERIM, UPDATE, PATCH and BUILD
$currentVersion = 0,0,0,0
# Get the latest JDK runtime version
"${env:RUNTIME_VERSION_HELPER}" | Set-Content -Path "GetRuntimeVersion.java"
$latestVersionString = & java GetRuntimeVersion.java
$runtimeVersionAndBuild = $latestVersionString.Split('+')
if($runtimeVersionAndBuild.Length -eq 2) {
$latestVersion[3]=$runtimeVersionAndBuild[1];
}
$tmp=$runtimeVersionAndBuild[0].Split('.')
for($i=0;$i -lt $latestVersion.Length; $i++) {
$latestVersion[$i]=$tmp[$i+1];
}
# Get the current JDK version
$runtimeVersionAndBuild = '${{ env.JDK_VERSION}}'.Split('+')
if($runtimeVersionAndBuild.Length -eq 2) {
$currentVersion[3]=$runtimeVersionAndBuild[1];
}
$tmp=$runtimeVersionAndBuild[0].Split('.')
for($i=0;$i -lt $currentVersion.Length; $i++) {
$currentVersion[$i]=$tmp[$i+1];
}
# compare
for($i=0; $i -lt $currentVersion.Length ; $i++) {
if($latestVersion[$i] -gt $currentVersion[$i]){
echo 'UPDATE_AVAILABLE=true' >> "$env:GITHUB_OUTPUT"
echo "LATEST_JDK_VERSION='${latestVersionString}'" >> "$env:GITHUB_OUTPUT"
return 0;
}
}
- name: Notify
if: steps.determine.outputs.UPDATE_AVAILABLE == 'true'
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
@@ -59,6 +78,6 @@ jobs:
SLACK_ICON_EMOJI: ':bot:'
SLACK_CHANNEL: 'cryptomator-desktop'
SLACK_TITLE: "JDK update available"
SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ needs.jdk-latest.outputs.jdk-version }}. See https://github.com/cryptomator/cryptomator/wiki/How-to-update-the-build-JDK for instructions."
SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ steps.determine.outputs.LATEST_JDK_VERSION }}. Check the Nextcloud collective for instructions."
SLACK_FOOTER: false
MSG_MINIMAL: true
MSG_MINIMAL: true

View File

@@ -17,9 +17,9 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '22.0.2+9'
COFFEELIBS_JDK: 22
COFFEELIBS_JDK_VERSION: '22.0.2+9-0ppa1'
JAVA_VERSION: '23.0.1+11'
COFFEELIBS_JDK: 23
COFFEELIBS_JDK_VERSION: '23.0.1+11-0ppa1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'd44bff3b94d5668fdee18a938d7b1269026d663d44765f02d29a9bdfd3fa1eb0'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_linux-aarch64_bin-jmods.zip'

View File

@@ -11,7 +11,7 @@ jobs:
with:
runner-os: 'ubuntu-latest'
java-distribution: 'temurin'
java-version: 22
java-version: 23
check-command: 'mvn -B validate -Pdependency-check -Djavafx.platform=linux'
secrets:
nvd-api-key: ${{ secrets.NVD_API_KEY }}

View File

@@ -23,7 +23,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: 22
JAVA_VERSION: 23
jobs:
determine-version:

264
.github/workflows/mac-dmg-x64.yml vendored Normal file
View File

@@ -0,0 +1,264 @@
name: Build macOS .dmg for x64
#######################################
# STOP! DO NOT EDIT THIS FILE!
#
# It is a copy of mac-dmg.yml with tiny adjustements (mainly lines 42 to 47)
# It was made necessary, since Github does not offer free macos intel runners for macos 15 and above.
# This workflow can only be triggered by a release.
#
#######################################
on:
release:
types: [published]
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '23.0.1+11'
jobs:
get-version:
uses: ./.github/workflows/get-version.yml
with:
version: ${{ inputs.version }}
build-arm:
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
runs-on: ${{ matrix.os }}
needs: [get-version]
strategy:
fail-fast: false
matrix:
include:
- os: macos-15-large
architecture: x64
output-suffix: x64
fuse-lib: macFUSE
openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_osx-x64_bin-jmods.zip'
openjfx-sha: '115cb08bb59d880cfff6e51e0bf0dcc45785ed9d456b8b8425597b04da6ab3d4'
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: ${{ env.JAVA_DIST }}
java-version: ${{ env.JAVA_VERSION }}
architecture: ${{ matrix.architecture }}
check-latest: true
cache: 'maven'
- name: Download OpenJFX jmods
id: download-jmods
run: |
curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip
echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check
mkdir -p openjfx-jmods/
unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
- name: Ensure major jfx version in pom and in jmods is the same
run: |
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
JMOD_VERSION=${JMOD_VERSION#*@}
JMOD_VERSION=${JMOD_VERSION%%.*}
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
exit 1
fi
- name: Set version
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
- name: Run maven
run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests
- name: Patch target dir
run: |
cp LICENSE.txt target
cp target/cryptomator-*.jar target/mods
- name: Run jlink
#Remark: no compression is applied for improved build compression later (here dmg)
run: >
${JAVA_HOME}/bin/jlink
--verbose
--output runtime
--module-path "${JAVA_HOME}/jmods:openjfx-jmods"
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
--strip-native-commands
--no-header-files
--no-man-pages
--strip-debug
--compress zip-0
- name: Run jpackage
run: >
${JAVA_HOME}/bin/jpackage
--verbose
--type app-image
--runtime-image runtime
--input target/libs
--module-path target/mods
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
--dest appdir
--name Cryptomator
--vendor "Skymatic GmbH"
--copyright "(C) 2016 - 2024 Skymatic GmbH"
--app-version "${{ needs.get-version.outputs.semVerNum }}"
--java-options "--enable-preview"
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
--java-options "-Xss5m"
--java-options "-Xmx256m"
--java-options "-Dfile.encoding=\"utf-8\""
--java-options "-Djava.net.useSystemProxies=true"
--java-options "-Dapple.awt.enableTemplateImages=true"
--java-options "-Dsun.java2d.metal=true"
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/Cryptomator/key.p12\""
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/Cryptomator/ipc.socket\""
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
--java-options "-Dcryptomator.showTrayIcon=true"
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
--mac-package-identifier org.cryptomator
--resource-dir dist/mac/resources
- name: Patch Cryptomator.app
run: |
mv appdir/Cryptomator.app Cryptomator.app
mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile
env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
- name: Generate license for dmg
run: >
mvn -B -Djavafx.platform=mac license:add-third-party
-Dlicense.thirdPartyFilename=license.rtf
-Dlicense.outputDirectory=dist/mac/dmg/resources
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
-Dlicense.includedScopes=compile
-Dlicense.excludedGroups=^org\.cryptomator
-Dlicense.failOnMissing=true
-Dlicense.licenseMergesUrl=file://${{ github.workspace }}/license/merges
- name: Install codesign certificate
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12
KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
security set-keychain-settings -lut 900 $KEYCHAIN_PATH
security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
env:
CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }}
CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }}
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
- name: Codesign
run: |
echo "Codesigning jdk files..."
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ \( -name 'jspawnhelper' -o -name 'pauseengine' -o -name 'simengine' \) -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
echo "Codesigning jar contents..."
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
JAR_FILENAME=$(basename ${JAR_PATH})
OUTPUT_PATH=${JAR_PATH%.*}
echo "Codesigning libs in ${JAR_FILENAME}..."
unzip -q ${JAR_PATH} -d ${OUTPUT_PATH}
find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
rm ${JAR_PATH}
pushd ${OUTPUT_PATH} > /dev/null
zip -qr ../${JAR_FILENAME} *
popd > /dev/null
rm -r ${OUTPUT_PATH}
fi
done
echo "Codesigning Cryptomator.app..."
sed -i '' "s|###APP_IDENTIFIER_PREFIX###|${TEAM_IDENTIFIER}.|g" dist/mac/Cryptomator.entitlements
sed -i '' "s|###TEAM_IDENTIFIER###|${TEAM_IDENTIFIER}|g" dist/mac/Cryptomator.entitlements
codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app
env:
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
TEAM_IDENTIFIER: ${{ secrets.MACOS_TEAM_IDENTIFIER }}
- name: Prepare .dmg contents
run: |
mkdir dmg
mv Cryptomator.app dmg
cp dist/mac/dmg/resources/${{ matrix.fuse-lib }}.webloc dmg
ls -l dmg
- name: Install create-dmg
run: |
brew install create-dmg
create-dmg --help
- name: Create .dmg
run: >
create-dmg
--volname Cryptomator
--volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns"
--background "dist/mac/dmg/resources/Cryptomator-${{ matrix.fuse-lib }}-background.tiff"
--window-pos 400 100
--window-size 640 694
--icon-size 128
--icon "Cryptomator.app" 128 245
--hide-extension "Cryptomator.app"
--icon "${{ matrix.fuse-lib }}.webloc" 320 501
--hide-extension "${{ matrix.fuse-lib }}.webloc"
--app-drop-link 512 245
--eula "dist/mac/dmg/resources/license.rtf"
--icon ".background" 128 758
--icon ".VolumeIcon.icns" 512 758
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
env:
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
- name: Notarize .dmg
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
uses: cocoalibs/xcode-notarization-action@v1
with:
app-path: 'Cryptomator-*.dmg'
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
xcode-path: '/Applications/Xcode_16.app'
- name: Add possible alpha/beta tags to installer name
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
- name: Create detached GPG signature with key 615D449FE6E6A235
run: |
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.dmg
env:
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
- name: Clean up codesign certificate
if: ${{ always() }}
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
continue-on-error: true
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dmg-${{ matrix.output-suffix }}
path: |
Cryptomator-*.dmg
Cryptomator-*.asc
if-no-files-found: error
- name: Publish dmg on GitHub Releases
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
uses: softprops/action-gh-release@v2
with:
fail_on_unmatched_files: true
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
files: |
Cryptomator-*.dmg
Cryptomator-*.asc

View File

@@ -1,4 +1,4 @@
name: Build macOS .dmg
name: Build macOS .dmg for arm64
on:
release:
@@ -16,7 +16,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '22.0.2+9'
JAVA_VERSION: '23.0.1+11'
jobs:
get-version:
@@ -32,17 +32,9 @@ jobs:
fail-fast: false
matrix:
include:
- os: macos-12
architecture: x64
output-suffix: x64
xcode-path: '/Applications/Xcode_13.2.1.app'
fuse-lib: macFUSE
openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_osx-x64_bin-jmods.zip'
openjfx-sha: '115cb08bb59d880cfff6e51e0bf0dcc45785ed9d456b8b8425597b04da6ab3d4'
- os: [self-hosted, macOS, ARM64]
- os: macos-15
architecture: aarch64
output-suffix: arm64
xcode-path: '/Applications/Xcode_13.2.1.app'
fuse-lib: FUSE-T
openjfx-url: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_osx-aarch64_bin-jmods.zip'
openjfx-sha: '813c6748f7c99cb7a579d48b48a087b4682b1fad1fc1a4fe5f9b21cf872b15a7'
@@ -240,7 +232,7 @@ jobs:
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
xcode-path: ${{ matrix.xcode-path }}
xcode-path: '/Applications/Xcode_16.app'
- name: Add possible alpha/beta tags to installer name
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
- name: Create detached GPG signature with key 615D449FE6E6A235

View File

@@ -5,7 +5,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: 22
JAVA_VERSION: 23
defaults:
run:

View File

@@ -12,7 +12,7 @@ defaults:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: 22
JAVA_VERSION: 23
jobs:
check-preconditions:

View File

@@ -16,7 +16,7 @@ on:
env:
JAVA_DIST: 'zulu'
JAVA_VERSION: '22.0.2+9'
JAVA_VERSION: '23.0.1+11'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/22.0.2/openjfx-22.0.2_windows-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: 'f9376d200f5c5b85327d575c1ec1482e6455f19916577f7e2fc9be2f48bb29b6'
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
@@ -130,6 +130,7 @@ jobs:
--java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\""
--java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\""
--java-options "-Dcryptomator.integrationsWin.keychainPaths=\"@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json\""
--java-options "-Dcryptomator.integrationsWin.windowsHelloKeychainPaths=\"@{appdata}/Cryptomator/windowsHelloKeychain.json;@{userhome}/AppData/Roaming/Cryptomator/windowsHelloKeychain.json\""
--java-options "-Djavafx.verbose=${{ inputs.isDebug }}"
--resource-dir dist/win/resources
--icon dist/win/resources/Cryptomator.ico

View File

@@ -18,7 +18,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
- name: Submit package
uses: vedantmgoyal2009/winget-releaser@v2
uses: vedantmgoyal2009/winget-releaser@main
with:
identifier: Cryptomator.Cryptomator
version: ${{ inputs.tag }}

21
.idea/compiler.xml generated
View File

@@ -14,22 +14,23 @@
<option name="dagger.fastInit" value="enabled" />
<option name="dagger.formatGeneratedSource" value="enabled" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.49/dagger-compiler-2.49.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.49/dagger-2.49.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.52/dagger-compiler-2.52.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.52/dagger-2.52.jar" />
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.49/dagger-spi-2.49.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.52/dagger-spi-2.52.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.9.20-1.0.14/symbol-processing-api-1.9.20-1.0.14.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.9.24-1.0.20/symbol-processing-api-1.9.24-1.0.20.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.20/kotlin-stdlib-1.9.20.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.24/kotlin-stdlib-1.9.24.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/31.0.1-jre/guava-31.0.1-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/3.12.0/checker-qual-3.12.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/3.41.0/checker-qual-3.41.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.23.0/error_prone_annotations-2.23.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />

2
.idea/misc.xml generated
View File

@@ -8,7 +8,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" project-jdk-name="22" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" project-jdk-name="23" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json&quot; -Dcryptomator.integrationsWin.windowsHelloKeychainPaths=&quot;@{appdata}/Cryptomator/windowsHelloKeychain.json;@{userhome}/AppData/Roaming/Cryptomator/windowsHelloKeychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="cryptomator" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator-Dev/settings.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator-Dev/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator-Dev/keychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator-Dev/key.p12;@{userhome}/AppData/Roaming/Cryptomator-Dev/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" />
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath=&quot;@{appdata}/Cryptomator-Dev/settings.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/settings.json&quot; -Dcryptomator.ipcSocketPath=&quot;@{localappdata}/Cryptomator-Dev/ipc.socket&quot; -Dcryptomator.logDir=&quot;@{localappdata}/Cryptomator-Dev&quot; -Dcryptomator.pluginDir=&quot;@{appdata}/Cryptomator-Dev/Plugins&quot; -Dcryptomator.integrationsWin.keychainPaths=&quot;@{appdata}/Cryptomator-Dev/keychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/keychain.json&quot; -Dcryptomator.integrationsWin.windowsHelloKeychainPaths=&quot;@{appdata}/Cryptomator-Dev/windowsHelloKeychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/windowsHelloKeychain.json&quot; -Dcryptomator.p12Path=&quot;@{appdata}/Cryptomator-Dev/key.p12;@{userhome}/AppData/Roaming/Cryptomator-Dev/key.p12&quot; -Dcryptomator.mountPointsDir=&quot;@{userhome}/Cryptomator-Dev&quot; -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" />
<method v="2">
<option name="Make" enabled="true" />
</method>

View File

@@ -17,20 +17,14 @@ Cryptomator is provided free of charge as an open-source project despite the hig
### Gold Sponsors
<table>
<tbody>
<tr>
<td><a href="https://www.gee-whiz.de/"><img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="80"></a></td>
</tr>
</tbody>
</table>
Become our Gold Sponsor and showcase your brand to a targeted audience! Please contact us if you are interested.
### Silver Sponsors
<table>
<tbody>
<tr>
<td><a href="https://mowcapital.com/"><img src="https://cryptomator.org/img/sponsors/mowcapital.svg" alt="Mow Capital" height="28"></a></td>
<td><a href="https://www.gee-whiz.de/"><img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="56"></a></td>
<td><a href="https://www.route4me.com/"><img src="https://cryptomator.org/img/sponsors/route4me.svg" alt="Route4Me" height="56"></a></td>
</tr>
</tbody>

View File

@@ -4,7 +4,7 @@
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
JAVA_HOME = /usr/lib/jvm/java-22-coffeelibs
JAVA_HOME = /usr/lib/jvm/java-23-coffeelibs
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
ifeq ($(DEB_BUILD_ARCH),amd64)
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods

68
pom.xml
View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.14.2</version>
<version>1.15.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -26,7 +26,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.jdk.version>22</project.jdk.version>
<project.jdk.version>23</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now -->
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
@@ -35,41 +35,40 @@
<!-- cryptomator dependencies -->
<cryptomator.cryptofs.version>2.7.1</cryptomator.cryptofs.version>
<cryptomator.integrations.version>1.4.0</cryptomator.integrations.version>
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.2.4</cryptomator.integrations.mac.version>
<cryptomator.integrations.win.version>1.4.1</cryptomator.integrations.win.version>
<cryptomator.integrations.mac.version>1.3.0</cryptomator.integrations.mac.version>
<cryptomator.integrations.linux.version>1.5.1</cryptomator.integrations.linux.version>
<cryptomator.fuse.version>5.0.2</cryptomator.fuse.version>
<cryptomator.webdav.version>2.0.7</cryptomator.webdav.version>
<!-- 3rd party dependencies -->
<commons-lang3.version>3.16.0</commons-lang3.version>
<dagger.version>2.51.1</dagger.version>
<commons-lang3.version>3.17.0</commons-lang3.version>
<dagger.version>2.52</dagger.version>
<easybind.version>2.2</easybind.version>
<guava.version>33.3.0-jre</guava.version>
<jackson.version>2.17.2</jackson.version>
<jackson.version>2.18.1</jackson.version>
<javafx.version>22.0.2</javafx.version>
<jwt.version>4.4.0</jwt.version>
<nimbus-jose.version>9.37.3</nimbus-jose.version>
<logback.version>1.5.7</logback.version>
<logback.version>1.5.12</logback.version>
<slf4j.version>2.0.16</slf4j.version>
<tinyoauth2.version>0.8.0</tinyoauth2.version>
<zxcvbn.version>1.9.0</zxcvbn.version>
<!-- test dependencies -->
<junit.jupiter.version>5.11.0</junit.jupiter.version>
<mockito.version>5.12.0</mockito.version>
<junit.jupiter.version>5.11.3</junit.jupiter.version>
<mockito.version>5.14.2</mockito.version>
<hamcrest.version>3.0</hamcrest.version>
<!-- build-time dependencies -->
<jetbrains.annotations.version>24.1.0</jetbrains.annotations.version>
<dependency-check.version>10.0.3</dependency-check.version>
<jetbrains.annotations.version>26.0.1</jetbrains.annotations.version>
<dependency-check.version>11.1.0</dependency-check.version>
<jacoco.version>0.8.12</jacoco.version>
<license-generator.version>2.4.0</license-generator.version>
<junit-tree-reporter.version>1.3.0</junit-tree-reporter.version>
<mvn-compiler.version>3.13.0</mvn-compiler.version>
<mvn-resources.version>3.3.1</mvn-resources.version>
<mvn-dependency.version>3.7.1</mvn-dependency.version>
<mvn-surefire.version>3.4.0</mvn-surefire.version>
<mvn-dependency.version>3.8.1</mvn-dependency.version>
<mvn-surefire.version>3.5.2</mvn-surefire.version>
<mvn-jar.version>3.4.2</mvn-jar.version>
</properties>
@@ -191,40 +190,23 @@
</dependency>
<!-- Google -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
<exclusions>
<!-- see https://github.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies -->
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>listenablefuture</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.j2objc</groupId>
<artifactId>j2objc-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
<version>${dagger.version}</version>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<version>2.0.1</version>
</dependency>
<!-- Caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<!-- JUnit / Mockito / Hamcrest -->
<dependency>
<groupId>org.junit.jupiter</groupId>

View File

@@ -2,7 +2,8 @@ import ch.qos.logback.classic.spi.Configurator;
import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider;
import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.GoogleDriveLocationPresetsProvider;
import org.cryptomator.common.locationpresets.GoogleDriveMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.GoogleDriveWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.ICloudMacLocationPresetsProvider;
import org.cryptomator.common.locationpresets.ICloudWindowsLocationPresetsProvider;
import org.cryptomator.common.locationpresets.LeitzcloudLocationPresetsProvider;
@@ -42,13 +43,15 @@ open module org.cryptomator.desktop {
requires com.nulabinc.zxcvbn;
requires com.tobiasdiez.easybind;
requires dagger;
requires java.compiler;
requires io.github.coffeelibs.tinyoauth2client;
requires org.slf4j;
requires org.apache.commons.lang3;
/* TODO: filename-based modules: */
requires static javax.inject; /* ugly dagger/guava crap */
/* dagger bs */
requires jakarta.inject;
requires static javax.inject;
requires java.compiler;
requires com.github.benmanes.caffeine;
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
@@ -56,7 +59,7 @@ open module org.cryptomator.desktop {
provides Configurator with LogbackConfiguratorFactory;
provides LocationPresetsProvider with //
DropboxWindowsLocationPresetsProvider, DropboxMacLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, //
GoogleDriveLocationPresetsProvider, //
GoogleDriveMacLocationPresetsProvider, GoogleDriveWindowsLocationPresetsProvider, //
ICloudWindowsLocationPresetsProvider, ICloudMacLocationPresetsProvider, //
LeitzcloudLocationPresetsProvider, //
MegaLocationPresetsProvider, //

View File

@@ -0,0 +1,20 @@
package org.cryptomator;
import javafx.application.Platform;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class JavaFXUtil {
public static boolean startPlatform() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
try {
Platform.startup(latch::countDown);
} catch (IllegalStateException e) {
//already initialized
latch.countDown();
}
return latch.await(5, TimeUnit.SECONDS);
}
}

View File

@@ -23,6 +23,7 @@ public class Environment {
private static final String SETTINGS_PATH_PROP_NAME = "cryptomator.settingsPath";
private static final String IPC_SOCKET_PATH_PROP_NAME = "cryptomator.ipcSocketPath";
private static final String KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.keychainPaths";
private static final String WINDOWS_HELLO_KEYCHAIN_PATHS_PROP_NAME = "cryptomator.integrationsWin.windowsHelloKeychainPaths";
private static final String P12_PATH_PROP_NAME = "cryptomator.p12Path";
private static final String LOG_DIR_PROP_NAME = "cryptomator.logDir";
private static final String LOOPBACK_ALIAS_PROP_NAME = "cryptomator.loopbackAlias";
@@ -45,6 +46,7 @@ public class Environment {
logCryptomatorSystemProperty(SETTINGS_PATH_PROP_NAME);
logCryptomatorSystemProperty(IPC_SOCKET_PATH_PROP_NAME);
logCryptomatorSystemProperty(KEYCHAIN_PATHS_PROP_NAME);
logCryptomatorSystemProperty(WINDOWS_HELLO_KEYCHAIN_PATHS_PROP_NAME);
logCryptomatorSystemProperty(P12_PATH_PROP_NAME);
logCryptomatorSystemProperty(LOG_DIR_PROP_NAME);
logCryptomatorSystemProperty(LOOPBACK_ALIAS_PROP_NAME);
@@ -85,6 +87,10 @@ public class Environment {
return getPaths(KEYCHAIN_PATHS_PROP_NAME);
}
public Stream<Path> getWindowsHelloKeychainPath() {
return getPaths(WINDOWS_HELLO_KEYCHAIN_PATHS_PROP_NAME);
}
public Stream<Path> getP12Path() {
return getPaths(P12_PATH_PROP_NAME);
}

View File

@@ -1,8 +1,7 @@
package org.cryptomator.common.keychain;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
@@ -24,9 +23,9 @@ public class KeychainManager implements KeychainAccessProvider {
@Inject
KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
this.keychain = selectedKeychain;
this.passphraseStoredProperties = CacheBuilder.newBuilder() //
.weakValues() //
.build(CacheLoader.from(this::createStoredPassphraseProperty));
this.passphraseStoredProperties = Caffeine.newBuilder() //
.softValues() //
.build(this::createStoredPassphraseProperty);
keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
}
@@ -44,8 +43,13 @@ public class KeychainManager implements KeychainAccessProvider {
}
@Override
public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean ignored) throws KeychainAccessException {
getKeychainOrFail().storePassphrase(key, displayName, passphrase);
public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
storePassphrase(key, displayName, passphrase, true); //TODO: currently only TouchID is using this parameter, so this is okayish
}
@Override
public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean requireOsAuthentication) throws KeychainAccessException {
getKeychainOrFail().storePassphrase(key, displayName, passphrase, requireOsAuthentication);
setPassphraseStored(key, true);
}
@@ -102,13 +106,11 @@ public class KeychainManager implements KeychainAccessProvider {
}
private void setPassphraseStored(String key, boolean value) {
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
if (property != null) {
if (Platform.isFxApplicationThread()) {
property.set(value);
} else {
Platform.runLater(() -> property.set(value));
}
BooleanProperty property = passphraseStoredProperties.get(key, _ -> new SimpleBooleanProperty(value));
if (Platform.isFxApplicationThread()) {
property.set(value);
} else {
Platform.runLater(() -> property.set(value));
}
}
@@ -124,7 +126,7 @@ public class KeychainManager implements KeychainAccessProvider {
* @see #isPassphraseStored(String)
*/
public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
return passphraseStoredProperties.getUnchecked(key);
return passphraseStoredProperties.get(key);
}
private BooleanProperty createStoredPassphraseProperty(String key) {
@@ -135,4 +137,8 @@ public class KeychainManager implements KeychainAccessProvider {
}
}
public ObjectExpression<KeychainAccessProvider> getKeychainImplementation() {
return this.keychain;
}
}

View File

@@ -0,0 +1,144 @@
package org.cryptomator.common.locationpresets;
import org.cryptomator.integrations.common.CheckAvailability;
import org.cryptomator.integrations.common.OperatingSystem;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
@OperatingSystem(MAC)
@CheckAvailability
public final class GoogleDriveMacLocationPresetsProvider implements LocationPresetsProvider {
private static final Path ROOT_LOCATION = LocationPresetsProvider.resolveLocation("~/Library/CloudStorage/").toAbsolutePath();
private static final Predicate<String> PATTERN = Pattern.compile("^GoogleDrive-[^/]+$").asMatchPredicate();
private static final List<Path> FALLBACK_LOCATIONS = List.of( //
LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), //
LocationPresetsProvider.resolveLocation("~/Google Drive/My Drive"), //
LocationPresetsProvider.resolveLocation("~/GoogleDrive"), //
LocationPresetsProvider.resolveLocation("~/Google Drive") //
);
@Override
public Stream<LocationPreset> getLocations() {
List<LocationPreset> cloudStorageDirLocations = getCloudStorageDirLocations();
return cloudStorageDirLocations.isEmpty() ? getFallbackLocation().stream() : cloudStorageDirLocations.stream();
}
@CheckAvailability
public static boolean isPresent() {
return isRootLocationPresent() || FALLBACK_LOCATIONS.stream().anyMatch(Files::isDirectory);
}
/**
* Checks if a root location directory is present that matches the specified pattern.
* <p>
* This method scans the {@code ROOT_LOCATION} directory for subdirectories and tests each one against a pre-defined pattern ({@code PATTERN}).
*
* @return {@code true} if a matching root location is present, otherwise {@code false}.
*/
public static boolean isRootLocationPresent() {
try (var dirStream = Files.list(ROOT_LOCATION)) {
return dirStream.anyMatch(path -> Files.isDirectory(path) && PATTERN.test(path.getFileName().toString()));
} catch (IOException | UncheckedIOException e) {
return false;
}
}
/**
* Returns Google Drive preset String.
*
* @param accountPath The path to the Google Drive account directory (e.g. {@code ~/Library/CloudStorage/GoogleDrive-username})
* @return {@code String}. For example: "Google Drive - username"
*/
private String getDriveLocationString(Path accountPath) {
String accountName = accountPath.getFileName().toString().replace("GoogleDrive-", "");
return "Google Drive - " + accountName;
}
/**
* Retrieves a list of cloud storage directory locations based on the {@code ROOT_LOCATION}.
* <p>
* This method lists all directories in the {@code ROOT_LOCATION}, filters them based on whether their names match
* a predefined pattern ({@code PATTERN}), and then extracts presets using {@code getPresetsFromAccountPath(Path)}.
* <p>
*
* @return a list of {@code LocationPreset} objects representing valid cloud storage directory locations.
*/
private List<LocationPreset> getCloudStorageDirLocations() {
try (var dirStream = Files.list(ROOT_LOCATION)) {
return dirStream.filter(path -> Files.isDirectory(path) && PATTERN.test(path.getFileName().toString()))
.flatMap(this::getPresetsFromAccountPath)
.toList();
} catch (IOException | UncheckedIOException e) {
return List.of();
}
}
/**
* Retrieves a stream of {@code LocationPreset} objects from a given Google Drive account path.
* <p>
* This method lists all directories within the provided {@code accountPath} and filters them
* to identify folders whose names match any of the translations defined in {@code MY_DRIVE_TRANSLATIONS}.
*
* @param accountPath the root path of the Google Drive account to scan.
* @return a stream of {@code LocationPreset} objects representing matching directories.
*/
private Stream<LocationPreset> getPresetsFromAccountPath(Path accountPath) {
try (var driveStream = Files.list(accountPath)) {
return driveStream
.filter(preset -> MY_DRIVE_TRANSLATIONS
.contains(preset.getFileName().toString()))
.map(drivePath -> new LocationPreset(
getDriveLocationString(accountPath),
drivePath
)).toList().stream();
} catch (IOException e) {
return Stream.empty();
}
}
/**
* Returns a list containing a fallback location preset for Google Drive.
* <p>
* This method iterates through the predefined fallback locations, checks if any of them is a directory,
* and creates a {@code LocationPreset} object for the first matching directory found.
*
* @return a list containing a single fallback location preset if a valid directory is found, otherwise an empty list.
* @deprecated This method is intended for legacy support and may be removed in future releases.
*/
@Deprecated
private List<LocationPreset> getFallbackLocation() {
return FALLBACK_LOCATIONS.stream()
.filter(Files::isDirectory)
.map(location -> new LocationPreset("Google Drive", location))
.findFirst()
.stream()
.toList();
}
/**
* Set of translations for "My Drive" in various languages.
* <p>
* This constant is used to identify different language-specific labels for "My Drive" in Google Drive.
* <p>
* The translations were originally extracted from the Chromium projects Chrome OS translation files.
* <p>
* Source: `ui/chromeos/translations` directory in the Chromium repository.
*/
private static final Set<String> MY_DRIVE_TRANSLATIONS = Set.of("My Drive", "የእኔ Drive", "ملفاتي", "মোৰ ড্ৰাইভ", "Diskim", "Мой Дыск", "Моят диск", "আমার ড্রাইভ", "Moj disk", "La meva unitat", "Můj disk", "Mit drev", "Meine Ablage", "Το Drive μου", "Mi unidad", "Minu ketas", "Nire unitatea", "Aking Drive", "Oma Drive", "Mon disque", "Mon Drive", "A miña unidade", "મારી ડ્રાઇવ", "मेरी ड्राइव", "Saját meghajtó", "Իմ դրայվը", "Drive Saya", "Drifið mitt", "I miei file", "האחסון שלי", "マイドライブ", "ჩემი Drive", "Менің Drive дискім", "ដ្រាយរបស់ខ្ញុំ", "ನನ್ನ ಡ್ರೈವ್", "내 드라이브", "Менин Drive'ым", "Mano Diskas", "Mans disks", "Мојот Drive", "എന്റെ ഡ്രൈവ്", "Миний Драйв", "माझा ड्राइव्ह", "मेरो ड्राइभ", "Mijn Drive", "Min disk", "ମୋ ଡ୍ରାଇଭ୍", "Mój dysk", "Meu Drive", "O meu disco", "Contul meu Drive", "Мой диск", "මගේ Drive", "Môj disk", "Disku im", "Мој диск", "Min enhet", "Hifadhi Yangu", "எனது இயக்ககம்", "నా డ్రైవ్‌", "ไดรฟ์ของฉัน", "Drive'ım", "Мій диск", "میری ڈرائیو", "Drive của tôi", "我的云端硬盘", "我的雲端硬碟", "IDrayivu yami");
}

View File

@@ -9,13 +9,11 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
@OperatingSystem(WINDOWS)
@OperatingSystem(MAC)
@CheckAvailability
public final class GoogleDriveLocationPresetsProvider implements LocationPresetsProvider {
public final class GoogleDriveWindowsLocationPresetsProvider implements LocationPresetsProvider {
private static final List<Path> LOCATIONS = Arrays.asList( //
LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), //
@@ -37,5 +35,4 @@ public final class GoogleDriveLocationPresetsProvider implements LocationPresets
.findFirst() //
.stream();
}
}

View File

@@ -160,7 +160,7 @@ public class Mounter {
var mountService = mountProviders.stream().filter(s -> s.getClass().getName().equals(vaultSettings.mountService.getValue())).findFirst().orElse(defaultMountService.getValue());
if (isConflictingMountService(mountService)) {
var msg = STR."\{mountService.getClass()} unavailable due to conflict with either of \{CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName())}";
var msg = mountService.getClass() + " unavailable due to conflict with either of " + CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName());
throw new ConflictingMountServiceException(msg);
}

View File

@@ -32,7 +32,6 @@ public class Settings {
private static final Logger LOG = LoggerFactory.getLogger(Settings.class);
static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
static final boolean DEFAULT_START_HIDDEN = false;
static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
@@ -50,11 +49,9 @@ public class Settings {
SystemUtils.IS_OS_LINUX ? "org.cryptomator.linux.quickaccess.NautilusBookmarks" : null;
static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
public static final Instant DEFAULT_TIMESTAMP = Instant.parse("2000-01-01T00:00:00Z");
public final ObservableList<VaultSettings> directories;
public final BooleanProperty askedForUpdateCheck;
public final BooleanProperty checkForUpdates;
public final BooleanProperty startHidden;
public final BooleanProperty autoCloseVaults;
public final BooleanProperty useKeychain;
@@ -67,14 +64,16 @@ public class Settings {
public final StringProperty quickAccessService;
public final ObjectProperty<NodeOrientation> userInterfaceOrientation;
public final StringProperty licenseKey;
public final BooleanProperty showMinimizeButton;
public final BooleanProperty showTrayIcon;
public final BooleanProperty compactMode;
public final IntegerProperty windowXPosition;
public final IntegerProperty windowYPosition;
public final IntegerProperty windowWidth;
public final IntegerProperty windowHeight;
public final StringProperty language;
public final StringProperty mountService;
public final BooleanProperty checkForUpdates;
public final ObjectProperty<Instant> lastUpdateCheckReminder;
public final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
private Consumer<Settings> saveCmd;
@@ -92,8 +91,6 @@ public class Settings {
*/
Settings(SettingsJson json) {
this.directories = FXCollections.observableArrayList(VaultSettings::observables);
this.askedForUpdateCheck = new SimpleBooleanProperty(this, "askedForUpdateCheck", json.askedForUpdateCheck);
this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled);
this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden);
this.autoCloseVaults = new SimpleBooleanProperty(this, "autoCloseVaults", json.autoCloseVaults);
this.useKeychain = new SimpleBooleanProperty(this, "useKeychain", json.useKeychain);
@@ -105,8 +102,8 @@ public class Settings {
this.keychainProvider = new SimpleStringProperty(this, "keychainProvider", json.keychainProvider);
this.userInterfaceOrientation = new SimpleObjectProperty<>(this, "userInterfaceOrientation", parseEnum(json.uiOrientation, NodeOrientation.class, NodeOrientation.LEFT_TO_RIGHT));
this.licenseKey = new SimpleStringProperty(this, "licenseKey", json.licenseKey);
this.showMinimizeButton = new SimpleBooleanProperty(this, "showMinimizeButton", json.showMinimizeButton);
this.showTrayIcon = new SimpleBooleanProperty(this, "showTrayIcon", json.showTrayIcon);
this.compactMode = new SimpleBooleanProperty(this, "compactMode", json.compactMode);
this.windowXPosition = new SimpleIntegerProperty(this, "windowXPosition", json.windowXPosition);
this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition);
this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth);
@@ -114,6 +111,8 @@ public class Settings {
this.language = new SimpleStringProperty(this, "language", json.language);
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
this.quickAccessService = new SimpleStringProperty(this, "quickAccessService", json.quickAccessService);
this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled);
this.lastUpdateCheckReminder = new SimpleObjectProperty<>(this, "lastUpdateCheckReminder", json.lastReminderForUpdateCheck);
this.lastSuccessfulUpdateCheck = new SimpleObjectProperty<>(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck);
this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
@@ -121,8 +120,6 @@ public class Settings {
migrateLegacySettings(json);
directories.addListener(this::somethingChanged);
askedForUpdateCheck.addListener(this::somethingChanged);
checkForUpdates.addListener(this::somethingChanged);
startHidden.addListener(this::somethingChanged);
autoCloseVaults.addListener(this::somethingChanged);
useKeychain.addListener(this::somethingChanged);
@@ -134,8 +131,8 @@ public class Settings {
keychainProvider.addListener(this::somethingChanged);
userInterfaceOrientation.addListener(this::somethingChanged);
licenseKey.addListener(this::somethingChanged);
showMinimizeButton.addListener(this::somethingChanged);
showTrayIcon.addListener(this::somethingChanged);
compactMode.addListener(this::somethingChanged);
windowXPosition.addListener(this::somethingChanged);
windowYPosition.addListener(this::somethingChanged);
windowWidth.addListener(this::somethingChanged);
@@ -143,6 +140,8 @@ public class Settings {
language.addListener(this::somethingChanged);
mountService.addListener(this::somethingChanged);
quickAccessService.addListener(this::somethingChanged);
checkForUpdates.addListener(this::somethingChanged);
lastUpdateCheckReminder.addListener(this::somethingChanged);
lastSuccessfulUpdateCheck.addListener(this::somethingChanged);
}
@@ -177,8 +176,6 @@ public class Settings {
SettingsJson serialized() {
var json = new SettingsJson();
json.directories = directories.stream().map(VaultSettings::serialized).toList();
json.askedForUpdateCheck = askedForUpdateCheck.get();
json.checkForUpdatesEnabled = checkForUpdates.get();
json.startHidden = startHidden.get();
json.autoCloseVaults = autoCloseVaults.get();
json.useKeychain = useKeychain.get();
@@ -190,8 +187,8 @@ public class Settings {
json.keychainProvider = keychainProvider.get();
json.uiOrientation = userInterfaceOrientation.get().name();
json.licenseKey = licenseKey.get();
json.showMinimizeButton = showMinimizeButton.get();
json.showTrayIcon = showTrayIcon.get();
json.compactMode = compactMode.get();
json.windowXPosition = windowXPosition.get();
json.windowYPosition = windowYPosition.get();
json.windowWidth = windowWidth.get();
@@ -199,6 +196,8 @@ public class Settings {
json.language = language.get();
json.mountService = mountService.get();
json.quickAccessService = quickAccessService.get();
json.checkForUpdatesEnabled = checkForUpdates.get();
json.lastReminderForUpdateCheck = lastUpdateCheckReminder.get();
json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.get();
return json;
}

View File

@@ -18,15 +18,9 @@ class SettingsJson {
@JsonProperty("writtenByVersion")
String writtenByVersion;
@JsonProperty("askedForUpdateCheck")
boolean askedForUpdateCheck = Settings.DEFAULT_ASKED_FOR_UPDATE_CHECK;
@JsonProperty("autoCloseVaults")
boolean autoCloseVaults = Settings.DEFAULT_AUTO_CLOSE_VAULTS;
@JsonProperty("checkForUpdatesEnabled")
boolean checkForUpdatesEnabled = Settings.DEFAULT_CHECK_FOR_UPDATES;
@JsonProperty("debugMode")
boolean debugMode = Settings.DEFAULT_DEBUG_MODE;
@@ -51,12 +45,12 @@ class SettingsJson {
@JsonProperty("port")
int port = Settings.DEFAULT_PORT;
@JsonProperty("showMinimizeButton")
boolean showMinimizeButton = Settings.DEFAULT_SHOW_MINIMIZE_BUTTON;
@JsonProperty("showTrayIcon")
boolean showTrayIcon;
@JsonProperty("compactMode")
boolean compactMode;
@JsonProperty("startHidden")
boolean startHidden = Settings.DEFAULT_START_HIDDEN;
@@ -82,6 +76,13 @@ class SettingsJson {
@JsonProperty(value = "preferredVolumeImpl", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
String preferredVolumeImpl;
@JsonProperty("checkForUpdatesEnabled")
boolean checkForUpdatesEnabled = Settings.DEFAULT_CHECK_FOR_UPDATES;
@JsonProperty("lastReminderForUpdateCheck")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
Instant lastReminderForUpdateCheck = Settings.DEFAULT_TIMESTAMP;
@JsonProperty("lastSuccessfulUpdateCheck")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
Instant lastSuccessfulUpdateCheck = Settings.DEFAULT_TIMESTAMP;

View File

@@ -20,7 +20,7 @@ public class SupportedLanguages {
// "en" is not part of this list - it is always inserted at the top.
public static final List<String> LANGUAGE_TAGS = List.of("ar", "be", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "fr", "gl", "he", //
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "nb", "nl", "nn", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", "sr-Latn", "sv", "sw", //
"ta", "th", "tr", "uk", "vi", "zh", "zh-HK", "zh-TW");
"ta", "th", "tr", "ug", "uk", "vi", "zh", "zh-HK", "zh-TW");
public static final String ENGLISH = "en";
private final List<String> sortedLanguageTags;

View File

@@ -12,7 +12,6 @@ public enum FxmlFile {
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), //
DOKANY_SUPPORT_END("/fxml/dokany_support_end.fxml"), //
ERROR("/fxml/error.fxml"), //
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
HEALTH_START("/fxml/health_start.fxml"), //
@@ -45,8 +44,8 @@ public enum FxmlFile {
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
SHARE_VAULT("/fxml/share_vault.fxml"), //
SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), //
UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), //

View File

@@ -17,6 +17,7 @@ public enum FontAwesome5Icon {
COGS("\uF085"), //
COPY("\uF0C5"), //
CROWN("\uF521"), //
DONATE("\uF4B9"), //
EDIT("\uF044"), //
EXCHANGE_ALT("\uF362"), //
EXCLAMATION("\uF12A"), //
@@ -49,6 +50,7 @@ public enum FontAwesome5Icon {
SEARCH("\uF002"), //
SHARE("\uF064"), //
SPINNER("\uF110"), //
SPONSORS("\uF2B5"), //
STETHOSCOPE("\uF0f1"), //
SYNC("\uF021"), //
TIMES("\uF00D"), //

View File

@@ -0,0 +1,96 @@
package org.cryptomator.ui.controls;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
public class NotificationBar extends HBox {
@FXML
private Label notificationLabel;
private final BooleanProperty dismissable = new SimpleBooleanProperty();
private final BooleanProperty notify = new SimpleBooleanProperty();
public NotificationBar() {
setAlignment(Pos.CENTER);
setStyle("-fx-alignment: center;");
Region spacer = new Region();
spacer.setMinWidth(40);
Region leftRegion = new Region();
HBox.setHgrow(leftRegion, javafx.scene.layout.Priority.ALWAYS);
Region rightRegion = new Region();
HBox.setHgrow(rightRegion, javafx.scene.layout.Priority.ALWAYS);
VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
HBox.setHgrow(vbox, javafx.scene.layout.Priority.ALWAYS);
notificationLabel = new Label();
notificationLabel.getStyleClass().add("notification-label");
notificationLabel.setStyle("-fx-alignment: center;");
vbox.getChildren().add(notificationLabel);
Button closeButton = new Button("X");
closeButton.setMinWidth(40);
closeButton.setStyle("-fx-background-color: transparent; -fx-text-fill: white; -fx-font-weight: bold;");
closeButton.visibleProperty().bind(dismissable);
closeButton.setOnAction(_ -> {
visibleProperty().unbind();
managedProperty().unbind();
visibleProperty().set(false);
managedProperty().set(false);
});
closeButton.visibleProperty().bind(dismissable);
getChildren().addAll(spacer, leftRegion, vbox, rightRegion, closeButton);
visibleProperty().bind(notifyProperty());
managedProperty().bind(notifyProperty());
}
public String getText() {
return notificationLabel.getText();
}
public void setText(String text) {
notificationLabel.setText(text);
}
public void setStyleClass(String styleClass) {
getStyleClass().clear();
getStyleClass().add(styleClass);
}
public boolean isDismissable() {
return dismissable.get();
}
public void setDismissable(boolean value) {
dismissable.set(value);
}
public boolean getNotify() {
return notify.get();
}
public void setNotify(boolean value) {
notify.set(value);
}
public BooleanProperty notifyProperty() {
return notify;
}
}

View File

@@ -0,0 +1,75 @@
package org.cryptomator.ui.dialogs;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import java.util.ResourceBundle;
import java.util.function.Consumer;
@Singleton
public class Dialogs {
private final ResourceBundle resourceBundle;
@Inject
public Dialogs(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
private static final Logger LOG = LoggerFactory.getLogger(Dialogs.class);
private SimpleDialog.Builder createDialogBuilder() {
return new SimpleDialog.Builder(resourceBundle);
}
public SimpleDialog.Builder prepareRemoveVaultDialog(Stage window, Vault vault, ObservableList<Vault> vaults) {
return createDialogBuilder().setOwner(window) //
.setTitleKey("removeVault.title", vault.getDisplayName()) //
.setMessageKey("removeVault.message") //
.setDescriptionKey("removeVault.description") //
.setIcon(FontAwesome5Icon.QUESTION) //
.setOkButtonKey("removeVault.confirmBtn") //
.setCancelButtonKey("generic.button.cancel") //
.setOkAction(stage -> {
LOG.debug("Removing vault {}.", vault.getDisplayName());
vaults.remove(vault);
stage.close();
});
}
public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) {
return createDialogBuilder() //
.setOwner(window) //
.setTitleKey("removeCert.title") //
.setMessageKey("removeCert.message") //
.setDescriptionKey("removeCert.description") //
.setIcon(FontAwesome5Icon.QUESTION) //
.setOkButtonKey("removeCert.confirmBtn") //
.setCancelButtonKey("generic.button.cancel") //
.setOkAction(stage -> {
settings.licenseKey.set(null);
stage.close();
});
}
public SimpleDialog.Builder prepareDokanySupportEndDialog(Stage window, Consumer<Stage> cancelAction) {
return createDialogBuilder() //
.setOwner(window) //
.setTitleKey("dokanySupportEnd.title") //
.setMessageKey("dokanySupportEnd.message") //
.setDescriptionKey("dokanySupportEnd.description") //
.setIcon(FontAwesome5Icon.QUESTION) //
.setOkButtonKey("generic.button.close") //
.setCancelButtonKey("dokanySupportEnd.preferencesBtn") //
.setOkAction(Stage::close) //
.setCancelAction(cancelAction);
}
}

View File

@@ -0,0 +1,138 @@
package org.cryptomator.ui.dialogs;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.IllegalFormatException;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.function.Consumer;
public class SimpleDialog {
private final ResourceBundle resourceBundle;
private final Stage dialogStage;
SimpleDialog(Builder builder) throws IOException {
this.resourceBundle = builder.resourceBundle;
dialogStage = new Stage();
dialogStage.initOwner(builder.owner);
dialogStage.initModality(Modality.WINDOW_MODAL);
dialogStage.setTitle(resolveText(builder.titleKey, builder.titleArgs));
dialogStage.setResizable(false);
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController(
new SimpleDialogController(resolveText(builder.messageKey, null),
resolveText(builder.descriptionKey, null),
builder.icon,resolveText(builder.okButtonKey, null),
resolveText(builder.cancelButtonKey, null),
() -> builder.okAction.accept(dialogStage),
() -> builder.cancelAction.accept(dialogStage)),
Scene::new, builder.resourceBundle);
dialogStage.setScene(new Scene(loaderFactory.load(FxmlFile.SIMPLE_DIALOG.getRessourcePathString()).getRoot()));
}
public void showAndWait() {
dialogStage.showAndWait();
}
private String resolveText(String key, String[] args) {
if (key == null || key.isEmpty() || !resourceBundle.containsKey(key)) {
throw new IllegalArgumentException(String.format("Invalid key: '%s'. Key not found in ResourceBundle.", key));
}
String text = resourceBundle.getString(key);
try {
return args != null && args.length > 0 ? String.format(text, (Object[]) args) : text;
} catch (IllegalFormatException e) {
throw new IllegalArgumentException("Formatting error: Check if arguments match placeholders in the text.", e);
}
}
public static class Builder {
private Stage owner;
private final ResourceBundle resourceBundle;
private String titleKey;
private String[] titleArgs;
private String messageKey;
private String descriptionKey;
private String okButtonKey;
private String cancelButtonKey;
private FontAwesome5Icon icon;
private Consumer<Stage> okAction = Stage::close;
private Consumer<Stage> cancelAction = Stage::close;
public Builder(ResourceBundle resourceBundle) {
this.resourceBundle = resourceBundle;
}
public Builder setOwner(Stage owner) {
this.owner = owner;
return this;
}
public Builder setTitleKey(String titleKey, String... args) {
this.titleKey = titleKey;
this.titleArgs = args;
return this;
}
public Builder setMessageKey(String messageKey) {
this.messageKey = messageKey;
return this;
}
public Builder setDescriptionKey(String descriptionKey) {
this.descriptionKey = descriptionKey;
return this;
}
public Builder setIcon(FontAwesome5Icon icon) {
this.icon = icon;
return this;
}
public Builder setOkButtonKey(String okButtonKey) {
this.okButtonKey = okButtonKey;
return this;
}
public Builder setCancelButtonKey(String cancelButtonKey) {
this.cancelButtonKey = cancelButtonKey;
return this;
}
public Builder setOkAction(Consumer<Stage> okAction) {
this.okAction = okAction;
return this;
}
public Builder setCancelAction(Consumer<Stage> cancelAction) {
this.cancelAction = cancelAction;
return this;
}
public SimpleDialog build() {
Objects.requireNonNull(titleKey,"SimpleDialog titleKey must be set.");
Objects.requireNonNull(messageKey,"SimpleDialog messageKey must be set.");
Objects.requireNonNull(descriptionKey,"SimpleDialog descriptionKey must be set.");
Objects.requireNonNull(okButtonKey,"SimpleDialog okButtonKey must be set.");
Objects.requireNonNull(cancelButtonKey,"SimpleDialog cancelButtonKey must be set.");
try {
return new SimpleDialog(this);
} catch (IOException e) {
throw new UncheckedIOException("Failed to create SimpleDialog.", e);
}
}
}
}

View File

@@ -0,0 +1,61 @@
package org.cryptomator.ui.dialogs;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import javafx.fxml.FXML;
public class SimpleDialogController implements FxController {
private final String message;
private final String description;
private final FontAwesome5Icon icon;
private final String okButtonText;
private final String cancelButtonText;
private final Runnable okAction;
private final Runnable cancelAction;
public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) {
this.message = message;
this.description = description;
this.icon = icon;
this.okButtonText = okButtonText;
this.cancelButtonText = cancelButtonText;
this.okAction = okAction;
this.cancelAction = cancelAction;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
public FontAwesome5Icon getIcon() {
return icon;
}
public String getOkButtonText() {
return okButtonText;
}
public String getCancelButtonText() {
return cancelButtonText;
}
@FXML
private void handleOk() {
if (okAction != null) {
okAction.run();
}
}
@FXML
private void handleCancel() {
if (cancelAction != null) {
cancelAction.run();
}
}
}

View File

@@ -1,34 +0,0 @@
package org.cryptomator.ui.dokanysupportend;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene;
import javafx.stage.Stage;
@DokanySupportEndScoped
@Subcomponent(modules = {DokanySupportEndModule.class})
public interface DokanySupportEndComponent {
@DokanySupportEndWindow
Stage window();
@FxmlScene(FxmlFile.DOKANY_SUPPORT_END)
Lazy<Scene> dokanySupportEndScene();
default void showDokanySupportEndWindow() {
Stage stage = window();
stage.setScene(dokanySupportEndScene().get());
stage.sizeToScene();
stage.show();
}
@Subcomponent.Factory
interface Factory {
DokanySupportEndComponent create();
}
}

View File

@@ -1,34 +0,0 @@
package org.cryptomator.ui.dokanysupportend;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@DokanySupportEndScoped
public class DokanySupportEndController implements FxController {
private final Stage window;
private final FxApplicationWindows applicationWindows;
@Inject
DokanySupportEndController(@DokanySupportEndWindow Stage window, FxApplicationWindows applicationWindows) {
this.window = window;
this.applicationWindows = applicationWindows;
}
@FXML
public void close() {
window.close();
}
public void openVolumePreferences() {
applicationWindows.showPreferencesWindow(SelectedPreferencesTab.VOLUME);
window.close();
}
}

View File

@@ -1,57 +0,0 @@
package org.cryptomator.ui.dokanysupportend;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class DokanySupportEndModule {
@Provides
@DokanySupportEndWindow
@DokanySupportEndScoped
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@DokanySupportEndWindow
@DokanySupportEndScoped
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(resourceBundle.getString("dokanySupportEnd.title"));
stage.setMinWidth(500);
stage.setMinHeight(100);
stage.initModality(Modality.APPLICATION_MODAL);
return stage;
}
@Provides
@FxmlScene(FxmlFile.DOKANY_SUPPORT_END)
@DokanySupportEndScoped
static Scene provideDokanySupportEndScene(@DokanySupportEndWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.DOKANY_SUPPORT_END);
}
@Binds
@IntoMap
@FxControllerKey(DokanySupportEndController.class)
abstract FxController bindDokanySupportEndController(DokanySupportEndController controller);
}

View File

@@ -1,13 +0,0 @@
package org.cryptomator.ui.dokanysupportend;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface DokanySupportEndScoped {
}

View File

@@ -1,14 +0,0 @@
package org.cryptomator.ui.dokanysupportend;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface DokanySupportEndWindow {
}

View File

@@ -20,6 +20,8 @@ public interface ErrorComponent {
default Stage show() {
Stage stage = window();
stage.setScene(scene());
stage.setMinWidth(420);
stage.setMinHeight(300);
stage.show();
return stage;
}

View File

@@ -11,6 +11,8 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
@FxApplicationScoped
@@ -72,8 +74,12 @@ public class FxApplication {
return null;
});
if (!environment.disableUpdateCheck()) {
appWindows.checkAndShowUpdateReminderWindow();
var time14DaysAgo = Instant.now().minus(Duration.ofDays(14));
if (!environment.disableUpdateCheck() //
&& !settings.checkForUpdates.getValue() //
&& settings.lastSuccessfulUpdateCheck.get().isBefore(time14DaysAgo) //
&& settings.lastUpdateCheckReminder.get().isBefore(time14DaysAgo)) {
appWindows.showUpdateReminderWindow();
}
migrateAndInformDokanyRemoval();

View File

@@ -7,7 +7,6 @@ package org.cryptomator.ui.fxapp;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.ui.dokanysupportend.DokanySupportEndComponent;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.lock.LockComponent;
@@ -34,7 +33,6 @@ import java.io.InputStream;
ErrorComponent.class, //
HealthCheckComponent.class, //
UpdateReminderComponent.class, //
DokanySupportEndComponent.class, //
ShareVaultComponent.class})
abstract class FxApplicationModule {

View File

@@ -5,7 +5,7 @@ import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.ui.dokanysupportend.DokanySupportEndComponent;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
@@ -48,14 +48,14 @@ public class FxApplicationWindows {
private final Lazy<PreferencesComponent> preferencesWindow;
private final QuitComponent.Builder quitWindowBuilder;
private final UnlockComponent.Factory unlockWorkflowFactory;
private final UpdateReminderComponent.Factory updateReminderWindowBuilder;
private final DokanySupportEndComponent.Factory dokanySupportEndWindowBuilder;
private final UpdateReminderComponent.Factory updateReminderWindowFactory;
private final LockComponent.Factory lockWorkflowFactory;
private final ErrorComponent.Factory errorWindowFactory;
private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow;
private final FilteredList<Window> visibleWindows;
private final Dialogs dialogs;
@Inject
public FxApplicationWindows(@PrimaryStage Stage primaryStage, //
@@ -64,27 +64,27 @@ public class FxApplicationWindows {
Lazy<PreferencesComponent> preferencesWindow, //
QuitComponent.Builder quitWindowBuilder, //
UnlockComponent.Factory unlockWorkflowFactory, //
UpdateReminderComponent.Factory updateReminderWindowBuilder, //
DokanySupportEndComponent.Factory dokanySupportEndWindowBuilder, //
UpdateReminderComponent.Factory updateReminderWindowFactory, //
LockComponent.Factory lockWorkflowFactory, //
ErrorComponent.Factory errorWindowFactory, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, //
ExecutorService executor) {
ExecutorService executor, //
Dialogs dialogs) {
this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.quitWindowBuilder = quitWindowBuilder;
this.unlockWorkflowFactory = unlockWorkflowFactory;
this.updateReminderWindowBuilder = updateReminderWindowBuilder;
this.dokanySupportEndWindowBuilder = dokanySupportEndWindowBuilder;
this.updateReminderWindowFactory = updateReminderWindowFactory;
this.lockWorkflowFactory = lockWorkflowFactory;
this.errorWindowFactory = errorWindowFactory;
this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow;
this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
this.dialogs = dialogs;
}
public void initialize() {
@@ -142,15 +142,20 @@ public class FxApplicationWindows {
CompletableFuture.runAsync(() -> quitWindowBuilder.build().showQuitWindow(response,forced), Platform::runLater);
}
public void checkAndShowUpdateReminderWindow() {
CompletableFuture.runAsync(() -> updateReminderWindowBuilder.create().checkAndShowUpdateReminderWindow(), Platform::runLater);
public void showUpdateReminderWindow() {
CompletableFuture.runAsync(() -> updateReminderWindowFactory.create().showUpdateReminderWindow(), Platform::runLater);
}
public void showDokanySupportEndWindow() {
CompletableFuture.runAsync(() -> dokanySupportEndWindowBuilder.create().showDokanySupportEndWindow(), Platform::runLater);
CompletableFuture.runAsync(() -> dialogs.prepareDokanySupportEndDialog(
mainWindow.get().window(),
stage -> {
showPreferencesWindow(SelectedPreferencesTab.VOLUME);
stage.close();
}
).build().showAndWait(), Platform::runLater);
}
public CompletionStage<Void> startUnlockWorkflow(Vault vault, @Nullable Stage owner) {
return CompletableFuture.supplyAsync(() -> {
Preconditions.checkState(vault.stateProperty().transition(VaultState.Value.LOCKED, VaultState.Value.PROCESSING), "Vault not locked.");

View File

@@ -25,6 +25,8 @@ public interface HealthCheckComponent {
default Stage showHealthCheckWindow() {
Stage stage = window();
stage.setScene(startScene().get());
stage.setMinWidth(420);
stage.setMinHeight(300);
stage.show();
return stage;
}

View File

@@ -20,7 +20,7 @@ public class HubConfig {
public String devicesResourceUrl;
/**
* A collection of String template processors to construct URIs related to this Hub instance.
* A collection of functions to construct URIs related to this Hub instance.
*/
@JsonIgnore
public final URIProcessors URIs = new URIProcessors();
@@ -49,14 +49,20 @@ public class HubConfig {
public class URIProcessors {
public final URIProcessor API = this::fromApiEndpoint;
/**
* Resolves paths relative to the <code>/api/</code> endpoint of this Hub instance.
*/
public final StringTemplate.Processor<URI, RuntimeException> API = template -> {
var path = template.interpolate();
public URI fromApiEndpoint(String path) {
var relPath = path.startsWith("/") ? path.substring(1) : path;
return getApiBaseUrl().resolve(relPath);
};
}
}
@FunctionalInterface
public interface URIProcessor {
URI resolve(String path);
}
}

View File

@@ -88,7 +88,7 @@ public class ReceiveKeyController implements FxController {
* STEP 0 (Request): GET /api/config
*/
private void requestApiConfig() {
var configUri = hubConfig.URIs.API."config";
var configUri = hubConfig.URIs.API.resolve("config");
var request = HttpRequest.newBuilder(configUri) //
.GET() //
.timeout(REQ_TIMEOUT) //
@@ -122,7 +122,7 @@ public class ReceiveKeyController implements FxController {
* STEP 1 (Request): GET user key for this device
*/
private void requestDeviceData() {
var deviceUri = hubConfig.URIs.API."devices/\{deviceId}";
var deviceUri = hubConfig.URIs.API.resolve("devices/" + deviceId);
var request = HttpRequest.newBuilder(deviceUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
@@ -162,7 +162,7 @@ public class ReceiveKeyController implements FxController {
* STEP 2 (Request): GET vault key for this user
*/
private void requestVaultMasterkey(String encryptedUserKey) {
var vaultKeyUri = hubConfig.URIs.API."vaults/\{vaultId}/access-token";
var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token");
var request = HttpRequest.newBuilder(vaultKeyUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //
@@ -205,7 +205,7 @@ public class ReceiveKeyController implements FxController {
*/
@Deprecated
private void requestLegacyAccessToken() {
var legacyAccessTokenUri = hubConfig.URIs.API."vaults/\{vaultId}/keys/\{deviceId}";
var legacyAccessTokenUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/keys/" + deviceId);
var request = HttpRequest.newBuilder(legacyAccessTokenUri) //
.header("Authorization", "Bearer " + bearerToken) //
.GET() //

View File

@@ -115,7 +115,7 @@ public class RegisterDeviceController implements FxController {
workInProgress.set(true);
var userReq = HttpRequest.newBuilder(hubConfig.URIs.API."users/me") //
var userReq = HttpRequest.newBuilder(hubConfig.URIs.API.resolve("users/me")) //
.GET() //
.timeout(REQ_TIMEOUT) //
.header("Authorization", "Bearer " + bearerToken) //
@@ -143,7 +143,7 @@ public class RegisterDeviceController implements FxController {
var now = Instant.now().toString();
var dto = new CreateDeviceDto(deviceId, deviceNameField.getText(), BaseEncoding.base64().encode(deviceKeyPair.getPublic().getEncoded()), "DESKTOP", jwe.serialize(), now);
var json = toJson(dto);
var deviceUri = hubConfig.URIs.API."devices/\{deviceId}";
var deviceUri = hubConfig.URIs.fromApiEndpoint("devices/" + deviceId);
var putDeviceReq = HttpRequest.newBuilder(deviceUri) //
.PUT(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) //
.timeout(REQ_TIMEOUT) //
@@ -164,7 +164,7 @@ public class RegisterDeviceController implements FxController {
private void migrateLegacyDevices(ECPublicKey userPublicKey) {
try {
// GET legacy access tokens
var getUri = hubConfig.URIs.API."devices/\{deviceId}/legacy-access-tokens";
var getUri = hubConfig.URIs.API.resolve("devices/" + deviceId + "/legacy-access-tokens");
var getReq = HttpRequest.newBuilder(getUri).GET().timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build();
var getRes = httpClient.send(getReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (getRes.statusCode() != 200) {
@@ -185,12 +185,12 @@ public class RegisterDeviceController implements FxController {
LOG.warn("Failed to decrypt legacy access token for vault {}. Skipping migration.", entry.getKey());
}
}).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
var postUri = hubConfig.URIs.API."users/me/access-tokens";
var postUri = hubConfig.URIs.fromApiEndpoint("users/me/access-tokens");
var postBody = JSON.writer().writeValueAsString(newAccessTokens);
var postReq = HttpRequest.newBuilder(postUri).POST(HttpRequest.BodyPublishers.ofString(postBody)).timeout(REQ_TIMEOUT).header("Authorization", "Bearer " + bearerToken).build();
var postRes = httpClient.send(postReq, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (postRes.statusCode() != 200) {
throw new IOException(STR."Unexpected response from POST \{postUri}: \{postRes.statusCode()}");
throw new IOException("Unexpected response from POST " + postUri + ": " + postRes.statusCode());
}
} catch (IOException e) {
// log and ignore: this is merely a best-effort attempt of migrating legacy devices. Failure is uncritical as this is merely a convenience feature.

View File

@@ -112,12 +112,12 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
}
private void savePasswordToSystemkeychain(Passphrase passphrase) {
if (keychain.isSupported()) {
try {
try {
if (keychain.isSupported() && !keychain.getPassphraseStoredProperty(vault.getId()).getValue()) {
keychain.storePassphrase(vault.getId(), vault.getDisplayName(), passphrase);
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
}

View File

@@ -1,19 +1,26 @@
package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.Observable;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
@MainWindowScoped
public class MainWindowController implements FxController {
@@ -22,22 +29,61 @@ public class MainWindowController implements FxController {
private final Stage window;
private final ReadOnlyObjectProperty<Vault> selectedVault;
private final Settings settings;
private final FxApplicationWindows appWindows;
private final BooleanBinding updateAvailable;
private final LicenseHolder licenseHolder;
public StackPane root;
@FXML
private StackPane root;
@Inject
public MainWindowController(@MainWindow Stage window, ObjectProperty<Vault> selectedVault) {
public MainWindowController(@MainWindow Stage window, //
ObjectProperty<Vault> selectedVault, //
Settings settings, //
FxApplicationWindows appWindows, //
UpdateChecker updateChecker, //
LicenseHolder licenseHolder) {
this.window = window;
this.selectedVault = selectedVault;
this.settings = settings;
this.appWindows = appWindows;
this.updateAvailable = updateChecker.updateAvailableProperty();
this.licenseHolder = licenseHolder;
updateChecker.automaticallyCheckForUpdatesIfEnabled();
}
@FXML
public void initialize() {
LOG.trace("init MainWindowController");
if (SystemUtils.IS_OS_WINDOWS) {
root.getStyleClass().add("os-windows");
}
window.focusedProperty().addListener(this::mainWindowFocusChanged);
if (!neverTouched()) {
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
}
window.widthProperty().addListener((_, _, _) -> savePositionalSettings());
window.heightProperty().addListener((_, _, _) -> savePositionalSettings());
window.xProperty().addListener((_, _, _) -> savePositionalSettings());
window.yProperty().addListener((_, _, _) -> savePositionalSettings());
}
private boolean neverTouched() {
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
}
public void savePositionalSettings() {
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
}
private void mainWindowFocusChanged(Observable observable) {
@@ -47,4 +93,43 @@ public class MainWindowController implements FxController {
}
}
@FXML
public void showGeneralPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
}
@FXML
public void showContributePreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
}
@FXML
public void showUpdatePreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.UPDATES);
}
public ReadOnlyBooleanProperty debugModeEnabledProperty() {
return settings.debugMode;
}
public boolean getDebugModeEnabled() {
return debugModeEnabledProperty().get();
}
public BooleanBinding updateAvailableProperty() {
return updateAvailable;
}
public boolean getUpdateAvailable() {
return updateAvailable.get();
}
public BooleanBinding licenseValidProperty(){
return licenseHolder.validLicenseProperty();
}
public boolean getLicenseValid() {
return licenseHolder.isValidLicense();
}
}

View File

@@ -6,7 +6,6 @@ import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
@@ -14,10 +13,9 @@ import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.common.StageInitializer;
import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.health.HealthCheckComponent;
import org.cryptomator.ui.migration.MigrationComponent;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
@@ -28,11 +26,10 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.Map;
import java.util.ResourceBundle;
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
abstract class MainWindowModule {
@Provides
@@ -41,9 +38,8 @@ abstract class MainWindowModule {
static Stage provideMainWindow(@PrimaryStage Stage stage, StageInitializer initializer) {
initializer.accept(stage);
stage.setTitle("Cryptomator");
stage.initStyle(StageStyle.UNDECORATED);
stage.setMinWidth(650);
stage.setMinHeight(440);
stage.setMinHeight(498);
return stage;
}
@@ -85,16 +81,6 @@ abstract class MainWindowModule {
@FxControllerKey(MainWindowController.class)
abstract FxController bindMainWindowController(MainWindowController controller);
@Binds
@IntoMap
@FxControllerKey(MainWindowTitleController.class)
abstract FxController bindMainWindowTitleController(MainWindowTitleController controller);
@Binds
@IntoMap
@FxControllerKey(ResizeController.class)
abstract FxController bindResizeController(ResizeController controller);
@Binds
@IntoMap
@FxControllerKey(VaultListController.class)

View File

@@ -18,22 +18,20 @@ public class MainWindowSceneFactory extends DefaultSceneFactory {
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
protected static final KeyCodeCombination SHORTCUT_O = new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN);
private final Lazy<MainWindowTitleController> mainWindowTitleController;
private final Stage window;
private final Lazy<VaultListController> vaultListController;
@Inject
public MainWindowSceneFactory(Settings settings, Lazy<MainWindowTitleController> mainWindowTitleController, Lazy<VaultListController> vaultListController) {
public MainWindowSceneFactory(Settings settings, @MainWindow Stage window, Lazy<VaultListController> vaultListController) {
super(settings);
this.mainWindowTitleController = mainWindowTitleController;
this.window = window;
this.vaultListController = vaultListController;
}
@Override
protected void setupDefaultAccelerators(Scene scene, Stage stage) {
if (SystemUtils.IS_OS_WINDOWS) {
scene.getAccelerators().put(ALT_F4, mainWindowTitleController.get()::close);
} else {
scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
if (!SystemUtils.IS_OS_WINDOWS) {
scene.getAccelerators().put(SHORTCUT_W, window::close);
}
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddNewVault);
scene.getAccelerators().put(SHORTCUT_O, vaultListController.get()::didClickAddExistingVault);

View File

@@ -1,157 +0,0 @@
package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.fxapp.UpdateChecker;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
@MainWindowScoped
public class MainWindowTitleController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
private final Stage window;
private final FxApplicationTerminator terminator;
private final FxApplicationWindows appWindows;
private final boolean trayMenuInitialized;
private final UpdateChecker updateChecker;
private final BooleanBinding updateAvailable;
private final LicenseHolder licenseHolder;
private final Settings settings;
private final BooleanBinding showMinimizeButton;
public HBox titleBar;
private double xOffset;
private double yOffset;
@Inject
MainWindowTitleController(@MainWindow Stage window, FxApplicationTerminator terminator, FxApplicationWindows appWindows, TrayMenuComponent trayMenu, UpdateChecker updateChecker, LicenseHolder licenseHolder, Settings settings) {
this.window = window;
this.terminator = terminator;
this.appWindows = appWindows;
this.trayMenuInitialized = trayMenu.isInitialized();
this.updateChecker = updateChecker;
this.updateAvailable = updateChecker.updateAvailableProperty();
this.licenseHolder = licenseHolder;
this.settings = settings;
this.showMinimizeButton = Bindings.createBooleanBinding(this::isShowMinimizeButton, settings.showMinimizeButton, settings.showTrayIcon);
}
@FXML
public void initialize() {
LOG.trace("init MainWindowTitleController");
updateChecker.automaticallyCheckForUpdatesIfEnabled();
titleBar.setOnMousePressed(event -> {
xOffset = event.getSceneX();
yOffset = event.getSceneY();
});
titleBar.setOnMouseClicked(event -> {
if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) {
window.setFullScreen(!window.isFullScreen());
}
});
titleBar.setOnMouseDragged(event -> {
if (window.isFullScreen()) return;
window.setX(event.getScreenX() - xOffset);
window.setY(event.getScreenY() - yOffset);
});
titleBar.setOnDragDetected(mouseDragEvent -> {
titleBar.startFullDrag();
});
titleBar.setOnMouseDragReleased(mouseDragEvent -> {
saveWindowSettings();
});
window.setOnCloseRequest(event -> {
close();
event.consume();
});
}
private void saveWindowSettings() {
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
}
@FXML
public void close() {
if (trayMenuInitialized) {
window.close();
} else {
terminator.terminate();
}
}
@FXML
public void minimize() {
window.setIconified(true);
}
@FXML
public void showPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
@FXML
public void showGeneralPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
}
@FXML
public void showContributePreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.CONTRIBUTE);
}
/* Getter/Setter */
public LicenseHolder getLicenseHolder() {
return licenseHolder;
}
public BooleanBinding updateAvailableProperty() {
return updateAvailable;
}
public boolean isUpdateAvailable() {
return updateAvailable.get();
}
public boolean isTrayIconPresent() {
return trayMenuInitialized;
}
public ReadOnlyBooleanProperty debugModeEnabledProperty() {
return settings.debugMode;
}
public boolean isDebugModeEnabled() {
return debugModeEnabledProperty().get();
}
public BooleanBinding showMinimizeButtonProperty() {
return showMinimizeButton;
}
public boolean isShowMinimizeButton() {
// always show the minimize button if no tray icon is present OR it is explicitly enabled
return !trayMenuInitialized || settings.showMinimizeButton.get();
}
}

View File

@@ -1,194 +0,0 @@
package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.BooleanBinding;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
@MainWindow
public class ResizeController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(ResizeController.class);
private final Stage window;
public Region tlResizer;
public Region trResizer;
public Region blResizer;
public Region brResizer;
public Region tResizer;
public Region rResizer;
public Region bResizer;
public Region lResizer;
public Region lDefaultRegion;
public Region tDefaultRegion;
public Region rDefaultRegion;
public Region bDefaultRegion;
private double origX, origY, origW, origH;
private final Settings settings;
private final BooleanBinding showResizingArrows;
@Inject
ResizeController(@MainWindow Stage window, Settings settings) {
this.window = window;
this.settings = settings;
this.showResizingArrows = window.fullScreenProperty().not();
}
@FXML
public void initialize() {
LOG.trace("init ResizeController");
if (!neverTouched()) {
window.setHeight(settings.windowHeight.get() > window.getMinHeight() ? settings.windowHeight.get() : window.getMinHeight());
window.setWidth(settings.windowWidth.get() > window.getMinWidth() ? settings.windowWidth.get() : window.getMinWidth());
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
}
window.setOnShowing(this::checkDisplayBounds);
}
private boolean neverTouched() {
return (settings.windowHeight.get() == 0) && (settings.windowWidth.get() == 0) && (settings.windowXPosition.get() == 0) && (settings.windowYPosition.get() == 0);
}
private void checkDisplayBounds(WindowEvent evt) {
// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000)
// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453
// If the position is (-32000, -32000), restore to the last saved position
if (window.getX() == -32000 && window.getY() == -32000) {
window.setX(settings.windowXPosition.get());
window.setY(settings.windowYPosition.get());
window.setWidth(settings.windowWidth.get());
window.setHeight(settings.windowHeight.get());
}
if (isOutOfDisplayBounds()) {
// If the position is illegal, then the window appears on the main screen in the middle of the window.
LOG.debug("Resetting window position due to insufficient screen overlap");
Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
window.setX((primaryScreenBounds.getWidth() - window.getMinWidth()) / 2);
window.setY((primaryScreenBounds.getHeight() - window.getMinHeight()) / 2);
window.setWidth(window.getMinWidth());
window.setHeight(window.getMinHeight());
savePositionalSettings();
}
}
private boolean isOutOfDisplayBounds() {
// define a rect which is inset on all sides from the window's rect:
final double x = window.getX() + 20; // 20px left
final double y = window.getY() + 5; // 5px top
final double w = window.getWidth() - 40; // 20px left + 20px right
final double h = window.getHeight() - 25; // 5px top + 20px bottom
return isRectangleOutOfScreen(x, y, 0, h) // Left pixel column
|| isRectangleOutOfScreen(x + w, y, 0, h) // Right pixel column
|| isRectangleOutOfScreen(x, y, w, 0) // Top pixel row
|| isRectangleOutOfScreen(x, y + h, w, 0); // Bottom pixel row
}
private boolean isRectangleOutOfScreen(double x, double y, double width, double height) {
return Screen.getScreensForRectangle(x, y, width, height).isEmpty();
}
private void startResize(MouseEvent evt) {
origX = window.getX();
origY = window.getY();
origW = window.getWidth();
origH = window.getHeight();
}
@FXML
private void resizeTopLeft(MouseEvent evt) {
resizeTop(evt);
resizeLeft(evt);
}
@FXML
private void resizeTopRight(MouseEvent evt) {
resizeTop(evt);
resizeRight(evt);
}
@FXML
private void resizeBottomLeft(MouseEvent evt) {
resizeBottom(evt);
resizeLeft(evt);
}
@FXML
private void resizeBottomRight(MouseEvent evt) {
resizeBottom(evt);
resizeRight(evt);
}
@FXML
private void resizeTop(MouseEvent evt) {
startResize(evt);
double newY = evt.getScreenY();
double dy = newY - origY;
double newH = origH - dy;
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
window.setY(newY);
window.setHeight(newH);
}
}
@FXML
private void resizeLeft(MouseEvent evt) {
startResize(evt);
double newX = evt.getScreenX();
double dx = newX - origX;
double newW = origW - dx;
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
window.setX(newX);
window.setWidth(newW);
}
}
@FXML
private void resizeBottom(MouseEvent evt) {
double newH = evt.getSceneY();
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
window.setHeight(newH);
}
}
@FXML
private void resizeRight(MouseEvent evt) {
double newW = evt.getSceneX();
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
window.setWidth(newW);
}
}
@FXML
public void savePositionalSettings() {
settings.windowWidth.setValue(window.getWidth());
settings.windowHeight.setValue(window.getHeight());
settings.windowXPosition.setValue(window.getX());
settings.windowYPosition.setValue(window.getY());
}
public BooleanBinding showResizingArrowsProperty() {
return showResizingArrows;
}
public boolean isShowResizingArrows() {
return showResizingArrows.get();
}
}

View File

@@ -8,9 +8,9 @@ import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -21,7 +21,6 @@ public class VaultDetailLockedController implements FxController {
private final ReadOnlyObjectProperty<Vault> vault;
private final FxApplicationWindows appWindows;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final KeychainManager keychain;
private final Stage mainWindow;
private final ObservableValue<Boolean> passwordSaved;
@@ -30,13 +29,11 @@ public class VaultDetailLockedController implements FxController {
this.vault = vault;
this.appWindows = appWindows;
this.vaultOptionsWindow = vaultOptionsWindow;
this.keychain = keychain;
this.mainWindow = mainWindow;
if (keychain.isSupported() && !keychain.isLocked()) {
this.passwordSaved = vault.flatMap(v -> keychain.getPassphraseStoredProperty(v.getId())).orElse(false);
} else {
this.passwordSaved = new SimpleBooleanProperty(false);
}
this.passwordSaved = Bindings.createBooleanBinding(() -> {
var v = vault.get();
return v != null && keychain.getPassphraseStoredProperty(v.getId()).getValue();
}, vault, keychain.getKeychainImplementation());
}
@FXML

View File

@@ -3,10 +3,11 @@ package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.cryptomator.ui.dialogs.Dialogs;
import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
@@ -19,17 +20,22 @@ import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
public class VaultDetailMissingVaultController implements FxController {
private final ObjectProperty<Vault> vault;
private final RemoveVaultComponent.Builder removeVault;
private final ObservableList<Vault> vaults;
private final ResourceBundle resourceBundle;
private final Stage window;
private final Dialogs dialogs;
@Inject
public VaultDetailMissingVaultController(ObjectProperty<Vault> vault, RemoveVaultComponent.Builder removeVault, ResourceBundle resourceBundle, @MainWindow Stage window) {
public VaultDetailMissingVaultController(ObjectProperty<Vault> vault, //
ObservableList<Vault> vaults, //
ResourceBundle resourceBundle, //
@MainWindow Stage window, //
Dialogs dialogs) {
this.vault = vault;
this.removeVault = removeVault;
this.vaults = vaults;
this.resourceBundle = resourceBundle;
this.window = window;
this.dialogs = dialogs;
}
@FXML
@@ -39,7 +45,7 @@ public class VaultDetailMissingVaultController implements FxController {
@FXML
void didClickRemoveVault() {
removeVault.vault(vault.get()).build().showRemoveVault();
dialogs.prepareRemoveVaultDialog(window, vault.get(), vaults).build().showAndWait();
}
@FXML

View File

@@ -3,12 +3,13 @@ package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.beans.property.ObjectProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -18,14 +19,23 @@ public class VaultDetailUnknownErrorController implements FxController {
private final ObjectProperty<Vault> vault;
private final FxApplicationWindows appWindows;
private final Stage errorWindow;
private final RemoveVaultComponent.Builder removeVault;
private final ObservableList<Vault> vaults;
private final Stage mainWindow;
private final Dialogs dialogs;
@Inject
public VaultDetailUnknownErrorController(ObjectProperty<Vault> vault, FxApplicationWindows appWindows, @Named("errorWindow") Stage errorWindow, RemoveVaultComponent.Builder removeVault) {
public VaultDetailUnknownErrorController(@MainWindow Stage mainWindow, //
ObjectProperty<Vault> vault, //
ObservableList<Vault> vaults, //
FxApplicationWindows appWindows, //
@Named("errorWindow") Stage errorWindow, //
Dialogs dialogs) {
this.mainWindow = mainWindow;
this.vault = vault;
this.vaults = vaults;
this.appWindows = appWindows;
this.errorWindow = errorWindow;
this.removeVault = removeVault;
this.dialogs = dialogs;
}
@FXML
@@ -40,6 +50,6 @@ public class VaultDetailUnknownErrorController implements FxController {
@FXML
void didClickRemoveVault() {
removeVault.vault(vault.get()).build().showRemoveVault();
dialogs.prepareRemoveVaultDialog(mainWindow, vault.get(), vaults).build().showAndWait();
}
}

View File

@@ -1,5 +1,6 @@
package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.Animations;
@@ -18,6 +19,7 @@ public class VaultListCellController implements FxController {
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
private final ObservableValue<FontAwesome5Icon> glyph;
private final ObservableValue<Boolean> compactMode;
private AutoAnimator spinAnimation;
@@ -25,8 +27,9 @@ public class VaultListCellController implements FxController {
public FontAwesome5IconView vaultStateView;
@Inject
VaultListCellController() {
VaultListCellController(Settings settings) {
this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState);
this.compactMode = settings.compactMode;
}
public void initialize() {
@@ -68,6 +71,14 @@ public class VaultListCellController implements FxController {
return vault.get();
}
public ObservableValue<Boolean> compactModeProperty() {
return compactMode;
}
public boolean getCompactMode() {
return compactMode.getValue();
}
public void setVault(Vault value) {
vault.set(value);
}

View File

@@ -5,8 +5,8 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.cryptomator.ui.vaultoptions.SelectedVaultOptionsTab;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
@@ -14,6 +14,7 @@ import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.util.EnumSet;
@@ -33,23 +34,32 @@ public class VaultListContextMenuController implements FxController {
private final FxApplicationWindows appWindows;
private final VaultService vaultService;
private final KeychainManager keychain;
private final RemoveVaultComponent.Builder removeVault;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ObservableValue<VaultState.Value> selectedVaultState;
private final ObservableValue<Boolean> selectedVaultPassphraseStored;
private final ObservableValue<Boolean> selectedVaultRemovable;
private final ObservableValue<Boolean> selectedVaultUnlockable;
private final ObservableValue<Boolean> selectedVaultLockable;
private final ObservableList<Vault> vaults;
private final Dialogs dialogs;
@Inject
VaultListContextMenuController(ObjectProperty<Vault> selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Factory vaultOptionsWindow) {
VaultListContextMenuController(ObjectProperty<Vault> selectedVault, //
ObservableList<Vault> vaults, //
@MainWindow Stage mainWindow, //
FxApplicationWindows appWindows, //
VaultService vaultService, //
KeychainManager keychain, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
Dialogs dialogs) {
this.selectedVault = selectedVault;
this.vaults = vaults;
this.mainWindow = mainWindow;
this.appWindows = appWindows;
this.vaultService = vaultService;
this.keychain = keychain;
this.removeVault = removeVault;
this.vaultOptionsWindow = vaultOptionsWindow;
this.dialogs = dialogs;
this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null);
this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false);
@@ -65,7 +75,7 @@ public class VaultListContextMenuController implements FxController {
@FXML
public void didClickRemoveVault() {
var vault = Objects.requireNonNull(selectedVault.get());
removeVault.vault(vault).build().showRemoveVault();
dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait();
}
@FXML

View File

@@ -1,6 +1,7 @@
package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
@@ -8,8 +9,9 @@ import org.cryptomator.cryptofs.DirStructure;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.removevault.RemoveVaultComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,7 +26,6 @@ import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Side;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListView;
import javafx.scene.input.ContextMenuEvent;
@@ -33,6 +34,7 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.File;
@@ -64,15 +66,17 @@ public class VaultListController implements FxController {
private final VaultListCellFactory cellFactory;
private final AddVaultWizardComponent.Builder addVaultWizard;
private final BooleanBinding emptyVaultList;
private final RemoveVaultComponent.Builder removeVaultDialogue;
private final VaultListManager vaultListManager;
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
private final ResourceBundle resourceBundle;
private final FxApplicationWindows appWindows;
private final ObservableValue<Double> cellSize;
private final Dialogs dialogs;
public ListView<Vault> vaultList;
public StackPane root;
public Button addVaultBtn;
@FXML
private HBox addVaultButton;
@FXML
private ContextMenu addVaultContextMenu;
@@ -83,29 +87,36 @@ public class VaultListController implements FxController {
VaultListCellFactory cellFactory, //
VaultService vaultService, //
AddVaultWizardComponent.Builder addVaultWizard, //
RemoveVaultComponent.Builder removeVaultDialogue, //
VaultListManager vaultListManager, //
ResourceBundle resourceBundle, //
FxApplicationWindows appWindows) {
FxApplicationWindows appWindows, //
Settings settings, //
Dialogs dialogs) {
this.mainWindow = mainWindow;
this.vaults = vaults;
this.selectedVault = selectedVault;
this.cellFactory = cellFactory;
this.vaultService = vaultService;
this.addVaultWizard = addVaultWizard;
this.removeVaultDialogue = removeVaultDialogue;
this.vaultListManager = vaultListManager;
this.resourceBundle = resourceBundle;
this.appWindows = appWindows;
this.dialogs = dialogs;
this.emptyVaultList = Bindings.isEmpty(vaults);
selectedVault.addListener(this::selectedVaultDidChange);
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
}
public void initialize() {
vaultList.setItems(vaults);
vaultList.setCellFactory(cellFactory);
vaultList.prefHeightProperty().bind(
vaultList.fixedCellSizeProperty().multiply(Bindings.size(vaultList.getItems()))
);
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
vaults.addListener((ListChangeListener.Change<? extends Vault> c) -> {
while (c.next()) {
@@ -171,7 +182,7 @@ public class VaultListController implements FxController {
if (addVaultContextMenu.isShowing()) {
addVaultContextMenu.hide();
} else {
addVaultContextMenu.show(addVaultBtn, Side.BOTTOM, 0.0, 0.0);
addVaultContextMenu.show(addVaultButton, Side.BOTTOM, 0.0, 0.0);
}
}
@@ -202,7 +213,7 @@ public class VaultListController implements FxController {
private void pressedShortcutToRemoveVault() {
final var vault = selectedVault.get();
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) {
removeVaultDialogue.vault(vault).build().showRemoveVault();
dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait();
}
}
@@ -247,6 +258,11 @@ public class VaultListController implements FxController {
}
}
@FXML
public void showPreferences() {
appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
}
// Getter and Setter
public BooleanBinding emptyVaultListProperty() {
@@ -265,5 +281,12 @@ public class VaultListController implements FxController {
return draggingVaultOver.get();
}
public ObservableValue<Double> cellSizeProperty() {
return cellSize;
}
public Double getCellSize() {
return cellSize.getValue();
}
}

View File

@@ -1,10 +1,14 @@
package org.cryptomator.ui.preferences;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.Passphrase;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
import org.cryptomator.integrations.common.NamedServiceProvider;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import org.cryptomator.integrations.quickaccess.QuickAccessService;
import org.cryptomator.ui.common.FxController;
@@ -14,6 +18,7 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
@@ -36,6 +41,7 @@ public class GeneralPreferencesController implements FxController {
private final Application application;
private final Environment environment;
private final List<KeychainAccessProvider> keychainAccessProviders;
private final KeychainManager keychain;
private final FxApplicationWindows appWindows;
public CheckBox useKeychainCheckbox;
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
@@ -48,11 +54,12 @@ public class GeneralPreferencesController implements FxController {
public ToggleGroup nodeOrientation;
@Inject
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, List<KeychainAccessProvider> keychainAccessProviders, Application application, Environment environment, FxApplicationWindows appWindows) {
GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional<AutoStartProvider> autoStartProvider, List<KeychainAccessProvider> keychainAccessProviders, KeychainManager keychain, Application application, Environment environment, FxApplicationWindows appWindows) {
this.window = window;
this.settings = settings;
this.autoStartProvider = autoStartProvider;
this.keychainAccessProviders = keychainAccessProviders;
this.keychain = keychain;
this.quickAccessServices = QuickAccessService.get().toList();
this.application = application;
this.environment = environment;
@@ -73,6 +80,7 @@ public class GeneralPreferencesController implements FxController {
Bindings.bindBidirectional(settings.keychainProvider, keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain);
keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not());
keychainBackendChoiceBox.valueProperty().addListener(this::migrateKeychainEntriesOnMac);
useQuickAccessCheckbox.selectedProperty().bindBidirectional(settings.useQuickAccess);
var quickAccessSettingsConverter = new ServiceToSettingsConverter<>(quickAccessServices);
@@ -83,6 +91,35 @@ public class GeneralPreferencesController implements FxController {
quickAccessServiceChoiceBox.disableProperty().bind(useQuickAccessCheckbox.selectedProperty().not());
}
public void migrateKeychainEntriesOnMac(Observable observable) {
if (!SystemUtils.IS_OS_MAC) {
return;
}
var provider = keychainBackendChoiceBox.getSelectionModel().getSelectedItem();
var providerId = "org.cryptomator.macos.keychain.MacSystemKeychainAccess";
var isSystemKeychain = provider.getClass().getName().equals(providerId);
List<String> vaults = settings.directories.stream()
.map(vault -> vault.id)
.toList();
if (!vaults.isEmpty()) {
LOG.info("Migrating keychain entries for vaults: {}", vaults);
}
for (String vaultId :vaults) {
try {
if (keychain.isPassphraseStored(vaultId)) {
var passphrase = keychain.loadPassphrase(vaultId);
keychain.deletePassphrase(vaultId);
keychain.storePassphrase(vaultId, vaultId, new Passphrase(passphrase), !isSystemKeychain);
}
} catch (KeychainAccessException e) {
LOG.error("Failed to migrate keychain entries.", e);
}
}
}
public boolean isAutoStartSupported() {
return autoStartProvider.isPresent();
}

View File

@@ -36,8 +36,8 @@ public class InterfacePreferencesController implements FxController {
private final ResourceBundle resourceBundle;
private final SupportedLanguages supportedLanguages;
public ChoiceBox<UiTheme> themeChoiceBox;
public CheckBox showMinimizeButtonCheckbox;
public CheckBox showTrayIconCheckbox;
public CheckBox compactModeCheckbox;
public ChoiceBox<String> preferredLanguageChoiceBox;
public ToggleGroup nodeOrientation;
public RadioButton nodeOrientationLtr;
@@ -63,9 +63,8 @@ public class InterfacePreferencesController implements FxController {
themeChoiceBox.valueProperty().bindBidirectional(settings.theme);
themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle));
showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton);
showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon);
compactModeCheckbox.selectedProperty().bindBidirectional(settings.compactMode);
preferredLanguageChoiceBox.getItems().addAll(supportedLanguages.getLanguageTags());
preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.language);

View File

@@ -31,7 +31,7 @@ public interface PreferencesComponent {
Stage stage = window();
stage.setScene(scene().get());
stage.setMinWidth(420);
stage.setMinHeight(300);
stage.setMinHeight(400);
stage.show();
stage.requestFocus();
return stage;

View File

@@ -5,6 +5,7 @@ import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.dialogs.Dialogs;
import javax.inject.Inject;
import javafx.application.Application;
@@ -12,22 +13,36 @@ import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.stage.Stage;
@PreferencesScoped
public class SupporterCertificateController implements FxController {
private static final String DONATE_URI = "https://cryptomator.org/donate";
private static final String SPONSORS_URI = "https://cryptomator.org/sponsors";
private static final String SUPPORTER_URI = "https://store.cryptomator.org/desktop";
private final Application application;
private final Stage window;
private final LicenseHolder licenseHolder;
private final Settings settings;
public TextArea supporterCertificateField;
private final Dialogs dialogs;
@FXML
private TextArea supporterCertificateField;
@Inject
SupporterCertificateController(Application application, LicenseHolder licenseHolder, Settings settings) {
SupporterCertificateController(Application application, //
@PreferencesWindow Stage window, //
LicenseHolder licenseHolder, //
Settings settings, //
Dialogs dialogs) {
this.application = application;
this.window = window;
this.licenseHolder = licenseHolder;
this.settings = settings;
this.dialogs = dialogs;
}
@FXML
@@ -35,6 +50,11 @@ public class SupporterCertificateController implements FxController {
supporterCertificateField.setText(licenseHolder.getLicenseKey().orElse(null));
supporterCertificateField.textProperty().addListener(this::registrationKeyChanged);
supporterCertificateField.setTextFormatter(new TextFormatter<>(this::removeWhitespaces));
settings.licenseKey.addListener((_, _, newValue) -> {
if (newValue == null) {
supporterCertificateField.setText(null);
}
});
}
private TextFormatter.Change removeWhitespaces(TextFormatter.Change change) {
@@ -57,7 +77,23 @@ public class SupporterCertificateController implements FxController {
application.getHostServices().showDocument(SUPPORTER_URI);
}
@FXML
public void showDonate() {
application.getHostServices().showDocument(DONATE_URI);
}
@FXML
public void showSponsors() {
application.getHostServices().showDocument(SPONSORS_URI);
}
@FXML
void didClickRemoveCert() {
dialogs.prepareRemoveCertDialog(window, settings).build().showAndWait();
}
public LicenseHolder getLicenseHolder() {
return licenseHolder;
}
}

View File

@@ -1,39 +0,0 @@
package org.cryptomator.ui.removevault;
import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene;
import javafx.stage.Stage;
@RemoveVaultScoped
@Subcomponent(modules = {RemoveVaultModule.class})
public interface RemoveVaultComponent {
@RemoveVaultWindow
Stage window();
@FxmlScene(FxmlFile.REMOVE_VAULT)
Lazy<Scene> scene();
default void showRemoveVault() {
Stage stage = window();
stage.setScene(scene().get());
stage.sizeToScene();
stage.show();
}
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder vault(@RemoveVaultWindow Vault vault);
RemoveVaultComponent build();
}
}

View File

@@ -1,40 +0,0 @@
package org.cryptomator.ui.removevault;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@RemoveVaultScoped
public class RemoveVaultController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RemoveVaultController.class);
private final Stage window;
private final Vault vault;
private final ObservableList<Vault> vaults;
@Inject
public RemoveVaultController(@RemoveVaultWindow Stage window, @RemoveVaultWindow Vault vault, ObservableList<Vault> vaults) {
this.window = window;
this.vault = vault;
this.vaults = vaults;
}
@FXML
public void close() {
window.close();
}
@FXML
public void finish() {
vaults.remove(vault);
LOG.debug("Removing vault {}.", vault.getDisplayName());
window.close();
}
}

View File

@@ -1,59 +0,0 @@
package org.cryptomator.ui.removevault;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.fxapp.PrimaryStage;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class RemoveVaultModule {
@Provides
@RemoveVaultWindow
@RemoveVaultScoped
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@RemoveVaultWindow
@RemoveVaultScoped
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, @RemoveVaultWindow Vault vault, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setTitle(String.format(resourceBundle.getString("removeVault.title"), vault.getDisplayName()));
stage.setResizable(false);
stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(primaryStage);
return stage;
}
@Provides
@FxmlScene(FxmlFile.REMOVE_VAULT)
@RemoveVaultScoped
static Scene provideRemoveVaultScene(@RemoveVaultWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.REMOVE_VAULT);
}
// ------------------
@Binds
@IntoMap
@FxControllerKey(RemoveVaultController.class)
abstract FxController bindRemoveVaultController(RemoveVaultController controller);
}

View File

@@ -1,13 +0,0 @@
package org.cryptomator.ui.removevault;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface RemoveVaultScoped {
}

View File

@@ -1,14 +0,0 @@
package org.cryptomator.ui.removevault;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface RemoveVaultWindow {
}

View File

@@ -2,14 +2,11 @@ package org.cryptomator.ui.updatereminder;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.time.Duration;
import java.time.Instant;
@UpdateReminderScoped
@Subcomponent(modules = {UpdateReminderModule.class})
@@ -21,16 +18,11 @@ public interface UpdateReminderComponent {
@FxmlScene(FxmlFile.UPDATE_REMINDER)
Lazy<Scene> updateReminderScene();
Settings settings();
default void checkAndShowUpdateReminderWindow() {
var now = Instant.now();
if (!settings().checkForUpdates.getValue() && settings().lastSuccessfulUpdateCheck.get().isBefore(now.minus(Duration.ofDays(14)))) {
Stage stage = window();
stage.setScene(updateReminderScene().get());
stage.sizeToScene();
stage.show();
}
default void showUpdateReminderWindow() {
Stage stage = window();
stage.setScene(updateReminderScene().get());
stage.sizeToScene();
stage.show();
}
@Subcomponent.Factory

View File

@@ -7,6 +7,7 @@ import org.cryptomator.ui.fxapp.UpdateChecker;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.time.Instant;
@UpdateReminderScoped
public class UpdateReminderController implements FxController {
@@ -23,6 +24,11 @@ public class UpdateReminderController implements FxController {
this.updateChecker = updateChecker;
}
@FXML
public void initialize() {
settings.lastUpdateCheckReminder.set(Instant.now());
}
@FXML
public void cancel() {
window.close();

View File

@@ -57,9 +57,14 @@ public class GeneralVaultOptionsController implements FxController {
}
private void trimVaultNameOnFocusLoss(Observable observable, Boolean wasFocussed, Boolean isFocussed) {
var displayNameSetting = vault.getVaultSettings().displayName;
if (!isFocussed) {
var trimmed = vaultName.getText().trim();
vault.getVaultSettings().displayName.set(trimmed);
if (!trimmed.isEmpty()) {
displayNameSetting.set(trimmed); //persist changes
} else {
vaultName.setText(displayNameSetting.get()); //revert changes
}
}
}

View File

@@ -100,6 +100,7 @@
.label-extra-large {
-fx-font-family: 'Open Sans SemiBold';
-fx-fill: TEXT_FILL;
-fx-font-size: 1.5em;
}
@@ -181,31 +182,20 @@
-fx-border-width: 1px;
}
.main-window .title {
-fx-background-color: CONTROL_BORDER_NORMAL, TITLE_BG;
-fx-background-insets: 0, 0 0 1px 0;
.main-window .button-bar {
-fx-background-color: MAIN_BG;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
}
.main-window .title .button {
-fx-pref-height: 30px;
-fx-pref-width: 30px;
-fx-background-color: none;
-fx-padding: 0;
.main-window .button-left {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 1px 0 0;
}
.main-window .title .button .glyph-icon {
-fx-fill: white;
}
.main-window .title .button:armed .glyph-icon {
-fx-fill: GRAY_8;
}
.main-window .update-indicator {
-fx-background-color: white, RED_5;
-fx-background-insets: 1px, 2px;
-fx-background-radius: 6px, 5px;
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
.main-window .button-right {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
}
/*******************************************************************************
@@ -322,23 +312,33 @@
-fx-fill: transparent;
}
.button.toolbar-button {
-fx-min-height: 40px;
-fx-background-color: transparent;
-fx-background-insets: 0;
-fx-background-radius: 0;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
-fx-padding: 0;
/*******************************************************************************
* *
* NotificationBar *
* *
******************************************************************************/
.notification-label {
-fx-text-fill: white;
-fx-font-weight: bold;
}
.button.toolbar-button:focused {
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
-fx-background-insets: 0, 2px 1px 1px 1px;
.notification-debug {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: RED_5;
}
.button.toolbar-button:armed {
-fx-background-color: CONTROL_BG_ARMED;
.notification-update {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: YELLOW_5;
}
.notification-support {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: PRIMARY;
}
/*******************************************************************************
@@ -394,16 +394,6 @@
-fx-background-color: MUTED_BG;
}
/* Note: These values below are kinda random such that it looks ok. I'm pretty sure there is room for improvement. Additionally, fx-text-fill does not work*/
.badge-debug {
-fx-font-family: 'Open Sans Bold';
-fx-font-size: 1.0em;
-fx-background-radius: 8px;
-fx-padding: 0.3em 0.55em 0.3em 0.55em;
-fx-background-color: RED_5;
-fx-background-radius: 2em;
}
/*******************************************************************************
* *
* Password Strength Indicator *

View File

@@ -177,34 +177,24 @@
/* windows needs an explicit border: */
.main-window.os-windows {
-fx-border-color: TITLE_BG;
-fx-border-width: 1px;
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 1px 0 0 0;
}
.main-window .title {
-fx-background-color: TITLE_BG;
.main-window .button-bar {
-fx-background-color: MAIN_BG;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
}
.main-window .title .button {
-fx-pref-height: 30px;
-fx-pref-width: 30px;
-fx-background-color: none;
-fx-padding: 0;
.main-window .button-bar .button-left {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 1px 0 0;
}
.main-window .title .button .glyph-icon {
-fx-fill: white;
}
.main-window .title .button:armed .glyph-icon {
-fx-fill: GRAY_8;
}
.main-window .update-indicator {
-fx-background-color: white, RED_5;
-fx-background-insets: 1px, 2px;
-fx-background-radius: 6px, 5px;
-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 2, 0, 0, 0);
.main-window .button-bar .button-right {
-fx-border-color: CONTROL_BORDER_NORMAL;
-fx-border-width: 0 0 0 1px;
}
/*******************************************************************************
@@ -321,23 +311,33 @@
-fx-fill: transparent;
}
.button.toolbar-button {
-fx-min-height: 40px;
-fx-background-color: transparent;
-fx-background-insets: 0;
-fx-background-radius: 0;
-fx-border-color: CONTROL_BORDER_NORMAL transparent transparent transparent;
-fx-border-width: 1px 0 0 0;
-fx-padding: 0;
/*******************************************************************************
* *
* NotificationBar *
* *
******************************************************************************/
.notification-label {
-fx-text-fill: white;
-fx-font-weight: bold;
}
.button.toolbar-button:focused {
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
-fx-background-insets: 0, 2px 1px 1px 1px;
.notification-debug {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: RED_5;
}
.button.toolbar-button:armed {
-fx-background-color: CONTROL_BG_ARMED;
.notification-update {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: YELLOW_5;
}
.notification-support {
-fx-min-height:24px;
-fx-max-height:24px;
-fx-background-color: PRIMARY;
}
/*******************************************************************************
@@ -393,16 +393,6 @@
-fx-background-color: MUTED_BG;
}
/* Note: These values below are kinda random such that it looks ok. I'm pretty sure there is room for improvement. Additionally, fx-text-fill does not work*/
.badge-debug {
-fx-font-family: 'Open Sans Bold';
-fx-font-size: 1.0em;
-fx-background-radius: 8px;
-fx-padding: 0.3em 0.55em 0.3em 0.55em;
-fx-background-color: RED_5;
-fx-background-radius: 2em;
}
/*******************************************************************************
* *
* Password Strength Indicator *

View File

@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.dokanysupportend.DokanySupportEndController"
minWidth="500"
prefWidth="500"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%dokanySupportEnd.message" wrapText="true">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%dokanySupportEnd.description" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+UC">
<buttons>
<Button text="%dokanySupportEnd.preferencesBtn" ButtonBar.buttonData="OTHER" cancelButton="true" onAction="#openVolumePreferences"/>
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close" defaultButton="true"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>

View File

@@ -25,8 +25,8 @@
<children>
<HBox VBox.vgrow="ALWAYS">
<VBox alignment="CENTER" minWidth="175" maxWidth="175">
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
<Image url="@../img/logo.png"/>
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" cache="true">
<Image url="@../img/logo128.png"/>
</ImageView>
</VBox>
<VBox HBox.hgrow="ALWAYS" alignment="CENTER">

View File

@@ -3,17 +3,31 @@
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.NotificationBar?>
<StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="root"
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
styleClass="main-window">
<VBox minWidth="650">
<fx:include source="main_window_title.fxml" VBox.vgrow="NEVER"/>
<VBox minWidth="600">
<NotificationBar onMouseClicked="#showUpdatePreferences"
text="%main.notification.updateAvailable"
dismissable="true"
notify="${controller.updateAvailable}"
styleClass="notification-update"/>
<NotificationBar onMouseClicked="#showContributePreferences"
text="%main.notification.support"
dismissable="true"
notify="${!controller.licenseValid}"
styleClass="notification-support"/>
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL" VBox.vgrow="ALWAYS">
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
</SplitPane>
<NotificationBar onMouseClicked="#showGeneralPreferences"
text="DEBUG MODE"
styleClass="notification-debug"
notify="${controller.debugModeEnabled}"/>
</VBox>
<fx:include source="main_window_resize.fxml"/>
</StackPane>

View File

@@ -1,30 +0,0 @@
<?import javafx.scene.Cursor?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Region?>
<AnchorPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.ResizeController"
nodeOrientation="LEFT_TO_RIGHT"
pickOnBounds="false">
<fx:define>
<Cursor fx:id="nwResize" fx:constant="NW_RESIZE"/>
<Cursor fx:id="neResize" fx:constant="NE_RESIZE"/>
<Cursor fx:id="nsResize" fx:constant="N_RESIZE"/>
<Cursor fx:id="ewResize" fx:constant="E_RESIZE"/>
<Cursor fx:id="default" fx:constant="DEFAULT"/>
</fx:define>
<Region fx:id="tlResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeTopLeft" onMouseReleased="#savePositionalSettings" AnchorPane.topAnchor="0" AnchorPane.leftAnchor="0"/>
<Region fx:id="trResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeTopRight" onMouseReleased="#savePositionalSettings" AnchorPane.topAnchor="0" AnchorPane.rightAnchor="0"/>
<Region fx:id="blResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeBottomLeft" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0"/>
<Region fx:id="brResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeBottomRight" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="0" AnchorPane.rightAnchor="0"/>
<Region fx:id="tResizer" cursor="${nsResize}" prefWidth="0" prefHeight="6" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeTop" onMouseReleased="#savePositionalSettings" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="0.0"/>
<Region fx:id="rResizer" cursor="${ewResize}" prefWidth="6" prefHeight="0" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeRight" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="10.0"/>
<Region fx:id="bResizer" cursor="${nsResize}" prefWidth="6" prefHeight="6" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeBottom" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0"/>
<Region fx:id="lResizer" cursor="${ewResize}" prefWidth="6" prefHeight="6" maxWidth="-Infinity" maxHeight="-Infinity" visible="${controller.showResizingArrows}" managed="${controller.showResizingArrows}" onMouseDragged="#resizeLeft" onMouseReleased="#savePositionalSettings" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="10.0"/>
<Region fx:id="lDefaultRegion" cursor="${default}" prefWidth="1" prefHeight="1" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.bottomAnchor="-1" AnchorPane.leftAnchor="-1" AnchorPane.topAnchor="-1"/>
<Region fx:id="tDefaultRegion" cursor="${default}" prefWidth="1" prefHeight="1" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.leftAnchor="-1" AnchorPane.topAnchor="-1" AnchorPane.rightAnchor="-1"/>
<Region fx:id="rDefaultRegion" cursor="${default}" prefWidth="1" prefHeight="1" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.topAnchor="-1" AnchorPane.rightAnchor="-1" AnchorPane.bottomAnchor="-1"/>
<Region fx:id="bDefaultRegion" cursor="${default}" prefWidth="1" prefHeight="1" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.rightAnchor="-1" AnchorPane.bottomAnchor="-1" AnchorPane.leftAnchor="-1"/>
</AnchorPane>

View File

@@ -1,78 +0,0 @@
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="titleBar"
fx:controller="org.cryptomator.ui.mainwindow.MainWindowTitleController"
styleClass="title"
alignment="CENTER"
minHeight="50"
maxHeight="50"
spacing="6">
<padding>
<Insets bottom="6" left="12" right="12" top="6"/>
</padding>
<children>
<ImageView HBox.hgrow="ALWAYS" fitHeight="14" preserveRatio="true" cache="true">
<Image url="@../img/title-logo.png"/>
</ImageView>
<Region HBox.hgrow="ALWAYS"/>
<Hyperlink onAction="#showGeneralPreferences" focusTraversable="false" visible="${controller.debugModeEnabled}" styleClass="badge-debug" text="DEBUG MODE" textFill="white">
<tooltip>
<Tooltip text="%main.debugModeEnabled.tooltip"/>
</tooltip>
</Hyperlink>
<Region HBox.hgrow="ALWAYS"/>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showContributePreferences" focusTraversable="false" visible="${!controller.licenseHolder.validLicense}">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="EXCLAMATION_CIRCLE" glyphSize="16"/>
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.supporterCertificateMissing.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences" focusTraversable="false">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="COGS" glyphSize="16"/>
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="12" prefHeight="12" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.preferencesBtn.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#minimize" focusTraversable="false" visible="${controller.showMinimizeButton}" managed="${controller.showMinimizeButton}">
<graphic>
<FontAwesome5IconView glyph="WINDOW_MINIMIZE" glyphSize="12"/>
</graphic>
<tooltip>
<Tooltip text="%main.minimizeBtn.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
<graphic>
<FontAwesome5IconView glyph="TIMES" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%main.closeBtn.tooltip"/>
</tooltip>
</Button>
</children>
</HBox>

View File

@@ -17,8 +17,8 @@
</padding>
<children>
<HBox spacing="12" VBox.vgrow="NEVER">
<ImageView VBox.vgrow="ALWAYS" fitHeight="64" preserveRatio="true" smooth="true" cache="true">
<Image url="@../img/logo.png"/>
<ImageView VBox.vgrow="ALWAYS" fitHeight="64" preserveRatio="true" cache="true">
<Image url="@../img/logo64.png"/>
</ImageView>
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
<FormattedLabel styleClass="label-extra-large" format="Cryptomator %s" arg1="${controller.fullApplicationVersion}"/>

View File

@@ -3,13 +3,18 @@
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.text.Text?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.preferences.SupporterCertificateController"
@@ -18,31 +23,49 @@
<Insets topRightBottomLeft="24"/>
</padding>
<children>
<StackPane VBox.vgrow="NEVER" prefHeight="60">
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.licenseHolder.validLicense}">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CROWN" glyphSize="24"/>
</StackPane>
<StackPane VBox.vgrow="NEVER" prefHeight="60" alignment="TOP_CENTER">
<VBox alignment="TOP_CENTER" visible="${controller.licenseHolder.validLicense}">
<Text styleClass="label-extra-large" text="%preferences.contribute.thankYou" wrappingWidth="400" textAlignment="CENTER"/>
<ImageView fitHeight="180">
<Image url="@../img/supporter_cert_stamp.png"/>
</ImageView>
<FormattedLabel format="%preferences.contribute.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
</HBox>
<HBox spacing="12" alignment="CENTER_LEFT" visible="${!controller.licenseHolder.validLicense}">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>
</StackPane>
<VBox HBox.hgrow="ALWAYS" spacing="6">
<Label text="%preferences.contribute.noCertificate" wrapText="true" VBox.vgrow="ALWAYS"/>
<Hyperlink text="%preferences.contribute.getCertificate" onAction="#getSupporterCertificate" contentDisplay="LEFT">
<Region minHeight="12"/>
<HBox alignment="BOTTOM_CENTER" spacing="6">
<Button onAction="#didClickRemoveCert">
<graphic>
<FontAwesome5IconView glyph="LINK"/>
<FontAwesome5IconView glyph="TRASH"/>
</graphic>
</Hyperlink>
</VBox>
</HBox>
</Button>
<Button text="%preferences.contribute.donate" minWidth="100" onAction="#showDonate">
<graphic>
<FontAwesome5IconView glyph="DONATE"/>
</graphic>
</Button>
<Button text="%preferences.contribute.sponsor" minWidth="100" onAction="#showSponsors">
<graphic>
<FontAwesome5IconView glyph="SPONSORS"/>
</graphic>
</Button>
</HBox>
</VBox>
<VBox spacing="12" visible="${!controller.licenseHolder.validLicense}">
<HBox spacing="12" alignment="CENTER_LEFT">
<StackPane HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>
</StackPane>
<VBox HBox.hgrow="ALWAYS" spacing="6">
<Label text="%preferences.contribute.noCertificate" wrapText="true" VBox.vgrow="ALWAYS"/>
<Hyperlink text="%preferences.contribute.getCertificate" onAction="#getSupporterCertificate">
<graphic>
<FontAwesome5IconView glyph="LINK"/>
</graphic>
</Hyperlink>
</VBox>
</HBox>
<TextArea fx:id="supporterCertificateField" promptText="%preferences.contribute.promptText" wrapText="true" VBox.vgrow="ALWAYS" prefRowCount="6"/>
</VBox>
</StackPane>
<TextArea fx:id="supporterCertificateField" promptText="%preferences.contribute.promptText" wrapText="true" VBox.vgrow="ALWAYS" prefRowCount="6"/>
</children>
</VBox>

View File

@@ -37,9 +37,7 @@
<RadioButton fx:id="nodeOrientationRtl" text="%preferences.interface.interfaceOrientation.rtl" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/>
</HBox>
<CheckBox fx:id="showMinimizeButtonCheckbox" text="%preferences.interface.showMinimizeButton" visible="${controller.trayMenuInitialized}" managed="${controller.trayMenuInitialized}"/>
<CheckBox fx:id="showTrayIconCheckbox" text="%preferences.interface.showTrayIcon" visible="${controller.trayMenuSupported}" managed="${controller.trayMenuSupported}"/>
<CheckBox fx:id="compactModeCheckbox" text="%preferences.interface.compactMode"/>
</children>
</VBox>

View File

@@ -1,24 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.control.ButtonBar?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.removevault.RemoveVaultController"
fx:controller="org.cryptomator.ui.dialogs.SimpleDialogController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
@@ -29,24 +28,23 @@
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="QUESTION" glyphSize="24"/>
<FontAwesome5IconView glyph="${controller.icon}" styleClass="glyph-icon-white" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%removeVault.message" wrapText="true" textAlignment="LEFT">
<Label text="${controller.message}" styleClass="label-large" wrapText="true">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%removeVault.description" wrapText="true" textAlignment="LEFT" />
<Label text="${controller.description}" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" cancelButton="true" onAction="#close"/>
<Button text="%removeVault.confirmBtn" ButtonBar.buttonData="FINISH" onAction="#finish"/>
<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel"/>
<Button text="${controller.okButtonText}" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#handleOk"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>
</HBox>

View File

@@ -9,7 +9,7 @@
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailMissingVaultController"
alignment="TOP_CENTER"
spacing="24">
spacing="9">
<children>
<VBox spacing="9" alignment="CENTER">
<StackPane alignment="CENTER">

View File

@@ -8,10 +8,10 @@
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.WelcomeController"
alignment="CENTER"
spacing="24">
spacing="9">
<children>
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
<Image url="@../img/logo.png"/>
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" cache="true">
<Image url="@../img/logo128.png"/>
</ImageView>
<TextFlow styleClass="text-flow" prefWidth="-Infinity" visible="${controller.noVaultPresent}" managed="${controller.noVaultPresent}">

View File

@@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Arc?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.shape.Arc?>
<StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="root"
@@ -17,38 +18,53 @@
minWidth="206">
<VBox>
<StackPane VBox.vgrow="ALWAYS">
<ListView fx:id="vaultList" editable="true" fixedCellSize="60">
<contextMenu>
<fx:include source="vault_list_contextmenu.fxml"/>
</contextMenu>
</ListView>
<VBox>
<ListView fx:id="vaultList" editable="true" fixedCellSize="${controller.cellSize}">
<contextMenu>
<fx:include source="vault_list_contextmenu.fxml"/>
</contextMenu>
</ListView>
<Region VBox.vgrow="ALWAYS" styleClass="list-view"/>
</VBox>
<VBox visible="${controller.emptyVaultList}" spacing="6" alignment="CENTER">
<Region VBox.vgrow="ALWAYS"/>
<Label VBox.vgrow="NEVER" text="%main.vaultlist.emptyList.onboardingInstruction" textAlignment="CENTER" wrapText="true"/>
<Arc VBox.vgrow="NEVER" styleClass="onboarding-overlay-arc" type="OPEN" centerX="50" centerY="0" radiusY="100" radiusX="50" startAngle="0" length="-60" strokeWidth="1"/>
<HBox>
<Arc styleClass="onboarding-overlay-arc" translateX="20" radiusY="100" radiusX="50" length="-60"/>
</HBox>
</VBox>
</StackPane>
<Button fx:id="addVaultBtn" onAction="#toggleMenu" styleClass="toolbar-button" text="%main.vaultlist.addVaultBtn" alignment="BASELINE_CENTER" maxWidth="Infinity" contentDisplay="RIGHT">
<graphic>
<FontAwesome5IconView glyph="CARET_DOWN"/>
</graphic>
</Button>
<fx:define>
<ContextMenu fx:id="addVaultContextMenu">
<items>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
<graphic>
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>
<HBox styleClass="button-bar">
<HBox fx:id="addVaultButton" onMouseClicked="#toggleMenu" styleClass="button-left" alignment="CENTER" minWidth="20">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<FontAwesome5IconView glyph="PLUS" HBox.hgrow="NEVER" glyphSize="16"/>
</HBox>
<Region HBox.hgrow="ALWAYS"/>
<HBox onMouseClicked="#showPreferences" styleClass="button-right" alignment="CENTER" minWidth="20">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<FontAwesome5IconView glyph="COG" HBox.hgrow="NEVER" glyphSize="16"/>
</HBox>
</HBox>
</VBox>
<Region styleClass="drag-n-drop-border" visible="${controller.draggingVaultOver}"/>
<fx:define>
<ContextMenu fx:id="addVaultContextMenu">
<items>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemNew" onAction="#didClickAddNewVault" >
<graphic>
<FontAwesome5IconView glyph="PLUS" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
<MenuItem styleClass="add-vault-menu-item" text="%main.vaultlist.addVaultBtn.menuItemExisting" onAction="#didClickAddExistingVault" >
<graphic>
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14" />
</graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>
</StackPane>

View File

@@ -23,7 +23,7 @@
</VBox>
<VBox spacing="4" HBox.hgrow="ALWAYS">
<Label styleClass="header-label" text="${controller.vault.displayName}"/>
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS">
<Label styleClass="detail-label" text="${controller.vault.displayablePath}" textOverrun="CENTER_ELLIPSIS" visible="${!controller.compactMode}" managed="${!controller.compactMode}">
<tooltip>
<Tooltip text="${controller.vault.displayablePath}"/>
</tooltip>

View File

@@ -14,6 +14,7 @@ generic.button.copied=Copied!
generic.button.done=Done
generic.button.next=Next
generic.button.print=Print
generic.button.remove=Remove
# Error
error.message=An error occurred
@@ -106,7 +107,6 @@ addvaultwizard.success.unlockNow=Unlock Now
removeVault.title=Remove "%s"
removeVault.message=Remove vault?
removeVault.description=This will only make Cryptomator forget about this vault. You can add it again. No encrypted files will be deleted from your hard drive.
removeVault.confirmBtn=Remove Vault
# Change Password
changepassword.title=Change Password
@@ -301,8 +301,8 @@ preferences.interface.language.auto=System Default
preferences.interface.interfaceOrientation=Interface Orientation
preferences.interface.interfaceOrientation.ltr=Left to Right
preferences.interface.interfaceOrientation.rtl=Right to Left
preferences.interface.showMinimizeButton=Show minimize button
preferences.interface.showTrayIcon=Show tray icon (requires restart)
preferences.interface.compactMode=Enable compact vault list
## Volume
preferences.volume=Virtual Drive
preferences.volume.type=Default Volume Type
@@ -336,6 +336,14 @@ preferences.contribute.registeredFor=Supporter certificate registered for %s
preferences.contribute.noCertificate=Support Cryptomator and receive a supporter certificate. It's like a license key but for awesome people using free software. ;-)
preferences.contribute.getCertificate=Don't have one already? Learn how you can obtain it.
preferences.contribute.promptText=Paste supporter certificate code here
preferences.contribute.thankYou=Thank you for supporting Cryptomator's open-source development!
preferences.contribute.donate=Donate
preferences.contribute.sponsor=Sponsor
### Remove License Key Dialog
removeCert.title=Remove Certificate
removeCert.message=Remove supporter certificate?
removeCert.description=Cryptomator's core features are not affected by this. Neither access to your vaults is restricted nor the level of security is lowered.
#<-- Add entries for donations and code/translation/documentation contribution -->
## About
@@ -377,11 +385,6 @@ stats.access.total=Total accesses: %d
# Main Window
main.closeBtn.tooltip=Close
main.minimizeBtn.tooltip=Minimize
main.preferencesBtn.tooltip=Preferences
main.debugModeEnabled.tooltip=Debug mode is enabled
main.supporterCertificateMissing.tooltip=Please consider donating
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
main.vaultlist.contextMenu.remove=Remove…
@@ -390,9 +393,11 @@ main.vaultlist.contextMenu.unlock=Unlock…
main.vaultlist.contextMenu.unlockNow=Unlock Now
main.vaultlist.contextMenu.vaultoptions=Show Vault Options
main.vaultlist.contextMenu.reveal=Reveal Drive
main.vaultlist.addVaultBtn=Add
main.vaultlist.addVaultBtn.menuItemNew=New Vault...
main.vaultlist.addVaultBtn.menuItemExisting=Existing Vault...
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault...
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault...
##Notificaition
main.notification.updateAvailable=Update is available.
main.notification.support=Support Cryptomator.
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=Thanks for choosing Cryptomator to protect your files. If you need any assistance, check out our getting started guides:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -121,6 +121,13 @@ public class EnvironmentTest {
env.getKeychainPath();
Mockito.verify(env).getPaths("cryptomator.integrationsWin.keychainPaths");
}
@Test
public void testWindowsHelloKeychainPath() {
Mockito.doReturn(Stream.of()).when(env).getPaths(Mockito.anyString());
env.getWindowsHelloKeychainPath();
Mockito.verify(env).getPaths("cryptomator.integrationsWin.windowsHelloKeychainPaths");
}
}
}

View File

@@ -1,6 +1,7 @@
package org.cryptomator.common.keychain;
import org.cryptomator.JavaFXUtil;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
@@ -19,6 +20,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class KeychainManagerTest {
@BeforeAll
public static void startup() throws InterruptedException {
var isRunning = JavaFXUtil.startPlatform();
Assumptions.assumeTrue(isRunning);
}
@Test
public void testStoreAndLoad() throws KeychainAccessException {
KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
@@ -27,15 +34,7 @@ public class KeychainManagerTest {
}
@Nested
public static class WhenObservingProperties {
@BeforeAll
public static void startup() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Platform.startup(latch::countDown);
var javafxStarted = latch.await(5, TimeUnit.SECONDS);
Assumptions.assumeTrue(javafxStarted);
}
public class WhenObservingProperties {
@Test
public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
@@ -43,7 +42,7 @@ public class KeychainManagerTest {
ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
Assertions.assertFalse(property.get());
keychainManager.storePassphrase("test", null,"bar");
keychainManager.storePassphrase("test", null, "bar");
AtomicBoolean result = new AtomicBoolean(false);
CountDownLatch latch = new CountDownLatch(1);

Some files were not shown because too many files have changed in this diff Show More