diff --git a/docs/docs/administration/oauth.md b/docs/docs/administration/oauth.md index 66a752f0b..f97f8da7d 100644 --- a/docs/docs/administration/oauth.md +++ b/docs/docs/administration/oauth.md @@ -11,7 +11,7 @@ Unable to set `app.immich:/` as a valid redirect URI? See [Mobile Redirect URI]( Immich supports 3rd party authentication via [OpenID Connect][oidc] (OIDC), an identity layer built on top of OAuth2. OIDC is supported by most identity providers, including: - [Authentik](https://goauthentik.io/integrations/sources/oauth/#openid-connect) -- [Authelia](https://www.authelia.com/configuration/identity-providers/open-id-connect/) +- [Authelia](https://www.authelia.com/configuration/identity-providers/openid-connect/clients/) - [Okta](https://www.okta.com/openid-connect/) - [Google](https://developers.google.com/identity/openid-connect/openid-connect) diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 74f4b51b6..1b6d8ad19 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -1158,9 +1158,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", - "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", + "version": "20.11.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", + "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" diff --git a/mobile/assets/immich-logo.json b/mobile/assets/immich-logo.json new file mode 100644 index 000000000..a83bd4660 --- /dev/null +++ b/mobile/assets/immich-logo.json @@ -0,0 +1 @@ +{"content": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAFACAYAAAAszc0KAAAAxnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbEoMwCPzPKXoEXonkOLHqTG/Q4xeEzKjTdbKLICuh7N/PUV4OQilSF229NTBIl07DAoXAOBlBTj5hJcrsLV9khmTKppwNLRRnPhum4rCoXoz0nYX1XugSSvowinmAfSKPtzTqacQUBUyDEdeC1nW5XmHd4Q6NU5zWw+bxHWXz810W295W7T9MtDMyGDO3GID9SOFhARsjd/vQn5GMrDmJLeTfnibKD6lPWpnDBF3gAAABhGlDQ1BJQ0MgcHJvZmlsZQAAeJx9kT1Iw0AcxV9bpSJVQYuIOGSoTnZRUcdahSJUCLVCqw4ml35Bk4YkxcVRcC04+LFYdXBx1tXBVRAEP0CcHZwUXaTE/yWFFjEeHPfj3b3H3TvAXy8z1eyIAapmGalEXMhkV4XgKwLoxyBm0CsxU58TxSQ8x9c9fHy9i/Is73N/jh4lZzLAJxDHmG5YxBvE05uWznmfOMyKkkJ8Tjxu0AWJH7kuu/zGueCwn2eGjXRqnjhMLBTaWG5jVjRU4iniiKJqlO/PuKxw3uKslquseU/+wlBOW1nmOs0RJLCIJYgQIKOKEsqwEKVVI8VEivbjHv5hxy+SSyZXCYwcC6hAheT4wf/gd7dmfnLCTQrFgc4X2/4YBYK7QKNm29/Htt04AQLPwJXW8lfqwOwn6bWWFjkC+raBi+uWJu8BlzvA0JMuGZIjBWj683ng/Yy+KQsM3ALda25vzX2cPgBp6ip5AxwcAmMFyl73eHdXe2//nmn29wO1pXLBEGyzRgAADXppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6MDY0YmE0N2YtMDU3MC00OTBmLWJkOGUtY2UzNDFhYjk0ZGY1IgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjdmYzk2NDZlLTE5NWQtNGE1Mi1iYTE3LWM0NDM4NmQyMWU1ZiIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjM0MGQxNWRmLTgzZDMtNDQ1Yi1hMzkwLTBjNTQ1NGNiMWI3ZCIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTWFjIE9TIgogICBHSU1QOlRpbWVTdGFtcD0iMTcxMDgwMjgyMTQ5NjYwMyIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjM2IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDM6MTlUMDA6MDA6MTgrMDE6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjAzOjE5VDAwOjAwOjE4KzAxOjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MTEwZjhhOTQtNTQ4MS00MTkxLTkxYjktZjQ3NzA3ZDUyYTVlIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKE1hYyBPUykiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDMtMTlUMDA6MDA6MjErMDE6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+fd4ZrQAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAAd0SU1FB+gDEhcAFf/Hh5UAACAASURBVHja7d15nBT1nTfwT3fPTPfcMMoMwqgzcnvwgERAnnXETTYZnyCR4OYBEhLdx30E8yhiEkUTA2zceCUq+CyQXfcRY4Q8CTgq7oKRuGj2EUhUDrkPRTkEBplh7p6rnj9qqqeq+ldXd3V3dffn/XrNS+mjuqq6uuvT3/odPkmSJBARERFR1vBzFxARERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASERERMQASEREREQMgERERETEAEhEREREDIBERERGlixzuAnKTz+dzdXmSJHGnEhERMQBSuoe6RL02wyIREREDIKVJyEvGNjAcEhERMQAy7HGbGQqJiIgBkBj4uF8YComIiAGQGPgcSWR4StY2qV+HYZCIiDI2K0g8yzHwpTjcpct+4EeFiIgYACnjwk6mHgrcV0RERAyADDIMMNyXRETEAMjdkNlBhW8x9zUREREDYBaEEb6t3P9EREQMgBkeOvg28r0hIiJiAMyCcMG3ju8XERERA2AWBAm+XQyDREREDIBZEBr4FjHQExERMQAy+Al1bXkXrQ8vRu/xEwnbBv+llchfeA+Cf/tNvqEMgkRExABIqQh9ao3XTkFvfX3itykYxMAje/jmeuz9JyIisotzAafJiT8Z1T27gt9i9c/p+2r3eFAexyBIREQJzSmsAKY2/Nnd/U6re2XHDzte7/anl6P9medcXy4l7ocBERERA2AGBj8vVf6c8l9aidy/moKu/3zPcv2zrW1hIo4VIiIiBsAMOZk3TrkpKjzpq3DJavuX8P2YhW0LGQSJiIgBMINP4HZ3c/j3r6D9mefcq/j5fIDdt9jngyRJ8CXjtSyWkY29je0cS/y4EhERA2AGBT/AXhs8K2XHD8ddEcxfeA/Cv/mta1XF3Kk1KHp+BXzBoOZ2q/XM1t7GDIJERMQAmEHBz7XqnhuVtiS9pg+A1bOkvsfphebORsHP/4HHGYMgERG5zM9d4O5JWZIk4Um5/enlaL3/QVvhL3dqDQYe2YOy44cjf/5Bg9QvYnud1cvKX3hP7Buve01luVbLtLOmor1advxw1oY/s+Mo1qBIRESkOX+wAuhO8FNO2mqu9eBNRcXPzjoJgqGdil/Ur5BLKxH6u++hbek/Gm5zts9AwsvCRETEAJgmJ15RD1435Ez8EqSuTvTs2O3qcvMX3oOOFf8MKRx2ZXkD936AjudXo+NfX4TU1BR1f+7U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QGJIJCIiIo/ycxcQERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgEREREQMgERERETEAEhEREREDIBERERExABIRERFRuvn/XcNQ24xp80UAAAAASUVORK5CYII="} diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index ffab2548e..6bf81eb64 100644 --- a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1 +1,354 @@ -{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"102.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"41mm","scale":"2x","size":"45x45","expected-size":"102","role":"appLauncher"},{"idiom":"watch","filename":"92.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"41mm","scale":"2x","size":"46x46","expected-size":"92","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"idiom":"watch","filename":"66.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"45mm","scale":"2x","size":"33x33","expected-size":"66","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} \ No newline at end of file +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + }, + { + "filename" : "48.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", + "subtype" : "38mm" + }, + { + "filename" : "55.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", + "subtype" : "42mm" + }, + { + "filename" : "58.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "66.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "33x33", + "subtype" : "45mm" + }, + { + "filename" : "80.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", + "subtype" : "38mm" + }, + { + "filename" : "88.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", + "subtype" : "40mm" + }, + { + "filename" : "92.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "46x46", + "subtype" : "41mm" + }, + { + "filename" : "100.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "51x51", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "54x54", + "subtype" : "49mm" + }, + { + "filename" : "172.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", + "subtype" : "38mm" + }, + { + "filename" : "196.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", + "subtype" : "42mm" + }, + { + "filename" : "216.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "108x108", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "117x117", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "129x129", + "subtype" : "49mm" + }, + { + "filename" : "1024.png", + "idiom" : "watch-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "102.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "45x45", + "subtype" : "41mm" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/lib/modules/asset_viewer/ui/exif_sheet/exif_people.dart b/mobile/lib/modules/asset_viewer/ui/exif_sheet/exif_people.dart index a94a1239f..620f4f3cd 100644 --- a/mobile/lib/modules/asset_viewer/ui/exif_sheet/exif_people.dart +++ b/mobile/lib/modules/asset_viewer/ui/exif_sheet/exif_people.dart @@ -22,8 +22,11 @@ class ExifPeople extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final peopleProvider = ref.watch(assetPeopleNotifierProvider(asset).notifier); - final people = ref.watch(assetPeopleNotifierProvider(asset)); - final double imageSize = math.min(context.width / 3, 150); + final people = ref + .watch(assetPeopleNotifierProvider(asset)) + .value + ?.where((p) => !p.isHidden); + final double imageSize = math.min(context.width / 3, 120); showPersonNameEditModel( String personId, @@ -40,15 +43,14 @@ class ExifPeople extends ConsumerWidget { }); } - if (people.value?.isEmpty ?? true) { + if (people?.isEmpty ?? true) { // Empty list or loading return Container(); } - final curatedPeople = people.value - ?.map((p) => CuratedContent(id: p.id, label: p.name)) - .toList() ?? - []; + final curatedPeople = + people?.map((p) => CuratedContent(id: p.id, label: p.name)).toList() ?? + []; return Column( children: [ diff --git a/mobile/lib/modules/backup/models/available_album.model.dart b/mobile/lib/modules/backup/models/available_album.model.dart index 7469581bf..0b428eea0 100644 --- a/mobile/lib/modules/backup/models/available_album.model.dart +++ b/mobile/lib/modules/backup/models/available_album.model.dart @@ -5,11 +5,9 @@ import 'package:photo_manager/photo_manager.dart'; class AvailableAlbum { final AssetPathEntity albumEntity; final DateTime? lastBackup; - final Uint8List? thumbnailData; AvailableAlbum({ required this.albumEntity, this.lastBackup, - this.thumbnailData, }); AvailableAlbum copyWith({ @@ -20,7 +18,6 @@ class AvailableAlbum { return AvailableAlbum( albumEntity: albumEntity ?? this.albumEntity, lastBackup: lastBackup ?? this.lastBackup, - thumbnailData: thumbnailData ?? this.thumbnailData, ); } @@ -34,7 +31,7 @@ class AvailableAlbum { @override String toString() => - 'AvailableAlbum(albumEntity: $albumEntity, lastBackup: $lastBackup, thumbnailData: $thumbnailData)'; + 'AvailableAlbum(albumEntity: $albumEntity, lastBackup: $lastBackup)'; @override bool operator ==(Object other) { diff --git a/mobile/lib/modules/backup/providers/backup.provider.dart b/mobile/lib/modules/backup/providers/backup.provider.dart index a02ddf4e3..a2de92d6d 100644 --- a/mobile/lib/modules/backup/providers/backup.provider.dart +++ b/mobile/lib/modules/backup/providers/backup.provider.dart @@ -234,33 +234,9 @@ class BackupNotifier extends StateNotifier { for (AssetPathEntity album in albums) { AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album); - final assetCountInAlbum = await album.assetCountAsync; - if (assetCountInAlbum > 0) { - final assetList = await album.getAssetListPaged(page: 0, size: 1); + availableAlbums.add(availableAlbum); - // Even though we check assetCountInAlbum to make sure that there are assets in album - // The `getAssetListPaged` method still return empty list and cause not assets get rendered - if (assetList.isEmpty) { - continue; - } - final thumbnailAsset = assetList.first; - try { - final thumbnailData = await thumbnailAsset - .thumbnailDataWithSize(const ThumbnailSize(512, 512)); - availableAlbum = - availableAlbum.copyWith(thumbnailData: thumbnailData); - } catch (e, stack) { - log.severe( - "Failed to get thumbnail for album ${album.name}", - e, - stack, - ); - } - - availableAlbums.add(availableAlbum); - - albumMap[album.id] = album; - } + albumMap[album.id] = album; } state = state.copyWith(availableAlbums: availableAlbums); diff --git a/mobile/lib/modules/backup/ui/album_info_card.dart b/mobile/lib/modules/backup/ui/album_info_card.dart index 0008c0a9e..5380360ff 100644 --- a/mobile/lib/modules/backup/ui/album_info_card.dart +++ b/mobile/lib/modules/backup/ui/album_info_card.dart @@ -11,17 +11,16 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; class AlbumInfoCard extends HookConsumerWidget { - final Uint8List? imageData; - final AvailableAlbum albumInfo; + final AvailableAlbum album; - const AlbumInfoCard({super.key, this.imageData, required this.albumInfo}); + const AlbumInfoCard({super.key, required this.album}); @override Widget build(BuildContext context, WidgetRef ref) { final bool isSelected = - ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo); + ref.watch(backupProvider).selectedBackupAlbums.contains(album); final bool isExcluded = - ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo); + ref.watch(backupProvider).excludedBackupAlbums.contains(album); final isDarkTheme = context.isDarkTheme; ColorFilter selectedFilter = ColorFilter.mode( @@ -82,9 +81,9 @@ class AlbumInfoCard extends HookConsumerWidget { HapticFeedback.selectionClick(); if (isSelected) { - ref.read(backupProvider.notifier).removeAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).removeAlbumForBackup(album); } else { - ref.read(backupProvider.notifier).addAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).addAlbumForBackup(album); } }, onDoubleTap: () { @@ -92,13 +91,11 @@ class AlbumInfoCard extends HookConsumerWidget { if (isExcluded) { // Remove from exclude album list - ref - .read(backupProvider.notifier) - .removeExcludedAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album); } else { // Add to exclude album list - if (albumInfo.id == 'isAll' || albumInfo.name == 'Recents') { + if (album.id == 'isAll' || album.name == 'Recents') { ImmichToast.show( context: context, msg: 'Cannot exclude album contains all assets', @@ -108,9 +105,7 @@ class AlbumInfoCard extends HookConsumerWidget { return; } - ref - .read(backupProvider.notifier) - .addExcludedAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album); } }, child: Card( @@ -136,14 +131,12 @@ class AlbumInfoCard extends HookConsumerWidget { children: [ ColorFiltered( colorFilter: buildImageFilter(), - child: Image( + child: const Image( width: double.infinity, height: double.infinity, - image: imageData != null - ? MemoryImage(imageData!) - : const AssetImage( - 'assets/immich-logo.png', - ) as ImageProvider, + image: AssetImage( + 'assets/immich-logo.png', + ), fit: BoxFit.cover, ), ), @@ -168,7 +161,7 @@ class AlbumInfoCard extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - albumInfo.name, + album.name, style: TextStyle( fontSize: 14, color: context.primaryColor, @@ -182,7 +175,7 @@ class AlbumInfoCard extends HookConsumerWidget { if (snapshot.hasData) { return Text( snapshot.data.toString() + - (albumInfo.isAll + (album.isAll ? " (${'backup_all'.tr()})" : ""), style: TextStyle( @@ -193,7 +186,7 @@ class AlbumInfoCard extends HookConsumerWidget { } return const Text("0"); }), - future: albumInfo.assetCount, + future: album.assetCount, ), ), ], @@ -202,7 +195,7 @@ class AlbumInfoCard extends HookConsumerWidget { IconButton( onPressed: () { context.pushRoute( - AlbumPreviewRoute(album: albumInfo.albumEntity), + AlbumPreviewRoute(album: album.albumEntity), ); }, icon: Icon( diff --git a/mobile/lib/modules/backup/ui/album_info_list_tile.dart b/mobile/lib/modules/backup/ui/album_info_list_tile.dart index c87bec09a..dcf0923a1 100644 --- a/mobile/lib/modules/backup/ui/album_info_list_tile.dart +++ b/mobile/lib/modules/backup/ui/album_info_list_tile.dart @@ -11,47 +11,26 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; class AlbumInfoListTile extends HookConsumerWidget { - final Uint8List? imageData; - final AvailableAlbum albumInfo; + final AvailableAlbum album; - const AlbumInfoListTile({super.key, this.imageData, required this.albumInfo}); + const AlbumInfoListTile({super.key, required this.album}); @override Widget build(BuildContext context, WidgetRef ref) { final bool isSelected = - ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo); + ref.watch(backupProvider).selectedBackupAlbums.contains(album); final bool isExcluded = - ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo); - - ColorFilter selectedFilter = ColorFilter.mode( - context.primaryColor.withAlpha(100), - BlendMode.darken, - ); - ColorFilter excludedFilter = - ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken); - ColorFilter unselectedFilter = - const ColorFilter.mode(Colors.black, BlendMode.color); - + ref.watch(backupProvider).excludedBackupAlbums.contains(album); var assetCount = useState(0); useEffect( () { - albumInfo.assetCount.then((value) => assetCount.value = value); + album.assetCount.then((value) => assetCount.value = value); return null; }, - [albumInfo], + [album], ); - buildImageFilter() { - if (isSelected) { - return selectedFilter; - } else if (isExcluded) { - return excludedFilter; - } else { - return unselectedFilter; - } - } - buildTileColor() { if (isSelected) { return context.isDarkTheme @@ -66,19 +45,38 @@ class AlbumInfoListTile extends HookConsumerWidget { } } + buildIcon() { + if (isSelected) { + return const Icon( + Icons.check_circle_rounded, + color: Colors.green, + ); + } + + if (isExcluded) { + return const Icon( + Icons.remove_circle_rounded, + color: Colors.red, + ); + } + + return Icon( + Icons.circle, + color: context.isDarkTheme ? Colors.grey[400] : Colors.black45, + ); + } + return GestureDetector( onDoubleTap: () { HapticFeedback.selectionClick(); if (isExcluded) { // Remove from exclude album list - ref - .read(backupProvider.notifier) - .removeExcludedAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album); } else { // Add to exclude album list - if (albumInfo.id == 'isAll' || albumInfo.name == 'Recents') { + if (album.id == 'isAll' || album.name == 'Recents') { ImmichToast.show( context: context, msg: 'Cannot exclude album contains all assets', @@ -88,9 +86,7 @@ class AlbumInfoListTile extends HookConsumerWidget { return; } - ref - .read(backupProvider.notifier) - .addExcludedAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album); } }, child: ListTile( @@ -99,33 +95,14 @@ class AlbumInfoListTile extends HookConsumerWidget { onTap: () { HapticFeedback.selectionClick(); if (isSelected) { - ref.read(backupProvider.notifier).removeAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).removeAlbumForBackup(album); } else { - ref.read(backupProvider.notifier).addAlbumForBackup(albumInfo); + ref.read(backupProvider.notifier).addAlbumForBackup(album); } }, - leading: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: SizedBox( - height: 80, - width: 80, - child: ColorFiltered( - colorFilter: buildImageFilter(), - child: Image( - width: double.infinity, - height: double.infinity, - image: imageData != null - ? MemoryImage(imageData!) - : const AssetImage( - 'assets/immich-logo.png', - ) as ImageProvider, - fit: BoxFit.cover, - ), - ), - ), - ), + leading: buildIcon(), title: Text( - albumInfo.name, + album.name, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, @@ -135,7 +112,7 @@ class AlbumInfoListTile extends HookConsumerWidget { trailing: IconButton( onPressed: () { context.pushRoute( - AlbumPreviewRoute(album: albumInfo.albumEntity), + AlbumPreviewRoute(album: album.albumEntity), ); }, icon: Icon( diff --git a/mobile/lib/modules/backup/views/backup_album_selection_page.dart b/mobile/lib/modules/backup/views/backup_album_selection_page.dart index e892e57c1..c2bcbb15c 100644 --- a/mobile/lib/modules/backup/views/backup_album_selection_page.dart +++ b/mobile/lib/modules/backup/views/backup_album_selection_page.dart @@ -43,10 +43,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { sliver: SliverList( delegate: SliverChildBuilderDelegate( ((context, index) { - var thumbnailData = albums[index].thumbnailData; return AlbumInfoListTile( - imageData: thumbnailData, - albumInfo: albums[index], + album: albums[index], ); }), childCount: albums.length, @@ -74,10 +72,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget { ), itemCount: albums.length, itemBuilder: ((context, index) { - var thumbnailData = albums[index].thumbnailData; return AlbumInfoCard( - imageData: thumbnailData, - albumInfo: albums[index], + album: albums[index], ); }), ), diff --git a/mobile/lib/modules/backup/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart index 3b219e3f6..0e22adeb9 100644 --- a/mobile/lib/modules/backup/views/backup_controller_page.dart +++ b/mobile/lib/modules/backup/views/backup_controller_page.dart @@ -26,7 +26,7 @@ class BackupControllerPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { BackUpState backupState = ref.watch(backupProvider); final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty; - + final didGetBackupInfo = useState(false); bool hasExclusiveAccess = backupState.backupProgress != BackUpProgressEnum.inBackground; bool shouldBackup = backupState.allUniqueAssets.length - @@ -38,11 +38,6 @@ class BackupControllerPage extends HookConsumerWidget { useEffect( () { - if (backupState.backupProgress != BackUpProgressEnum.inProgress && - backupState.backupProgress != BackUpProgressEnum.manualInProgress) { - ref.watch(backupProvider.notifier).getBackupInfo(); - } - // Update the background settings information just to make sure we // have the latest, since the platform channel will not update // automatically @@ -58,6 +53,18 @@ class BackupControllerPage extends HookConsumerWidget { [], ); + useEffect( + () { + if (backupState.backupProgress == BackUpProgressEnum.idle && + !didGetBackupInfo.value) { + ref.watch(backupProvider.notifier).getBackupInfo(); + didGetBackupInfo.value = true; + } + return null; + }, + [backupState.backupProgress], + ); + Widget buildSelectedAlbumName() { var text = "backup_controller_page_backup_selected".tr(); var albums = ref.watch(backupProvider).selectedBackupAlbums; @@ -235,6 +242,15 @@ class BackupControllerPage extends HookConsumerWidget { ); } + buildLoadingIndicator() { + return const Padding( + padding: EdgeInsets.only(top: 42.0), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } + return Scaffold( appBar: AppBar( elevation: 0, @@ -297,7 +313,10 @@ class BackupControllerPage extends HookConsumerWidget { if (!hasExclusiveAccess) buildBackgroundBackupInfo(), buildBackupButton(), ] - : [buildFolderSelectionTile()], + : [ + buildFolderSelectionTile(), + if (!didGetBackupInfo.value) buildLoadingIndicator(), + ], ), ), ); diff --git a/mobile/lib/modules/home/ui/asset_grid/asset_drag_region.dart b/mobile/lib/modules/home/ui/asset_grid/asset_drag_region.dart new file mode 100644 index 000000000..cf1de0383 --- /dev/null +++ b/mobile/lib/modules/home/ui/asset_grid/asset_drag_region.dart @@ -0,0 +1,223 @@ +// ignore_for_file: library_private_types_in_public_api +// Based on https://stackoverflow.com/a/52625182 + +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +class AssetDragRegion extends StatefulWidget { + final Widget child; + + final void Function(AssetIndex valueKey)? onStart; + final void Function(AssetIndex valueKey)? onAssetEnter; + final void Function()? onEnd; + final void Function()? onScrollStart; + final void Function(ScrollDirection direction)? onScroll; + + const AssetDragRegion({ + super.key, + required this.child, + this.onStart, + this.onAssetEnter, + this.onEnd, + this.onScrollStart, + this.onScroll, + }); + @override + State createState() => _AssetDragRegionState(); +} + +class _AssetDragRegionState extends State { + late AssetIndex? assetUnderPointer; + late AssetIndex? anchorAsset; + + // Scroll related state + static const double scrollOffset = 0.10; + double? topScrollOffset; + double? bottomScrollOffset; + Timer? scrollTimer; + late bool scrollNotified; + + @override + void initState() { + super.initState(); + assetUnderPointer = null; + anchorAsset = null; + scrollNotified = false; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + topScrollOffset = null; + bottomScrollOffset = null; + } + + @override + void dispose() { + scrollTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RawGestureDetector( + gestures: { + _CustomLongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers< + _CustomLongPressGestureRecognizer>( + () => _CustomLongPressGestureRecognizer(), + _registerCallbacks, + ), + }, + child: widget.child, + ); + } + + void _registerCallbacks(_CustomLongPressGestureRecognizer recognizer) { + recognizer.onLongPressMoveUpdate = (details) => _onLongPressMove(details); + recognizer.onLongPressStart = (details) => _onLongPressStart(details); + recognizer.onLongPressUp = _onLongPressEnd; + recognizer.onLongPressCancel = _onLongPressEnd; + } + + AssetIndex? _getValueKeyAtPositon(Offset position) { + final box = context.findAncestorRenderObjectOfType(); + if (box == null) return null; + + final hitTestResult = BoxHitTestResult(); + final local = box.globalToLocal(position); + if (!box.hitTest(hitTestResult, position: local)) return null; + + return (hitTestResult.path + .firstWhereOrNull((hit) => hit.target is _AssetIndexProxy) + ?.target as _AssetIndexProxy?) + ?.index; + } + + void _onLongPressStart(LongPressStartDetails event) { + /// Calculate widget height and scroll offset when long press starting instead of in [initState] + /// or [didChangeDependencies] as the grid might still be rendering into view to get the actual size + final height = context.size?.height; + if (height != null && + (topScrollOffset == null || bottomScrollOffset == null)) { + topScrollOffset = height * scrollOffset; + bottomScrollOffset = height - topScrollOffset!; + } + + final initialHit = _getValueKeyAtPositon(event.globalPosition); + anchorAsset = initialHit; + if (initialHit == null) return; + + if (anchorAsset != null) { + widget.onStart?.call(anchorAsset!); + } + } + + void _onLongPressEnd() { + scrollNotified = false; + scrollTimer?.cancel(); + widget.onEnd?.call(); + } + + void _onLongPressMove(LongPressMoveUpdateDetails event) { + if (anchorAsset == null) return; + if (topScrollOffset == null || bottomScrollOffset == null) return; + + final currentDy = event.localPosition.dy; + + if (currentDy > bottomScrollOffset!) { + scrollTimer ??= Timer.periodic( + const Duration(milliseconds: 50), + (_) => widget.onScroll?.call(ScrollDirection.forward), + ); + } else if (currentDy < topScrollOffset!) { + scrollTimer ??= Timer.periodic( + const Duration(milliseconds: 50), + (_) => widget.onScroll?.call(ScrollDirection.reverse), + ); + } else { + scrollTimer?.cancel(); + scrollTimer = null; + } + + final currentlyTouchingAsset = _getValueKeyAtPositon(event.globalPosition); + if (currentlyTouchingAsset == null) return; + + if (assetUnderPointer != currentlyTouchingAsset) { + if (!scrollNotified) { + scrollNotified = true; + widget.onScrollStart?.call(); + } + + widget.onAssetEnter?.call(currentlyTouchingAsset); + assetUnderPointer = currentlyTouchingAsset; + } + } +} + +class _CustomLongPressGestureRecognizer extends LongPressGestureRecognizer { + @override + void rejectGesture(int pointer) { + acceptGesture(pointer); + } +} + +// ignore: prefer-single-widget-per-file +class AssetIndexWrapper extends SingleChildRenderObjectWidget { + final int rowIndex; + final int sectionIndex; + + const AssetIndexWrapper({ + required Widget super.child, + required this.rowIndex, + required this.sectionIndex, + super.key, + }); + + @override + _AssetIndexProxy createRenderObject(BuildContext context) { + return _AssetIndexProxy( + index: AssetIndex(rowIndex: rowIndex, sectionIndex: sectionIndex), + ); + } + + @override + void updateRenderObject( + BuildContext context, + _AssetIndexProxy renderObject, + ) { + renderObject.index = + AssetIndex(rowIndex: rowIndex, sectionIndex: sectionIndex); + } +} + +class _AssetIndexProxy extends RenderProxyBox { + AssetIndex index; + + _AssetIndexProxy({ + required this.index, + }); +} + +class AssetIndex { + final int rowIndex; + final int sectionIndex; + + const AssetIndex({ + required this.rowIndex, + required this.sectionIndex, + }); + + @override + bool operator ==(covariant AssetIndex other) { + if (identical(this, other)) return true; + + return other.rowIndex == rowIndex && other.sectionIndex == sectionIndex; + } + + @override + int get hashCode => rowIndex.hashCode ^ sectionIndex.hashCode; +} diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart index 8a6310816..4c520fe6f 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid_view.dart @@ -5,12 +5,15 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart'; +import 'package:immich_mobile/modules/home/ui/asset_grid/asset_drag_region.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart'; +import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; @@ -73,6 +76,8 @@ class ImmichAssetGridView extends StatefulWidget { class ImmichAssetGridViewState extends State { final ItemScrollController _itemScrollController = ItemScrollController(); + final ScrollOffsetController _scrollOffsetController = + ScrollOffsetController(); final ItemPositionsListener _itemPositionsListener = ItemPositionsListener.create(); @@ -83,6 +88,12 @@ class ImmichAssetGridViewState extends State { final Set _selectedAssets = LinkedHashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id); + bool _dragging = false; + int? _dragAnchorAssetIndex; + int? _dragAnchorSectionIndex; + final Set _draggedAssets = + HashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id); + Set _getSelectedAssets() { return Set.from(_selectedAssets); } @@ -93,20 +104,26 @@ class ImmichAssetGridViewState extends State { void _selectAssets(List assets) { setState(() { + if (_dragging) { + _draggedAssets.addAll(assets); + } _selectedAssets.addAll(assets); _callSelectionListener(true); }); } void _deselectAssets(List assets) { + final assetsToDeselect = assets.where( + (a) => + widget.canDeselect || + !(widget.preselectedAssets?.contains(a) ?? false), + ); + setState(() { - _selectedAssets.removeAll( - assets.where( - (a) => - widget.canDeselect || - !(widget.preselectedAssets?.contains(a) ?? false), - ), - ); + _selectedAssets.removeAll(assetsToDeselect); + if (_dragging) { + _draggedAssets.removeAll(assetsToDeselect); + } _callSelectionListener(_selectedAssets.isNotEmpty); }); } @@ -114,6 +131,10 @@ class ImmichAssetGridViewState extends State { void _deselectAll() { setState(() { _selectedAssets.clear(); + _dragAnchorAssetIndex = null; + _dragAnchorSectionIndex = null; + _draggedAssets.clear(); + _dragging = false; if (!widget.canDeselect && widget.preselectedAssets != null && widget.preselectedAssets!.isNotEmpty) { @@ -142,6 +163,7 @@ class ImmichAssetGridViewState extends State { showStorageIndicator: widget.showStorageIndicator, selectedAssets: _selectedAssets, selectionActive: widget.selectionActive, + sectionIndex: index, section: section, margin: widget.margin, renderList: widget.renderList, @@ -199,6 +221,7 @@ class ImmichAssetGridViewState extends State { itemBuilder: _itemBuilder, itemPositionsListener: _itemPositionsListener, itemScrollController: _itemScrollController, + scrollOffsetController: _scrollOffsetController, itemCount: widget.renderList.elements.length + (widget.topWidget != null ? 1 : 0), addRepaintBoundaries: true, @@ -253,6 +276,7 @@ class ImmichAssetGridViewState extends State { if (widget.visibleItemsListener != null) { _itemPositionsListener.itemPositions.removeListener(_positionListener); } + _itemPositionsListener.itemPositions.removeListener(_hapticsListener); super.dispose(); } @@ -308,6 +332,107 @@ class ImmichAssetGridViewState extends State { ); } + void _setDragStartIndex(AssetIndex index) { + setState(() { + _dragAnchorAssetIndex = index.rowIndex; + _dragAnchorSectionIndex = index.sectionIndex; + _dragging = true; + }); + } + + void _stopDrag() { + setState(() { + _dragging = false; + _draggedAssets.clear(); + }); + } + + void _dragDragScroll(ScrollDirection direction) { + _scrollOffsetController.animateScroll( + offset: direction == ScrollDirection.forward ? 175 : -175, + duration: const Duration(milliseconds: 125), + ); + } + + void _handleDragAssetEnter(AssetIndex index) { + if (_dragAnchorSectionIndex == null || _dragAnchorAssetIndex == null) { + return; + } + + final dragAnchorSectionIndex = _dragAnchorSectionIndex!; + final dragAnchorAssetIndex = _dragAnchorAssetIndex!; + + late final int startSectionIndex; + late final int startSectionAssetIndex; + late final int endSectionIndex; + late final int endSectionAssetIndex; + + if (index.sectionIndex < dragAnchorSectionIndex) { + startSectionIndex = index.sectionIndex; + startSectionAssetIndex = index.rowIndex; + endSectionIndex = dragAnchorSectionIndex; + endSectionAssetIndex = dragAnchorAssetIndex; + } else if (index.sectionIndex > dragAnchorSectionIndex) { + startSectionIndex = dragAnchorSectionIndex; + startSectionAssetIndex = dragAnchorAssetIndex; + endSectionIndex = index.sectionIndex; + endSectionAssetIndex = index.rowIndex; + } else { + startSectionIndex = dragAnchorSectionIndex; + endSectionIndex = dragAnchorSectionIndex; + + // If same section, assign proper start / end asset Index + if (dragAnchorAssetIndex < index.rowIndex) { + startSectionAssetIndex = dragAnchorAssetIndex; + endSectionAssetIndex = index.rowIndex; + } else { + startSectionAssetIndex = index.rowIndex; + endSectionAssetIndex = dragAnchorAssetIndex; + } + } + + final selectedAssets = {}; + var currentSectionIndex = startSectionIndex; + while (currentSectionIndex < endSectionIndex) { + final section = + widget.renderList.elements.elementAtOrNull(currentSectionIndex); + if (section == null) continue; + + final sectionAssets = + widget.renderList.loadAssets(section.offset, section.count); + + if (currentSectionIndex == startSectionIndex) { + selectedAssets.addAll( + sectionAssets.slice(startSectionAssetIndex, sectionAssets.length), + ); + } else { + selectedAssets.addAll(sectionAssets); + } + + currentSectionIndex += 1; + } + + final section = widget.renderList.elements.elementAtOrNull(endSectionIndex); + if (section != null) { + final sectionAssets = + widget.renderList.loadAssets(section.offset, section.count); + if (startSectionIndex == endSectionIndex) { + selectedAssets.addAll( + sectionAssets.slice(startSectionAssetIndex, endSectionAssetIndex + 1), + ); + } else { + selectedAssets.addAll( + sectionAssets.slice(0, endSectionAssetIndex + 1), + ); + } + } + + _deselectAssets(_draggedAssets.toList()); + _draggedAssets.clear(); + _draggedAssets.addAll(selectedAssets); + _selectAssets(_draggedAssets.toList()); + } + @override Widget build(BuildContext context) { return PopScope( @@ -315,7 +440,16 @@ class ImmichAssetGridViewState extends State { onPopInvoked: (didPop) => !didPop ? _deselectAll() : null, child: Stack( children: [ - _buildAssetGrid(), + AssetDragRegion( + onStart: _setDragStartIndex, + onAssetEnter: _handleDragAssetEnter, + onEnd: _stopDrag, + onScroll: _dragDragScroll, + onScrollStart: () => WidgetsBinding.instance.addPostFrameCallback( + (_) => controlBottomAppBarNotifier.minimize(), + ), + child: _buildAssetGrid(), + ), if (widget.showMultiSelectIndicator && widget.selectionActive) _buildMultiSelectIndicator(), ], @@ -361,6 +495,7 @@ class _PlaceholderRow extends StatelessWidget { /// A section for the render grid class _Section extends StatelessWidget { final RenderAssetGridElement section; + final int sectionIndex; final Set selectedAssets; final bool scrolling; final double margin; @@ -377,6 +512,7 @@ class _Section extends StatelessWidget { const _Section({ required this.section, + required this.sectionIndex, required this.scrolling, required this.margin, required this.assetsPerRow, @@ -435,6 +571,8 @@ class _Section extends StatelessWidget { ) : _AssetRow( key: ValueKey(i), + rowStartIndex: i * assetsPerRow, + sectionIndex: sectionIndex, assets: assetsToRender.nestedSlice( i * assetsPerRow, min((i + 1) * assetsPerRow, section.count), @@ -522,6 +660,8 @@ class _Title extends StatelessWidget { /// The row of assets class _AssetRow extends StatelessWidget { final List assets; + final int rowStartIndex; + final int sectionIndex; final Set selectedAssets; final int absoluteOffset; final double width; @@ -539,6 +679,8 @@ class _AssetRow extends StatelessWidget { const _AssetRow({ super.key, + required this.rowStartIndex, + required this.sectionIndex, required this.assets, required this.absoluteOffset, required this.width, @@ -594,18 +736,22 @@ class _AssetRow extends StatelessWidget { bottom: margin, right: last ? 0.0 : margin, ), - child: ThumbnailImage( - asset: asset, - index: absoluteOffset + index, - loadAsset: renderList.loadAsset, - totalAssets: renderList.totalAssets, - multiselectEnabled: selectionActive, - isSelected: isSelectionActive && selectedAssets.contains(asset), - onSelect: () => onSelect?.call(asset), - onDeselect: () => onDeselect?.call(asset), - showStorageIndicator: showStorageIndicator, - heroOffset: heroOffset, - showStack: showStack, + child: AssetIndexWrapper( + rowIndex: rowStartIndex + index, + sectionIndex: sectionIndex, + child: ThumbnailImage( + asset: asset, + index: absoluteOffset + index, + loadAsset: renderList.loadAsset, + totalAssets: renderList.totalAssets, + multiselectEnabled: selectionActive, + isSelected: isSelectionActive && selectedAssets.contains(asset), + onSelect: () => onSelect?.call(asset), + onDeselect: () => onDeselect?.call(asset), + showStorageIndicator: showStorageIndicator, + heroOffset: heroOffset, + showStack: showStack, + ), ), ); }).toList(), diff --git a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart index 2a2843089..23bb2ed61 100644 --- a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart +++ b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart @@ -1,5 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/album.provider.dart'; @@ -11,8 +12,17 @@ import 'package:immich_mobile/modules/home/ui/upload_dialog.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart'; import 'package:immich_mobile/shared/ui/drag_sheet.dart'; import 'package:immich_mobile/shared/models/album.dart'; +import 'package:immich_mobile/utils/draggable_scroll_controller.dart'; -class ControlBottomAppBar extends ConsumerWidget { +final controlBottomAppBarNotifier = ControlBottomAppBarNotifier(); + +class ControlBottomAppBarNotifier with ChangeNotifier { + void minimize() { + notifyListeners(); + } +} + +class ControlBottomAppBar extends HookConsumerWidget { final void Function(bool shareLocal) onShare; final void Function()? onFavorite; final void Function()? onArchive; @@ -64,6 +74,25 @@ class ControlBottomAppBar extends ConsumerWidget { final albums = ref.watch(albumProvider).where((a) => a.isRemote).toList(); final sharedAlbums = ref.watch(sharedAlbumProvider); const bottomPadding = 0.20; + final scrollController = useDraggableScrollController(); + + void minimize() { + scrollController.animateTo( + bottomPadding, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + + useEffect( + () { + controlBottomAppBarNotifier.addListener(minimize); + return () { + controlBottomAppBarNotifier.removeListener(minimize); + }; + }, + [], + ); void showForceDeleteDialog( Function(bool) deleteCb, { @@ -242,6 +271,7 @@ class ControlBottomAppBar extends ConsumerWidget { } return DraggableScrollableSheet( + controller: scrollController, initialChildSize: hasRemote ? 0.35 : bottomPadding, minChildSize: bottomPadding, maxChildSize: hasRemote ? 0.65 : bottomPadding, diff --git a/mobile/lib/modules/search/ui/curated_people_row.dart b/mobile/lib/modules/search/ui/curated_people_row.dart index 78dc1af4f..049c1ce46 100644 --- a/mobile/lib/modules/search/ui/curated_people_row.dart +++ b/mobile/lib/modules/search/ui/curated_people_row.dart @@ -23,7 +23,7 @@ class CuratedPeopleRow extends StatelessWidget { @override Widget build(BuildContext context) { - const imageSize = 85.0; + const imageSize = 80.0; // Guard empty [content] if (content.isEmpty) { diff --git a/mobile/lib/shared/providers/immich_logo_provider.dart b/mobile/lib/shared/providers/immich_logo_provider.dart new file mode 100644 index 000000000..c5c65fcfe --- /dev/null +++ b/mobile/lib/shared/providers/immich_logo_provider.dart @@ -0,0 +1,13 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'immich_logo_provider.g.dart'; + +@riverpod +Future immichLogo(ImmichLogoRef ref) async { + final json = await rootBundle.loadString('assets/immich-logo.json'); + final j = jsonDecode(json); + return base64Decode(j['content']); +} diff --git a/mobile/lib/shared/providers/immich_logo_provider.g.dart b/mobile/lib/shared/providers/immich_logo_provider.g.dart new file mode 100644 index 000000000..1a95814e3 --- /dev/null +++ b/mobile/lib/shared/providers/immich_logo_provider.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'immich_logo_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$immichLogoHash() => r'040cc44fae3339e0f40a091fb3b2f2abe9f83acd'; + +/// See also [immichLogo]. +@ProviderFor(immichLogo) +final immichLogoProvider = AutoDisposeFutureProvider.internal( + immichLogo, + name: r'immichLogoProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$immichLogoHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef ImmichLogoRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/mobile/lib/shared/ui/immich_app_bar.dart b/mobile/lib/shared/ui/immich_app_bar.dart index 84f070863..5b26432d8 100644 --- a/mobile/lib/shared/ui/immich_app_bar.dart +++ b/mobile/lib/shared/ui/immich_app_bar.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/shared/models/store.dart'; +import 'package:immich_mobile/shared/providers/immich_logo_provider.dart'; import 'package:immich_mobile/shared/ui/app_bar_dialog/app_bar_dialog.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; @@ -26,6 +27,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup; final ServerInfo serverInfoState = ref.watch(serverInfoProvider); + final immichLogo = ref.watch(immichLogoProvider); final user = Store.tryGet(StoreKey.currentUser); final isDarkTheme = context.isDarkTheme; const widgetSize = 30.0; @@ -152,14 +154,29 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { builder: (BuildContext context) { return Row( children: [ - Container( - padding: const EdgeInsets.only(top: 3), - height: 30, - child: Image.asset( - context.isDarkTheme - ? 'assets/immich-logo-inline-dark.png' - : 'assets/immich-logo-inline-light.png', - ), + Builder( + builder: (context) { + final today = DateTime.now(); + if (today.month == 4 && today.day == 1) { + if (immichLogo.value == null) { + return const SizedBox.shrink(); + } + return Image.memory( + immichLogo.value!, + fit: BoxFit.cover, + height: 80, + ); + } + return Padding( + padding: const EdgeInsets.only(top: 3.0), + child: Image.asset( + height: 30, + context.isDarkTheme + ? 'assets/immich-logo-inline-dark.png' + : 'assets/immich-logo-inline-light.png', + ), + ); + }, ), ], ); diff --git a/web/src/lib/assets/immich-logo.json b/web/src/lib/assets/immich-logo.json new file mode 100644 index 000000000..a83bd4660 --- /dev/null +++ b/web/src/lib/assets/immich-logo.json @@ -0,0 +1 @@ +{"content": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAFACAYAAAAszc0KAAAAxnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbEoMwCPzPKXoEXonkOLHqTG/Q4xeEzKjTdbKLICuh7N/PUV4OQilSF229NTBIl07DAoXAOBlBTj5hJcrsLV9khmTKppwNLRRnPhum4rCoXoz0nYX1XugSSvowinmAfSKPtzTqacQUBUyDEdeC1nW5XmHd4Q6NU5zWw+bxHWXz810W295W7T9MtDMyGDO3GID9SOFhARsjd/vQn5GMrDmJLeTfnibKD6lPWpnDBF3gAAABhGlDQ1BJQ0MgcHJvZmlsZQAAeJx9kT1Iw0AcxV9bpSJVQYuIOGSoTnZRUcdahSJUCLVCqw4ml35Bk4YkxcVRcC04+LFYdXBx1tXBVRAEP0CcHZwUXaTE/yWFFjEeHPfj3b3H3TvAXy8z1eyIAapmGalEXMhkV4XgKwLoxyBm0CsxU58TxSQ8x9c9fHy9i/Is73N/jh4lZzLAJxDHmG5YxBvE05uWznmfOMyKkkJ8Tjxu0AWJH7kuu/zGueCwn2eGjXRqnjhMLBTaWG5jVjRU4iniiKJqlO/PuKxw3uKslquseU/+wlBOW1nmOs0RJLCIJYgQIKOKEsqwEKVVI8VEivbjHv5hxy+SSyZXCYwcC6hAheT4wf/gd7dmfnLCTQrFgc4X2/4YBYK7QKNm29/Htt04AQLPwJXW8lfqwOwn6bWWFjkC+raBi+uWJu8BlzvA0JMuGZIjBWj683ng/Yy+KQsM3ALda25vzX2cPgBp6ip5AxwcAmMFyl73eHdXe2//nmn29wO1pXLBEGyzRgAADXppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDQuNC4wLUV4aXYyIj4KIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJnaW1wOmRvY2lkOmdpbXA6MDY0YmE0N2YtMDU3MC00OTBmLWJkOGUtY2UzNDFhYjk0ZGY1IgogICB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjdmYzk2NDZlLTE5NWQtNGE1Mi1iYTE3LWM0NDM4NmQyMWU1ZiIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjM0MGQxNWRmLTgzZDMtNDQ1Yi1hMzkwLTBjNTQ1NGNiMWI3ZCIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTWFjIE9TIgogICBHSU1QOlRpbWVTdGFtcD0iMTcxMDgwMjgyMTQ5NjYwMyIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjM2IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDM6MTlUMDA6MDA6MTgrMDE6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjAzOjE5VDAwOjAwOjE4KzAxOjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MTEwZjhhOTQtNTQ4MS00MTkxLTkxYjktZjQ3NzA3ZDUyYTVlIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKE1hYyBPUykiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDMtMTlUMDA6MDA6MjErMDE6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+fd4ZrQAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAAd0SU1FB+gDEhcAFf/Hh5UAACAASURBVHja7d15nBT1nTfwT3fPTPfcMMoMwqgzcnvwgERAnnXETTYZnyCR4OYBEhLdx30E8yhiEkUTA2zceCUq+CyQXfcRY4Q8CTgq7oKRuGj2EUhUDrkPRTkEBplh7p6rnj9qqqeq+ldXd3V3dffn/XrNS+mjuqq6uuvT3/odPkmSJBARERFR1vBzFxARERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASERERMQASEREREQMgERERETEAEhEREREDIBERERGlixzuAnKTz+dzdXmSJHGnEhERMQBSuoe6RL02wyIREREDIKVJyEvGNjAcEhERMQAy7HGbGQqJiIgBkBj4uF8YComIiAGQGPgcSWR4StY2qV+HYZCIiDI2K0g8yzHwpTjcpct+4EeFiIgYACnjwk6mHgrcV0RERAyADDIMMNyXRETEAMjdkNlBhW8x9zUREREDYBaEEb6t3P9EREQMgBkeOvg28r0hIiJiAMyCcMG3ju8XERERA2AWBAm+XQyDREREDIBZEBr4FjHQExERMQAy+Al1bXkXrQ8vRu/xEwnbBv+llchfeA+Cf/tNvqEMgkRExABIqQh9ao3XTkFvfX3itykYxMAje/jmeuz9JyIisotzAafJiT8Z1T27gt9i9c/p+2r3eFAexyBIREQJzSmsAKY2/Nnd/U6re2XHDzte7/anl6P9medcXy4l7ocBERERA2AGBj8vVf6c8l9aidy/moKu/3zPcv2zrW1hIo4VIiIiBsAMOZk3TrkpKjzpq3DJavuX8P2YhW0LGQSJiIgBMINP4HZ3c/j3r6D9mefcq/j5fIDdt9jngyRJ8CXjtSyWkY29je0cS/y4EhERA2AGBT/AXhs8K2XHD8ddEcxfeA/Cv/mta1XF3Kk1KHp+BXzBoOZ2q/XM1t7GDIJERMQAmEHBz7XqnhuVtiS9pg+A1bOkvsfphebORsHP/4HHGYMgERG5zM9d4O5JWZIk4Um5/enlaL3/QVvhL3dqDQYe2YOy44cjf/5Bg9QvYnud1cvKX3hP7Buve01luVbLtLOmor1advxw1oY/s+Mo1qBIRESkOX+wAuhO8FNO2mqu9eBNRcXPzjoJgqGdil/Ur5BLKxH6u++hbek/Gm5zts9AwsvCRETEAJgmJ15RD1435Ez8EqSuTvTs2O3qcvMX3oOOFf8MKRx2ZXkD936AjudXo+NfX4TU1BR1f+7U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QGJIJCIiIo/ycxcQERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgERERETEAEhEREREDIBERERExABIRERERAyARERERMQASEREREQMgEREREQMgERERETEAEhEREREDIBERERExABIRERFRuvn/XcNQ24xp80UAAAAASUVORK5CYII="} diff --git a/web/src/lib/components/asset-viewer/panorama-viewer.svelte b/web/src/lib/components/asset-viewer/panorama-viewer.svelte index fa8740d20..66d8f6309 100644 --- a/web/src/lib/components/asset-viewer/panorama-viewer.svelte +++ b/web/src/lib/components/asset-viewer/panorama-viewer.svelte @@ -2,11 +2,11 @@ import { serveFile, type AssetResponseDto } from '@immich/sdk'; import { fade } from 'svelte/transition'; import LoadingSpinner from '../shared-components/loading-spinner.svelte'; - + import { getKey } from '$lib/utils'; export let asset: AssetResponseDto; const loadAssetData = async () => { - const data = await serveFile({ id: asset.id, isWeb: false, isThumb: false }); + const data = await serveFile({ id: asset.id, isWeb: false, isThumb: false, key: getKey() }); return URL.createObjectURL(data); }; diff --git a/web/src/lib/components/elements/buttons/skip-link.svelte b/web/src/lib/components/elements/buttons/skip-link.svelte index 457661d65..8a304469d 100644 --- a/web/src/lib/components/elements/buttons/skip-link.svelte +++ b/web/src/lib/components/elements/buttons/skip-link.svelte @@ -14,7 +14,7 @@ }; -
+
- - +
diff --git a/web/src/lib/components/shared-components/combobox.svelte b/web/src/lib/components/shared-components/combobox.svelte index c69460640..19fd73d25 100644 --- a/web/src/lib/components/shared-components/combobox.svelte +++ b/web/src/lib/components/shared-components/combobox.svelte @@ -11,48 +11,93 @@ -
+ +
{ + if (e.relatedTarget instanceof Node && !e.currentTarget.contains(e.relatedTarget)) { + deactivate(); + } + }} +>
- {#if isOpen} + {#if isActive}
- +
{/if} (searchQuery = e.currentTarget.value)} - on:focus={handleClick} - on:blur={() => (inputFocused = false)} + class:cursor-pointer={!isActive} + class="immich-form-input text-sm text-left w-full !pr-12 transition-all" + id={inputId} + on:click={activate} + on:focus={activate} + on:input={onInput} + role="combobox" + type="text" + value={searchQuery} + use:shortcuts={[ + { + shortcut: { key: 'ArrowUp' }, + onShortcut: () => { + openDropdown(); + void incrementSelectedIndex(-1); + }, + }, + { + shortcut: { key: 'ArrowDown' }, + onShortcut: () => { + openDropdown(); + void incrementSelectedIndex(1); + }, + }, + { + shortcut: { key: 'ArrowDown', alt: true }, + onShortcut: () => { + openDropdown(); + }, + }, + { + shortcut: { key: 'Enter' }, + onShortcut: () => { + if (selectedIndex !== undefined && filteredOptions.length > 0) { + onSelect(filteredOptions[selectedIndex]); + } + closeDropdown(); + }, + }, + { + shortcut: { key: 'Escape' }, + onShortcut: () => { + closeDropdown(); + }, + }, + ]} />
{#if selectedOption} - + {:else if !isOpen} - + {/if}
- {#if isOpen} -
+
    + {#if isOpen} {#if filteredOptions.length === 0} -
    No results
    - {/if} - {#each filteredOptions as option (option.label)} - {@const selected = option.label === selectedOption?.label} - + {/each} -
- {/if} + {/if} +
diff --git a/web/src/lib/components/shared-components/immich-logo.svelte b/web/src/lib/components/shared-components/immich-logo.svelte index ad747150a..fbfc5f845 100644 --- a/web/src/lib/components/shared-components/immich-logo.svelte +++ b/web/src/lib/components/shared-components/immich-logo.svelte @@ -2,8 +2,10 @@ import logoDarkUrl from '$lib/assets/immich-logo-inline-dark.svg'; import logoLightUrl from '$lib/assets/immich-logo-inline-light.svg'; import logoNoText from '$lib/assets/immich-logo.svg'; + import { content as alternativeLogo } from '$lib/assets/immich-logo.json'; import { Theme } from '$lib/constants'; import { colorTheme } from '$lib/stores/preferences.store'; + import { DateTime } from 'luxon'; import type { HTMLImgAttributes } from 'svelte/elements'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -14,11 +16,17 @@ export let noText = false; export let draggable = false; + + const today = DateTime.now().toLocal(); -Immich Logo +{#if today.month === 4 && today.day === 1} + Immich Logo +{:else} + Immich Logo +{/if} diff --git a/web/src/lib/components/shared-components/search-bar/search-bar.svelte b/web/src/lib/components/shared-components/search-bar/search-bar.svelte index 041181f71..8afa56df7 100644 --- a/web/src/lib/components/shared-components/search-bar/search-bar.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-bar.svelte @@ -11,6 +11,7 @@ import type { MetadataSearchDto, SmartSearchDto } from '@immich/sdk'; import { getMetadataSearchQuery } from '$lib/utils/metadata-search'; import { handlePromiseError } from '$lib/utils'; + import { shortcut } from '$lib/utils/shortcut'; export let value = ''; export let grayTheme: boolean; @@ -84,7 +85,16 @@ }; -
+ { + onFocusOut(); + }, + }} +/> + +
{ + onFocusOut(); + }, + }} />
diff --git a/web/src/lib/components/shared-components/search-bar/search-camera-section.svelte b/web/src/lib/components/shared-components/search-bar/search-camera-section.svelte index 6f377faff..7acac54d8 100644 --- a/web/src/lib/components/shared-components/search-bar/search-camera-section.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-camera-section.svelte @@ -40,24 +40,24 @@
- (filters.make = detail?.value)} + options={toComboBoxOptions(makes)} placeholder="Search camera make..." + selectedOption={makeFilter ? { label: makeFilter, value: makeFilter } : undefined} />
- (filters.model = detail?.value)} + options={toComboBoxOptions(models)} placeholder="Search camera model..." + selectedOption={modelFilter ? { label: modelFilter, value: modelFilter } : undefined} />
diff --git a/web/src/lib/components/shared-components/search-bar/search-location-section.svelte b/web/src/lib/components/shared-components/search-bar/search-location-section.svelte index ac412c513..fdaabe0f7 100644 --- a/web/src/lib/components/shared-components/search-bar/search-location-section.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-location-section.svelte @@ -62,35 +62,35 @@
- (filters.country = detail?.value)} + options={toComboBoxOptions(countries)} placeholder="Search country..." + selectedOption={filters.country ? { label: filters.country, value: filters.country } : undefined} />
- (filters.state = detail?.value)} + options={toComboBoxOptions(states)} placeholder="Search state..." + selectedOption={filters.state ? { label: filters.state, value: filters.state } : undefined} />
- (filters.city = detail?.value)} + options={toComboBoxOptions(cities)} placeholder="Search city..." + selectedOption={filters.city ? { label: filters.city, value: filters.city } : undefined} />
diff --git a/web/src/lib/components/shared-components/settings/setting-combobox.svelte b/web/src/lib/components/shared-components/settings/setting-combobox.svelte index 7f3dc1906..ee396935c 100644 --- a/web/src/lib/components/shared-components/settings/setting-combobox.svelte +++ b/web/src/lib/components/shared-components/settings/setting-combobox.svelte @@ -3,6 +3,7 @@ import { fly } from 'svelte/transition'; import Combobox, { type ComboBoxOption } from '$lib/components/shared-components/combobox.svelte'; + export let id: string; export let title: string; export let comboboxPlaceholder: string; export let subtitle = ''; @@ -32,6 +33,9 @@