1
0
mirror of https://github.com/immich-app/immich.git synced 2025-03-11 15:09:45 +02:00

resolve merge conflict

This commit is contained in:
Michel Heusschen 2023-02-12 06:15:27 +01:00
parent 0a58280f3d
commit 0e00805187
83 changed files with 1733 additions and 1101 deletions

View File

@ -39,7 +39,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.3.10"
flutter-version: "3.7.3"
cache: true
- name: Create the Keystore

View File

@ -19,7 +19,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.3.10'
flutter-version: '3.7.3'
- name: Install dependencies
run: dart pub get

View File

@ -49,7 +49,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.3.10'
flutter-version: '3.7.3'
- name: Run tests
working-directory: ./mobile
run: flutter test
@ -78,7 +78,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.3.10'
flutter-version: '3.7.3'
- name: Run integration tests
uses: reactivecircus/android-emulator-runner@v2.27.0
with:

View File

@ -2,6 +2,11 @@
xmlns:tools="http://schemas.android.com/tools">
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="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"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

View File

@ -7,7 +7,8 @@ void main() async {
await ImmichTestHelper.initialize();
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.acknowledgeNewServerVersion();
@ -17,15 +18,21 @@ void main() async {
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(
email: "demo@immich.app ",
email: "demo@immich.app ",
);
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 {
@ -33,13 +40,12 @@ void main() async {
await helper.loginHelper.acknowledgeNewServerVersion();
await helper.loginHelper.enterCredentials(
email: "demo.immich.app",
email: "demo.immich.app",
);
await tester.pump(const Duration(milliseconds: 300));
expect(find.text("login_form_err_invalid_email".tr()), findsOneWidget);
});
});
}

View File

@ -1,5 +1,5 @@
# 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.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@ -34,19 +34,8 @@ target 'Runner' do
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
# post_install do |installer|
# installer.pods_project.targets.each do |target|
# flutter_additional_ios_build_settings(target)
# 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
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@ -21,16 +21,18 @@ PODS:
- Flutter
- package_info_plus (0.4.5):
- Flutter
- path_provider_ios (0.0.1):
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- photo_manager (2.0.0):
- Flutter
- FlutterMacOS
- SAMKeychain (1.5.3)
- share_plus (0.0.1):
- Flutter
- shared_preferences_ios (0.0.1):
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.2):
- Flutter
- FMDB (>= 2.7.5)
@ -52,10 +54,10 @@ DEPENDENCIES:
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/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`)
- 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`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
@ -86,14 +88,14 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/isar_flutter_libs/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
url_launcher_ios:
@ -108,23 +110,23 @@ SPEC CHECKSUMS:
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: 05c3056158482c567a3e0cdab1351ceeee238a07
PODFILE CHECKSUM: c798208781ca5116c4a3d5927d689946791f0189
COCOAPODS: 1.11.3

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@ -201,6 +201,7 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -237,6 +238,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -341,7 +343,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
NEW_SETTING = "";
SDKROOT = iphoneos;
@ -360,7 +362,8 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -425,7 +428,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
NEW_SETTING = "";
ONLY_ACTIVE_ARCH = YES;
@ -475,7 +478,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
NEW_SETTING = "";
SDKROOT = iphoneos;
@ -495,7 +498,8 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -522,7 +526,8 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

File diff suppressed because one or more lines are too long

View File

@ -1,97 +1,101 @@
<?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">
<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>
<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>1.46.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>85</string>
<key>LSRequiresIPhoneOS</key>
<key>NSAllowsArbitraryLoads</key>
<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>
<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>

View File

@ -91,7 +91,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
children: [
Text(
'Add to album',
style: Theme.of(context).textTheme.headline2,
style: Theme.of(context).textTheme.displayMedium,
),
TextButton.icon(
icon: const Icon(Icons.add),

View File

@ -24,90 +24,97 @@ class AlbumThumbnailCard extends StatelessWidget {
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
return LayoutBuilder(
builder: (context, constraints) {
var cardSize = constraints.maxWidth;
var cardSize = constraints.maxWidth;
buildEmptyThumbnail() {
return Container(
height: cardSize,
width: cardSize,
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
),
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
buildEmptyThumbnail() {
return Container(
height: cardSize,
width: cardSize,
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
),
),
);
}
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
),
),
);
}
buildAlbumThumbnail() {
return CachedNetworkImage(
width: cardSize,
height: cardSize,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
imageUrl: getAlbumThumbnailUrl(
album,
type: ThumbnailFormat.JPEG,
),
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
);
}
buildAlbumThumbnail() {
return CachedNetworkImage(
width: cardSize,
height: cardSize,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
imageUrl: getAlbumThumbnailUrl(
album,
type: ThumbnailFormat.JPEG,
),
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
cacheKey:
getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
);
}
return GestureDetector(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.only(bottom: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
return GestureDetector(
onTap: onTap,
child: Flex(
direction: Axis.vertical,
children: [
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: album.albumThumbnailAssetId == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
),
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,
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: cardSize,
height: cardSize,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: album.albumThumbnailAssetId == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
).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()
],
)
],
),
),
],
),
),
);
);
},
);
}

View File

@ -34,7 +34,7 @@ class AlbumTitleTextField extends ConsumerWidget {
focusNode: albumTitleTextFieldFocusNode,
style: TextStyle(
fontSize: 28,
color: Colors.grey[700],
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
),
controller: albumTitleController,

View File

@ -19,7 +19,7 @@ class CreateAlbumPage extends HookConsumerWidget {
final List<Asset>? initialAssets;
const CreateAlbumPage({
Key? key,
Key? key,
required this.isSharedAlbum,
this.initialAssets,
}) : super(key: key);
@ -84,7 +84,7 @@ class CreateAlbumPage extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 200, left: 18),
child: Text(
'create_shared_album_page_share_add_assets',
style: Theme.of(context).textTheme.headline2?.copyWith(
style: Theme.of(context).textTheme.displayMedium?.copyWith(
fontSize: 12,
fontWeight: FontWeight.normal,
),
@ -214,7 +214,7 @@ class CreateAlbumPage extends HookConsumerWidget {
),
title: Text(
'share_create_album',
style: Theme.of(context).textTheme.headline2?.copyWith(
style: Theme.of(context).textTheme.displayMedium?.copyWith(
color: Theme.of(context).primaryColor,
),
).tr(),
@ -228,7 +228,9 @@ class CreateAlbumPage extends HookConsumerWidget {
'create_shared_album_page_share'.tr(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
color: albumTitleController.text.isEmpty
? Theme.of(context).disabledColor
: Theme.of(context).primaryColor,
),
),
),

View File

@ -15,6 +15,7 @@ class LibraryPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final albums = ref.watch(albumProvider);
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
useEffect(
() {
@ -122,9 +123,12 @@ class LibraryPage extends HookConsumerWidget {
child: Container(
decoration: BoxDecoration(
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: Icon(
@ -168,25 +172,22 @@ class LibraryPage extends HookConsumerWidget {
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12.0,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
),
),
),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.all(12),
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.grey[50],
side: BorderSide(
color: Theme.of(context).brightness == Brightness.dark
? Colors.grey[600]!
: Colors.grey[300]!,
color: isDarkMode ? Colors.grey[800]! : Colors.grey[300]!,
),
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(
childCount: sorted.length + 1,
(context, index) {
if (index == 0) {
if (index == 0) {
return buildCreateAlbumButton();
}

View File

@ -77,13 +77,13 @@ class SharingPage extends HookConsumerWidget {
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10), // if you need this
borderRadius: BorderRadius.circular(20),
side: const BorderSide(
color: Colors.grey,
width: 1,
width: 0.5,
),
),
color: Colors.transparent,
// color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Column(
@ -92,7 +92,7 @@ class SharingPage extends HookConsumerWidget {
Padding(
padding: const EdgeInsets.only(left: 5.0, bottom: 5),
child: Icon(
Icons.offline_share_outlined,
Icons.insert_photo_rounded,
size: 50,
color: Theme.of(context).primaryColor,
),
@ -101,7 +101,7 @@ class SharingPage extends HookConsumerWidget {
padding: const EdgeInsets.all(8.0),
child: Text(
'sharing_page_empty_list',
style: Theme.of(context).textTheme.headline3,
style: Theme.of(context).textTheme.displaySmall,
).tr(),
),
Padding(

View File

@ -14,53 +14,60 @@ class ExifBottomSheet extends HookConsumerWidget {
const ExifBottomSheet({Key? key, required this.assetDetail})
: super(key: key);
bool get showMap => assetDetail.latitude != null && assetDetail.longitude != null;
@override
Widget build(BuildContext context, WidgetRef ref) {
buildMap() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Container(
height: 150,
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(15)),
),
child: FlutterMap(
options: MapOptions(
center: LatLng(
assetDetail.latitude ?? 0,
assetDetail.longitude ?? 0,
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
height: 150,
width: constraints.maxWidth,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(15)),
),
zoom: 16.0,
),
layers: [
TileLayerOptions(
urlTemplate:
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c'],
attributionBuilder: (_) {
return const Text(
"© OpenStreetMap",
style: TextStyle(fontSize: 10),
);
},
),
MarkerLayerOptions(
markers: [
Marker(
anchorPos: AnchorPos.align(AnchorAlign.top),
point: LatLng(
assetDetail.latitude ?? 0,
assetDetail.longitude ?? 0,
),
builder: (ctx) => const Image(
image: AssetImage('assets/location-pin.png'),
),
child: FlutterMap(
options: MapOptions(
interactiveFlags: InteractiveFlag.none,
center: LatLng(
assetDetail.latitude ?? 0,
assetDetail.longitude ?? 0,
),
zoom: 16.0,
),
layers: [
TileLayerOptions(
urlTemplate:
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c'],
attributionBuilder: (_) {
return const Text(
"© OpenStreetMap",
style: TextStyle(fontSize: 10),
);
},
),
MarkerLayerOptions(
markers: [
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);
}
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(
child: Card(
shape: const RoundedRectangleBorder(
@ -102,104 +210,69 @@ class ExifBottomSheet extends HookConsumerWidget {
margin: const EdgeInsets.all(0),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
const Align(
alignment: Alignment.center,
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: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
// Two column
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Divider(
thickness: 1,
buildDragHeader(),
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(
"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),
)
const SizedBox(height: 50),
],
),
),
// Detail
Padding(
padding: const EdgeInsets.only(top: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
);
}
// One column
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
buildDragHeader(),
buildDate(),
const SizedBox(height: 16.0),
if (showMap)
Divider(
thickness: 1,
color: Colors.grey[600],
),
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} ",
),
),
],
),
),
const SizedBox(
height: 50,
),
],
const SizedBox(height: 16.0),
buildLocation(),
const SizedBox(height: 16.0),
Divider(
thickness: 1,
color: Colors.grey[600],
),
const SizedBox(height: 16.0),
buildDetail(),
const SizedBox(height: 50),
],
);
},
),
),
),

View File

@ -31,12 +31,10 @@ class TopControlAppBar extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
double iconSize = 18.0;
const double iconSize = 18.0;
Widget buildFavoriteButton() {
return IconButton(
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
onFavorite();
},
@ -60,12 +58,13 @@ class TopControlAppBar extends HookConsumerWidget {
color: Colors.grey[200],
),
),
actionsIconTheme: const IconThemeData(
size: iconSize,
),
actions: [
if (asset.isRemote) buildFavoriteButton(),
if (asset.livePhotoVideoId != null)
IconButton(
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
onToggleMotionVideo();
},
@ -81,17 +80,13 @@ class TopControlAppBar extends HookConsumerWidget {
),
if (!asset.isLocal)
IconButton(
iconSize: iconSize,
splashRadius: iconSize,
onPressed: onDownloadPressed,
icon: Icon(
Icons.cloud_download_rounded,
Icons.cloud_download_outlined,
color: Colors.grey[200],
),
),
IconButton(
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
onSharePressed();
},
@ -102,8 +97,6 @@ class TopControlAppBar extends HookConsumerWidget {
),
if (asset.isRemote)
IconButton(
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
onAddToAlbumPressed();
},
@ -113,8 +106,6 @@ class TopControlAppBar extends HookConsumerWidget {
),
),
IconButton(
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
onDeletePressed();
},
@ -124,13 +115,11 @@ class TopControlAppBar extends HookConsumerWidget {
),
),
IconButton(
iconSize: iconSize,
splashRadius: iconSize,
onPressed: () {
onMoreInfoPressed();
},
icon: Icon(
Icons.more_horiz_rounded,
Icons.info_outline_rounded,
color: Colors.grey[200],
),
),

View File

@ -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/favorite/providers/favorite_provider.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/services/app_settings.service.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) {
int sensitivity = 15;
int dxThreshhold = 50;
int dxThreshold = 50;
if (isZoomed.value) {
return;
@ -222,7 +222,7 @@ class GalleryViewerPage extends HookConsumerWidget {
// Check for delta from initial down point
final d = details.localPosition - localPosition;
// 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;
}
@ -247,8 +247,8 @@ class GalleryViewerPage extends HookConsumerWidget {
isPlayingMotionVideo: isPlayingMotionVideo.value,
asset: assetList[indexOfAsset.value],
isFavorite: ref.watch(favoriteProvider).contains(
assetList[indexOfAsset.value].id,
),
assetList[indexOfAsset.value].id,
),
onMoreInfoPressed: () {
showInfo();
},
@ -314,7 +314,7 @@ class GalleryViewerPage extends HookConsumerWidget {
? (context, event) {
final asset = assetList[indexOfAsset.value];
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)
final webPThumbnail = CachedNetworkImage(
imageUrl: getThumbnailUrl(

View File

@ -81,10 +81,6 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
child: GestureDetector(
onTap: removeSelection,
child: Chip(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
label: Text(
album.name,
style: TextStyle(
@ -119,10 +115,6 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Chip(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
label: Text(
album.name,
style: TextStyle(

View File

@ -75,22 +75,23 @@ class RenderList {
RenderList(this.elements);
static Map<String, List<Asset>> _groupAssets(
static Map<DateTime, List<Asset>> _groupAssets(
List<Asset> assets,
GroupAssetsBy groupBy,
) {
assets.sortByCompare<DateTime>(
(e) => e.createdAt,
(a, b) => b.compareTo(a),
);
if (groupBy == GroupAssetsBy.day) {
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) {
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);
groups.forEach((groupName, assets) {
try {
final date = assets.first.createdAt.toLocal();
groups.entries.sortedBy((e) =>e.key).reversed.forEach((entry) {
final date = entry.key;
final assets = entry.value;
try {
// Month title
if (groupBy == GroupAssetsBy.day &&
(lastDate == null || lastDate!.month != date.month)) {

View File

@ -92,11 +92,10 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
RenderAssetGridRow row,
bool scrolling,
) {
return LayoutBuilder(
builder: (context, constraints) {
final size = constraints.maxWidth / widget.assetsPerRow -
widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow;
widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow;
return Row(
key: Key("asset-row-${row.assets.first.id}"),
children: row.assets.mapIndexed((int index, Asset asset) {
@ -141,7 +140,7 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
style: TextStyle(
fontSize: 26,
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) {
final date = widget.renderList.elements[pos].date;
return Text(
DateFormat.yMMMd().format(date),
DateFormat.yMMMM().format(date),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,

View File

@ -22,7 +22,7 @@ class MonthlyTitleText extends StatelessWidget {
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Theme.of(context).textTheme.headline1?.color,
color: Theme.of(context).textTheme.displayLarge?.color,
),
),
),

View File

@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/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/models/album.dart';
@ -29,6 +29,8 @@ class ControlBottomAppBar extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
Widget renderActionButtons() {
return Row(
children: [
@ -60,7 +62,6 @@ class ControlBottomAppBar extends ConsumerWidget {
);
},
),
],
);
}
@ -75,7 +76,9 @@ class ControlBottomAppBar extends ConsumerWidget {
ScrollController scrollController,
) {
return Card(
elevation: 12.0,
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
surfaceTintColor: Colors.transparent,
elevation: 18.0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
@ -83,45 +86,37 @@ class ControlBottomAppBar extends ConsumerWidget {
),
),
margin: const EdgeInsets.all(0),
child: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
child: CustomScrollView(
controller: scrollController,
slivers: [
SliverToBoxAdapter(
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),
],
),
),
),
child: CustomScrollView(
controller: scrollController,
slivers: [
SliverToBoxAdapter(
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(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: AddToAlbumSliverList(
albums: albums,
sharedAlbums: sharedAlbums,
onAddToAlbum: onAddToAlbum,
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: AddToAlbumSliverList(
albums: albums,
sharedAlbums: sharedAlbums,
onAddToAlbum: onAddToAlbum,
),
),
const SliverToBoxAdapter(
child: SizedBox(height: 200),
)
],
),
),
const SliverToBoxAdapter(
child: SizedBox(height: 200),
)
],
),
);
},

View File

@ -1,5 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.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);
return AppBar(
centerTitle: true,
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
@ -44,10 +42,9 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
top: 5,
child: IconButton(
splashRadius: 25,
icon: Icon(
icon: const Icon(
Icons.face_outlined,
size: 30,
color: Theme.of(context).primaryColor,
),
onPressed: () {
Scaffold.of(context).openDrawer();
@ -112,16 +109,13 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
splashRadius: 25,
iconSize: 30,
icon: isEnableAutoBackup
? Icon(
? const Icon(
Icons.backup_rounded,
color: Theme.of(context).primaryColor,
)
: Badge(
padding: const EdgeInsets.all(4),
elevation: 3,
position: BadgePosition.bottomEnd(bottom: -4, end: -4),
badgeColor: Colors.white,
badgeContent: const Icon(
backgroundColor: Colors.white,
label: const Icon(
Icons.cloud_off_rounded,
size: 8,
color: Colors.indigo,

View File

@ -15,7 +15,7 @@ class ProfileDrawer extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
buildSignoutButton() {
buildSignOutButton() {
return ListTile(
horizontalTitleGap: 0,
leading: SizedBox(
@ -95,6 +95,9 @@ class ProfileDrawer extends HookConsumerWidget {
}
return Drawer(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -105,7 +108,7 @@ class ProfileDrawer extends HookConsumerWidget {
const ProfileDrawerHeader(),
buildSettingButton(),
buildAppLogButton(),
buildSignoutButton(),
buildSignOutButton(),
],
),
const ServerInfoBox()

View File

@ -22,7 +22,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
AuthenticationState authState = ref.watch(authenticationProvider);
final uploadProfileImageStatus =
ref.watch(uploadProfileImageProvider).status;
var dummmy = Random().nextInt(1024);
var dummy = Random().nextInt(1024);
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
buildUserProfileImage() {
@ -39,7 +39,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
return CircleAvatar(
radius: 35,
backgroundImage: NetworkImage(
'$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}',
'$endpoint/user/profile-image/${authState.userId}?d=${dummy++}',
),
backgroundColor: Colors.transparent,
);
@ -56,7 +56,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
return CircleAvatar(
radius: 35,
backgroundImage: NetworkImage(
'$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}',
'$endpoint/user/profile-image/${authState.userId}?d=${dummy++}',
),
backgroundColor: Colors.transparent,
);

View File

@ -53,6 +53,9 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
},
decoration: InputDecoration(
hintText: 'search_bar_hint'.tr(),
hintStyle: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),

View File

@ -11,7 +11,6 @@ class TabControllerPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
navigationRail(TabsRouter tabsRouter) {
return NavigationRail(
labelType: NavigationRailLabelType.all,
@ -35,32 +34,33 @@ class TabControllerPage extends ConsumerWidget {
right: 4,
bottom: 4,
),
icon: const Icon(Icons.photo_outlined),
icon: const Icon(Icons.photo_outlined),
selectedIcon: const Icon(Icons.photo),
label: const Text('tab_controller_nav_photos').tr(),
),
NavigationRailDestination(
padding: const EdgeInsets.all(4),
icon: const Icon(Icons.search_rounded),
selectedIcon: const Icon(Icons.search),
icon: const Icon(Icons.search_rounded),
selectedIcon: const Icon(Icons.search),
label: const Text('tab_controller_nav_search').tr(),
),
NavigationRailDestination(
padding: const EdgeInsets.all(4),
icon: const Icon(Icons.share_rounded),
selectedIcon: const Icon(Icons.share),
icon: const Icon(Icons.share_rounded),
selectedIcon: const Icon(Icons.share),
label: const Text('tab_controller_nav_sharing').tr(),
),
NavigationRailDestination(
padding: const EdgeInsets.all(4),
icon: const Icon(Icons.photo_album_outlined),
selectedIcon: const Icon(Icons.photo_album),
icon: const Icon(Icons.photo_album_outlined),
selectedIcon: const Icon(Icons.photo_album),
label: const Text('tab_controller_nav_library').tr(),
),
],
);
}
// ignore: unused_element
bottomNavigationBar(TabsRouter tabsRouter) {
return BottomNavigationBar(
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);
return AutoTabsRouter(
routes: [
@ -116,7 +168,7 @@ class TabControllerPage extends ConsumerWidget {
bool atHomeTab = tabsRouter.activeIndex == 0;
if (!atHomeTab) {
tabsRouter.setActiveIndex(0);
}
}
return atHomeTab;
},
@ -127,7 +179,7 @@ class TabControllerPage extends ConsumerWidget {
final Widget body;
if (constraints.maxWidth < medium) {
// Normal phone width
bottom = bottomNavigationBar(tabsRouter);
bottom = experimentalNavigationBar(tabsRouter);
body = FadeTransition(
opacity: animation,
child: child,
@ -146,13 +198,13 @@ class TabControllerPage extends ConsumerWidget {
),
],
);
} return Scaffold(
body: body,
bottomNavigationBar: multiselectEnabled
? null
: bottom,
);
},),
}
return Scaffold(
body: body,
bottomNavigationBar: multiselectEnabled ? null : bottom,
);
},
),
);
},
);

View File

@ -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(
useMaterial3: true,
brightness: Brightness.dark,
@ -43,7 +120,8 @@ ThemeData immichDarkTheme = ThemeData(
),
backgroundColor: const Color.fromARGB(255, 32, 33, 35),
foregroundColor: immichDarkThemePrimaryColor,
elevation: 1,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
),
bottomNavigationBarTheme: BottomNavigationBarThemeData(
@ -56,17 +134,17 @@ ThemeData immichDarkTheme = ThemeData(
scrimColor: Colors.white.withOpacity(0.1),
),
textTheme: TextTheme(
headline1: const TextStyle(
displayLarge: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 255, 255, 255),
),
headline2: const TextStyle(
displayMedium: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 255, 255, 255),
),
headline3: TextStyle(
displaySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: immichDarkThemePrimaryColor,
@ -79,57 +157,19 @@ ThemeData immichDarkTheme = ThemeData(
backgroundColor: immichDarkThemePrimaryColor,
),
),
);
ThemeData immichLightTheme = ThemeData(
useMaterial3: true,
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,
chipTheme: base.chipTheme,
popupMenuTheme: const PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
backgroundColor: immichBackgroundColor,
foregroundColor: Colors.indigo,
elevation: 1,
centerTitle: true,
surfaceTintColor: Colors.transparent,
),
bottomNavigationBarTheme: BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: immichBackgroundColor,
selectedItemColor: Colors.indigo,
),
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,
navigationBarTheme: NavigationBarThemeData(
indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
iconTheme: const MaterialStatePropertyAll(
IconThemeData(color: Colors.white),
),
backgroundColor: Colors.grey[900],
surfaceTintColor: Colors.transparent,
),
);

View File

@ -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';

View File

@ -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);
}
// ignore: deprecated_member_use_from_same_package

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
function mobile {
rm -rf ../mobile/openapi
@ -7,6 +7,10 @@ function mobile {
patch -u native_class.mustache <native_class.mustache.patch
cd ../../../../..
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 {

View File

@ -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';

View File

@ -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);
}

View File

@ -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
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',
'^\\@api(.*)$': '<rootDir>/src/api$1',
'^\\@test-data(.*)$': '<rootDir>/src/test-data$1'

48
web/package-lock.json generated
View File

@ -17,7 +17,6 @@
"lodash-es": "^4.17.21",
"luxon": "^3.1.1",
"socket.io-client": "^4.5.1",
"svelte-keydown": "^0.5.0",
"svelte-material-icons": "^2.0.2"
},
"devDependencies": {
@ -45,6 +44,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^4.0.0",
"factory.ts": "^1.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.0.2",
"jest-environment-jsdom": "^29.0.2",
"postcss": "^8.4.13",
@ -53,7 +53,6 @@
"svelte": "^3.44.0",
"svelte-check": "^2.7.1",
"svelte-jester": "^2.3.2",
"svelte-keydown": "^0.5.0",
"svelte-preprocess": "^4.10.7",
"tailwindcss": "^3.0.24",
"tslib": "^2.3.1",
@ -6202,6 +6201,12 @@
"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": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -6337,6 +6342,18 @@
"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": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
@ -10573,12 +10590,6 @@
"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": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svelte-material-icons/-/svelte-material-icons-2.0.4.tgz",
@ -15822,6 +15833,12 @@
"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": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -15918,6 +15935,15 @@
"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": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
@ -18997,12 +19023,6 @@
"dev": true,
"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": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svelte-material-icons/-/svelte-material-icons-2.0.4.tgz",

View File

@ -43,6 +43,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^4.0.0",
"factory.ts": "^1.2.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.0.2",
"jest-environment-jsdom": "^29.0.2",
"postcss": "^8.4.13",
@ -51,7 +52,6 @@
"svelte": "^3.44.0",
"svelte-check": "^2.7.1",
"svelte-jester": "^2.3.2",
"svelte-keydown": "^0.5.0",
"svelte-preprocess": "^4.10.7",
"tailwindcss": "^3.0.24",
"tslib": "^2.3.1",
@ -69,7 +69,6 @@
"lodash-es": "^4.17.21",
"luxon": "^3.1.1",
"socket.io-client": "^4.5.1",
"svelte-keydown": "^0.5.0",
"svelte-material-icons": "^2.0.2"
}
}

View File

@ -4,13 +4,13 @@
@font-face {
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-face {
font-family: 'Snowburst One';
src: url('/fonts/SnowburstOne-Regular.ttf') format('truetype');
src: url('$lib/assets/fonts/SnowburstOne-Regular.ttf') format('truetype');
}
:root {

View File

@ -2,7 +2,6 @@
<html lang="en" class="dark">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 584 B

View File

@ -1,8 +1,8 @@
<script lang="ts">
import type Icon from 'svelte-material-icons/AbTesting.svelte';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let logo: any;
export let logo: typeof Icon;
export let title: string;
export let value: string;
export let unit: string | undefined = undefined;
@ -13,13 +13,10 @@
}
const maxLength = 13;
let result = '';
const valueLength = parseInt(value).toString().length;
const zeroLength = maxLength - valueLength;
for (let i = 0; i < zeroLength; i++) {
result += '0';
}
return result;
return '0'.repeat(zeroLength);
};
</script>

View File

@ -44,10 +44,10 @@ describe('AlbumCard component', () => {
const albumDetailsElement = sut.getByTestId('album-details');
const detailsText = `${count} items` + (shared ? ' . Shared' : '');
expect(albumImgElement).toHaveAttribute('src', 'no-thumbnail.png');
expect(albumImgElement).toHaveAttribute('src');
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(apiMock.assetApi.getAssetThumbnail).not.toHaveBeenCalled();
@ -108,7 +108,7 @@ describe('AlbumCard component', () => {
sut = render(AlbumCard, { album });
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 () => {

View File

@ -16,14 +16,13 @@
import { createEventDispatcher, onMount } from 'svelte';
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
import CircleIconButton from '../shared-components/circle-icon-button.svelte';
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
export let album: AlbumResponseDto;
const NO_THUMBNAIL = 'no-thumbnail.png';
let imageData = `/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=${ThumbnailFormat.Webp}`;
if (!album.albumThumbnailAssetId) {
imageData = NO_THUMBNAIL;
imageData = noThumbnailUrl;
}
const dispatchClick = createEventDispatcher<OnClick>();
@ -51,7 +50,7 @@
};
onMount(async () => {
imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || NO_THUMBNAIL;
imageData = (await loadHighQualityThumbnail(album.albumThumbnailAssetId)) || noThumbnailUrl;
});
const locale = navigator.language;

View File

@ -40,6 +40,7 @@
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { bulkDownload } from '$lib/utils/asset-utils';
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
export let album: AlbumResponseDto;
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
@ -329,12 +330,8 @@
}
};
const showAlbumOptionsMenu = (event: CustomEvent) => {
contextMenuPosition = {
x: event.detail.mouseEvent.x,
y: event.detail.mouseEvent.y
};
const showAlbumOptionsMenu = ({ x, y }: MouseEvent) => {
contextMenuPosition = { x, y };
isShowAlbumOptions = !isShowAlbumOptions;
};
@ -419,13 +416,7 @@
class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
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">
IMMICH
</h1>
@ -472,7 +463,7 @@
{#if !isPublicShared}
<CircleIconButton
title="Album options"
on:click={(event) => showAlbumOptionsMenu(event)}
on:click={showAlbumOptionsMenu}
logo={DotsVertical}
/>
{/if}

View File

@ -6,6 +6,7 @@
import Link from 'svelte-material-icons/Link.svelte';
import ShareCircle from 'svelte-material-icons/ShareCircle.svelte';
import { goto } from '$app/navigation';
import ImmichLogo from '../shared-components/immich-logo.svelte';
export let album: AlbumResponseDto;
export let sharedUsersInAlbum: Set<UserResponseDto>;
@ -53,7 +54,7 @@
<BaseModal on:close={() => dispatch('close')}>
<svelte:fragment slot="title">
<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>
</span>
</svelte:fragment>

View File

@ -31,12 +31,8 @@
let contextMenuPosition = { x: 0, y: 0 };
let isShowAssetOptions = false;
const showOptionsMenu = (event: CustomEvent) => {
contextMenuPosition = {
x: event.detail.mouseEvent.x,
y: event.detail.mouseEvent.y
};
const showOptionsMenu = ({ x, y }: MouseEvent) => {
contextMenuPosition = { x, y };
isShowAssetOptions = !isShowAssetOptions;
};
@ -101,11 +97,7 @@
{#if isOwner}
<CircleIconButton logo={DeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
<CircleIconButton
logo={DotsVertical}
on:click={(event) => showOptionsMenu(event)}
title="More"
/>
<CircleIconButton logo={DotsVertical} on:click={showOptionsMenu} title="More" />
{/if}
</div>
</div>

View File

@ -304,7 +304,7 @@
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
/>
{:else}
<PhotoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />
<PhotoViewer {publicSharedKey} {asset} on:close={closeViewer} />
{/if}
{:else}
<VideoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />

View File

@ -1,39 +1,21 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { onMount } from 'svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { api, AssetResponseDto } from '@api';
import Keydown from 'svelte-keydown';
import { copyImageToClipboard } from 'copy-image-clipboard';
import {
notificationController,
NotificationType
} from '../shared-components/notification/notification';
export let assetId: string;
export let asset: AssetResponseDto;
export let publicSharedKey = '';
let assetInfo: AssetResponseDto;
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 () => {
try {
const { data } = await api.assetApi.serveFile(assetInfo.id, false, true, {
const { data } = await api.assetApi.serveFile(asset.id, false, true, {
params: {
key: publicSharedKey
},
@ -51,42 +33,51 @@
}
};
const handleKeypress = async (keyEvent: CustomEvent<string>) => {
if (keyEvent.detail == 'Control-c' || keyEvent.detail == 'Meta-c') {
const handleKeypress = async ({ metaKey, ctrlKey, key }: KeyboardEvent) => {
if ((metaKey || ctrlKey) && key === 'c') {
await doCopy();
}
};
export const doCopy = async () => {
await copyImageToClipboard(assetData);
notificationController.show({
type: NotificationType.Info,
message: 'Copied image to clipboard.',
timeout: 3000
});
try {
await copyImageToClipboard(assetData);
notificationController.show({
type: NotificationType.Info,
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>
<Keydown on:combo={handleKeypress} />
<svelte:window on:copyImage={async () => await doCopy()} />
<svelte:window on:keydown={handleKeypress} on:copyImage={doCopy} />
<div
transition:fade={{ duration: 150 }}
class="flex place-items-center place-content-center h-full select-none"
>
{#if assetInfo}
{#await loadAssetData()}
<LoadingSpinner />
{:then assetData}
<img
transition:fade={{ duration: 150 }}
src={assetData}
alt={assetId}
class="object-contain h-full transition-all"
loading="lazy"
draggable="false"
/>
{/await}
{/if}
{#await loadAssetData()}
<LoadingSpinner />
{:then assetData}
<img
transition:fade={{ duration: 150 }}
src={assetData}
alt={asset.id}
class="object-contain h-full transition-all"
draggable="false"
/>
{/await}
</div>

View File

@ -1,40 +1,17 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { createEventDispatcher, onMount } from 'svelte';
import { createEventDispatcher } from '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 publicSharedKey = '';
let asset: AssetResponseDto;
let isVideoLoading = true;
let videoUrl: string;
const dispatch = createEventDispatcher();
onMount(async () => {
const { data: assetInfo } = await api.assetApi.getAssetById(assetId, {
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;
const handleCanPlay = (ev: Event & { currentTarget: HTMLVideoElement }) => {
const playerNode = ev.currentTarget;
playerNode.muted = true;
playerNode.play();
@ -48,21 +25,19 @@
transition:fade={{ duration: 150 }}
class="flex place-items-center place-content-center h-full select-none"
>
{#if asset}
<video
controls
class="h-full object-contain"
on:canplay={handleCanPlay}
on:ended={() => dispatch('onVideoEnded')}
>
<source src={videoUrl} type="video/mp4" />
<track kind="captions" />
</video>
<video
controls
class="h-full object-contain"
src={getFileUrl(assetId, false, true, publicSharedKey)}
on:canplay={handleCanPlay}
on:ended={() => dispatch('onVideoEnded')}
>
<track kind="captions" />
</video>
{#if isVideoLoading}
<div class="absolute flex place-items-center place-content-center">
<LoadingSpinner />
</div>
{/if}
{#if isVideoLoading}
<div class="absolute flex place-items-center place-content-center">
<LoadingSpinner />
</div>
{/if}
</div>

View File

@ -1,7 +1,8 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { api } from '@api';
import ImmichLogo from '../shared-components/immich-logo.svelte';
let error: 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"
>
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
<img
class="text-center"
src="/immich-logo.svg"
height="100"
width="100"
alt="immich-logo"
draggable="false"
/>
<ImmichLogo class="text-center" height="100" width="100" />
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
Admin Registration
</h1>

View File

@ -1,6 +1,7 @@
<script lang="ts">
import { api, UserResponseDto } from '@api';
import { createEventDispatcher } from 'svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
export let user: UserResponseDto;
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"
>
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
<img
class="text-center"
src="/immich-logo.svg"
height="100"
width="100"
alt="immich-logo"
draggable="false"
/>
<ImmichLogo class="text-center" height="100" width="100" />
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
Change Password
</h1>

View File

@ -1,6 +1,7 @@
<script lang="ts">
import { api } from '@api';
import { createEventDispatcher } from 'svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
import {
notificationController,
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"
>
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
<img
class="text-center"
src="/immich-logo.svg"
height="100"
width="100"
alt="immich-logo"
draggable="false"
/>
<ImmichLogo class="text-center" height="100" width="100" />
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">
Create new user
</h1>

View File

@ -5,6 +5,7 @@
import { handleError } from '$lib/utils/handle-error';
import { api, oauth, OAuthConfigResponseDto } from '@api';
import { createEventDispatcher, onMount } from 'svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
let error: string;
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"
>
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
<img
class="text-center"
src="/immich-logo.svg"
height="100"
width="100"
alt="immich-logo"
draggable="false"
/>
<ImmichLogo class="text-center" height="100" width="100" />
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium">Login</h1>
</div>

View File

@ -17,6 +17,7 @@
notificationController,
NotificationType
} from '../shared-components/notification/notification';
import ImmichLogo from '../shared-components/immich-logo.svelte';
export let sharedLink: SharedLinkResponseDto;
export let isOwned: boolean;
@ -122,7 +123,7 @@
class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
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">
IMMICH
</h1>

View File

@ -2,31 +2,21 @@
/**
* This is the circle icon component.
*/
import { createEventDispatcher } from 'svelte';
import type Icon from 'svelte-material-icons/AbTesting.svelte';
// TODO: why any here?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let logo: any;
export let logo: typeof Icon;
export let backgroundColor = 'transparent';
export let hoverColor = '#e2e7e9';
export let size = '24';
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>
<button
{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`}
on:click={(mouseEvent) => dispatch('click', { mouseEvent })}
on:click
>
<svelte:component this={logo} {size} />
</button>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import ImmichLogo from './immich-logo.svelte';
export let dropHandler: (event: DragEvent) => void;
export let dragOverHandler: (event: DragEvent) => void;
@ -14,13 +15,6 @@
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"
>
<img
src="/immich-logo.svg"
alt="immich logo"
height="200"
width="200"
class="animate-bounce pb-16"
draggable="false"
/>
<ImmichLogo height="200" width="200" class="animate-bounce pb-16" />
<div class="text-2xl">Drop files anywhere to upload</div>
</div>

View File

@ -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} />

View File

@ -8,6 +8,7 @@
import ThemeButton from '../theme-button.svelte';
import { AppRoute } from '../../../constants';
import AccountInfoPanel from './account-info-panel.svelte';
import ImmichLogo from '../immich-logo.svelte';
export let user: UserResponseDto;
export let shouldShowUploadButton = true;
@ -50,7 +51,7 @@
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">
IMMICH
</h1>

View File

@ -1,8 +1,8 @@
<script lang="ts">
import type Icon from 'svelte-material-icons/AbTesting.svelte';
export let title: string;
// TODO: why `any` here? There should be a expected type for this
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let logo: any;
export let logo: typeof Icon;
export let isSelected: boolean;
import { createEventDispatcher } from 'svelte';

View File

@ -2,6 +2,7 @@
import { fade } from 'svelte/transition';
import { asByteUnitString } from '$lib/utils/byte-units';
import { UploadAsset } from '$lib/models/upload-asset';
import ImmichLogo from './immich-logo.svelte';
export let uploadAsset: UploadAsset;
@ -16,13 +17,9 @@
>
<div class="relative">
{#if showFallbackImage}
<img
in:fade={{ duration: 250 }}
src="immich-logo.svg"
alt="Immich Logo"
class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg"
draggable="false"
/>
<div in:fade={{ duration: 250 }}>
<ImmichLogo class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg" />
</div>
{:else}
<img
in:fade={{ duration: 250 }}

View File

@ -1,13 +1,14 @@
<script lang="ts">
import { AlbumResponseDto, api, ThumbnailFormat, UserResponseDto } from '@api';
import { fade } from 'svelte/transition';
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
export let album: AlbumResponseDto;
export let user: UserResponseDto;
const loadImageData = async (thubmnailId: string | null) => {
if (thubmnailId == null) {
return '/no-thumbnail.png';
return noThumbnailUrl;
}
const { data } = await api.assetApi.getAssetThumbnail(thubmnailId, ThumbnailFormat.Webp, {

View File

@ -9,6 +9,7 @@
NotificationType
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
const handleCopy = async () => {
//
@ -33,7 +34,7 @@
<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">
<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">
IMMICH
</h1>

View File

@ -13,6 +13,7 @@
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
import { fileUploadHandler } from '$lib/utils/file-uploader';
import faviconUrl from '$lib/assets/favicon.png';
let shouldShowAnnouncement: boolean;
let localVersion: string;
@ -80,6 +81,7 @@
<svelte:head>
<title>{$page.data.meta?.title || 'Web'} - Immich</title>
{#if $page.data.meta}
<link rel="icon" href={faviconUrl} />
<meta name="description" content={$page.data.meta.description} />
<!-- Facebook Meta Tags -->

View File

@ -1,18 +1,12 @@
<script lang="ts">
import { goto } from '$app/navigation';
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
</script>
<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 place-items-center place-content-center ">
<img
class="text-center"
src="immich-logo.svg"
height="200"
width="200"
alt="immich-logo"
draggable="false"
/>
<ImmichLogo class="text-center" height="200" width="200" />
</div>
<h1
class="text-4xl text-immich-primary dark:text-immich-dark-primary font-bold font-immich-title"

View File

@ -10,6 +10,7 @@
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
import { useAlbums } from './albums.bloc';
import empty1Url from '$lib/assets/empty-1.svg';
export let data: PageData;
@ -93,7 +94,7 @@
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"
>
<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">
Create an album to organize your photos and videos

View File

@ -13,6 +13,7 @@
import StarMinusOutline from 'svelte-material-icons/StarMinusOutline.svelte';
import Error from '../+error.svelte';
import type { PageData } from './$types';
import empty1Url from '$lib/assets/empty-1.svg';
export let data: PageData;
@ -126,7 +127,7 @@
<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"
>
<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">
Add favorites to quickly find your best pictures and videos

View File

@ -64,12 +64,8 @@
let isShowAlbumPicker = false;
let addToSharedAlbum = false;
const handleShowMenu = (event: CustomEvent) => {
contextMenuPosition = {
x: event.detail.mouseEvent.x,
y: event.detail.mouseEvent.y
};
const handleShowMenu = ({ x, y }: MouseEvent) => {
contextMenuPosition = { x, y };
isShowAddMenu = !isShowAddMenu;
};

View File

@ -4,6 +4,7 @@ import { error } from '@sveltejs/kit';
import { getThumbnailUrl } from '$lib/utils/asset-utils';
import { serverApi, ThumbnailFormat } from '@api';
import type { PageServerLoad } from './$types';
import featurePanelUrl from '$lib/assets/feature-panel.png';
export const load: PageServerLoad = async ({ params, parent }) => {
const { user } = await parent();
@ -23,7 +24,7 @@ export const load: PageServerLoad = async ({ params, parent }) => {
description: sharedLink.description || `${assetCount} shared photos & videos.`,
imageUrl: assetId
? getThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
: 'feature-panel.png'
: featurePanelUrl
},
user
};

View File

@ -12,6 +12,7 @@
notificationController,
NotificationType
} from '$lib/components/shared-components/notification/notification';
import empty2Url from '$lib/assets/empty-2.svg';
export let data: PageData;
@ -94,7 +95,7 @@
<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"
>
<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">
Create a shared album to share photos and videos with people in your network
</p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB