resolve merge conflict
|
|
@ -39,7 +39,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: "3.3.10"
|
flutter-version: "3.7.3"
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- name: Create the Keystore
|
- name: Create the Keystore
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
flutter-version: '3.3.10'
|
flutter-version: '3.7.3'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dart pub get
|
run: dart pub get
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
flutter-version: '3.3.10'
|
flutter-version: '3.7.3'
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: flutter test
|
run: flutter test
|
||||||
|
|
@ -78,7 +78,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
flutter-version: '3.3.10'
|
flutter-version: '3.7.3'
|
||||||
- name: Run integration tests
|
- name: Run integration tests
|
||||||
uses: reactivecircus/android-emulator-runner@v2.27.0
|
uses: reactivecircus/android-emulator-runner@v2.27.0
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,11 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
||||||
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
|
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
|
android:value="false" />
|
||||||
|
|
||||||
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop"
|
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ void main() async {
|
||||||
await ImmichTestHelper.initialize();
|
await ImmichTestHelper.initialize();
|
||||||
|
|
||||||
group("Login input validation test", () {
|
group("Login input validation test", () {
|
||||||
immichWidgetTest("Test leading/trailing whitespace", (tester, helper) async {
|
immichWidgetTest("Test leading/trailing whitespace",
|
||||||
|
(tester, helper) async {
|
||||||
await helper.loginHelper.waitForLoginScreen();
|
await helper.loginHelper.waitForLoginScreen();
|
||||||
await helper.loginHelper.acknowledgeNewServerVersion();
|
await helper.loginHelper.acknowledgeNewServerVersion();
|
||||||
|
|
||||||
|
|
@ -17,15 +18,21 @@ void main() async {
|
||||||
|
|
||||||
await tester.pump(const Duration(milliseconds: 300));
|
await tester.pump(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
expect(find.text("login_form_err_leading_whitespace".tr()), findsOneWidget);
|
expect(
|
||||||
|
find.text("login_form_err_leading_whitespace".tr()),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
|
||||||
await helper.loginHelper.enterCredentials(
|
await helper.loginHelper.enterCredentials(
|
||||||
email: "demo@immich.app ",
|
email: "demo@immich.app ",
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pump(const Duration(milliseconds: 300));
|
await tester.pump(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
expect(find.text("login_form_err_trailing_whitespace".tr()), findsOneWidget);
|
expect(
|
||||||
|
find.text("login_form_err_trailing_whitespace".tr()),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
immichWidgetTest("Test invalid email", (tester, helper) async {
|
immichWidgetTest("Test invalid email", (tester, helper) async {
|
||||||
|
|
@ -33,13 +40,12 @@ void main() async {
|
||||||
await helper.loginHelper.acknowledgeNewServerVersion();
|
await helper.loginHelper.acknowledgeNewServerVersion();
|
||||||
|
|
||||||
await helper.loginHelper.enterCredentials(
|
await helper.loginHelper.enterCredentials(
|
||||||
email: "demo.immich.app",
|
email: "demo.immich.app",
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pump(const Duration(milliseconds: 300));
|
await tester.pump(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
expect(find.text("login_form_err_invalid_email".tr()), findsOneWidget);
|
expect(find.text("login_form_err_invalid_email".tr()), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
platform :ios, '11.0'
|
# platform :ios, '11.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
@ -34,19 +34,8 @@ target 'Runner' do
|
||||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
end
|
end
|
||||||
|
|
||||||
# post_install do |installer|
|
post_install do |installer|
|
||||||
# installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
# flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
# end
|
end
|
||||||
# end
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
|
||||||
installer.pods_project.targets.each do |target|
|
|
||||||
flutter_additional_ios_build_settings(target)
|
|
||||||
target.build_configurations.each do |config|
|
|
||||||
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
|
||||||
config.build_settings['ENABLE_BITCODE'] = 'YES'
|
|
||||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -21,16 +21,18 @@ PODS:
|
||||||
- Flutter
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_ios (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- photo_manager (2.0.0):
|
- photo_manager (2.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- SAMKeychain (1.5.3)
|
- SAMKeychain (1.5.3)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_ios (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- sqflite (0.0.2):
|
- sqflite (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
|
|
@ -52,10 +54,10 @@ DEPENDENCIES:
|
||||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
||||||
|
|
@ -86,14 +88,14 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_ios:
|
path_provider_foundation:
|
||||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||||
photo_manager:
|
photo_manager:
|
||||||
:path: ".symlinks/plugins/photo_manager/ios"
|
:path: ".symlinks/plugins/photo_manager/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_ios:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
|
|
@ -108,23 +110,23 @@ SPEC CHECKSUMS:
|
||||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||||
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
|
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
|
||||||
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
|
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
|
||||||
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
|
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
|
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
|
||||||
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
||||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
|
||||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||||
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
|
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
|
||||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||||
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
|
||||||
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
|
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
|
||||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||||
|
|
||||||
PODFILE CHECKSUM: 05c3056158482c567a3e0cdab1351ceeee238a07
|
PODFILE CHECKSUM: c798208781ca5116c4a3d5927d689946791f0189
|
||||||
|
|
||||||
COCOAPODS: 1.11.3
|
COCOAPODS: 1.11.3
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 51;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
|
@ -201,6 +201,7 @@
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
|
|
@ -237,6 +238,7 @@
|
||||||
};
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
|
|
@ -341,7 +343,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
NEW_SETTING = "";
|
NEW_SETTING = "";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|
@ -360,7 +362,8 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 85;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
@ -425,7 +428,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
NEW_SETTING = "";
|
NEW_SETTING = "";
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
|
@ -475,7 +478,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
NEW_SETTING = "";
|
NEW_SETTING = "";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|
@ -495,7 +498,8 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 85;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
@ -522,7 +526,8 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 85;
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,101 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Immich</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>immich_mobile</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>Immich</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>immich_mobile</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.46.0</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>85</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
|
||||||
<true/>
|
<true/>
|
||||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>NSLocationAlwaysUsageDescription</key>
|
|
||||||
<string>Enable location setting to show position of assets on map</string>
|
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
|
||||||
<string>Enable location setting to show position of assets on map</string>
|
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
|
||||||
<string>We need to manage backup your photos album</string>
|
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
|
||||||
<string>We need to manage backup your photos album</string>
|
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>We need to access the camera to let you take beautiful video using this app</string>
|
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
|
||||||
<string>We need to access the microphone to let you take beautiful video using this app</string>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIMainStoryboardFile</key>
|
|
||||||
<string>Main</string>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
||||||
<true/>
|
|
||||||
<key>io.flutter.embedded_views_preview</key>
|
|
||||||
<true/>
|
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
|
||||||
<false/>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>https</string>
|
|
||||||
</array>
|
|
||||||
<key>CFBundleLocalizations</key>
|
|
||||||
<array>
|
|
||||||
<string>cs</string>
|
|
||||||
<string>da</string>
|
|
||||||
<string>de</string>
|
|
||||||
<string>en</string>
|
|
||||||
<string>es</string>
|
|
||||||
<string>fi</string>
|
|
||||||
<string>fr</string>
|
|
||||||
<string>it</string>
|
|
||||||
<string>ja</string>
|
|
||||||
<string>ko</string>
|
|
||||||
<string>nl</string>
|
|
||||||
<string>pl</string>
|
|
||||||
<string>pt</string>
|
|
||||||
<string>ru</string>
|
|
||||||
<string>sk</string>
|
|
||||||
<string>zh</string>
|
|
||||||
</array>
|
|
||||||
<key>UIStatusBarHidden</key>
|
|
||||||
<false/>
|
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>NSLocationAlwaysUsageDescription</key>
|
||||||
|
<string>Enable location setting to show position of assets on map</string>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string>Enable location setting to show position of assets on map</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>We need to manage backup your photos album</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>We need to manage backup your photos album</string>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>We need to access the camera to let you take beautiful video using this app</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>We need to access the microphone to let you take beautiful video using this app</string>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<true/>
|
||||||
|
<key>io.flutter.embedded_views_preview</key>
|
||||||
|
<true/>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>https</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleLocalizations</key>
|
||||||
|
<array>
|
||||||
|
<string>cs</string>
|
||||||
|
<string>da</string>
|
||||||
|
<string>de</string>
|
||||||
|
<string>en</string>
|
||||||
|
<string>es</string>
|
||||||
|
<string>fi</string>
|
||||||
|
<string>fr</string>
|
||||||
|
<string>it</string>
|
||||||
|
<string>ja</string>
|
||||||
|
<string>ko</string>
|
||||||
|
<string>nl</string>
|
||||||
|
<string>pl</string>
|
||||||
|
<string>pt</string>
|
||||||
|
<string>ru</string>
|
||||||
|
<string>sk</string>
|
||||||
|
<string>zh</string>
|
||||||
|
</array>
|
||||||
|
<key>UIStatusBarHidden</key>
|
||||||
|
<false/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>FLTEnableImpeller</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Add to album',
|
'Add to album',
|
||||||
style: Theme.of(context).textTheme.headline2,
|
style: Theme.of(context).textTheme.displayMedium,
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
|
|
|
||||||
|
|
@ -24,90 +24,97 @@ class AlbumThumbnailCard extends StatelessWidget {
|
||||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
var cardSize = constraints.maxWidth;
|
var cardSize = constraints.maxWidth;
|
||||||
|
|
||||||
buildEmptyThumbnail() {
|
buildEmptyThumbnail() {
|
||||||
return Container(
|
return Container(
|
||||||
height: cardSize,
|
height: cardSize,
|
||||||
width: cardSize,
|
width: cardSize,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
|
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Icon(
|
|
||||||
Icons.no_photography,
|
|
||||||
size: cardSize * .15,
|
|
||||||
),
|
),
|
||||||
),
|
child: Center(
|
||||||
);
|
child: Icon(
|
||||||
}
|
Icons.no_photography,
|
||||||
|
size: cardSize * .15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
buildAlbumThumbnail() {
|
buildAlbumThumbnail() {
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
width: cardSize,
|
width: cardSize,
|
||||||
height: cardSize,
|
height: cardSize,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
fadeInDuration: const Duration(milliseconds: 200),
|
fadeInDuration: const Duration(milliseconds: 200),
|
||||||
imageUrl: getAlbumThumbnailUrl(
|
imageUrl: getAlbumThumbnailUrl(
|
||||||
album,
|
album,
|
||||||
type: ThumbnailFormat.JPEG,
|
type: ThumbnailFormat.JPEG,
|
||||||
),
|
),
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
||||||
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
|
cacheKey:
|
||||||
);
|
getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Padding(
|
child: Flex(
|
||||||
padding: const EdgeInsets.only(bottom: 32.0),
|
direction: Axis.vertical,
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Flexible(
|
||||||
child: ClipRRect(
|
child: Column(
|
||||||
borderRadius: BorderRadius.circular(8),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: album.albumThumbnailAssetId == null
|
children: [
|
||||||
? buildEmptyThumbnail()
|
SizedBox(
|
||||||
: buildAlbumThumbnail(),
|
width: cardSize,
|
||||||
),
|
height: cardSize,
|
||||||
),
|
child: ClipRRect(
|
||||||
Padding(
|
borderRadius: BorderRadius.circular(20),
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
child: album.albumThumbnailAssetId == null
|
||||||
child: SizedBox(
|
? buildEmptyThumbnail()
|
||||||
width: cardSize,
|
: buildAlbumThumbnail(),
|
||||||
child: Text(
|
|
||||||
album.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
album.assetCount == 1
|
|
||||||
? 'album_thumbnail_card_item'
|
|
||||||
: 'album_thumbnail_card_items',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
).tr(args: ['${album.assetCount}']),
|
|
||||||
if (album.shared)
|
|
||||||
const Text(
|
|
||||||
'album_thumbnail_card_shared',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
),
|
||||||
).tr()
|
),
|
||||||
],
|
Padding(
|
||||||
)
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: SizedBox(
|
||||||
|
width: cardSize,
|
||||||
|
child: Text(
|
||||||
|
album.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
album.assetCount == 1
|
||||||
|
? 'album_thumbnail_card_item'
|
||||||
|
: 'album_thumbnail_card_items',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
).tr(args: ['${album.assetCount}']),
|
||||||
|
if (album.shared)
|
||||||
|
const Text(
|
||||||
|
'album_thumbnail_card_shared',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
).tr()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class AlbumTitleTextField extends ConsumerWidget {
|
||||||
focusNode: albumTitleTextFieldFocusNode,
|
focusNode: albumTitleTextFieldFocusNode,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
color: Colors.grey[700],
|
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
controller: albumTitleController,
|
controller: albumTitleController,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
final List<Asset>? initialAssets;
|
final List<Asset>? initialAssets;
|
||||||
|
|
||||||
const CreateAlbumPage({
|
const CreateAlbumPage({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.isSharedAlbum,
|
required this.isSharedAlbum,
|
||||||
this.initialAssets,
|
this.initialAssets,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
@ -84,7 +84,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
padding: const EdgeInsets.only(top: 200, left: 18),
|
padding: const EdgeInsets.only(top: 200, left: 18),
|
||||||
child: Text(
|
child: Text(
|
||||||
'create_shared_album_page_share_add_assets',
|
'create_shared_album_page_share_add_assets',
|
||||||
style: Theme.of(context).textTheme.headline2?.copyWith(
|
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
),
|
),
|
||||||
|
|
@ -214,7 +214,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'share_create_album',
|
'share_create_album',
|
||||||
style: Theme.of(context).textTheme.headline2?.copyWith(
|
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
|
|
@ -228,7 +228,9 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||||
'create_shared_album_page_share'.tr(),
|
'create_shared_album_page_share'.tr(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Theme.of(context).primaryColor,
|
color: albumTitleController.text.isEmpty
|
||||||
|
? Theme.of(context).disabledColor
|
||||||
|
: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class LibraryPage extends HookConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final albums = ref.watch(albumProvider);
|
final albums = ref.watch(albumProvider);
|
||||||
|
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
|
|
@ -122,9 +123,12 @@ class LibraryPage extends HookConsumerWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.grey,
|
color: isDarkMode
|
||||||
|
? const Color.fromARGB(255, 53, 53, 53)
|
||||||
|
: const Color.fromARGB(255, 203, 203, 203),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(8),
|
color: isDarkMode ? Colors.grey[900] : Colors.grey[50],
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
|
|
@ -168,25 +172,22 @@ class LibraryPage extends HookConsumerWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: isDarkMode ? Colors.white : Colors.black,
|
||||||
? Colors.white
|
|
||||||
: Colors.black,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
|
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.grey[50],
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: isDarkMode ? Colors.grey[800]! : Colors.grey[300]!,
|
||||||
? Colors.grey[600]!
|
|
||||||
: Colors.grey[300]!,
|
|
||||||
),
|
),
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
icon: Icon(icon, color: Theme.of(context).primaryColor),
|
icon: Icon(
|
||||||
|
icon,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +254,7 @@ class LibraryPage extends HookConsumerWidget {
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
childCount: sorted.length + 1,
|
childCount: sorted.length + 1,
|
||||||
(context, index) {
|
(context, index) {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
return buildCreateAlbumButton();
|
return buildCreateAlbumButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,13 +77,13 @@ class SharingPage extends HookConsumerWidget {
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10), // if you need this
|
borderRadius: BorderRadius.circular(20),
|
||||||
side: const BorderSide(
|
side: const BorderSide(
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
width: 1,
|
width: 0.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
color: Colors.transparent,
|
// color: Colors.transparent,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(18.0),
|
padding: const EdgeInsets.all(18.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -92,7 +92,7 @@ class SharingPage extends HookConsumerWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 5.0, bottom: 5),
|
padding: const EdgeInsets.only(left: 5.0, bottom: 5),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.offline_share_outlined,
|
Icons.insert_photo_rounded,
|
||||||
size: 50,
|
size: 50,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
|
|
@ -101,7 +101,7 @@ class SharingPage extends HookConsumerWidget {
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'sharing_page_empty_list',
|
'sharing_page_empty_list',
|
||||||
style: Theme.of(context).textTheme.headline3,
|
style: Theme.of(context).textTheme.displaySmall,
|
||||||
).tr(),
|
).tr(),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
|
||||||
|
|
@ -14,53 +14,60 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
const ExifBottomSheet({Key? key, required this.assetDetail})
|
const ExifBottomSheet({Key? key, required this.assetDetail})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
|
bool get showMap => assetDetail.latitude != null && assetDetail.longitude != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
buildMap() {
|
buildMap() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
child: Container(
|
child: LayoutBuilder(
|
||||||
height: 150,
|
builder: (context, constraints) {
|
||||||
width: MediaQuery.of(context).size.width,
|
return Container(
|
||||||
decoration: const BoxDecoration(
|
height: 150,
|
||||||
borderRadius: BorderRadius.all(Radius.circular(15)),
|
width: constraints.maxWidth,
|
||||||
),
|
decoration: const BoxDecoration(
|
||||||
child: FlutterMap(
|
borderRadius: BorderRadius.all(Radius.circular(15)),
|
||||||
options: MapOptions(
|
|
||||||
center: LatLng(
|
|
||||||
assetDetail.latitude ?? 0,
|
|
||||||
assetDetail.longitude ?? 0,
|
|
||||||
),
|
),
|
||||||
zoom: 16.0,
|
child: FlutterMap(
|
||||||
),
|
options: MapOptions(
|
||||||
layers: [
|
interactiveFlags: InteractiveFlag.none,
|
||||||
TileLayerOptions(
|
center: LatLng(
|
||||||
urlTemplate:
|
assetDetail.latitude ?? 0,
|
||||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
assetDetail.longitude ?? 0,
|
||||||
subdomains: ['a', 'b', 'c'],
|
),
|
||||||
attributionBuilder: (_) {
|
zoom: 16.0,
|
||||||
return const Text(
|
),
|
||||||
"© OpenStreetMap",
|
layers: [
|
||||||
style: TextStyle(fontSize: 10),
|
TileLayerOptions(
|
||||||
);
|
urlTemplate:
|
||||||
},
|
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||||
),
|
subdomains: ['a', 'b', 'c'],
|
||||||
MarkerLayerOptions(
|
attributionBuilder: (_) {
|
||||||
markers: [
|
return const Text(
|
||||||
Marker(
|
"© OpenStreetMap",
|
||||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
style: TextStyle(fontSize: 10),
|
||||||
point: LatLng(
|
);
|
||||||
assetDetail.latitude ?? 0,
|
},
|
||||||
assetDetail.longitude ?? 0,
|
),
|
||||||
),
|
MarkerLayerOptions(
|
||||||
builder: (ctx) => const Image(
|
markers: [
|
||||||
image: AssetImage('assets/location-pin.png'),
|
Marker(
|
||||||
),
|
anchorPos: AnchorPos.align(AnchorAlign.top),
|
||||||
|
point: LatLng(
|
||||||
|
assetDetail.latitude ?? 0,
|
||||||
|
assetDetail.longitude ?? 0,
|
||||||
|
),
|
||||||
|
builder: (ctx) => const Image(
|
||||||
|
image: AssetImage('assets/location-pin.png'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +98,107 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
return text.isEmpty ? null : Text(text);
|
return text.isEmpty ? null : Text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildDragHeader() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: const [
|
||||||
|
SizedBox(height: 12),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: CustomDraggingHandle(),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildLocation() {
|
||||||
|
// Guard no lat/lng
|
||||||
|
if (!showMap) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
// Location
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"exif_bottom_sheet_location",
|
||||||
|
style: TextStyle(fontSize: 11, color: textColor),
|
||||||
|
).tr(),
|
||||||
|
buildMap(),
|
||||||
|
if (exifInfo != null &&
|
||||||
|
exifInfo.city != null &&
|
||||||
|
exifInfo.state != null)
|
||||||
|
buildLocationText(),
|
||||||
|
Text(
|
||||||
|
"${assetDetail.latitude!.toStringAsFixed(4)}, ${assetDetail.longitude!.toStringAsFixed(4)}",
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDate() {
|
||||||
|
return Text(
|
||||||
|
DateFormat('date_format'.tr()).format(
|
||||||
|
assetDetail.createdAt.toLocal(),
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDetail() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"exif_bottom_sheet_details",
|
||||||
|
style: TextStyle(fontSize: 11, color: textColor),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
dense: true,
|
||||||
|
leading: const Icon(Icons.image),
|
||||||
|
title: Text(
|
||||||
|
assetDetail.fileName,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: buildSizeText(assetDetail),
|
||||||
|
),
|
||||||
|
if (exifInfo?.make != null)
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
dense: true,
|
||||||
|
leading: const Icon(Icons.camera),
|
||||||
|
title: Text(
|
||||||
|
"${exifInfo!.make} ${exifInfo.model}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
"ƒ/${exifInfo.fNumber} ${exifInfo.exposureTime} ${exifInfo.focalLength} mm ISO${exifInfo.iso} ",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Card(
|
child: Card(
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
|
|
@ -102,104 +210,69 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
margin: const EdgeInsets.all(0),
|
margin: const EdgeInsets.all(0),
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Column(
|
child: LayoutBuilder(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
builder: (context, constraints) {
|
||||||
children: [
|
if (constraints.maxWidth > 600) {
|
||||||
const SizedBox(height: 12),
|
// Two column
|
||||||
const Align(
|
return Padding(
|
||||||
alignment: Alignment.center,
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
child: CustomDraggingHandle(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
DateFormat('date_format'.tr()).format(
|
|
||||||
assetDetail.createdAt.toLocal(),
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Location
|
|
||||||
if (assetDetail.latitude != null && assetDetail.longitude != null)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 32.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const Divider(
|
buildDragHeader(),
|
||||||
thickness: 1,
|
buildDate(),
|
||||||
|
const SizedBox(height: 32.0),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
flex: showMap ? 5 : 0,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: buildLocation(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
flex: 5,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: buildDetail(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Text(
|
const SizedBox(height: 50),
|
||||||
"exif_bottom_sheet_location",
|
|
||||||
style: TextStyle(fontSize: 11, color: textColor),
|
|
||||||
).tr(),
|
|
||||||
buildMap(),
|
|
||||||
if (exifInfo != null &&
|
|
||||||
exifInfo.city != null &&
|
|
||||||
exifInfo.state != null)
|
|
||||||
buildLocationText(),
|
|
||||||
Text(
|
|
||||||
"${assetDetail.latitude!.toStringAsFixed(4)}, ${assetDetail.longitude!.toStringAsFixed(4)}",
|
|
||||||
style: const TextStyle(fontSize: 12),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
// Detail
|
}
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 32.0),
|
// One column
|
||||||
child: Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
buildDragHeader(),
|
||||||
|
buildDate(),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
if (showMap)
|
||||||
Divider(
|
Divider(
|
||||||
thickness: 1,
|
thickness: 1,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
),
|
),
|
||||||
Padding(
|
const SizedBox(height: 16.0),
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
buildLocation(),
|
||||||
child: Text(
|
const SizedBox(height: 16.0),
|
||||||
"exif_bottom_sheet_details",
|
Divider(
|
||||||
style: TextStyle(fontSize: 11, color: textColor),
|
thickness: 1,
|
||||||
).tr(),
|
color: Colors.grey[600],
|
||||||
),
|
),
|
||||||
ListTile(
|
const SizedBox(height: 16.0),
|
||||||
contentPadding: const EdgeInsets.all(0),
|
buildDetail(),
|
||||||
dense: true,
|
const SizedBox(height: 50),
|
||||||
leading: const Icon(Icons.image),
|
],
|
||||||
title: Text(
|
);
|
||||||
assetDetail.fileName,
|
},
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: textColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: buildSizeText(assetDetail),
|
|
||||||
),
|
|
||||||
if (exifInfo?.make != null)
|
|
||||||
ListTile(
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
dense: true,
|
|
||||||
leading: const Icon(Icons.camera),
|
|
||||||
title: Text(
|
|
||||||
"${exifInfo!.make} ${exifInfo.model}",
|
|
||||||
style: TextStyle(
|
|
||||||
color: textColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
"ƒ/${exifInfo.fNumber} ${exifInfo.exposureTime} ${exifInfo.focalLength} mm ISO${exifInfo.iso} ",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 50,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,10 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
double iconSize = 18.0;
|
const double iconSize = 18.0;
|
||||||
|
|
||||||
Widget buildFavoriteButton() {
|
Widget buildFavoriteButton() {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
iconSize: iconSize,
|
|
||||||
splashRadius: iconSize,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onFavorite();
|
onFavorite();
|
||||||
},
|
},
|
||||||
|
|
@ -60,12 +58,13 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
actionsIconTheme: const IconThemeData(
|
||||||
|
size: iconSize,
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (asset.isRemote) buildFavoriteButton(),
|
if (asset.isRemote) buildFavoriteButton(),
|
||||||
if (asset.livePhotoVideoId != null)
|
if (asset.livePhotoVideoId != null)
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
|
||||||
splashRadius: iconSize,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onToggleMotionVideo();
|
onToggleMotionVideo();
|
||||||
},
|
},
|
||||||
|
|
@ -81,17 +80,13 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
if (!asset.isLocal)
|
if (!asset.isLocal)
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
|
||||||
splashRadius: iconSize,
|
|
||||||
onPressed: onDownloadPressed,
|
onPressed: onDownloadPressed,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.cloud_download_rounded,
|
Icons.cloud_download_outlined,
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
|
||||||
splashRadius: iconSize,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onSharePressed();
|
onSharePressed();
|
||||||
},
|
},
|
||||||
|
|
@ -102,8 +97,6 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
if (asset.isRemote)
|
if (asset.isRemote)
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
|
||||||
splashRadius: iconSize,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onAddToAlbumPressed();
|
onAddToAlbumPressed();
|
||||||
},
|
},
|
||||||
|
|
@ -113,8 +106,6 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
|
||||||
splashRadius: iconSize,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onDeletePressed();
|
onDeletePressed();
|
||||||
},
|
},
|
||||||
|
|
@ -124,13 +115,11 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
|
||||||
splashRadius: iconSize,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onMoreInfoPressed();
|
onMoreInfoPressed();
|
||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.more_horiz_rounded,
|
Icons.info_outline_rounded,
|
||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||||
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
|
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/asset.service.dart';
|
import 'package:immich_mobile/shared/services/asset.service.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
|
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/shared/ui/photo_view/photo_view_gallery.dart';
|
import 'package:immich_mobile/shared/ui/photo_view/photo_view_gallery.dart';
|
||||||
|
|
@ -213,7 +213,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||||
|
|
||||||
void handleSwipeUpDown(DragUpdateDetails details) {
|
void handleSwipeUpDown(DragUpdateDetails details) {
|
||||||
int sensitivity = 15;
|
int sensitivity = 15;
|
||||||
int dxThreshhold = 50;
|
int dxThreshold = 50;
|
||||||
|
|
||||||
if (isZoomed.value) {
|
if (isZoomed.value) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -222,7 +222,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||||
// Check for delta from initial down point
|
// Check for delta from initial down point
|
||||||
final d = details.localPosition - localPosition;
|
final d = details.localPosition - localPosition;
|
||||||
// If the magnitude of the dx swipe is large, we probably didn't mean to go down
|
// If the magnitude of the dx swipe is large, we probably didn't mean to go down
|
||||||
if (d.dx.abs() > dxThreshhold) {
|
if (d.dx.abs() > dxThreshold) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,8 +247,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||||
isPlayingMotionVideo: isPlayingMotionVideo.value,
|
isPlayingMotionVideo: isPlayingMotionVideo.value,
|
||||||
asset: assetList[indexOfAsset.value],
|
asset: assetList[indexOfAsset.value],
|
||||||
isFavorite: ref.watch(favoriteProvider).contains(
|
isFavorite: ref.watch(favoriteProvider).contains(
|
||||||
assetList[indexOfAsset.value].id,
|
assetList[indexOfAsset.value].id,
|
||||||
),
|
),
|
||||||
onMoreInfoPressed: () {
|
onMoreInfoPressed: () {
|
||||||
showInfo();
|
showInfo();
|
||||||
},
|
},
|
||||||
|
|
@ -314,7 +314,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||||
? (context, event) {
|
? (context, event) {
|
||||||
final asset = assetList[indexOfAsset.value];
|
final asset = assetList[indexOfAsset.value];
|
||||||
if (!asset.isLocal) {
|
if (!asset.isLocal) {
|
||||||
// Use the WEBP Thumbnail as a placeholder for the JPEG thumbnail to acheive
|
// Use the WEBP Thumbnail as a placeholder for the JPEG thumbnail to achieve
|
||||||
// Three-Stage Loading (WEBP -> JPEG -> Original)
|
// Three-Stage Loading (WEBP -> JPEG -> Original)
|
||||||
final webPThumbnail = CachedNetworkImage(
|
final webPThumbnail = CachedNetworkImage(
|
||||||
imageUrl: getThumbnailUrl(
|
imageUrl: getThumbnailUrl(
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,6 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: removeSelection,
|
onTap: removeSelection,
|
||||||
child: Chip(
|
child: Chip(
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
label: Text(
|
label: Text(
|
||||||
album.name,
|
album.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -119,10 +115,6 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Chip(
|
child: Chip(
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
label: Text(
|
label: Text(
|
||||||
album.name,
|
album.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
|
||||||
|
|
@ -75,22 +75,23 @@ class RenderList {
|
||||||
|
|
||||||
RenderList(this.elements);
|
RenderList(this.elements);
|
||||||
|
|
||||||
static Map<String, List<Asset>> _groupAssets(
|
static Map<DateTime, List<Asset>> _groupAssets(
|
||||||
List<Asset> assets,
|
List<Asset> assets,
|
||||||
GroupAssetsBy groupBy,
|
GroupAssetsBy groupBy,
|
||||||
) {
|
) {
|
||||||
assets.sortByCompare<DateTime>(
|
|
||||||
(e) => e.createdAt,
|
|
||||||
(a, b) => b.compareTo(a),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (groupBy == GroupAssetsBy.day) {
|
if (groupBy == GroupAssetsBy.day) {
|
||||||
return assets.groupListsBy(
|
return assets.groupListsBy(
|
||||||
(element) => DateFormat('y-MM-dd').format(element.createdAt.toLocal()),
|
(element) {
|
||||||
|
final date = element.createdAt.toLocal();
|
||||||
|
return DateTime(date.year, date.month, date.day);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} else if (groupBy == GroupAssetsBy.month) {
|
} else if (groupBy == GroupAssetsBy.month) {
|
||||||
return assets.groupListsBy(
|
return assets.groupListsBy(
|
||||||
(element) => DateFormat('y-MM').format(element.createdAt.toLocal()),
|
(element) {
|
||||||
|
final date = element.createdAt.toLocal();
|
||||||
|
return DateTime(date.year, date.month);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,10 +114,11 @@ class RenderList {
|
||||||
|
|
||||||
final groups = _groupAssets(allAssets, groupBy);
|
final groups = _groupAssets(allAssets, groupBy);
|
||||||
|
|
||||||
groups.forEach((groupName, assets) {
|
groups.entries.sortedBy((e) =>e.key).reversed.forEach((entry) {
|
||||||
try {
|
final date = entry.key;
|
||||||
final date = assets.first.createdAt.toLocal();
|
final assets = entry.value;
|
||||||
|
|
||||||
|
try {
|
||||||
// Month title
|
// Month title
|
||||||
if (groupBy == GroupAssetsBy.day &&
|
if (groupBy == GroupAssetsBy.day &&
|
||||||
(lastDate == null || lastDate!.month != date.month)) {
|
(lastDate == null || lastDate!.month != date.month)) {
|
||||||
|
|
|
||||||
|
|
@ -92,11 +92,10 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
|
||||||
RenderAssetGridRow row,
|
RenderAssetGridRow row,
|
||||||
bool scrolling,
|
bool scrolling,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final size = constraints.maxWidth / widget.assetsPerRow -
|
final size = constraints.maxWidth / widget.assetsPerRow -
|
||||||
widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow;
|
widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow;
|
||||||
return Row(
|
return Row(
|
||||||
key: Key("asset-row-${row.assets.first.id}"),
|
key: Key("asset-row-${row.assets.first.id}"),
|
||||||
children: row.assets.mapIndexed((int index, Asset asset) {
|
children: row.assets.mapIndexed((int index, Asset asset) {
|
||||||
|
|
@ -141,7 +140,7 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Theme.of(context).textTheme.headline1?.color,
|
color: Theme.of(context).textTheme.displayLarge?.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -164,7 +163,7 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
|
||||||
Text _labelBuilder(int pos) {
|
Text _labelBuilder(int pos) {
|
||||||
final date = widget.renderList.elements[pos].date;
|
final date = widget.renderList.elements[pos].date;
|
||||||
return Text(
|
return Text(
|
||||||
DateFormat.yMMMd().format(date),
|
DateFormat.yMMMM().format(date),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class MonthlyTitleText extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Theme.of(context).textTheme.headline1?.color,
|
color: Theme.of(context).textTheme.displayLarge?.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
|
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
|
||||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||||
import 'package:immich_mobile/shared/models/album.dart';
|
import 'package:immich_mobile/shared/models/album.dart';
|
||||||
|
|
||||||
|
|
@ -29,6 +29,8 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
|
||||||
Widget renderActionButtons() {
|
Widget renderActionButtons() {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -60,7 +62,6 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +76,9 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
ScrollController scrollController,
|
ScrollController scrollController,
|
||||||
) {
|
) {
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 12.0,
|
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
elevation: 18.0,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(12),
|
topLeft: Radius.circular(12),
|
||||||
|
|
@ -83,45 +86,37 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.all(0),
|
margin: const EdgeInsets.all(0),
|
||||||
child: Container(
|
child: CustomScrollView(
|
||||||
decoration: const BoxDecoration(
|
controller: scrollController,
|
||||||
borderRadius: BorderRadius.only(
|
slivers: [
|
||||||
topLeft: Radius.circular(12),
|
SliverToBoxAdapter(
|
||||||
topRight: Radius.circular(12),
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const CustomDraggingHandle(),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
renderActionButtons(),
|
||||||
|
const Divider(
|
||||||
|
indent: 16,
|
||||||
|
endIndent: 16,
|
||||||
|
thickness: 1,
|
||||||
|
),
|
||||||
|
AddToAlbumTitleRow(onCreateNewAlbum: onCreateNewAlbum),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
SliverPadding(
|
||||||
child: CustomScrollView(
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
controller: scrollController,
|
sliver: AddToAlbumSliverList(
|
||||||
slivers: [
|
albums: albums,
|
||||||
SliverToBoxAdapter(
|
sharedAlbums: sharedAlbums,
|
||||||
child: Column(
|
onAddToAlbum: onAddToAlbum,
|
||||||
children: <Widget>[
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
const CustomDraggingHandle(),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
renderActionButtons(),
|
|
||||||
const Divider(
|
|
||||||
indent: 16,
|
|
||||||
endIndent: 16,
|
|
||||||
thickness: 1,
|
|
||||||
),
|
|
||||||
AddToAlbumTitleRow(onCreateNewAlbum: onCreateNewAlbum),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SliverPadding(
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
const SliverToBoxAdapter(
|
||||||
sliver: AddToAlbumSliverList(
|
child: SizedBox(height: 200),
|
||||||
albums: albums,
|
)
|
||||||
sharedAlbums: sharedAlbums,
|
],
|
||||||
onAddToAlbum: onAddToAlbum,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SliverToBoxAdapter(
|
|
||||||
child: SizedBox(height: 200),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:badges/badges.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
|
|
@ -29,7 +28,6 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
|
||||||
final ServerInfoState serverInfoState = ref.watch(serverInfoProvider);
|
final ServerInfoState serverInfoState = ref.watch(serverInfoProvider);
|
||||||
|
|
||||||
return AppBar(
|
return AppBar(
|
||||||
centerTitle: true,
|
|
||||||
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(
|
borderRadius: BorderRadius.all(
|
||||||
|
|
@ -44,10 +42,9 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
|
||||||
top: 5,
|
top: 5,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
splashRadius: 25,
|
splashRadius: 25,
|
||||||
icon: Icon(
|
icon: const Icon(
|
||||||
Icons.face_outlined,
|
Icons.face_outlined,
|
||||||
size: 30,
|
size: 30,
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Scaffold.of(context).openDrawer();
|
Scaffold.of(context).openDrawer();
|
||||||
|
|
@ -112,16 +109,13 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
|
||||||
splashRadius: 25,
|
splashRadius: 25,
|
||||||
iconSize: 30,
|
iconSize: 30,
|
||||||
icon: isEnableAutoBackup
|
icon: isEnableAutoBackup
|
||||||
? Icon(
|
? const Icon(
|
||||||
Icons.backup_rounded,
|
Icons.backup_rounded,
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
)
|
)
|
||||||
: Badge(
|
: Badge(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
elevation: 3,
|
backgroundColor: Colors.white,
|
||||||
position: BadgePosition.bottomEnd(bottom: -4, end: -4),
|
label: const Icon(
|
||||||
badgeColor: Colors.white,
|
|
||||||
badgeContent: const Icon(
|
|
||||||
Icons.cloud_off_rounded,
|
Icons.cloud_off_rounded,
|
||||||
size: 8,
|
size: 8,
|
||||||
color: Colors.indigo,
|
color: Colors.indigo,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
buildSignoutButton() {
|
buildSignOutButton() {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
horizontalTitleGap: 0,
|
horizontalTitleGap: 0,
|
||||||
leading: SizedBox(
|
leading: SizedBox(
|
||||||
|
|
@ -95,6 +95,9 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Drawer(
|
return Drawer(
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.zero,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -105,7 +108,7 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||||
const ProfileDrawerHeader(),
|
const ProfileDrawerHeader(),
|
||||||
buildSettingButton(),
|
buildSettingButton(),
|
||||||
buildAppLogButton(),
|
buildAppLogButton(),
|
||||||
buildSignoutButton(),
|
buildSignOutButton(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const ServerInfoBox()
|
const ServerInfoBox()
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
AuthenticationState authState = ref.watch(authenticationProvider);
|
AuthenticationState authState = ref.watch(authenticationProvider);
|
||||||
final uploadProfileImageStatus =
|
final uploadProfileImageStatus =
|
||||||
ref.watch(uploadProfileImageProvider).status;
|
ref.watch(uploadProfileImageProvider).status;
|
||||||
var dummmy = Random().nextInt(1024);
|
var dummy = Random().nextInt(1024);
|
||||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
|
||||||
buildUserProfileImage() {
|
buildUserProfileImage() {
|
||||||
|
|
@ -39,7 +39,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
radius: 35,
|
radius: 35,
|
||||||
backgroundImage: NetworkImage(
|
backgroundImage: NetworkImage(
|
||||||
'$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}',
|
'$endpoint/user/profile-image/${authState.userId}?d=${dummy++}',
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
);
|
);
|
||||||
|
|
@ -56,7 +56,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
radius: 35,
|
radius: 35,
|
||||||
backgroundImage: NetworkImage(
|
backgroundImage: NetworkImage(
|
||||||
'$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}',
|
'$endpoint/user/profile-image/${authState.userId}?d=${dummy++}',
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,9 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'search_bar_hint'.tr(),
|
hintText: 'search_bar_hint'.tr(),
|
||||||
|
hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
|
||||||
|
),
|
||||||
enabledBorder: const UnderlineInputBorder(
|
enabledBorder: const UnderlineInputBorder(
|
||||||
borderSide: BorderSide(color: Colors.transparent),
|
borderSide: BorderSide(color: Colors.transparent),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ class TabControllerPage extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|
||||||
navigationRail(TabsRouter tabsRouter) {
|
navigationRail(TabsRouter tabsRouter) {
|
||||||
return NavigationRail(
|
return NavigationRail(
|
||||||
labelType: NavigationRailLabelType.all,
|
labelType: NavigationRailLabelType.all,
|
||||||
|
|
@ -35,32 +34,33 @@ class TabControllerPage extends ConsumerWidget {
|
||||||
right: 4,
|
right: 4,
|
||||||
bottom: 4,
|
bottom: 4,
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.photo_outlined),
|
icon: const Icon(Icons.photo_outlined),
|
||||||
selectedIcon: const Icon(Icons.photo),
|
selectedIcon: const Icon(Icons.photo),
|
||||||
label: const Text('tab_controller_nav_photos').tr(),
|
label: const Text('tab_controller_nav_photos').tr(),
|
||||||
),
|
),
|
||||||
NavigationRailDestination(
|
NavigationRailDestination(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
icon: const Icon(Icons.search_rounded),
|
icon: const Icon(Icons.search_rounded),
|
||||||
selectedIcon: const Icon(Icons.search),
|
selectedIcon: const Icon(Icons.search),
|
||||||
label: const Text('tab_controller_nav_search').tr(),
|
label: const Text('tab_controller_nav_search').tr(),
|
||||||
),
|
),
|
||||||
NavigationRailDestination(
|
NavigationRailDestination(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
icon: const Icon(Icons.share_rounded),
|
icon: const Icon(Icons.share_rounded),
|
||||||
selectedIcon: const Icon(Icons.share),
|
selectedIcon: const Icon(Icons.share),
|
||||||
label: const Text('tab_controller_nav_sharing').tr(),
|
label: const Text('tab_controller_nav_sharing').tr(),
|
||||||
),
|
),
|
||||||
NavigationRailDestination(
|
NavigationRailDestination(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
icon: const Icon(Icons.photo_album_outlined),
|
icon: const Icon(Icons.photo_album_outlined),
|
||||||
selectedIcon: const Icon(Icons.photo_album),
|
selectedIcon: const Icon(Icons.photo_album),
|
||||||
label: const Text('tab_controller_nav_library').tr(),
|
label: const Text('tab_controller_nav_library').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: unused_element
|
||||||
bottomNavigationBar(TabsRouter tabsRouter) {
|
bottomNavigationBar(TabsRouter tabsRouter) {
|
||||||
return BottomNavigationBar(
|
return BottomNavigationBar(
|
||||||
selectedLabelStyle: const TextStyle(
|
selectedLabelStyle: const TextStyle(
|
||||||
|
|
@ -101,6 +101,58 @@ class TabControllerPage extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
experimentalNavigationBar(TabsRouter tabsRouter) {
|
||||||
|
return NavigationBar(
|
||||||
|
selectedIndex: tabsRouter.activeIndex,
|
||||||
|
onDestinationSelected: (index) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
tabsRouter.setActiveIndex(index);
|
||||||
|
},
|
||||||
|
destinations: [
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'tab_controller_nav_photos'.tr(),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.photo_outlined,
|
||||||
|
),
|
||||||
|
selectedIcon: Icon(
|
||||||
|
Icons.photo,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'tab_controller_nav_search'.tr(),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.search_rounded,
|
||||||
|
),
|
||||||
|
selectedIcon: Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'tab_controller_nav_sharing'.tr(),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.group_outlined,
|
||||||
|
),
|
||||||
|
selectedIcon: Icon(
|
||||||
|
Icons.group,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
label: 'tab_controller_nav_library'.tr(),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.photo_album_outlined,
|
||||||
|
),
|
||||||
|
selectedIcon: Icon(
|
||||||
|
Icons.photo_album_rounded,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final multiselectEnabled = ref.watch(multiselectProvider);
|
final multiselectEnabled = ref.watch(multiselectProvider);
|
||||||
return AutoTabsRouter(
|
return AutoTabsRouter(
|
||||||
routes: [
|
routes: [
|
||||||
|
|
@ -116,7 +168,7 @@ class TabControllerPage extends ConsumerWidget {
|
||||||
bool atHomeTab = tabsRouter.activeIndex == 0;
|
bool atHomeTab = tabsRouter.activeIndex == 0;
|
||||||
if (!atHomeTab) {
|
if (!atHomeTab) {
|
||||||
tabsRouter.setActiveIndex(0);
|
tabsRouter.setActiveIndex(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return atHomeTab;
|
return atHomeTab;
|
||||||
},
|
},
|
||||||
|
|
@ -127,7 +179,7 @@ class TabControllerPage extends ConsumerWidget {
|
||||||
final Widget body;
|
final Widget body;
|
||||||
if (constraints.maxWidth < medium) {
|
if (constraints.maxWidth < medium) {
|
||||||
// Normal phone width
|
// Normal phone width
|
||||||
bottom = bottomNavigationBar(tabsRouter);
|
bottom = experimentalNavigationBar(tabsRouter);
|
||||||
body = FadeTransition(
|
body = FadeTransition(
|
||||||
opacity: animation,
|
opacity: animation,
|
||||||
child: child,
|
child: child,
|
||||||
|
|
@ -146,13 +198,13 @@ class TabControllerPage extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} return Scaffold(
|
}
|
||||||
body: body,
|
return Scaffold(
|
||||||
bottomNavigationBar: multiselectEnabled
|
body: body,
|
||||||
? null
|
bottomNavigationBar: multiselectEnabled ? null : bottom,
|
||||||
: bottom,
|
);
|
||||||
);
|
},
|
||||||
},),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,83 @@ final immichThemeProvider = StateProvider<ThemeMode>((ref) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ThemeData base = ThemeData(
|
||||||
|
chipTheme: const ChipThemeData(
|
||||||
|
side: BorderSide.none,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
ThemeData immichLightTheme = ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
brightness: Brightness.light,
|
||||||
|
primarySwatch: Colors.indigo,
|
||||||
|
primaryColor: Colors.indigo,
|
||||||
|
hintColor: Colors.indigo,
|
||||||
|
fontFamily: 'WorkSans',
|
||||||
|
scaffoldBackgroundColor: immichBackgroundColor,
|
||||||
|
snackBarTheme: const SnackBarThemeData(
|
||||||
|
contentTextStyle: TextStyle(fontFamily: 'WorkSans'),
|
||||||
|
),
|
||||||
|
appBarTheme: AppBarTheme(
|
||||||
|
titleTextStyle: const TextStyle(
|
||||||
|
fontFamily: 'WorkSans',
|
||||||
|
color: Colors.indigo,
|
||||||
|
),
|
||||||
|
backgroundColor: immichBackgroundColor,
|
||||||
|
foregroundColor: Colors.indigo,
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
|
backgroundColor: immichBackgroundColor,
|
||||||
|
selectedItemColor: Colors.indigo,
|
||||||
|
),
|
||||||
|
cardTheme: const CardTheme(
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
drawerTheme: DrawerThemeData(
|
||||||
|
backgroundColor: immichBackgroundColor,
|
||||||
|
),
|
||||||
|
textTheme: const TextTheme(
|
||||||
|
displayLarge: TextStyle(
|
||||||
|
fontSize: 26,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.indigo,
|
||||||
|
),
|
||||||
|
displayMedium: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
displaySmall: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.indigo,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.indigo,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
chipTheme: base.chipTheme,
|
||||||
|
popupMenuTheme: const PopupMenuThemeData(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
),
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
|
indicatorColor: Colors.indigo.withOpacity(0.15),
|
||||||
|
backgroundColor: immichBackgroundColor,
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
ThemeData immichDarkTheme = ThemeData(
|
ThemeData immichDarkTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
|
|
@ -43,7 +120,8 @@ ThemeData immichDarkTheme = ThemeData(
|
||||||
),
|
),
|
||||||
backgroundColor: const Color.fromARGB(255, 32, 33, 35),
|
backgroundColor: const Color.fromARGB(255, 32, 33, 35),
|
||||||
foregroundColor: immichDarkThemePrimaryColor,
|
foregroundColor: immichDarkThemePrimaryColor,
|
||||||
elevation: 1,
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||||
|
|
@ -56,17 +134,17 @@ ThemeData immichDarkTheme = ThemeData(
|
||||||
scrimColor: Colors.white.withOpacity(0.1),
|
scrimColor: Colors.white.withOpacity(0.1),
|
||||||
),
|
),
|
||||||
textTheme: TextTheme(
|
textTheme: TextTheme(
|
||||||
headline1: const TextStyle(
|
displayLarge: const TextStyle(
|
||||||
fontSize: 26,
|
fontSize: 26,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Color.fromARGB(255, 255, 255, 255),
|
color: Color.fromARGB(255, 255, 255, 255),
|
||||||
),
|
),
|
||||||
headline2: const TextStyle(
|
displayMedium: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Color.fromARGB(255, 255, 255, 255),
|
color: Color.fromARGB(255, 255, 255, 255),
|
||||||
),
|
),
|
||||||
headline3: TextStyle(
|
displaySmall: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: immichDarkThemePrimaryColor,
|
color: immichDarkThemePrimaryColor,
|
||||||
|
|
@ -79,57 +157,19 @@ ThemeData immichDarkTheme = ThemeData(
|
||||||
backgroundColor: immichDarkThemePrimaryColor,
|
backgroundColor: immichDarkThemePrimaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
chipTheme: base.chipTheme,
|
||||||
|
popupMenuTheme: const PopupMenuThemeData(
|
||||||
ThemeData immichLightTheme = ThemeData(
|
shape: RoundedRectangleBorder(
|
||||||
useMaterial3: true,
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
brightness: Brightness.light,
|
|
||||||
primarySwatch: Colors.indigo,
|
|
||||||
hintColor: Colors.indigo,
|
|
||||||
fontFamily: 'WorkSans',
|
|
||||||
scaffoldBackgroundColor: immichBackgroundColor,
|
|
||||||
snackBarTheme: const SnackBarThemeData(
|
|
||||||
contentTextStyle: TextStyle(fontFamily: 'WorkSans'),
|
|
||||||
),
|
|
||||||
appBarTheme: AppBarTheme(
|
|
||||||
titleTextStyle: const TextStyle(
|
|
||||||
fontFamily: 'WorkSans',
|
|
||||||
color: Colors.indigo,
|
|
||||||
),
|
),
|
||||||
backgroundColor: immichBackgroundColor,
|
surfaceTintColor: Colors.transparent,
|
||||||
foregroundColor: Colors.indigo,
|
|
||||||
elevation: 1,
|
|
||||||
centerTitle: true,
|
|
||||||
),
|
),
|
||||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
type: BottomNavigationBarType.fixed,
|
indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
|
||||||
backgroundColor: immichBackgroundColor,
|
iconTheme: const MaterialStatePropertyAll(
|
||||||
selectedItemColor: Colors.indigo,
|
IconThemeData(color: Colors.white),
|
||||||
),
|
|
||||||
drawerTheme: DrawerThemeData(
|
|
||||||
backgroundColor: immichBackgroundColor,
|
|
||||||
),
|
|
||||||
textTheme: const TextTheme(
|
|
||||||
headline1: TextStyle(
|
|
||||||
fontSize: 26,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.indigo,
|
|
||||||
),
|
|
||||||
headline2: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
headline3: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.indigo,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Colors.indigo,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
),
|
),
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
|
||||||
|
|
@ -144,19 +144,19 @@ class ApiClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> deserializeAsync(String json, String targetType, {bool growable = false,}) async =>
|
Future<dynamic> deserializeAsync(String json, String targetType, {bool growable = false,}) =>
|
||||||
// ignore: deprecated_member_use_from_same_package
|
// ignore: deprecated_member_use_from_same_package
|
||||||
deserialize(json, targetType, growable: growable);
|
deserialize(json, targetType, growable: growable);
|
||||||
|
|
||||||
@Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.')
|
@Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.')
|
||||||
dynamic deserialize(String json, String targetType, {bool growable = false,}) {
|
Future<dynamic> deserialize(String json, String targetType, {bool growable = false,}) async {
|
||||||
// Remove all spaces. Necessary for regular expressions as well.
|
// Remove all spaces. Necessary for regular expressions as well.
|
||||||
targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments
|
targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments
|
||||||
|
|
||||||
// If the expected target type is String, nothing to do...
|
// If the expected target type is String, nothing to do...
|
||||||
return targetType == 'String'
|
return targetType == 'String'
|
||||||
? json
|
? json
|
||||||
: _deserialize(jsonDecode(json), targetType, growable: growable);
|
: _deserialize(await compute((String j) => jsonDecode(j), json), targetType, growable: growable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: deprecated_member_use_from_same_package
|
// ignore: deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
function mobile {
|
function mobile {
|
||||||
rm -rf ../mobile/openapi
|
rm -rf ../mobile/openapi
|
||||||
|
|
@ -7,6 +7,10 @@ function mobile {
|
||||||
patch -u native_class.mustache <native_class.mustache.patch
|
patch -u native_class.mustache <native_class.mustache.patch
|
||||||
cd ../../../../..
|
cd ../../../../..
|
||||||
npx openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/openapi -t ./openapi-generator/templates/mobile
|
npx openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/openapi -t ./openapi-generator/templates/mobile
|
||||||
|
|
||||||
|
# Post generate patches
|
||||||
|
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/api_client.dart <./openapi-generator/patch/api_client.dart.patch
|
||||||
|
patch --no-backup-if-mismatch -u ../mobile/openapi/lib/api.dart <./openapi-generator/patch/api.dart.patch
|
||||||
}
|
}
|
||||||
|
|
||||||
function web {
|
function web {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
@@ -14,6 +14,7 @@ import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
+import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
@@ -144,19 +144,19 @@ class ApiClient {
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
- Future<dynamic> deserializeAsync(String json, String targetType, {bool growable = false,}) async =>
|
||||||
|
+ Future<dynamic> deserializeAsync(String json, String targetType, {bool growable = false,}) =>
|
||||||
|
// ignore: deprecated_member_use_from_same_package
|
||||||
|
deserialize(json, targetType, growable: growable);
|
||||||
|
|
||||||
|
@Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.')
|
||||||
|
- dynamic deserialize(String json, String targetType, {bool growable = false,}) {
|
||||||
|
+ Future<dynamic> deserialize(String json, String targetType, {bool growable = false,}) async {
|
||||||
|
// Remove all spaces. Necessary for regular expressions as well.
|
||||||
|
targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments
|
||||||
|
|
||||||
|
// If the expected target type is String, nothing to do...
|
||||||
|
return targetType == 'String'
|
||||||
|
? json
|
||||||
|
- : _deserialize(jsonDecode(json), targetType, growable: growable);
|
||||||
|
+ : _deserialize(await compute((String j) => jsonDecode(j), json), targetType, growable: growable);
|
||||||
|
}
|
||||||
|
|
@ -86,6 +86,8 @@ export default {
|
||||||
|
|
||||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
|
'\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||||
|
'identity-obj-proxy',
|
||||||
'^\\$lib(.*)$': '<rootDir>/src/lib$1',
|
'^\\$lib(.*)$': '<rootDir>/src/lib$1',
|
||||||
'^\\@api(.*)$': '<rootDir>/src/api$1',
|
'^\\@api(.*)$': '<rootDir>/src/api$1',
|
||||||
'^\\@test-data(.*)$': '<rootDir>/src/test-data$1'
|
'^\\@test-data(.*)$': '<rootDir>/src/test-data$1'
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"luxon": "^3.1.1",
|
"luxon": "^3.1.1",
|
||||||
"socket.io-client": "^4.5.1",
|
"socket.io-client": "^4.5.1",
|
||||||
"svelte-keydown": "^0.5.0",
|
|
||||||
"svelte-material-icons": "^2.0.2"
|
"svelte-material-icons": "^2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -45,6 +44,7 @@
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"factory.ts": "^1.2.0",
|
"factory.ts": "^1.2.0",
|
||||||
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.0.2",
|
"jest": "^29.0.2",
|
||||||
"jest-environment-jsdom": "^29.0.2",
|
"jest-environment-jsdom": "^29.0.2",
|
||||||
"postcss": "^8.4.13",
|
"postcss": "^8.4.13",
|
||||||
|
|
@ -53,7 +53,6 @@
|
||||||
"svelte": "^3.44.0",
|
"svelte": "^3.44.0",
|
||||||
"svelte-check": "^2.7.1",
|
"svelte-check": "^2.7.1",
|
||||||
"svelte-jester": "^2.3.2",
|
"svelte-jester": "^2.3.2",
|
||||||
"svelte-keydown": "^0.5.0",
|
|
||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-preprocess": "^4.10.7",
|
||||||
"tailwindcss": "^3.0.24",
|
"tailwindcss": "^3.0.24",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
|
|
@ -6202,6 +6201,12 @@
|
||||||
"uglify-js": "^3.1.4"
|
"uglify-js": "^3.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/harmony-reflect": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
|
||||||
|
"integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/has": {
|
"node_modules/has": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
|
|
@ -6337,6 +6342,18 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/identity-obj-proxy": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"harmony-reflect": "^1.4.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
|
||||||
|
|
@ -10573,12 +10590,6 @@
|
||||||
"svelte": ">= 3"
|
"svelte": ">= 3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte-keydown": {
|
|
||||||
"version": "0.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/svelte-keydown/-/svelte-keydown-0.5.0.tgz",
|
|
||||||
"integrity": "sha512-DgY6AYlKbBocSvjC3kUeNPcStJQOTOCxAGG9ymVHzJdsQ1hRJuB8pcnB4UFH8uH3bAPdYyXXa3LwenLDL41eqQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/svelte-material-icons": {
|
"node_modules/svelte-material-icons": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-material-icons/-/svelte-material-icons-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-material-icons/-/svelte-material-icons-2.0.4.tgz",
|
||||||
|
|
@ -15822,6 +15833,12 @@
|
||||||
"wordwrap": "^1.0.0"
|
"wordwrap": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"harmony-reflect": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
|
||||||
|
"integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"has": {
|
"has": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||||
|
|
@ -15918,6 +15935,15 @@
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"identity-obj-proxy": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"harmony-reflect": "^1.4.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ignore": {
|
"ignore": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
|
||||||
|
|
@ -18997,12 +19023,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"svelte-keydown": {
|
|
||||||
"version": "0.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/svelte-keydown/-/svelte-keydown-0.5.0.tgz",
|
|
||||||
"integrity": "sha512-DgY6AYlKbBocSvjC3kUeNPcStJQOTOCxAGG9ymVHzJdsQ1hRJuB8pcnB4UFH8uH3bAPdYyXXa3LwenLDL41eqQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"svelte-material-icons": {
|
"svelte-material-icons": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-material-icons/-/svelte-material-icons-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-material-icons/-/svelte-material-icons-2.0.4.tgz",
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"factory.ts": "^1.2.0",
|
"factory.ts": "^1.2.0",
|
||||||
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.0.2",
|
"jest": "^29.0.2",
|
||||||
"jest-environment-jsdom": "^29.0.2",
|
"jest-environment-jsdom": "^29.0.2",
|
||||||
"postcss": "^8.4.13",
|
"postcss": "^8.4.13",
|
||||||
|
|
@ -51,7 +52,6 @@
|
||||||
"svelte": "^3.44.0",
|
"svelte": "^3.44.0",
|
||||||
"svelte-check": "^2.7.1",
|
"svelte-check": "^2.7.1",
|
||||||
"svelte-jester": "^2.3.2",
|
"svelte-jester": "^2.3.2",
|
||||||
"svelte-keydown": "^0.5.0",
|
|
||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-preprocess": "^4.10.7",
|
||||||
"tailwindcss": "^3.0.24",
|
"tailwindcss": "^3.0.24",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
|
|
@ -69,7 +69,6 @@
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"luxon": "^3.1.1",
|
"luxon": "^3.1.1",
|
||||||
"socket.io-client": "^4.5.1",
|
"socket.io-client": "^4.5.1",
|
||||||
"svelte-keydown": "^0.5.0",
|
|
||||||
"svelte-material-icons": "^2.0.2"
|
"svelte-material-icons": "^2.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Work Sans';
|
font-family: 'Work Sans';
|
||||||
src: url('/fonts/WorkSans-VariableFont_wght.ttf') format('truetype-variations');
|
src: url('$lib/assets/fonts/WorkSans-VariableFont_wght.ttf') format('truetype-variations');
|
||||||
font-weight: 1 999;
|
font-weight: 1 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Snowburst One';
|
font-family: 'Snowburst One';
|
||||||
src: url('/fonts/SnowburstOne-Regular.ttf') format('truetype');
|
src: url('$lib/assets/fonts/SnowburstOne-Regular.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
<html lang="en" class="dark">
|
<html lang="en" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 584 B |
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type Icon from 'svelte-material-icons/AbTesting.svelte';
|
||||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
export let logo: typeof Icon;
|
||||||
export let logo: any;
|
|
||||||
export let title: string;
|
export let title: string;
|
||||||
export let value: string;
|
export let value: string;
|
||||||
export let unit: string | undefined = undefined;
|
export let unit: string | undefined = undefined;
|
||||||
|
|
@ -13,13 +13,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxLength = 13;
|
const maxLength = 13;
|
||||||
let result = '';
|
|
||||||
const valueLength = parseInt(value).toString().length;
|
const valueLength = parseInt(value).toString().length;
|
||||||
const zeroLength = maxLength - valueLength;
|
const zeroLength = maxLength - valueLength;
|
||||||
for (let i = 0; i < zeroLength; i++) {
|
|
||||||
result += '0';
|
return '0'.repeat(zeroLength);
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,10 @@ describe('AlbumCard component', () => {
|
||||||
const albumDetailsElement = sut.getByTestId('album-details');
|
const albumDetailsElement = sut.getByTestId('album-details');
|
||||||
const detailsText = `${count} items` + (shared ? ' . Shared' : '');
|
const detailsText = `${count} items` + (shared ? ' . Shared' : '');
|
||||||
|
|
||||||
expect(albumImgElement).toHaveAttribute('src', 'no-thumbnail.png');
|
expect(albumImgElement).toHaveAttribute('src');
|
||||||
expect(albumImgElement).toHaveAttribute('alt', album.id);
|
expect(albumImgElement).toHaveAttribute('alt', album.id);
|
||||||
|
|
||||||
await waitFor(() => expect(albumImgElement).toHaveAttribute('src', 'no-thumbnail.png'));
|
await waitFor(() => expect(albumImgElement).toHaveAttribute('src'));
|
||||||
|
|
||||||
expect(albumImgElement).toHaveAttribute('alt', album.id);
|
expect(albumImgElement).toHaveAttribute('alt', album.id);
|
||||||
expect(apiMock.assetApi.getAssetThumbnail).not.toHaveBeenCalled();
|
expect(apiMock.assetApi.getAssetThumbnail).not.toHaveBeenCalled();
|
||||||
|
|
@ -108,7 +108,7 @@ describe('AlbumCard component', () => {
|
||||||
sut = render(AlbumCard, { album });
|
sut = render(AlbumCard, { album });
|
||||||
|
|
||||||
const albumImgElement = sut.getByTestId('album-image');
|
const albumImgElement = sut.getByTestId('album-image');
|
||||||
await waitFor(() => expect(albumImgElement).toHaveAttribute('src', 'no-thumbnail.png'));
|
await waitFor(() => expect(albumImgElement).toHaveAttribute('src'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('dispatches custom "click" event with the album in context', async () => {
|
it('dispatches custom "click" event with the album in context', async () => {
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,13 @@
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
|
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
|
||||||
import CircleIconButton from '../shared-components/circle-icon-button.svelte';
|
import CircleIconButton from '../shared-components/circle-icon-button.svelte';
|
||||||
|
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
|
|
||||||
const NO_THUMBNAIL = 'no-thumbnail.png';
|
|
||||||
|
|
||||||
let imageData = `/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=${ThumbnailFormat.Webp}`;
|
let imageData = `/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=${ThumbnailFormat.Webp}`;
|
||||||
if (!album.albumThumbnailAssetId) {
|
if (!album.albumThumbnailAssetId) {
|
||||||
imageData = NO_THUMBNAIL;
|
imageData = noThumbnailUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatchClick = createEventDispatcher<OnClick>();
|
const dispatchClick = createEventDispatcher<OnClick>();
|
||||||
|
|
@ -51,7 +50,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || NO_THUMBNAIL;
|
imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || noThumbnailUrl;
|
||||||
});
|
});
|
||||||
|
|
||||||
const locale = navigator.language;
|
const locale = navigator.language;
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
import { bulkDownload } from '$lib/utils/asset-utils';
|
import { bulkDownload } from '$lib/utils/asset-utils';
|
||||||
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||||
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
|
|
@ -329,12 +330,8 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showAlbumOptionsMenu = (event: CustomEvent) => {
|
const showAlbumOptionsMenu = ({ x, y }: MouseEvent) => {
|
||||||
contextMenuPosition = {
|
contextMenuPosition = { x, y };
|
||||||
x: event.detail.mouseEvent.x,
|
|
||||||
y: event.detail.mouseEvent.y
|
|
||||||
};
|
|
||||||
|
|
||||||
isShowAlbumOptions = !isShowAlbumOptions;
|
isShowAlbumOptions = !isShowAlbumOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -419,13 +416,7 @@
|
||||||
class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
|
class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
|
||||||
href="https://immich.app"
|
href="https://immich.app"
|
||||||
>
|
>
|
||||||
<img
|
<ImmichLogo height={30} width={30} />
|
||||||
src="/immich-logo.svg"
|
|
||||||
alt="immich logo"
|
|
||||||
height="30"
|
|
||||||
width="30"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
<h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">
|
<h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">
|
||||||
IMMICH
|
IMMICH
|
||||||
</h1>
|
</h1>
|
||||||
|
|
@ -472,7 +463,7 @@
|
||||||
{#if !isPublicShared}
|
{#if !isPublicShared}
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
title="Album options"
|
title="Album options"
|
||||||
on:click={(event) => showAlbumOptionsMenu(event)}
|
on:click={showAlbumOptionsMenu}
|
||||||
logo={DotsVertical}
|
logo={DotsVertical}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
import Link from 'svelte-material-icons/Link.svelte';
|
import Link from 'svelte-material-icons/Link.svelte';
|
||||||
import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
|
import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
export let sharedUsersInAlbum: Set<UserResponseDto>;
|
export let sharedUsersInAlbum: Set<UserResponseDto>;
|
||||||
|
|
@ -53,7 +54,7 @@
|
||||||
<BaseModal on:close={() => dispatch('close')}>
|
<BaseModal on:close={() => dispatch('close')}>
|
||||||
<svelte:fragment slot="title">
|
<svelte:fragment slot="title">
|
||||||
<span class="flex gap-2 place-items-center">
|
<span class="flex gap-2 place-items-center">
|
||||||
<img src="/immich-logo.svg" width="24" alt="Immich" draggable="false" />
|
<ImmichLogo width={24} />
|
||||||
<p class="font-medium">Invite to album</p>
|
<p class="font-medium">Invite to album</p>
|
||||||
</span>
|
</span>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,8 @@
|
||||||
let contextMenuPosition = { x: 0, y: 0 };
|
let contextMenuPosition = { x: 0, y: 0 };
|
||||||
let isShowAssetOptions = false;
|
let isShowAssetOptions = false;
|
||||||
|
|
||||||
const showOptionsMenu = (event: CustomEvent) => {
|
const showOptionsMenu = ({ x, y }: MouseEvent) => {
|
||||||
contextMenuPosition = {
|
contextMenuPosition = { x, y };
|
||||||
x: event.detail.mouseEvent.x,
|
|
||||||
y: event.detail.mouseEvent.y
|
|
||||||
};
|
|
||||||
|
|
||||||
isShowAssetOptions = !isShowAssetOptions;
|
isShowAssetOptions = !isShowAssetOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -101,11 +97,7 @@
|
||||||
|
|
||||||
{#if isOwner}
|
{#if isOwner}
|
||||||
<CircleIconButton logo={DeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
|
<CircleIconButton logo={DeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
|
||||||
<CircleIconButton
|
<CircleIconButton logo={DotsVertical} on:click={showOptionsMenu} title="More" />
|
||||||
logo={DotsVertical}
|
|
||||||
on:click={(event) => showOptionsMenu(event)}
|
|
||||||
title="More"
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,7 @@
|
||||||
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
|
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<PhotoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />
|
<PhotoViewer {publicSharedKey} {asset} on:close={closeViewer} />
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<VideoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />
|
<VideoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,21 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { api, AssetResponseDto } from '@api';
|
import { api, AssetResponseDto } from '@api';
|
||||||
import Keydown from 'svelte-keydown';
|
import { copyImageToClipboard } from 'copy-image-clipboard';
|
||||||
import {
|
import {
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '../shared-components/notification/notification';
|
} from '../shared-components/notification/notification';
|
||||||
|
|
||||||
export let assetId: string;
|
export let asset: AssetResponseDto;
|
||||||
export let publicSharedKey = '';
|
export let publicSharedKey = '';
|
||||||
|
|
||||||
let assetInfo: AssetResponseDto;
|
|
||||||
let assetData: string;
|
let assetData: string;
|
||||||
|
|
||||||
let copyImageToClipboard: (src: string) => Promise<Blob>;
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const { data } = await api.assetApi.getAssetById(assetId, {
|
|
||||||
params: {
|
|
||||||
key: publicSharedKey
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assetInfo = data;
|
|
||||||
|
|
||||||
//Import hack :( see https://github.com/vadimkorr/svelte-carousel/issues/27#issuecomment-851022295
|
|
||||||
const module = await import('copy-image-clipboard');
|
|
||||||
copyImageToClipboard = module.copyImageToClipboard;
|
|
||||||
});
|
|
||||||
|
|
||||||
const loadAssetData = async () => {
|
const loadAssetData = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.serveFile(assetInfo.id, false, true, {
|
const { data } = await api.assetApi.serveFile(asset.id, false, true, {
|
||||||
params: {
|
params: {
|
||||||
key: publicSharedKey
|
key: publicSharedKey
|
||||||
},
|
},
|
||||||
|
|
@ -51,42 +33,51 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeypress = async (keyEvent: CustomEvent<string>) => {
|
const handleKeypress = async ({ metaKey, ctrlKey, key }: KeyboardEvent) => {
|
||||||
if (keyEvent.detail == 'Control-c' || keyEvent.detail == 'Meta-c') {
|
if ((metaKey || ctrlKey) && key === 'c') {
|
||||||
await doCopy();
|
await doCopy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doCopy = async () => {
|
export const doCopy = async () => {
|
||||||
await copyImageToClipboard(assetData);
|
try {
|
||||||
notificationController.show({
|
await copyImageToClipboard(assetData);
|
||||||
type: NotificationType.Info,
|
notificationController.show({
|
||||||
message: 'Copied image to clipboard.',
|
type: NotificationType.Info,
|
||||||
timeout: 3000
|
message: 'Copied image to clipboard.',
|
||||||
});
|
timeout: 3000
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
notificationController.show({
|
||||||
|
type: NotificationType.Error,
|
||||||
|
message: 'Copying image to clipboard failed. Click here to learn more.',
|
||||||
|
timeout: 5000,
|
||||||
|
action: {
|
||||||
|
type: 'link',
|
||||||
|
target:
|
||||||
|
'https://github.com/LuanEdCosta/copy-image-clipboard#enable-clipboard-api-features-in-firefox'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Keydown on:combo={handleKeypress} />
|
<svelte:window on:keydown={handleKeypress} on:copyImage={doCopy} />
|
||||||
|
|
||||||
<svelte:window on:copyImage={async () => await doCopy()} />
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
transition:fade={{ duration: 150 }}
|
transition:fade={{ duration: 150 }}
|
||||||
class="flex place-items-center place-content-center h-full select-none"
|
class="flex place-items-center place-content-center h-full select-none"
|
||||||
>
|
>
|
||||||
{#if assetInfo}
|
{#await loadAssetData()}
|
||||||
{#await loadAssetData()}
|
<LoadingSpinner />
|
||||||
<LoadingSpinner />
|
{:then assetData}
|
||||||
{:then assetData}
|
<img
|
||||||
<img
|
transition:fade={{ duration: 150 }}
|
||||||
transition:fade={{ duration: 150 }}
|
src={assetData}
|
||||||
src={assetData}
|
alt={asset.id}
|
||||||
alt={assetId}
|
class="object-contain h-full transition-all"
|
||||||
class="object-contain h-full transition-all"
|
draggable="false"
|
||||||
loading="lazy"
|
/>
|
||||||
draggable="false"
|
{/await}
|
||||||
/>
|
|
||||||
{/await}
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { api, AssetResponseDto, getFileUrl } from '@api';
|
import { getFileUrl } from '@api';
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
export let publicSharedKey = '';
|
export let publicSharedKey = '';
|
||||||
let asset: AssetResponseDto;
|
|
||||||
|
|
||||||
let isVideoLoading = true;
|
let isVideoLoading = true;
|
||||||
let videoUrl: string;
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
onMount(async () => {
|
const handleCanPlay = (ev: Event & { currentTarget: HTMLVideoElement }) => {
|
||||||
const { data: assetInfo } = await api.assetApi.getAssetById(assetId, {
|
const playerNode = ev.currentTarget;
|
||||||
params: {
|
|
||||||
key: publicSharedKey
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await loadVideoData(assetInfo);
|
|
||||||
|
|
||||||
asset = assetInfo;
|
|
||||||
});
|
|
||||||
|
|
||||||
const loadVideoData = async (assetInfo: AssetResponseDto) => {
|
|
||||||
isVideoLoading = true;
|
|
||||||
|
|
||||||
videoUrl = getFileUrl(assetInfo.id, false, true, publicSharedKey);
|
|
||||||
|
|
||||||
return assetInfo;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCanPlay = (ev: Event) => {
|
|
||||||
const playerNode = ev.target as HTMLVideoElement;
|
|
||||||
|
|
||||||
playerNode.muted = true;
|
playerNode.muted = true;
|
||||||
playerNode.play();
|
playerNode.play();
|
||||||
|
|
@ -48,21 +25,19 @@
|
||||||
transition:fade={{ duration: 150 }}
|
transition:fade={{ duration: 150 }}
|
||||||
class="flex place-items-center place-content-center h-full select-none"
|
class="flex place-items-center place-content-center h-full select-none"
|
||||||
>
|
>
|
||||||
{#if asset}
|
<video
|
||||||
<video
|
controls
|
||||||
controls
|
class="h-full object-contain"
|
||||||
class="h-full object-contain"
|
src={getFileUrl(assetId, false, true, publicSharedKey)}
|
||||||
on:canplay={handleCanPlay}
|
on:canplay={handleCanPlay}
|
||||||
on:ended={() => dispatch('onVideoEnded')}
|
on:ended={() => dispatch('onVideoEnded')}
|
||||||
>
|
>
|
||||||
<source src={videoUrl} type="video/mp4" />
|
<track kind="captions" />
|
||||||
<track kind="captions" />
|
</video>
|
||||||
</video>
|
|
||||||
|
|
||||||
{#if isVideoLoading}
|
{#if isVideoLoading}
|
||||||
<div class="absolute flex place-items-center place-content-center">
|
<div class="absolute flex place-items-center place-content-center">
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import { api } from '@api';
|
import { api } from '@api';
|
||||||
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
|
|
||||||
let error: string;
|
let error: string;
|
||||||
let success: string;
|
let success: string;
|
||||||
|
|
||||||
|
|
@ -55,14 +56,7 @@
|
||||||
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
|
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
||||||
<img
|
<ImmichLogo class="text-center" height="100" width="100" />
|
||||||
class="text-center"
|
|
||||||
src="/immich-logo.svg"
|
|
||||||
height="100"
|
|
||||||
width="100"
|
|
||||||
alt="immich-logo"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
||||||
Admin Registration
|
Admin Registration
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, UserResponseDto } from '@api';
|
import { api, UserResponseDto } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
|
|
||||||
export let user: UserResponseDto;
|
export let user: UserResponseDto;
|
||||||
let error: string;
|
let error: string;
|
||||||
|
|
@ -47,14 +48,7 @@
|
||||||
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
|
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
||||||
<img
|
<ImmichLogo class="text-center" height="100" width="100" />
|
||||||
class="text-center"
|
|
||||||
src="/immich-logo.svg"
|
|
||||||
height="100"
|
|
||||||
width="100"
|
|
||||||
alt="immich-logo"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
||||||
Change Password
|
Change Password
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api } from '@api';
|
import { api } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
import {
|
import {
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType
|
NotificationType
|
||||||
|
|
@ -80,14 +81,7 @@
|
||||||
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
|
class="border bg-immich-bg dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-3xl py-8 dark:text-immich-dark-fg"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
||||||
<img
|
<ImmichLogo class="text-center" height="100" width="100" />
|
||||||
class="text-center"
|
|
||||||
src="/immich-logo.svg"
|
|
||||||
height="100"
|
|
||||||
width="100"
|
|
||||||
alt="immich-logo"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
|
||||||
Create new user
|
Create new user
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { api, oauth, OAuthConfigResponseDto } from '@api';
|
import { api, oauth, OAuthConfigResponseDto } from '@api';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
|
|
||||||
let error: string;
|
let error: string;
|
||||||
let email = '';
|
let email = '';
|
||||||
|
|
@ -77,14 +78,7 @@
|
||||||
class="border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-md py-8"
|
class="border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-4 shadow-sm w-[500px] max-w-[95vw] rounded-md py-8"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
||||||
<img
|
<ImmichLogo class="text-center" height="100" width="100" />
|
||||||
class="text-center"
|
|
||||||
src="/immich-logo.svg"
|
|
||||||
height="100"
|
|
||||||
width="100"
|
|
||||||
alt="immich-logo"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">Login</h1>
|
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">Login</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '../shared-components/notification/notification';
|
} from '../shared-components/notification/notification';
|
||||||
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
|
|
||||||
export let sharedLink: SharedLinkResponseDto;
|
export let sharedLink: SharedLinkResponseDto;
|
||||||
export let isOwned: boolean;
|
export let isOwned: boolean;
|
||||||
|
|
@ -122,7 +123,7 @@
|
||||||
class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
|
class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
|
||||||
href="https://immich.app"
|
href="https://immich.app"
|
||||||
>
|
>
|
||||||
<img src="/immich-logo.svg" alt="immich logo" height="30" width="30" draggable="false" />
|
<ImmichLogo height="30" width="30" />
|
||||||
<h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">
|
<h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">
|
||||||
IMMICH
|
IMMICH
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,21 @@
|
||||||
/**
|
/**
|
||||||
* This is the circle icon component.
|
* This is the circle icon component.
|
||||||
*/
|
*/
|
||||||
import { createEventDispatcher } from 'svelte';
|
import type Icon from 'svelte-material-icons/AbTesting.svelte';
|
||||||
|
|
||||||
// TODO: why any here?
|
export let logo: typeof Icon;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export let logo: any;
|
|
||||||
export let backgroundColor = 'transparent';
|
export let backgroundColor = 'transparent';
|
||||||
export let hoverColor = '#e2e7e9';
|
export let hoverColor = '#e2e7e9';
|
||||||
export let size = '24';
|
export let size = '24';
|
||||||
export let title = '';
|
export let title = '';
|
||||||
let iconButton: HTMLButtonElement;
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if (iconButton) {
|
|
||||||
iconButton.style.backgroundColor = backgroundColor;
|
|
||||||
iconButton.style.setProperty('--immich-icon-button-hover-color', hoverColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
{title}
|
{title}
|
||||||
bind:this={iconButton}
|
style:backgroundColor
|
||||||
|
style:--immich-icon-button-hover-color={hoverColor}
|
||||||
class={`immich-circle-icon-button dark:text-immich-dark-fg hover:dark:text-immich-dark-gray rounded-full p-3 flex place-items-center place-content-center transition-all`}
|
class={`immich-circle-icon-button dark:text-immich-dark-fg hover:dark:text-immich-dark-gray rounded-full p-3 flex place-items-center place-content-center transition-all`}
|
||||||
on:click={(mouseEvent) => dispatch('click', { mouseEvent })}
|
on:click
|
||||||
>
|
>
|
||||||
<svelte:component this={logo} {size} />
|
<svelte:component this={logo} {size} />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
import ImmichLogo from './immich-logo.svelte';
|
||||||
|
|
||||||
export let dropHandler: (event: DragEvent) => void;
|
export let dropHandler: (event: DragEvent) => void;
|
||||||
export let dragOverHandler: (event: DragEvent) => void;
|
export let dragOverHandler: (event: DragEvent) => void;
|
||||||
|
|
@ -14,13 +15,6 @@
|
||||||
on:dragleave={dragLeaveHandler}
|
on:dragleave={dragLeaveHandler}
|
||||||
class="fixed inset-0 w-full h-full z-[1000] flex flex-col items-center justify-center bg-gray-100/90 dark:bg-immich-dark-bg/90 text-immich-dark-gray dark:text-immich-gray"
|
class="fixed inset-0 w-full h-full z-[1000] flex flex-col items-center justify-center bg-gray-100/90 dark:bg-immich-dark-bg/90 text-immich-dark-gray dark:text-immich-gray"
|
||||||
>
|
>
|
||||||
<img
|
<ImmichLogo height="200" width="200" class="animate-bounce pb-16" />
|
||||||
src="/immich-logo.svg"
|
|
||||||
alt="immich logo"
|
|
||||||
height="200"
|
|
||||||
width="200"
|
|
||||||
class="animate-bounce pb-16"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
<div class="text-2xl">Drop files anywhere to upload</div>
|
<div class="text-2xl">Drop files anywhere to upload</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import immichLogoUrl from '$lib/assets/immich-logo.svg';
|
||||||
|
|
||||||
|
export let draggable = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<img src={immichLogoUrl} alt="Immich Logo" {draggable} {...$$restProps} />
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import ThemeButton from '../theme-button.svelte';
|
import ThemeButton from '../theme-button.svelte';
|
||||||
import { AppRoute } from '../../../constants';
|
import { AppRoute } from '../../../constants';
|
||||||
import AccountInfoPanel from './account-info-panel.svelte';
|
import AccountInfoPanel from './account-info-panel.svelte';
|
||||||
|
import ImmichLogo from '../immich-logo.svelte';
|
||||||
export let user: UserResponseDto;
|
export let user: UserResponseDto;
|
||||||
export let shouldShowUploadButton = true;
|
export let shouldShowUploadButton = true;
|
||||||
|
|
||||||
|
|
@ -50,7 +51,7 @@
|
||||||
class="flex gap-2 place-items-center hover:cursor-pointer"
|
class="flex gap-2 place-items-center hover:cursor-pointer"
|
||||||
href="/photos"
|
href="/photos"
|
||||||
>
|
>
|
||||||
<img src="/immich-logo.svg" alt="immich logo" height="35" width="35" draggable="false" />
|
<ImmichLogo height="35" width="35" />
|
||||||
<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
|
<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
|
||||||
IMMICH
|
IMMICH
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type Icon from 'svelte-material-icons/AbTesting.svelte';
|
||||||
|
|
||||||
export let title: string;
|
export let title: string;
|
||||||
// TODO: why `any` here? There should be a expected type for this
|
export let logo: typeof Icon;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
export let logo: any;
|
|
||||||
export let isSelected: boolean;
|
export let isSelected: boolean;
|
||||||
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { asByteUnitString } from '$lib/utils/byte-units';
|
import { asByteUnitString } from '$lib/utils/byte-units';
|
||||||
import { UploadAsset } from '$lib/models/upload-asset';
|
import { UploadAsset } from '$lib/models/upload-asset';
|
||||||
|
import ImmichLogo from './immich-logo.svelte';
|
||||||
|
|
||||||
export let uploadAsset: UploadAsset;
|
export let uploadAsset: UploadAsset;
|
||||||
|
|
||||||
|
|
@ -16,13 +17,9 @@
|
||||||
>
|
>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
{#if showFallbackImage}
|
{#if showFallbackImage}
|
||||||
<img
|
<div in:fade={{ duration: 250 }}>
|
||||||
in:fade={{ duration: 250 }}
|
<ImmichLogo class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg" />
|
||||||
src="immich-logo.svg"
|
</div>
|
||||||
alt="Immich Logo"
|
|
||||||
class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
{:else}
|
{:else}
|
||||||
<img
|
<img
|
||||||
in:fade={{ duration: 250 }}
|
in:fade={{ duration: 250 }}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AlbumResponseDto, api, ThumbnailFormat, UserResponseDto } from '@api';
|
import { AlbumResponseDto, api, ThumbnailFormat, UserResponseDto } from '@api';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
export let user: UserResponseDto;
|
export let user: UserResponseDto;
|
||||||
|
|
||||||
const loadImageData = async (thubmnailId: string | null) => {
|
const loadImageData = async (thubmnailId: string | null) => {
|
||||||
if (thubmnailId == null) {
|
if (thubmnailId == null) {
|
||||||
return '/no-thumbnail.png';
|
return noThumbnailUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await api.assetApi.getAssetThumbnail(thubmnailId, ThumbnailFormat.Webp, {
|
const { data } = await api.assetApi.getAssetThumbnail(thubmnailId, ThumbnailFormat.Webp, {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
//
|
//
|
||||||
|
|
@ -33,7 +34,7 @@
|
||||||
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
||||||
<div class="flex border-b dark:border-b-immich-dark-gray place-items-center px-6 py-4">
|
<div class="flex border-b dark:border-b-immich-dark-gray place-items-center px-6 py-4">
|
||||||
<a class="flex gap-2 place-items-center hover:cursor-pointer" href="/photos">
|
<a class="flex gap-2 place-items-center hover:cursor-pointer" href="/photos">
|
||||||
<img src="/immich-logo.svg" alt="immich logo" height="35" width="35" draggable="false" />
|
<ImmichLogo height="35" width="35" />
|
||||||
<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
|
<h1 class="font-immich-title text-2xl text-immich-primary dark:text-immich-dark-primary">
|
||||||
IMMICH
|
IMMICH
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
|
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
|
||||||
import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
|
import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
|
||||||
import { fileUploadHandler } from '$lib/utils/file-uploader';
|
import { fileUploadHandler } from '$lib/utils/file-uploader';
|
||||||
|
import faviconUrl from '$lib/assets/favicon.png';
|
||||||
|
|
||||||
let shouldShowAnnouncement: boolean;
|
let shouldShowAnnouncement: boolean;
|
||||||
let localVersion: string;
|
let localVersion: string;
|
||||||
|
|
@ -80,6 +81,7 @@
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{$page.data.meta?.title || 'Web'} - Immich</title>
|
<title>{$page.data.meta?.title || 'Web'} - Immich</title>
|
||||||
{#if $page.data.meta}
|
{#if $page.data.meta}
|
||||||
|
<link rel="icon" href={faviconUrl} />
|
||||||
<meta name="description" content={$page.data.meta.description} />
|
<meta name="description" content={$page.data.meta.description} />
|
||||||
|
|
||||||
<!-- Facebook Meta Tags -->
|
<!-- Facebook Meta Tags -->
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="h-screen w-screen flex place-items-center place-content-center">
|
<section class="h-screen w-screen flex place-items-center place-content-center">
|
||||||
<div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
|
<div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
|
||||||
<div class="flex place-items-center place-content-center ">
|
<div class="flex place-items-center place-content-center ">
|
||||||
<img
|
<ImmichLogo class="text-center" height="200" width="200" />
|
||||||
class="text-center"
|
|
||||||
src="immich-logo.svg"
|
|
||||||
height="200"
|
|
||||||
width="200"
|
|
||||||
alt="immich-logo"
|
|
||||||
draggable="false"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<h1
|
<h1
|
||||||
class="text-4xl text-immich-primary dark:text-immich-dark-primary font-bold font-immich-title"
|
class="text-4xl text-immich-primary dark:text-immich-dark-primary font-bold font-immich-title"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||||
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
|
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
|
||||||
import { useAlbums } from './albums.bloc';
|
import { useAlbums } from './albums.bloc';
|
||||||
|
import empty1Url from '$lib/assets/empty-1.svg';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
|
@ -93,7 +94,7 @@
|
||||||
on:keydown={handleCreateAlbum}
|
on:keydown={handleCreateAlbum}
|
||||||
class="border dark:border-immich-dark-gray hover:bg-immich-primary/5 dark:hover:bg-immich-dark-primary/25 hover:cursor-pointer p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
|
class="border dark:border-immich-dark-gray hover:bg-immich-primary/5 dark:hover:bg-immich-dark-primary/25 hover:cursor-pointer p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
|
||||||
>
|
>
|
||||||
<img src="/empty-1.svg" alt="Empty shared album" width="500" draggable="false" />
|
<img src={empty1Url} alt="Empty shared album" width="500" draggable="false" />
|
||||||
|
|
||||||
<p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">
|
<p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">
|
||||||
Create an album to organize your photos and videos
|
Create an album to organize your photos and videos
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
import StarMinusOutline from 'svelte-material-icons/StarMinusOutline.svelte';
|
import StarMinusOutline from 'svelte-material-icons/StarMinusOutline.svelte';
|
||||||
import Error from '../+error.svelte';
|
import Error from '../+error.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import empty1Url from '$lib/assets/empty-1.svg';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
|
@ -126,7 +127,7 @@
|
||||||
<div
|
<div
|
||||||
class="border dark:border-immich-dark-gray hover:bg-immich-primary/5 dark:hover:bg-immich-dark-primary/25 hover:cursor-pointer p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
|
class="border dark:border-immich-dark-gray hover:bg-immich-primary/5 dark:hover:bg-immich-dark-primary/25 hover:cursor-pointer p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center"
|
||||||
>
|
>
|
||||||
<img src="/empty-1.svg" alt="Empty shared album" width="500" draggable="false" />
|
<img src={empty1Url} alt="Empty shared album" width="500" draggable="false" />
|
||||||
|
|
||||||
<p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">
|
<p class="text-center text-immich-text-gray-500 dark:text-immich-dark-fg">
|
||||||
Add favorites to quickly find your best pictures and videos
|
Add favorites to quickly find your best pictures and videos
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,8 @@
|
||||||
let isShowAlbumPicker = false;
|
let isShowAlbumPicker = false;
|
||||||
let addToSharedAlbum = false;
|
let addToSharedAlbum = false;
|
||||||
|
|
||||||
const handleShowMenu = (event: CustomEvent) => {
|
const handleShowMenu = ({ x, y }: MouseEvent) => {
|
||||||
contextMenuPosition = {
|
contextMenuPosition = { x, y };
|
||||||
x: event.detail.mouseEvent.x,
|
|
||||||
y: event.detail.mouseEvent.y
|
|
||||||
};
|
|
||||||
|
|
||||||
isShowAddMenu = !isShowAddMenu;
|
isShowAddMenu = !isShowAddMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { error } from '@sveltejs/kit';
|
||||||
import { getThumbnailUrl } from '$lib/utils/asset-utils';
|
import { getThumbnailUrl } from '$lib/utils/asset-utils';
|
||||||
import { serverApi, ThumbnailFormat } from '@api';
|
import { serverApi, ThumbnailFormat } from '@api';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
import featurePanelUrl from '$lib/assets/feature-panel.png';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, parent }) => {
|
export const load: PageServerLoad = async ({ params, parent }) => {
|
||||||
const { user } = await parent();
|
const { user } = await parent();
|
||||||
|
|
@ -23,7 +24,7 @@ export const load: PageServerLoad = async ({ params, parent }) => {
|
||||||
description: sharedLink.description || `${assetCount} shared photos & videos.`,
|
description: sharedLink.description || `${assetCount} shared photos & videos.`,
|
||||||
imageUrl: assetId
|
imageUrl: assetId
|
||||||
? getThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
|
? getThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
|
||||||
: 'feature-panel.png'
|
: featurePanelUrl
|
||||||
},
|
},
|
||||||
user
|
user
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import empty2Url from '$lib/assets/empty-2.svg';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
|
@ -94,7 +95,7 @@
|
||||||
<div
|
<div
|
||||||
class="border dark:border-immich-dark-gray p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center dark:text-immich-dark-fg"
|
class="border dark:border-immich-dark-gray p-5 w-[50%] m-auto mt-10 bg-gray-50 dark:bg-immich-dark-gray rounded-3xl flex flex-col place-content-center place-items-center dark:text-immich-dark-fg"
|
||||||
>
|
>
|
||||||
<img src="/empty-2.svg" alt="Empty shared album" width="500" draggable="false" />
|
<img src={empty2Url} alt="Empty shared album" width="500" draggable="false" />
|
||||||
<p class="text-center text-immich-text-gray-500">
|
<p class="text-center text-immich-text-gray-500">
|
||||||
Create a shared album to share photos and videos with people in your network
|
Create a shared album to share photos and videos with people in your network
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 113 KiB |