mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:49:11 -04:00 
			
		
		
		
	feat(web,server): user avatar color (#4779)
This commit is contained in:
		
							parent
							
								
									14c7187539
								
							
						
					
					
						commit
						d25a245049
					
				
							
								
								
									
										119
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										119
									
								
								cli/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @ -2355,6 +2355,12 @@ export interface OAuthConfigResponseDto { | ||||
|  * @interface PartnerResponseDto | ||||
|  */ | ||||
| export interface PartnerResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {UserAvatarColor} | ||||
|      * @memberof PartnerResponseDto | ||||
|      */ | ||||
|     'avatarColor': UserAvatarColor; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -2440,6 +2446,8 @@ export interface PartnerResponseDto { | ||||
|      */ | ||||
|     'updatedAt': string; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @ -4344,6 +4352,12 @@ export interface UpdateTagDto { | ||||
|  * @interface UpdateUserDto | ||||
|  */ | ||||
| export interface UpdateUserDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {UserAvatarColor} | ||||
|      * @memberof UpdateUserDto | ||||
|      */ | ||||
|     'avatarColor'?: UserAvatarColor; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -4399,6 +4413,8 @@ export interface UpdateUserDto { | ||||
|      */ | ||||
|     'storageLabel'?: string; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @ -4436,12 +4452,40 @@ export interface UsageByUserDto { | ||||
|      */ | ||||
|     'videos': number; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @enum {string} | ||||
|  */ | ||||
| 
 | ||||
| export const UserAvatarColor = { | ||||
|     Primary: 'primary', | ||||
|     Pink: 'pink', | ||||
|     Red: 'red', | ||||
|     Yellow: 'yellow', | ||||
|     Blue: 'blue', | ||||
|     Green: 'green', | ||||
|     Purple: 'purple', | ||||
|     Orange: 'orange', | ||||
|     Gray: 'gray', | ||||
|     Amber: 'amber' | ||||
| } as const; | ||||
| 
 | ||||
| export type UserAvatarColor = typeof UserAvatarColor[keyof typeof UserAvatarColor]; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface UserDto | ||||
|  */ | ||||
| export interface UserDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {UserAvatarColor} | ||||
|      * @memberof UserDto | ||||
|      */ | ||||
|     'avatarColor': UserAvatarColor; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -4467,12 +4511,20 @@ export interface UserDto { | ||||
|      */ | ||||
|     'profileImagePath': string; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface UserResponseDto | ||||
|  */ | ||||
| export interface UserResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {UserAvatarColor} | ||||
|      * @memberof UserResponseDto | ||||
|      */ | ||||
|     'avatarColor': UserAvatarColor; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -4552,6 +4604,8 @@ export interface UserResponseDto { | ||||
|      */ | ||||
|     'updatedAt': string; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @ -16477,6 +16531,44 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         deleteProfileImage: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             const localVarPath = `/user/profile-image`; | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||
|             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||
|             let baseOptions; | ||||
|             if (configuration) { | ||||
|                 baseOptions = configuration.baseOptions; | ||||
|             } | ||||
| 
 | ||||
|             const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; | ||||
|             const localVarHeaderParameter = {} as any; | ||||
|             const localVarQueryParameter = {} as any; | ||||
| 
 | ||||
|             // authentication cookie required
 | ||||
| 
 | ||||
|             // authentication api_key required
 | ||||
|             await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) | ||||
| 
 | ||||
|             // authentication bearer required
 | ||||
|             // http bearer authentication required
 | ||||
|             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
| 
 | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
| @ -16802,6 +16894,15 @@ export const UserApiFp = function(configuration?: Configuration) { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.createUser(createUserDto, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async deleteProfileImage(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.deleteProfileImage(options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
| @ -16899,6 +17000,14 @@ export const UserApiFactory = function (configuration?: Configuration, basePath? | ||||
|         createUser(requestParameters: UserApiCreateUserRequest, options?: AxiosRequestConfig): AxiosPromise<UserResponseDto> { | ||||
|             return localVarFp.createUser(requestParameters.createUserDto, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         deleteProfileImage(options?: AxiosRequestConfig): AxiosPromise<void> { | ||||
|             return localVarFp.deleteProfileImage(options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {UserApiDeleteUserRequest} requestParameters Request parameters. | ||||
| @ -17105,6 +17214,16 @@ export class UserApi extends BaseAPI { | ||||
|         return UserApiFp(this.configuration).createUser(requestParameters.createUserDto, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param {*} [options] Override http request option. | ||||
|      * @throws {RequiredError} | ||||
|      * @memberof UserApi | ||||
|      */ | ||||
|     public deleteProfileImage(options?: AxiosRequestConfig) { | ||||
|         return UserApiFp(this.configuration).deleteProfileImage(options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param {UserApiDeleteUserRequest} requestParameters Request parameters. | ||||
|  | ||||
| @ -117,12 +117,8 @@ class AlbumOptionsPage extends HookConsumerWidget { | ||||
| 
 | ||||
|     buildOwnerInfo() { | ||||
|       return ListTile( | ||||
|         leading: owner != null | ||||
|             ? UserCircleAvatar( | ||||
|                 user: owner, | ||||
|                 useRandomBackgroundColor: true, | ||||
|               ) | ||||
|             : const SizedBox(), | ||||
|         leading: | ||||
|             owner != null ? UserCircleAvatar(user: owner) : const SizedBox(), | ||||
|         title: Text( | ||||
|           album.owner.value?.name ?? "", | ||||
|           style: const TextStyle( | ||||
| @ -151,7 +147,6 @@ class AlbumOptionsPage extends HookConsumerWidget { | ||||
|           return ListTile( | ||||
|             leading: UserCircleAvatar( | ||||
|               user: user, | ||||
|               useRandomBackgroundColor: true, | ||||
|               radius: 22, | ||||
|             ), | ||||
|             title: Text( | ||||
|  | ||||
| @ -217,7 +217,6 @@ class AlbumViewerPage extends HookConsumerWidget { | ||||
|                   user: album.sharedUsers.toList()[index], | ||||
|                   radius: 18, | ||||
|                   size: 36, | ||||
|                   useRandomBackgroundColor: true, | ||||
|                 ), | ||||
|               ); | ||||
|             }), | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| import 'dart:ui'; | ||||
| 
 | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| import 'package:immich_mobile/utils/hash.dart'; | ||||
| import 'package:isar/isar.dart'; | ||||
| @ -16,6 +18,7 @@ class User { | ||||
|     this.isPartnerSharedBy = false, | ||||
|     this.isPartnerSharedWith = false, | ||||
|     this.profileImagePath = '', | ||||
|     this.avatarColor = AvatarColorEnum.primary, | ||||
|     this.memoryEnabled = true, | ||||
|     this.inTimeline = false, | ||||
|   }); | ||||
| @ -32,6 +35,7 @@ class User { | ||||
|         profileImagePath = dto.profileImagePath, | ||||
|         isAdmin = dto.isAdmin, | ||||
|         memoryEnabled = dto.memoriesEnabled ?? false, | ||||
|         avatarColor = dto.avatarColor.toAvatarColor(), | ||||
|         inTimeline = false; | ||||
| 
 | ||||
|   User.fromPartnerDto(PartnerResponseDto dto) | ||||
| @ -44,6 +48,7 @@ class User { | ||||
|         profileImagePath = dto.profileImagePath, | ||||
|         isAdmin = dto.isAdmin, | ||||
|         memoryEnabled = dto.memoriesEnabled ?? false, | ||||
|         avatarColor = dto.avatarColor.toAvatarColor(), | ||||
|         inTimeline = dto.inTimeline ?? false; | ||||
| 
 | ||||
|   @Index(unique: true, replace: false, type: IndexType.hash) | ||||
| @ -55,6 +60,8 @@ class User { | ||||
|   bool isPartnerSharedWith; | ||||
|   bool isAdmin; | ||||
|   String profileImagePath; | ||||
|   @Enumerated(EnumType.ordinal) | ||||
|   AvatarColorEnum avatarColor; | ||||
|   bool memoryEnabled; | ||||
|   bool inTimeline; | ||||
| 
 | ||||
| @ -68,6 +75,7 @@ class User { | ||||
|     if (other is! User) return false; | ||||
|     return id == other.id && | ||||
|         updatedAt.isAtSameMomentAs(other.updatedAt) && | ||||
|         avatarColor == other.avatarColor && | ||||
|         email == other.email && | ||||
|         name == other.name && | ||||
|         isPartnerSharedBy == other.isPartnerSharedBy && | ||||
| @ -88,7 +96,77 @@ class User { | ||||
|       isPartnerSharedBy.hashCode ^ | ||||
|       isPartnerSharedWith.hashCode ^ | ||||
|       profileImagePath.hashCode ^ | ||||
|       avatarColor.hashCode ^ | ||||
|       isAdmin.hashCode ^ | ||||
|       memoryEnabled.hashCode ^ | ||||
|       inTimeline.hashCode; | ||||
| } | ||||
| 
 | ||||
| enum AvatarColorEnum { | ||||
|   // do not change this order or reuse indices for other purposes, adding is OK | ||||
|   primary, | ||||
|   pink, | ||||
|   red, | ||||
|   yellow, | ||||
|   blue, | ||||
|   green, | ||||
|   purple, | ||||
|   orange, | ||||
|   gray, | ||||
|   amber, | ||||
| } | ||||
| 
 | ||||
| extension AvatarColorEnumHelper on UserAvatarColor { | ||||
|   AvatarColorEnum toAvatarColor() { | ||||
|     switch (this) { | ||||
|       case UserAvatarColor.primary: | ||||
|         return AvatarColorEnum.primary; | ||||
|       case UserAvatarColor.pink: | ||||
|         return AvatarColorEnum.pink; | ||||
|       case UserAvatarColor.red: | ||||
|         return AvatarColorEnum.red; | ||||
|       case UserAvatarColor.yellow: | ||||
|         return AvatarColorEnum.yellow; | ||||
|       case UserAvatarColor.blue: | ||||
|         return AvatarColorEnum.blue; | ||||
|       case UserAvatarColor.green: | ||||
|         return AvatarColorEnum.green; | ||||
|       case UserAvatarColor.purple: | ||||
|         return AvatarColorEnum.purple; | ||||
|       case UserAvatarColor.orange: | ||||
|         return AvatarColorEnum.orange; | ||||
|       case UserAvatarColor.gray: | ||||
|         return AvatarColorEnum.gray; | ||||
|       case UserAvatarColor.amber: | ||||
|         return AvatarColorEnum.amber; | ||||
|     } | ||||
|     return AvatarColorEnum.primary; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| extension AvatarColorToColorHelper on AvatarColorEnum { | ||||
|   Color toColor([bool isDarkTheme = false]) { | ||||
|     switch (this) { | ||||
|       case AvatarColorEnum.primary: | ||||
|         return isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF); | ||||
|       case AvatarColorEnum.pink: | ||||
|         return const Color.fromARGB(255, 244, 114, 182); | ||||
|       case AvatarColorEnum.red: | ||||
|         return const Color.fromARGB(255, 239, 68, 68); | ||||
|       case AvatarColorEnum.yellow: | ||||
|         return const Color.fromARGB(255, 234, 179, 8); | ||||
|       case AvatarColorEnum.blue: | ||||
|         return const Color.fromARGB(255, 59, 130, 246); | ||||
|       case AvatarColorEnum.green: | ||||
|         return const Color.fromARGB(255, 22, 163, 74); | ||||
|       case AvatarColorEnum.purple: | ||||
|         return const Color.fromARGB(255, 147, 51, 234); | ||||
|       case AvatarColorEnum.orange: | ||||
|         return const Color.fromARGB(255, 234, 88, 12); | ||||
|       case AvatarColorEnum.gray: | ||||
|         return const Color.fromARGB(255, 75, 85, 99); | ||||
|       case AvatarColorEnum.amber: | ||||
|         return const Color.fromARGB(255, 217, 119, 6); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -17,53 +17,59 @@ const UserSchema = CollectionSchema( | ||||
|   name: r'User', | ||||
|   id: -7838171048429979076, | ||||
|   properties: { | ||||
|     r'email': PropertySchema( | ||||
|     r'avatarColor': PropertySchema( | ||||
|       id: 0, | ||||
|       name: r'avatarColor', | ||||
|       type: IsarType.byte, | ||||
|       enumMap: _UseravatarColorEnumValueMap, | ||||
|     ), | ||||
|     r'email': PropertySchema( | ||||
|       id: 1, | ||||
|       name: r'email', | ||||
|       type: IsarType.string, | ||||
|     ), | ||||
|     r'id': PropertySchema( | ||||
|       id: 1, | ||||
|       id: 2, | ||||
|       name: r'id', | ||||
|       type: IsarType.string, | ||||
|     ), | ||||
|     r'inTimeline': PropertySchema( | ||||
|       id: 2, | ||||
|       id: 3, | ||||
|       name: r'inTimeline', | ||||
|       type: IsarType.bool, | ||||
|     ), | ||||
|     r'isAdmin': PropertySchema( | ||||
|       id: 3, | ||||
|       id: 4, | ||||
|       name: r'isAdmin', | ||||
|       type: IsarType.bool, | ||||
|     ), | ||||
|     r'isPartnerSharedBy': PropertySchema( | ||||
|       id: 4, | ||||
|       id: 5, | ||||
|       name: r'isPartnerSharedBy', | ||||
|       type: IsarType.bool, | ||||
|     ), | ||||
|     r'isPartnerSharedWith': PropertySchema( | ||||
|       id: 5, | ||||
|       id: 6, | ||||
|       name: r'isPartnerSharedWith', | ||||
|       type: IsarType.bool, | ||||
|     ), | ||||
|     r'memoryEnabled': PropertySchema( | ||||
|       id: 6, | ||||
|       id: 7, | ||||
|       name: r'memoryEnabled', | ||||
|       type: IsarType.bool, | ||||
|     ), | ||||
|     r'name': PropertySchema( | ||||
|       id: 7, | ||||
|       id: 8, | ||||
|       name: r'name', | ||||
|       type: IsarType.string, | ||||
|     ), | ||||
|     r'profileImagePath': PropertySchema( | ||||
|       id: 8, | ||||
|       id: 9, | ||||
|       name: r'profileImagePath', | ||||
|       type: IsarType.string, | ||||
|     ), | ||||
|     r'updatedAt': PropertySchema( | ||||
|       id: 9, | ||||
|       id: 10, | ||||
|       name: r'updatedAt', | ||||
|       type: IsarType.dateTime, | ||||
|     ) | ||||
| @ -130,16 +136,17 @@ void _userSerialize( | ||||
|   List<int> offsets, | ||||
|   Map<Type, List<int>> allOffsets, | ||||
| ) { | ||||
|   writer.writeString(offsets[0], object.email); | ||||
|   writer.writeString(offsets[1], object.id); | ||||
|   writer.writeBool(offsets[2], object.inTimeline); | ||||
|   writer.writeBool(offsets[3], object.isAdmin); | ||||
|   writer.writeBool(offsets[4], object.isPartnerSharedBy); | ||||
|   writer.writeBool(offsets[5], object.isPartnerSharedWith); | ||||
|   writer.writeBool(offsets[6], object.memoryEnabled); | ||||
|   writer.writeString(offsets[7], object.name); | ||||
|   writer.writeString(offsets[8], object.profileImagePath); | ||||
|   writer.writeDateTime(offsets[9], object.updatedAt); | ||||
|   writer.writeByte(offsets[0], object.avatarColor.index); | ||||
|   writer.writeString(offsets[1], object.email); | ||||
|   writer.writeString(offsets[2], object.id); | ||||
|   writer.writeBool(offsets[3], object.inTimeline); | ||||
|   writer.writeBool(offsets[4], object.isAdmin); | ||||
|   writer.writeBool(offsets[5], object.isPartnerSharedBy); | ||||
|   writer.writeBool(offsets[6], object.isPartnerSharedWith); | ||||
|   writer.writeBool(offsets[7], object.memoryEnabled); | ||||
|   writer.writeString(offsets[8], object.name); | ||||
|   writer.writeString(offsets[9], object.profileImagePath); | ||||
|   writer.writeDateTime(offsets[10], object.updatedAt); | ||||
| } | ||||
| 
 | ||||
| User _userDeserialize( | ||||
| @ -149,16 +156,19 @@ User _userDeserialize( | ||||
|   Map<Type, List<int>> allOffsets, | ||||
| ) { | ||||
|   final object = User( | ||||
|     email: reader.readString(offsets[0]), | ||||
|     id: reader.readString(offsets[1]), | ||||
|     inTimeline: reader.readBoolOrNull(offsets[2]) ?? false, | ||||
|     isAdmin: reader.readBool(offsets[3]), | ||||
|     isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false, | ||||
|     isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false, | ||||
|     memoryEnabled: reader.readBoolOrNull(offsets[6]) ?? true, | ||||
|     name: reader.readString(offsets[7]), | ||||
|     profileImagePath: reader.readStringOrNull(offsets[8]) ?? '', | ||||
|     updatedAt: reader.readDateTime(offsets[9]), | ||||
|     avatarColor: | ||||
|         _UseravatarColorValueEnumMap[reader.readByteOrNull(offsets[0])] ?? | ||||
|             AvatarColorEnum.primary, | ||||
|     email: reader.readString(offsets[1]), | ||||
|     id: reader.readString(offsets[2]), | ||||
|     inTimeline: reader.readBoolOrNull(offsets[3]) ?? false, | ||||
|     isAdmin: reader.readBool(offsets[4]), | ||||
|     isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false, | ||||
|     isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false, | ||||
|     memoryEnabled: reader.readBoolOrNull(offsets[7]) ?? true, | ||||
|     name: reader.readString(offsets[8]), | ||||
|     profileImagePath: reader.readStringOrNull(offsets[9]) ?? '', | ||||
|     updatedAt: reader.readDateTime(offsets[10]), | ||||
|   ); | ||||
|   return object; | ||||
| } | ||||
| @ -171,30 +181,58 @@ P _userDeserializeProp<P>( | ||||
| ) { | ||||
|   switch (propertyId) { | ||||
|     case 0: | ||||
|       return (reader.readString(offset)) as P; | ||||
|       return (_UseravatarColorValueEnumMap[reader.readByteOrNull(offset)] ?? | ||||
|           AvatarColorEnum.primary) as P; | ||||
|     case 1: | ||||
|       return (reader.readString(offset)) as P; | ||||
|     case 2: | ||||
|       return (reader.readBoolOrNull(offset) ?? false) as P; | ||||
|       return (reader.readString(offset)) as P; | ||||
|     case 3: | ||||
|       return (reader.readBool(offset)) as P; | ||||
|     case 4: | ||||
|       return (reader.readBoolOrNull(offset) ?? false) as P; | ||||
|     case 4: | ||||
|       return (reader.readBool(offset)) as P; | ||||
|     case 5: | ||||
|       return (reader.readBoolOrNull(offset) ?? false) as P; | ||||
|     case 6: | ||||
|       return (reader.readBoolOrNull(offset) ?? true) as P; | ||||
|       return (reader.readBoolOrNull(offset) ?? false) as P; | ||||
|     case 7: | ||||
|       return (reader.readString(offset)) as P; | ||||
|       return (reader.readBoolOrNull(offset) ?? true) as P; | ||||
|     case 8: | ||||
|       return (reader.readStringOrNull(offset) ?? '') as P; | ||||
|       return (reader.readString(offset)) as P; | ||||
|     case 9: | ||||
|       return (reader.readStringOrNull(offset) ?? '') as P; | ||||
|     case 10: | ||||
|       return (reader.readDateTime(offset)) as P; | ||||
|     default: | ||||
|       throw IsarError('Unknown property with id $propertyId'); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const _UseravatarColorEnumValueMap = { | ||||
|   'primary': 0, | ||||
|   'pink': 1, | ||||
|   'red': 2, | ||||
|   'yellow': 3, | ||||
|   'blue': 4, | ||||
|   'green': 5, | ||||
|   'purple': 6, | ||||
|   'orange': 7, | ||||
|   'gray': 8, | ||||
|   'amber': 9, | ||||
| }; | ||||
| const _UseravatarColorValueEnumMap = { | ||||
|   0: AvatarColorEnum.primary, | ||||
|   1: AvatarColorEnum.pink, | ||||
|   2: AvatarColorEnum.red, | ||||
|   3: AvatarColorEnum.yellow, | ||||
|   4: AvatarColorEnum.blue, | ||||
|   5: AvatarColorEnum.green, | ||||
|   6: AvatarColorEnum.purple, | ||||
|   7: AvatarColorEnum.orange, | ||||
|   8: AvatarColorEnum.gray, | ||||
|   9: AvatarColorEnum.amber, | ||||
| }; | ||||
| 
 | ||||
| Id _userGetId(User object) { | ||||
|   return object.isarId; | ||||
| } | ||||
| @ -382,6 +420,59 @@ extension UserQueryWhere on QueryBuilder<User, User, QWhereClause> { | ||||
| } | ||||
| 
 | ||||
| extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> { | ||||
|   QueryBuilder<User, User, QAfterFilterCondition> avatarColorEqualTo( | ||||
|       AvatarColorEnum value) { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addFilterCondition(FilterCondition.equalTo( | ||||
|         property: r'avatarColor', | ||||
|         value: value, | ||||
|       )); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QAfterFilterCondition> avatarColorGreaterThan( | ||||
|     AvatarColorEnum value, { | ||||
|     bool include = false, | ||||
|   }) { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addFilterCondition(FilterCondition.greaterThan( | ||||
|         include: include, | ||||
|         property: r'avatarColor', | ||||
|         value: value, | ||||
|       )); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QAfterFilterCondition> avatarColorLessThan( | ||||
|     AvatarColorEnum value, { | ||||
|     bool include = false, | ||||
|   }) { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addFilterCondition(FilterCondition.lessThan( | ||||
|         include: include, | ||||
|         property: r'avatarColor', | ||||
|         value: value, | ||||
|       )); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QAfterFilterCondition> avatarColorBetween( | ||||
|     AvatarColorEnum lower, | ||||
|     AvatarColorEnum upper, { | ||||
|     bool includeLower = true, | ||||
|     bool includeUpper = true, | ||||
|   }) { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addFilterCondition(FilterCondition.between( | ||||
|         property: r'avatarColor', | ||||
|         lower: lower, | ||||
|         includeLower: includeLower, | ||||
|         upper: upper, | ||||
|         includeUpper: includeUpper, | ||||
|       )); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QAfterFilterCondition> emailEqualTo( | ||||
|     String value, { | ||||
|     bool caseSensitive = true, | ||||
| @ -1167,6 +1258,18 @@ extension UserQueryLinks on QueryBuilder<User, User, QFilterCondition> { | ||||
| } | ||||
| 
 | ||||
| extension UserQuerySortBy on QueryBuilder<User, User, QSortBy> { | ||||
|   QueryBuilder<User, User, QAfterSortBy> sortByAvatarColor() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addSortBy(r'avatarColor', Sort.asc); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QAfterSortBy> sortByAvatarColorDesc() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addSortBy(r'avatarColor', Sort.desc); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QAfterSortBy> sortByEmail() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addSortBy(r'email', Sort.asc); | ||||
| @ -1289,6 +1392,18 @@ extension UserQuerySortBy on QueryBuilder<User, User, QSortBy> { | ||||
| } | ||||
| 
 | ||||
| extension UserQuerySortThenBy on QueryBuilder<User, User, QSortThenBy> { | ||||
|   QueryBuilder<User, User, QAfterSortBy> thenByAvatarColor() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addSortBy(r'avatarColor', Sort.asc); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QAfterSortBy> thenByAvatarColorDesc() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addSortBy(r'avatarColor', Sort.desc); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QAfterSortBy> thenByEmail() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addSortBy(r'email', Sort.asc); | ||||
| @ -1423,6 +1538,12 @@ extension UserQuerySortThenBy on QueryBuilder<User, User, QSortThenBy> { | ||||
| } | ||||
| 
 | ||||
| extension UserQueryWhereDistinct on QueryBuilder<User, User, QDistinct> { | ||||
|   QueryBuilder<User, User, QDistinct> distinctByAvatarColor() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addDistinctBy(r'avatarColor'); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, User, QDistinct> distinctByEmail( | ||||
|       {bool caseSensitive = true}) { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
| @ -1496,6 +1617,12 @@ extension UserQueryProperty on QueryBuilder<User, User, QQueryProperty> { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, AvatarColorEnum, QQueryOperations> avatarColorProperty() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addPropertyName(r'avatarColor'); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder<User, String, QQueryOperations> emailProperty() { | ||||
|     return QueryBuilder.apply(this, (query) { | ||||
|       return query.addPropertyName(r'email'); | ||||
|  | ||||
| @ -22,14 +22,12 @@ class AppBarProfileInfoBox extends HookConsumerWidget { | ||||
|     final user = Store.tryGet(StoreKey.currentUser); | ||||
| 
 | ||||
|     buildUserProfileImage() { | ||||
|       const immichImage = CircleAvatar( | ||||
|         radius: 20, | ||||
|         backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), | ||||
|         backgroundColor: Colors.transparent, | ||||
|       ); | ||||
| 
 | ||||
|       if (authState.profileImagePath.isEmpty || user == null) { | ||||
|         return immichImage; | ||||
|       if (user == null) { | ||||
|         return const CircleAvatar( | ||||
|           radius: 20, | ||||
|           backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), | ||||
|           backgroundColor: Colors.transparent, | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       final userImage = UserCircleAvatar( | ||||
| @ -38,18 +36,6 @@ class AppBarProfileInfoBox extends HookConsumerWidget { | ||||
|         user: user, | ||||
|       ); | ||||
| 
 | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.idle) { | ||||
|         return authState.profileImagePath.isNotEmpty ? userImage : immichImage; | ||||
|       } | ||||
| 
 | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.success) { | ||||
|         return userImage; | ||||
|       } | ||||
| 
 | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.failure) { | ||||
|         return immichImage; | ||||
|       } | ||||
| 
 | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.loading) { | ||||
|         return const SizedBox( | ||||
|           height: 40, | ||||
| @ -58,7 +44,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       return immichImage; | ||||
|       return userImage; | ||||
|     } | ||||
| 
 | ||||
|     pickUserProfileImage() async { | ||||
|  | ||||
| @ -4,8 +4,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/shared/models/store.dart'; | ||||
| import 'package:immich_mobile/shared/ui/app_bar_dialog/app_bar_dialog.dart'; | ||||
| import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; | ||||
| import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; | ||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||
| 
 | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; | ||||
| @ -26,7 +24,6 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { | ||||
|     final bool isEnableAutoBackup = | ||||
|         backupState.backgroundBackup || backupState.autoBackup; | ||||
|     final ServerInfo serverInfoState = ref.watch(serverInfoProvider); | ||||
|     AuthenticationState authState = ref.watch(authenticationProvider); | ||||
|     final user = Store.tryGet(StoreKey.currentUser); | ||||
|     final isDarkTheme = context.isDarkTheme; | ||||
|     const widgetSize = 30.0; | ||||
| @ -55,7 +52,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { | ||||
|           alignment: Alignment.bottomRight, | ||||
|           isLabelVisible: serverInfoState.isVersionMismatch, | ||||
|           offset: const Offset(2, 2), | ||||
|           child: authState.profileImagePath.isEmpty || user == null | ||||
|           child: user == null | ||||
|               ? const Icon( | ||||
|                   Icons.face_outlined, | ||||
|                   size: widgetSize, | ||||
|  | ||||
| @ -3,7 +3,6 @@ import 'dart:math'; | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/extensions/build_context_extensions.dart'; | ||||
| import 'package:immich_mobile/shared/models/store.dart'; | ||||
| import 'package:immich_mobile/shared/models/user.dart'; | ||||
| import 'package:immich_mobile/shared/ui/transparent_image.dart'; | ||||
| @ -13,32 +12,17 @@ class UserCircleAvatar extends ConsumerWidget { | ||||
|   final User user; | ||||
|   double radius; | ||||
|   double size; | ||||
|   bool useRandomBackgroundColor; | ||||
| 
 | ||||
|   UserCircleAvatar({ | ||||
|     super.key, | ||||
|     this.radius = 22, | ||||
|     this.size = 44, | ||||
|     this.useRandomBackgroundColor = false, | ||||
|     required this.user, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final randomColors = [ | ||||
|       Colors.red[200], | ||||
|       Colors.blue[200], | ||||
|       Colors.green[200], | ||||
|       Colors.yellow[200], | ||||
|       Colors.purple[200], | ||||
|       Colors.orange[200], | ||||
|       Colors.pink[200], | ||||
|       Colors.teal[200], | ||||
|       Colors.indigo[200], | ||||
|       Colors.cyan[200], | ||||
|       Colors.brown[200], | ||||
|     ]; | ||||
| 
 | ||||
|     bool isDarkTheme = Theme.of(context).brightness == Brightness.dark; | ||||
|     final profileImageUrl = | ||||
|         '${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}'; | ||||
| 
 | ||||
| @ -46,15 +30,16 @@ class UserCircleAvatar extends ConsumerWidget { | ||||
|       user.name[0].toUpperCase(), | ||||
|       style: TextStyle( | ||||
|         fontWeight: FontWeight.bold, | ||||
|         color: context.isDarkTheme ? Colors.black : Colors.white, | ||||
|         fontSize: 12, | ||||
|         color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary | ||||
|             ? Colors.black | ||||
|             : Colors.white, | ||||
|       ), | ||||
|     ); | ||||
|     return CircleAvatar( | ||||
|       backgroundColor: useRandomBackgroundColor | ||||
|           ? randomColors[Random().nextInt(randomColors.length)] | ||||
|           : context.primaryColor, | ||||
|       backgroundColor: user.avatarColor.toColor(), | ||||
|       radius: radius, | ||||
|       child: user.profileImagePath == "" | ||||
|       child: user.profileImagePath.isEmpty | ||||
|           ? textIcon | ||||
|           : ClipRRect( | ||||
|               borderRadius: BorderRadius.circular(50), | ||||
|  | ||||
							
								
								
									
										3
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/.openapi-generator/FILES
									
									
									
										generated
									
									
									
								
							| @ -166,6 +166,7 @@ doc/UpdateTagDto.md | ||||
| doc/UpdateUserDto.md | ||||
| doc/UsageByUserDto.md | ||||
| doc/UserApi.md | ||||
| doc/UserAvatarColor.md | ||||
| doc/UserDto.md | ||||
| doc/UserResponseDto.md | ||||
| doc/ValidateAccessTokenResponseDto.md | ||||
| @ -343,6 +344,7 @@ lib/model/update_stack_parent_dto.dart | ||||
| lib/model/update_tag_dto.dart | ||||
| lib/model/update_user_dto.dart | ||||
| lib/model/usage_by_user_dto.dart | ||||
| lib/model/user_avatar_color.dart | ||||
| lib/model/user_dto.dart | ||||
| lib/model/user_response_dto.dart | ||||
| lib/model/validate_access_token_response_dto.dart | ||||
| @ -511,6 +513,7 @@ test/update_tag_dto_test.dart | ||||
| test/update_user_dto_test.dart | ||||
| test/usage_by_user_dto_test.dart | ||||
| test/user_api_test.dart | ||||
| test/user_avatar_color_test.dart | ||||
| test/user_dto_test.dart | ||||
| test/user_response_dto_test.dart | ||||
| test/validate_access_token_response_dto_test.dart | ||||
|  | ||||
							
								
								
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							| @ -195,6 +195,7 @@ Class | Method | HTTP request | Description | ||||
| *TagApi* | [**updateTag**](doc//TagApi.md#updatetag) | **PATCH** /tag/{id} |  | ||||
| *UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image |  | ||||
| *UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user |  | ||||
| *UserApi* | [**deleteProfileImage**](doc//UserApi.md#deleteprofileimage) | **DELETE** /user/profile-image |  | ||||
| *UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{id} |  | ||||
| *UserApi* | [**getAllUsers**](doc//UserApi.md#getallusers) | **GET** /user |  | ||||
| *UserApi* | [**getMyUserInfo**](doc//UserApi.md#getmyuserinfo) | **GET** /user/me |  | ||||
| @ -352,6 +353,7 @@ Class | Method | HTTP request | Description | ||||
|  - [UpdateTagDto](doc//UpdateTagDto.md) | ||||
|  - [UpdateUserDto](doc//UpdateUserDto.md) | ||||
|  - [UsageByUserDto](doc//UsageByUserDto.md) | ||||
|  - [UserAvatarColor](doc//UserAvatarColor.md) | ||||
|  - [UserDto](doc//UserDto.md) | ||||
|  - [UserResponseDto](doc//UserResponseDto.md) | ||||
|  - [ValidateAccessTokenResponseDto](doc//ValidateAccessTokenResponseDto.md) | ||||
|  | ||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/PartnerResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/PartnerResponseDto.md
									
									
									
										generated
									
									
									
								
							| @ -8,6 +8,7 @@ import 'package:openapi/api.dart'; | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **avatarColor** | [**UserAvatarColor**](UserAvatarColor.md) |  |  | ||||
| **createdAt** | [**DateTime**](DateTime.md) |  |  | ||||
| **deletedAt** | [**DateTime**](DateTime.md) |  |  | ||||
| **email** | **String** |  |  | ||||
|  | ||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/UpdateUserDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/UpdateUserDto.md
									
									
									
										generated
									
									
									
								
							| @ -8,6 +8,7 @@ import 'package:openapi/api.dart'; | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **avatarColor** | [**UserAvatarColor**](UserAvatarColor.md) |  | [optional]  | ||||
| **email** | **String** |  | [optional]  | ||||
| **externalPath** | **String** |  | [optional]  | ||||
| **id** | **String** |  |  | ||||
|  | ||||
							
								
								
									
										51
									
								
								mobile/openapi/doc/UserApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										51
									
								
								mobile/openapi/doc/UserApi.md
									
									
									
										generated
									
									
									
								
							| @ -11,6 +11,7 @@ Method | HTTP request | Description | ||||
| ------------- | ------------- | ------------- | ||||
| [**createProfileImage**](UserApi.md#createprofileimage) | **POST** /user/profile-image |  | ||||
| [**createUser**](UserApi.md#createuser) | **POST** /user |  | ||||
| [**deleteProfileImage**](UserApi.md#deleteprofileimage) | **DELETE** /user/profile-image |  | ||||
| [**deleteUser**](UserApi.md#deleteuser) | **DELETE** /user/{id} |  | ||||
| [**getAllUsers**](UserApi.md#getallusers) | **GET** /user |  | ||||
| [**getMyUserInfo**](UserApi.md#getmyuserinfo) | **GET** /user/me |  | ||||
| @ -130,6 +131,56 @@ Name | Type | Description  | Notes | ||||
| 
 | ||||
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||
| 
 | ||||
| # **deleteProfileImage** | ||||
| > deleteProfileImage() | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ### Example | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| // TODO Configure API key authorization: cookie | ||||
| //defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY'; | ||||
| // uncomment below to setup prefix (e.g. Bearer) for API key, if needed | ||||
| //defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer'; | ||||
| // TODO Configure API key authorization: api_key | ||||
| //defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY'; | ||||
| // uncomment below to setup prefix (e.g. Bearer) for API key, if needed | ||||
| //defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer'; | ||||
| // TODO Configure HTTP Bearer authorization: bearer | ||||
| // Case 1. Use String Token | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); | ||||
| // Case 2. Use Function which generate token. | ||||
| // String yourTokenGeneratorFunction() { ... } | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction); | ||||
| 
 | ||||
| final api_instance = UserApi(); | ||||
| 
 | ||||
| try { | ||||
|     api_instance.deleteProfileImage(); | ||||
| } catch (e) { | ||||
|     print('Exception when calling UserApi->deleteProfileImage: $e\n'); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ### Parameters | ||||
| This endpoint does not need any parameter. | ||||
| 
 | ||||
| ### Return type | ||||
| 
 | ||||
| void (empty response body) | ||||
| 
 | ||||
| ### Authorization | ||||
| 
 | ||||
| [cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer) | ||||
| 
 | ||||
| ### HTTP request headers | ||||
| 
 | ||||
|  - **Content-Type**: Not defined | ||||
|  - **Accept**: Not defined | ||||
| 
 | ||||
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||
| 
 | ||||
| # **deleteUser** | ||||
| > UserResponseDto deleteUser(id) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										14
									
								
								mobile/openapi/doc/UserAvatarColor.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								mobile/openapi/doc/UserAvatarColor.md
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| # openapi.model.UserAvatarColor | ||||
| 
 | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
| 
 | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| 
 | ||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/UserDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/UserDto.md
									
									
									
										generated
									
									
									
								
							| @ -8,6 +8,7 @@ import 'package:openapi/api.dart'; | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **avatarColor** | [**UserAvatarColor**](UserAvatarColor.md) |  |  | ||||
| **email** | **String** |  |  | ||||
| **id** | **String** |  |  | ||||
| **name** | **String** |  |  | ||||
|  | ||||
							
								
								
									
										1
									
								
								mobile/openapi/doc/UserResponseDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/doc/UserResponseDto.md
									
									
									
										generated
									
									
									
								
							| @ -8,6 +8,7 @@ import 'package:openapi/api.dart'; | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **avatarColor** | [**UserAvatarColor**](UserAvatarColor.md) |  |  | ||||
| **createdAt** | [**DateTime**](DateTime.md) |  |  | ||||
| **deletedAt** | [**DateTime**](DateTime.md) |  |  | ||||
| **email** | **String** |  |  | ||||
|  | ||||
							
								
								
									
										1
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								mobile/openapi/lib/api.dart
									
									
									
										generated
									
									
									
								
							| @ -192,6 +192,7 @@ part 'model/update_stack_parent_dto.dart'; | ||||
| part 'model/update_tag_dto.dart'; | ||||
| part 'model/update_user_dto.dart'; | ||||
| part 'model/usage_by_user_dto.dart'; | ||||
| part 'model/user_avatar_color.dart'; | ||||
| part 'model/user_dto.dart'; | ||||
| part 'model/user_response_dto.dart'; | ||||
| part 'model/validate_access_token_response_dto.dart'; | ||||
|  | ||||
							
								
								
									
										33
									
								
								mobile/openapi/lib/api/user_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										33
									
								
								mobile/openapi/lib/api/user_api.dart
									
									
									
										generated
									
									
									
								
							| @ -120,6 +120,39 @@ class UserApi { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   /// Performs an HTTP 'DELETE /user/profile-image' operation and returns the [Response]. | ||||
|   Future<Response> deleteProfileImageWithHttpInfo() async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/user/profile-image'; | ||||
| 
 | ||||
|     // ignore: prefer_final_locals | ||||
|     Object? postBody; | ||||
| 
 | ||||
|     final queryParams = <QueryParam>[]; | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
| 
 | ||||
|     const contentTypes = <String>[]; | ||||
| 
 | ||||
| 
 | ||||
|     return apiClient.invokeAPI( | ||||
|       path, | ||||
|       'DELETE', | ||||
|       queryParams, | ||||
|       postBody, | ||||
|       headerParams, | ||||
|       formParams, | ||||
|       contentTypes.isEmpty ? null : contentTypes.first, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> deleteProfileImage() async { | ||||
|     final response = await deleteProfileImageWithHttpInfo(); | ||||
|     if (response.statusCode >= HttpStatus.badRequest) { | ||||
|       throw ApiException(response.statusCode, await _decodeBodyBytes(response)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Performs an HTTP 'DELETE /user/{id}' operation and returns the [Response]. | ||||
|   /// Parameters: | ||||
|   /// | ||||
|  | ||||
							
								
								
									
										2
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/lib/api_client.dart
									
									
									
										generated
									
									
									
								
							| @ -473,6 +473,8 @@ class ApiClient { | ||||
|           return UpdateUserDto.fromJson(value); | ||||
|         case 'UsageByUserDto': | ||||
|           return UsageByUserDto.fromJson(value); | ||||
|         case 'UserAvatarColor': | ||||
|           return UserAvatarColorTypeTransformer().decode(value); | ||||
|         case 'UserDto': | ||||
|           return UserDto.fromJson(value); | ||||
|         case 'UserResponseDto': | ||||
|  | ||||
							
								
								
									
										3
									
								
								mobile/openapi/lib/api_helper.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/lib/api_helper.dart
									
									
									
										generated
									
									
									
								
							| @ -127,6 +127,9 @@ String parameterToString(dynamic value) { | ||||
|   if (value is TranscodePolicy) { | ||||
|     return TranscodePolicyTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|   if (value is UserAvatarColor) { | ||||
|     return UserAvatarColorTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|   if (value is VideoCodec) { | ||||
|     return VideoCodecTypeTransformer().encode(value).toString(); | ||||
|   } | ||||
|  | ||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/partner_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/partner_response_dto.dart
									
									
									
										generated
									
									
									
								
							| @ -13,6 +13,7 @@ part of openapi.api; | ||||
| class PartnerResponseDto { | ||||
|   /// Returns a new [PartnerResponseDto] instance. | ||||
|   PartnerResponseDto({ | ||||
|     required this.avatarColor, | ||||
|     required this.createdAt, | ||||
|     required this.deletedAt, | ||||
|     required this.email, | ||||
| @ -29,6 +30,8 @@ class PartnerResponseDto { | ||||
|     required this.updatedAt, | ||||
|   }); | ||||
| 
 | ||||
|   UserAvatarColor avatarColor; | ||||
| 
 | ||||
|   DateTime createdAt; | ||||
| 
 | ||||
|   DateTime? deletedAt; | ||||
| @ -71,6 +74,7 @@ class PartnerResponseDto { | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is PartnerResponseDto && | ||||
|      other.avatarColor == avatarColor && | ||||
|      other.createdAt == createdAt && | ||||
|      other.deletedAt == deletedAt && | ||||
|      other.email == email && | ||||
| @ -89,6 +93,7 @@ class PartnerResponseDto { | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (avatarColor.hashCode) + | ||||
|     (createdAt.hashCode) + | ||||
|     (deletedAt == null ? 0 : deletedAt!.hashCode) + | ||||
|     (email.hashCode) + | ||||
| @ -105,10 +110,11 @@ class PartnerResponseDto { | ||||
|     (updatedAt.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'PartnerResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; | ||||
|   String toString() => 'PartnerResponseDto[avatarColor=$avatarColor, createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, inTimeline=$inTimeline, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|       json[r'avatarColor'] = this.avatarColor; | ||||
|       json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); | ||||
|     if (this.deletedAt != null) { | ||||
|       json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); | ||||
| @ -154,6 +160,7 @@ class PartnerResponseDto { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
| 
 | ||||
|       return PartnerResponseDto( | ||||
|         avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, | ||||
|         createdAt: mapDateTime(json, r'createdAt', '')!, | ||||
|         deletedAt: mapDateTime(json, r'deletedAt', ''), | ||||
|         email: mapValueOfType<String>(json, r'email')!, | ||||
| @ -215,6 +222,7 @@ class PartnerResponseDto { | ||||
| 
 | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'avatarColor', | ||||
|     'createdAt', | ||||
|     'deletedAt', | ||||
|     'email', | ||||
|  | ||||
							
								
								
									
										19
									
								
								mobile/openapi/lib/model/update_user_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								mobile/openapi/lib/model/update_user_dto.dart
									
									
									
										generated
									
									
									
								
							| @ -13,6 +13,7 @@ part of openapi.api; | ||||
| class UpdateUserDto { | ||||
|   /// Returns a new [UpdateUserDto] instance. | ||||
|   UpdateUserDto({ | ||||
|     this.avatarColor, | ||||
|     this.email, | ||||
|     this.externalPath, | ||||
|     required this.id, | ||||
| @ -24,6 +25,14 @@ class UpdateUserDto { | ||||
|     this.storageLabel, | ||||
|   }); | ||||
| 
 | ||||
|   /// | ||||
|   /// Please note: This property should have been non-nullable! Since the specification file | ||||
|   /// does not include a default value (using the "default:" property), however, the generated | ||||
|   /// source code must fall back to having a nullable type. | ||||
|   /// Consider adding a "default:" property in the specification file to hide this note. | ||||
|   /// | ||||
|   UserAvatarColor? avatarColor; | ||||
| 
 | ||||
|   /// | ||||
|   /// Please note: This property should have been non-nullable! Since the specification file | ||||
|   /// does not include a default value (using the "default:" property), however, the generated | ||||
| @ -92,6 +101,7 @@ class UpdateUserDto { | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is UpdateUserDto && | ||||
|      other.avatarColor == avatarColor && | ||||
|      other.email == email && | ||||
|      other.externalPath == externalPath && | ||||
|      other.id == id && | ||||
| @ -105,6 +115,7 @@ class UpdateUserDto { | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (avatarColor == null ? 0 : avatarColor!.hashCode) + | ||||
|     (email == null ? 0 : email!.hashCode) + | ||||
|     (externalPath == null ? 0 : externalPath!.hashCode) + | ||||
|     (id.hashCode) + | ||||
| @ -116,10 +127,15 @@ class UpdateUserDto { | ||||
|     (storageLabel == null ? 0 : storageLabel!.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'UpdateUserDto[email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, password=$password, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; | ||||
|   String toString() => 'UpdateUserDto[avatarColor=$avatarColor, email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, password=$password, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|     if (this.avatarColor != null) { | ||||
|       json[r'avatarColor'] = this.avatarColor; | ||||
|     } else { | ||||
|     //  json[r'avatarColor'] = null; | ||||
|     } | ||||
|     if (this.email != null) { | ||||
|       json[r'email'] = this.email; | ||||
|     } else { | ||||
| @ -172,6 +188,7 @@ class UpdateUserDto { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
| 
 | ||||
|       return UpdateUserDto( | ||||
|         avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), | ||||
|         email: mapValueOfType<String>(json, r'email'), | ||||
|         externalPath: mapValueOfType<String>(json, r'externalPath'), | ||||
|         id: mapValueOfType<String>(json, r'id')!, | ||||
|  | ||||
							
								
								
									
										109
									
								
								mobile/openapi/lib/model/user_avatar_color.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								mobile/openapi/lib/model/user_avatar_color.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
| 
 | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
| 
 | ||||
| part of openapi.api; | ||||
| 
 | ||||
| 
 | ||||
| class UserAvatarColor { | ||||
|   /// Instantiate a new enum with the provided [value]. | ||||
|   const UserAvatarColor._(this.value); | ||||
| 
 | ||||
|   /// The underlying value of this enum member. | ||||
|   final String value; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => value; | ||||
| 
 | ||||
|   String toJson() => value; | ||||
| 
 | ||||
|   static const primary = UserAvatarColor._(r'primary'); | ||||
|   static const pink = UserAvatarColor._(r'pink'); | ||||
|   static const red = UserAvatarColor._(r'red'); | ||||
|   static const yellow = UserAvatarColor._(r'yellow'); | ||||
|   static const blue = UserAvatarColor._(r'blue'); | ||||
|   static const green = UserAvatarColor._(r'green'); | ||||
|   static const purple = UserAvatarColor._(r'purple'); | ||||
|   static const orange = UserAvatarColor._(r'orange'); | ||||
|   static const gray = UserAvatarColor._(r'gray'); | ||||
|   static const amber = UserAvatarColor._(r'amber'); | ||||
| 
 | ||||
|   /// List of all possible values in this [enum][UserAvatarColor]. | ||||
|   static const values = <UserAvatarColor>[ | ||||
|     primary, | ||||
|     pink, | ||||
|     red, | ||||
|     yellow, | ||||
|     blue, | ||||
|     green, | ||||
|     purple, | ||||
|     orange, | ||||
|     gray, | ||||
|     amber, | ||||
|   ]; | ||||
| 
 | ||||
|   static UserAvatarColor? fromJson(dynamic value) => UserAvatarColorTypeTransformer().decode(value); | ||||
| 
 | ||||
|   static List<UserAvatarColor>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <UserAvatarColor>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = UserAvatarColor.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Transformation class that can [encode] an instance of [UserAvatarColor] to String, | ||||
| /// and [decode] dynamic data back to [UserAvatarColor]. | ||||
| class UserAvatarColorTypeTransformer { | ||||
|   factory UserAvatarColorTypeTransformer() => _instance ??= const UserAvatarColorTypeTransformer._(); | ||||
| 
 | ||||
|   const UserAvatarColorTypeTransformer._(); | ||||
| 
 | ||||
|   String encode(UserAvatarColor data) => data.value; | ||||
| 
 | ||||
|   /// Decodes a [dynamic value][data] to a UserAvatarColor. | ||||
|   /// | ||||
|   /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, | ||||
|   /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] | ||||
|   /// cannot be decoded successfully, then an [UnimplementedError] is thrown. | ||||
|   /// | ||||
|   /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, | ||||
|   /// and users are still using an old app with the old code. | ||||
|   UserAvatarColor? decode(dynamic data, {bool allowNull = true}) { | ||||
|     if (data != null) { | ||||
|       switch (data) { | ||||
|         case r'primary': return UserAvatarColor.primary; | ||||
|         case r'pink': return UserAvatarColor.pink; | ||||
|         case r'red': return UserAvatarColor.red; | ||||
|         case r'yellow': return UserAvatarColor.yellow; | ||||
|         case r'blue': return UserAvatarColor.blue; | ||||
|         case r'green': return UserAvatarColor.green; | ||||
|         case r'purple': return UserAvatarColor.purple; | ||||
|         case r'orange': return UserAvatarColor.orange; | ||||
|         case r'gray': return UserAvatarColor.gray; | ||||
|         case r'amber': return UserAvatarColor.amber; | ||||
|         default: | ||||
|           if (!allowNull) { | ||||
|             throw ArgumentError('Unknown enum value to decode: $data'); | ||||
|           } | ||||
|       } | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   /// Singleton [UserAvatarColorTypeTransformer] instance. | ||||
|   static UserAvatarColorTypeTransformer? _instance; | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/user_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/user_dto.dart
									
									
									
										generated
									
									
									
								
							| @ -13,12 +13,15 @@ part of openapi.api; | ||||
| class UserDto { | ||||
|   /// Returns a new [UserDto] instance. | ||||
|   UserDto({ | ||||
|     required this.avatarColor, | ||||
|     required this.email, | ||||
|     required this.id, | ||||
|     required this.name, | ||||
|     required this.profileImagePath, | ||||
|   }); | ||||
| 
 | ||||
|   UserAvatarColor avatarColor; | ||||
| 
 | ||||
|   String email; | ||||
| 
 | ||||
|   String id; | ||||
| @ -29,6 +32,7 @@ class UserDto { | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is UserDto && | ||||
|      other.avatarColor == avatarColor && | ||||
|      other.email == email && | ||||
|      other.id == id && | ||||
|      other.name == name && | ||||
| @ -37,16 +41,18 @@ class UserDto { | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (avatarColor.hashCode) + | ||||
|     (email.hashCode) + | ||||
|     (id.hashCode) + | ||||
|     (name.hashCode) + | ||||
|     (profileImagePath.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'UserDto[email=$email, id=$id, name=$name, profileImagePath=$profileImagePath]'; | ||||
|   String toString() => 'UserDto[avatarColor=$avatarColor, email=$email, id=$id, name=$name, profileImagePath=$profileImagePath]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|       json[r'avatarColor'] = this.avatarColor; | ||||
|       json[r'email'] = this.email; | ||||
|       json[r'id'] = this.id; | ||||
|       json[r'name'] = this.name; | ||||
| @ -62,6 +68,7 @@ class UserDto { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
| 
 | ||||
|       return UserDto( | ||||
|         avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, | ||||
|         email: mapValueOfType<String>(json, r'email')!, | ||||
|         id: mapValueOfType<String>(json, r'id')!, | ||||
|         name: mapValueOfType<String>(json, r'name')!, | ||||
| @ -113,6 +120,7 @@ class UserDto { | ||||
| 
 | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'avatarColor', | ||||
|     'email', | ||||
|     'id', | ||||
|     'name', | ||||
|  | ||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/model/user_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/model/user_response_dto.dart
									
									
									
										generated
									
									
									
								
							| @ -13,6 +13,7 @@ part of openapi.api; | ||||
| class UserResponseDto { | ||||
|   /// Returns a new [UserResponseDto] instance. | ||||
|   UserResponseDto({ | ||||
|     required this.avatarColor, | ||||
|     required this.createdAt, | ||||
|     required this.deletedAt, | ||||
|     required this.email, | ||||
| @ -28,6 +29,8 @@ class UserResponseDto { | ||||
|     required this.updatedAt, | ||||
|   }); | ||||
| 
 | ||||
|   UserAvatarColor avatarColor; | ||||
| 
 | ||||
|   DateTime createdAt; | ||||
| 
 | ||||
|   DateTime? deletedAt; | ||||
| @ -62,6 +65,7 @@ class UserResponseDto { | ||||
| 
 | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is UserResponseDto && | ||||
|      other.avatarColor == avatarColor && | ||||
|      other.createdAt == createdAt && | ||||
|      other.deletedAt == deletedAt && | ||||
|      other.email == email && | ||||
| @ -79,6 +83,7 @@ class UserResponseDto { | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (avatarColor.hashCode) + | ||||
|     (createdAt.hashCode) + | ||||
|     (deletedAt == null ? 0 : deletedAt!.hashCode) + | ||||
|     (email.hashCode) + | ||||
| @ -94,10 +99,11 @@ class UserResponseDto { | ||||
|     (updatedAt.hashCode); | ||||
| 
 | ||||
|   @override | ||||
|   String toString() => 'UserResponseDto[createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; | ||||
|   String toString() => 'UserResponseDto[avatarColor=$avatarColor, createdAt=$createdAt, deletedAt=$deletedAt, email=$email, externalPath=$externalPath, id=$id, isAdmin=$isAdmin, memoriesEnabled=$memoriesEnabled, name=$name, oauthId=$oauthId, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel, updatedAt=$updatedAt]'; | ||||
| 
 | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final json = <String, dynamic>{}; | ||||
|       json[r'avatarColor'] = this.avatarColor; | ||||
|       json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); | ||||
|     if (this.deletedAt != null) { | ||||
|       json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); | ||||
| @ -138,6 +144,7 @@ class UserResponseDto { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
| 
 | ||||
|       return UserResponseDto( | ||||
|         avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, | ||||
|         createdAt: mapDateTime(json, r'createdAt', '')!, | ||||
|         deletedAt: mapDateTime(json, r'deletedAt', ''), | ||||
|         email: mapValueOfType<String>(json, r'email')!, | ||||
| @ -198,6 +205,7 @@ class UserResponseDto { | ||||
| 
 | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'avatarColor', | ||||
|     'createdAt', | ||||
|     'deletedAt', | ||||
|     'email', | ||||
|  | ||||
| @ -16,6 +16,11 @@ void main() { | ||||
|   // final instance = PartnerResponseDto(); | ||||
| 
 | ||||
|   group('test PartnerResponseDto', () { | ||||
|     // UserAvatarColor avatarColor | ||||
|     test('to test the property `avatarColor`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // DateTime createdAt | ||||
|     test('to test the property `createdAt`', () async { | ||||
|       // TODO | ||||
|  | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/update_user_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/update_user_dto_test.dart
									
									
									
										generated
									
									
									
								
							| @ -16,6 +16,11 @@ void main() { | ||||
|   // final instance = UpdateUserDto(); | ||||
| 
 | ||||
|   group('test UpdateUserDto', () { | ||||
|     // UserAvatarColor avatarColor | ||||
|     test('to test the property `avatarColor`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // String email | ||||
|     test('to test the property `email`', () async { | ||||
|       // TODO | ||||
|  | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/user_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/user_api_test.dart
									
									
									
										generated
									
									
									
								
							| @ -27,6 +27,11 @@ void main() { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     //Future deleteProfileImage() async | ||||
|     test('test deleteProfileImage', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     //Future<UserResponseDto> deleteUser(String id) async | ||||
|     test('test deleteUser', () async { | ||||
|       // TODO | ||||
|  | ||||
							
								
								
									
										21
									
								
								mobile/openapi/test/user_avatar_color_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								mobile/openapi/test/user_avatar_color_test.dart
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
| 
 | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
| 
 | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:test/test.dart'; | ||||
| 
 | ||||
| // tests for UserAvatarColor | ||||
| void main() { | ||||
| 
 | ||||
|   group('test UserAvatarColor', () { | ||||
| 
 | ||||
|   }); | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/user_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/user_dto_test.dart
									
									
									
										generated
									
									
									
								
							| @ -16,6 +16,11 @@ void main() { | ||||
|   // final instance = UserDto(); | ||||
| 
 | ||||
|   group('test UserDto', () { | ||||
|     // UserAvatarColor avatarColor | ||||
|     test('to test the property `avatarColor`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // String email | ||||
|     test('to test the property `email`', () async { | ||||
|       // TODO | ||||
|  | ||||
							
								
								
									
										5
									
								
								mobile/openapi/test/user_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								mobile/openapi/test/user_response_dto_test.dart
									
									
									
										generated
									
									
									
								
							| @ -16,6 +16,11 @@ void main() { | ||||
|   // final instance = UserResponseDto(); | ||||
| 
 | ||||
|   group('test UserResponseDto', () { | ||||
|     // UserAvatarColor avatarColor | ||||
|     test('to test the property `avatarColor`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
| 
 | ||||
|     // DateTime createdAt | ||||
|     test('to test the property `createdAt`', () async { | ||||
|       // TODO | ||||
|  | ||||
| @ -5578,6 +5578,29 @@ | ||||
|       } | ||||
|     }, | ||||
|     "/user/profile-image": { | ||||
|       "delete": { | ||||
|         "operationId": "deleteProfileImage", | ||||
|         "parameters": [], | ||||
|         "responses": { | ||||
|           "204": { | ||||
|             "description": "" | ||||
|           } | ||||
|         }, | ||||
|         "security": [ | ||||
|           { | ||||
|             "bearer": [] | ||||
|           }, | ||||
|           { | ||||
|             "cookie": [] | ||||
|           }, | ||||
|           { | ||||
|             "api_key": [] | ||||
|           } | ||||
|         ], | ||||
|         "tags": [ | ||||
|           "User" | ||||
|         ] | ||||
|       }, | ||||
|       "post": { | ||||
|         "operationId": "createProfileImage", | ||||
|         "parameters": [], | ||||
| @ -7632,6 +7655,9 @@ | ||||
|       }, | ||||
|       "PartnerResponseDto": { | ||||
|         "properties": { | ||||
|           "avatarColor": { | ||||
|             "$ref": "#/components/schemas/UserAvatarColor" | ||||
|           }, | ||||
|           "createdAt": { | ||||
|             "format": "date-time", | ||||
|             "type": "string" | ||||
| @ -7682,6 +7708,7 @@ | ||||
|           } | ||||
|         }, | ||||
|         "required": [ | ||||
|           "avatarColor", | ||||
|           "id", | ||||
|           "name", | ||||
|           "email", | ||||
| @ -9140,6 +9167,9 @@ | ||||
|       }, | ||||
|       "UpdateUserDto": { | ||||
|         "properties": { | ||||
|           "avatarColor": { | ||||
|             "$ref": "#/components/schemas/UserAvatarColor" | ||||
|           }, | ||||
|           "email": { | ||||
|             "type": "string" | ||||
|           }, | ||||
| @ -9202,8 +9232,26 @@ | ||||
|         ], | ||||
|         "type": "object" | ||||
|       }, | ||||
|       "UserAvatarColor": { | ||||
|         "enum": [ | ||||
|           "primary", | ||||
|           "pink", | ||||
|           "red", | ||||
|           "yellow", | ||||
|           "blue", | ||||
|           "green", | ||||
|           "purple", | ||||
|           "orange", | ||||
|           "gray", | ||||
|           "amber" | ||||
|         ], | ||||
|         "type": "string" | ||||
|       }, | ||||
|       "UserDto": { | ||||
|         "properties": { | ||||
|           "avatarColor": { | ||||
|             "$ref": "#/components/schemas/UserAvatarColor" | ||||
|           }, | ||||
|           "email": { | ||||
|             "type": "string" | ||||
|           }, | ||||
| @ -9218,6 +9266,7 @@ | ||||
|           } | ||||
|         }, | ||||
|         "required": [ | ||||
|           "avatarColor", | ||||
|           "id", | ||||
|           "name", | ||||
|           "email", | ||||
| @ -9227,6 +9276,9 @@ | ||||
|       }, | ||||
|       "UserResponseDto": { | ||||
|         "properties": { | ||||
|           "avatarColor": { | ||||
|             "$ref": "#/components/schemas/UserAvatarColor" | ||||
|           }, | ||||
|           "createdAt": { | ||||
|             "format": "date-time", | ||||
|             "type": "string" | ||||
| @ -9274,6 +9326,7 @@ | ||||
|           } | ||||
|         }, | ||||
|         "required": [ | ||||
|           "avatarColor", | ||||
|           "id", | ||||
|           "name", | ||||
|           "email", | ||||
|  | ||||
| @ -248,6 +248,7 @@ describe('AuthService', () => { | ||||
|       userMock.getAdmin.mockResolvedValue(null); | ||||
|       userMock.create.mockResolvedValue({ ...dto, id: 'admin', createdAt: new Date('2021-01-01') } as UserEntity); | ||||
|       await expect(sut.adminSignUp(dto)).resolves.toEqual({ | ||||
|         avatarColor: expect.any(String), | ||||
|         id: 'admin', | ||||
|         createdAt: new Date('2021-01-01'), | ||||
|         email: 'test@immich.com', | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import { UserAvatarColor } from '@app/infra/entities'; | ||||
| import { BadRequestException } from '@nestjs/common'; | ||||
| import { authStub, newPartnerRepositoryMock, partnerStub } from '@test'; | ||||
| import { IAccessRepository, IPartnerRepository, PartnerDirection } from '../repositories'; | ||||
| @ -19,6 +20,7 @@ const responseDto = { | ||||
|     updatedAt: new Date('2021-01-01'), | ||||
|     externalPath: null, | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|     inTimeline: true, | ||||
|   }, | ||||
|   user1: <PartnerResponseDto>{ | ||||
| @ -35,6 +37,7 @@ const responseDto = { | ||||
|     updatedAt: new Date('2021-01-01'), | ||||
|     externalPath: null, | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|     inTimeline: true, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { UserAvatarColor } from '@app/infra/entities'; | ||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||
| import { Transform } from 'class-transformer'; | ||||
| import { IsBoolean, IsEmail, IsNotEmpty, IsString, IsUUID } from 'class-validator'; | ||||
| import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsString, IsUUID } from 'class-validator'; | ||||
| import { Optional, toEmail, toSanitized } from '../../domain.util'; | ||||
| 
 | ||||
| export class UpdateUserDto { | ||||
| @ -44,4 +45,9 @@ export class UpdateUserDto { | ||||
|   @Optional() | ||||
|   @IsBoolean() | ||||
|   memoriesEnabled?: boolean; | ||||
| 
 | ||||
|   @Optional() | ||||
|   @IsEnum(UserAvatarColor) | ||||
|   @ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor }) | ||||
|   avatarColor?: UserAvatarColor; | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,26 @@ | ||||
| import { UserEntity } from '@app/infra/entities'; | ||||
| import { UserAvatarColor, UserEntity } from '@app/infra/entities'; | ||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||
| import { IsEnum } from 'class-validator'; | ||||
| 
 | ||||
| export const getRandomAvatarColor = (user: UserEntity): UserAvatarColor => { | ||||
|   const values = Object.values(UserAvatarColor); | ||||
|   const randomIndex = Math.floor( | ||||
|     user.email | ||||
|       .split('') | ||||
|       .map((letter) => letter.charCodeAt(0)) | ||||
|       .reduce((a, b) => a + b, 0) % values.length, | ||||
|   ); | ||||
|   return values[randomIndex] as UserAvatarColor; | ||||
| }; | ||||
| 
 | ||||
| export class UserDto { | ||||
|   id!: string; | ||||
|   name!: string; | ||||
|   email!: string; | ||||
|   profileImagePath!: string; | ||||
|   @IsEnum(UserAvatarColor) | ||||
|   @ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor }) | ||||
|   avatarColor!: UserAvatarColor; | ||||
| } | ||||
| 
 | ||||
| export class UserResponseDto extends UserDto { | ||||
| @ -25,6 +41,7 @@ export const mapSimpleUser = (entity: UserEntity): UserDto => { | ||||
|     email: entity.email, | ||||
|     name: entity.name, | ||||
|     profileImagePath: entity.profileImagePath, | ||||
|     avatarColor: entity.avatarColor ?? getRandomAvatarColor(entity), | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -98,7 +98,6 @@ export class UserCore { | ||||
|     if (payload.storageLabel) { | ||||
|       payload.storageLabel = sanitize(payload.storageLabel); | ||||
|     } | ||||
| 
 | ||||
|     const userEntity = await this.userRepository.create(payload); | ||||
|     await this.libraryRepository.create({ | ||||
|       owner: { id: userEntity.id } as UserEntity, | ||||
|  | ||||
| @ -323,17 +323,52 @@ describe(UserService.name, () => { | ||||
|       const file = { path: '/profile/path' } as Express.Multer.File; | ||||
|       userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); | ||||
| 
 | ||||
|       await sut.createProfileImage(userStub.admin, file); | ||||
| 
 | ||||
|       expect(userMock.update).toHaveBeenCalledWith(userStub.admin.id, { profileImagePath: file.path }); | ||||
|       await expect(sut.createProfileImage(userStub.admin, file)).rejects.toThrowError(BadRequestException); | ||||
|     }); | ||||
| 
 | ||||
|     it('should throw an error if the user profile could not be updated with the new image', async () => { | ||||
|       const file = { path: '/profile/path' } as Express.Multer.File; | ||||
|       userMock.get.mockResolvedValue(userStub.profilePath); | ||||
|       userMock.update.mockRejectedValue(new InternalServerErrorException('mocked error')); | ||||
| 
 | ||||
|       await expect(sut.createProfileImage(userStub.admin, file)).rejects.toThrowError(InternalServerErrorException); | ||||
|     }); | ||||
| 
 | ||||
|     it('should delete the previous profile image', async () => { | ||||
|       const file = { path: '/profile/path' } as Express.Multer.File; | ||||
|       userMock.get.mockResolvedValue(userStub.profilePath); | ||||
|       const files = [userStub.profilePath.profileImagePath]; | ||||
|       userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); | ||||
| 
 | ||||
|       await sut.createProfileImage(userStub.admin, file); | ||||
|       await expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.DELETE_FILES, data: { files } }]]); | ||||
|     }); | ||||
| 
 | ||||
|     it('should not delete the profile image if it has not been set', async () => { | ||||
|       const file = { path: '/profile/path' } as Express.Multer.File; | ||||
|       userMock.get.mockResolvedValue(userStub.admin); | ||||
|       userMock.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); | ||||
| 
 | ||||
|       await sut.createProfileImage(userStub.admin, file); | ||||
|       expect(jobMock.queue).not.toHaveBeenCalled(); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('deleteProfileImage', () => { | ||||
|     it('should send an http error has no profile image', async () => { | ||||
|       userMock.get.mockResolvedValue(userStub.admin); | ||||
| 
 | ||||
|       await expect(sut.deleteProfileImage(userStub.admin)).rejects.toBeInstanceOf(BadRequestException); | ||||
|       expect(jobMock.queue).not.toHaveBeenCalled(); | ||||
|     }); | ||||
| 
 | ||||
|     it('should delete the profile image if user has one', async () => { | ||||
|       userMock.get.mockResolvedValue(userStub.profilePath); | ||||
|       const files = [userStub.profilePath.profileImagePath]; | ||||
| 
 | ||||
|       await sut.deleteProfileImage(userStub.admin); | ||||
|       await expect(jobMock.queue.mock.calls).toEqual([[{ name: JobName.DELETE_FILES, data: { files } }]]); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('getUserProfileImage', () => { | ||||
|  | ||||
| @ -93,10 +93,23 @@ export class UserService { | ||||
|     authUser: AuthUserDto, | ||||
|     fileInfo: Express.Multer.File, | ||||
|   ): Promise<CreateProfileImageResponseDto> { | ||||
|     const { profileImagePath: oldpath } = await this.findOrFail(authUser.id, { withDeleted: false }); | ||||
|     const updatedUser = await this.userRepository.update(authUser.id, { profileImagePath: fileInfo.path }); | ||||
|     if (oldpath !== '') { | ||||
|       await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [oldpath] } }); | ||||
|     } | ||||
|     return mapCreateProfileImageResponse(updatedUser.id, updatedUser.profileImagePath); | ||||
|   } | ||||
| 
 | ||||
|   async deleteProfileImage(authUser: AuthUserDto): Promise<void> { | ||||
|     const user = await this.findOrFail(authUser.id, { withDeleted: false }); | ||||
|     if (user.profileImagePath === '') { | ||||
|       throw new BadRequestException("Can't delete a missing profile Image"); | ||||
|     } | ||||
|     await this.userRepository.update(authUser.id, { profileImagePath: '' }); | ||||
|     await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [user.profileImagePath] } }); | ||||
|   } | ||||
| 
 | ||||
|   async getProfileImage(id: string): Promise<ImmichReadStream> { | ||||
|     const user = await this.findOrFail(id, {}); | ||||
|     if (!user.profileImagePath) { | ||||
| @ -111,7 +124,7 @@ export class UserService { | ||||
|       throw new BadRequestException('Admin account does not exist'); | ||||
|     } | ||||
| 
 | ||||
|     const providedPassword = await ask(admin); | ||||
|     const providedPassword = await ask(mapUser(admin)); | ||||
|     const password = providedPassword || randomBytes(24).toString('base64').replace(/\W/g, ''); | ||||
| 
 | ||||
|     await this.userCore.updateUser(admin, admin.id, { password }); | ||||
|  | ||||
| @ -12,6 +12,7 @@ import { | ||||
|   SignUpDto, | ||||
|   UserResponseDto, | ||||
|   ValidateAccessTokenResponseDto, | ||||
|   mapUser, | ||||
| } from '@app/domain'; | ||||
| import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common'; | ||||
| import { ApiTags } from '@nestjs/swagger'; | ||||
| @ -71,7 +72,7 @@ export class AuthController { | ||||
|   @Post('change-password') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   changePassword(@AuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> { | ||||
|     return this.service.changePassword(authUser, dto); | ||||
|     return this.service.changePassword(authUser, dto).then(mapUser); | ||||
|   } | ||||
| 
 | ||||
|   @Post('logout') | ||||
|  | ||||
| @ -13,6 +13,8 @@ import { | ||||
|   Delete, | ||||
|   Get, | ||||
|   Header, | ||||
|   HttpCode, | ||||
|   HttpStatus, | ||||
|   Param, | ||||
|   Post, | ||||
|   Put, | ||||
| @ -54,6 +56,12 @@ export class UserController { | ||||
|     return this.service.create(createUserDto); | ||||
|   } | ||||
| 
 | ||||
|   @Delete('profile-image') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   deleteProfileImage(@AuthUser() authUser: AuthUserDto): Promise<void> { | ||||
|     return this.service.deleteProfileImage(authUser); | ||||
|   } | ||||
| 
 | ||||
|   @AdminRoute() | ||||
|   @Delete(':id') | ||||
|   deleteUser(@AuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> { | ||||
|  | ||||
| @ -10,6 +10,19 @@ import { | ||||
| import { AssetEntity } from './asset.entity'; | ||||
| import { TagEntity } from './tag.entity'; | ||||
| 
 | ||||
| export enum UserAvatarColor { | ||||
|   PRIMARY = 'primary', | ||||
|   PINK = 'pink', | ||||
|   RED = 'red', | ||||
|   YELLOW = 'yellow', | ||||
|   BLUE = 'blue', | ||||
|   GREEN = 'green', | ||||
|   PURPLE = 'purple', | ||||
|   ORANGE = 'orange', | ||||
|   GRAY = 'gray', | ||||
|   AMBER = 'amber', | ||||
| } | ||||
| 
 | ||||
| @Entity('users') | ||||
| export class UserEntity { | ||||
|   @PrimaryGeneratedColumn('uuid') | ||||
| @ -18,6 +31,9 @@ export class UserEntity { | ||||
|   @Column({ default: '' }) | ||||
|   name!: string; | ||||
| 
 | ||||
|   @Column({ type: 'varchar', nullable: true }) | ||||
|   avatarColor!: UserAvatarColor | null; | ||||
| 
 | ||||
|   @Column({ default: false }) | ||||
|   isAdmin!: boolean; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										14
									
								
								server/src/infra/migrations/1699889987493-AddAvatarColor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								server/src/infra/migrations/1699889987493-AddAvatarColor.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| import { MigrationInterface, QueryRunner } from "typeorm"; | ||||
| 
 | ||||
| export class AddAvatarColor1699889987493 implements MigrationInterface { | ||||
|     name = 'AddAvatarColor1699889987493' | ||||
| 
 | ||||
|     public async up(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "users" ADD "avatarColor" character varying`); | ||||
|     } | ||||
| 
 | ||||
|     public async down(queryRunner: QueryRunner): Promise<void> { | ||||
|         await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "avatarColor"`); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -18,6 +18,7 @@ const password = 'Password123'; | ||||
| const email = 'admin@immich.app'; | ||||
| 
 | ||||
| const adminSignupResponse = { | ||||
|   avatarColor: expect.any(String), | ||||
|   id: expect.any(String), | ||||
|   name: 'Immich Admin', | ||||
|   email: 'admin@immich.app', | ||||
|  | ||||
							
								
								
									
										9
									
								
								server/test/fixtures/user.stub.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								server/test/fixtures/user.stub.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| import { UserEntity } from '@app/infra/entities'; | ||||
| import { UserAvatarColor, UserEntity } from '@app/infra/entities'; | ||||
| import { authStub } from './auth.stub'; | ||||
| 
 | ||||
| export const userStub = { | ||||
| @ -17,6 +17,7 @@ export const userStub = { | ||||
|     tags: [], | ||||
|     assets: [], | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|   }), | ||||
|   user1: Object.freeze<UserEntity>({ | ||||
|     ...authStub.user1, | ||||
| @ -33,6 +34,7 @@ export const userStub = { | ||||
|     tags: [], | ||||
|     assets: [], | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|   }), | ||||
|   user2: Object.freeze<UserEntity>({ | ||||
|     ...authStub.user2, | ||||
| @ -49,6 +51,7 @@ export const userStub = { | ||||
|     tags: [], | ||||
|     assets: [], | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|   }), | ||||
|   storageLabel: Object.freeze<UserEntity>({ | ||||
|     ...authStub.user1, | ||||
| @ -65,6 +68,7 @@ export const userStub = { | ||||
|     tags: [], | ||||
|     assets: [], | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|   }), | ||||
|   externalPath1: Object.freeze<UserEntity>({ | ||||
|     ...authStub.user1, | ||||
| @ -81,6 +85,7 @@ export const userStub = { | ||||
|     tags: [], | ||||
|     assets: [], | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|   }), | ||||
|   externalPath2: Object.freeze<UserEntity>({ | ||||
|     ...authStub.user1, | ||||
| @ -97,6 +102,7 @@ export const userStub = { | ||||
|     tags: [], | ||||
|     assets: [], | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|   }), | ||||
|   profilePath: Object.freeze<UserEntity>({ | ||||
|     ...authStub.user1, | ||||
| @ -113,5 +119,6 @@ export const userStub = { | ||||
|     tags: [], | ||||
|     assets: [], | ||||
|     memoriesEnabled: true, | ||||
|     avatarColor: UserAvatarColor.PRIMARY, | ||||
|   }), | ||||
| }; | ||||
|  | ||||
							
								
								
									
										119
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										119
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							| @ -2355,6 +2355,12 @@ export interface OAuthConfigResponseDto { | ||||
|  * @interface PartnerResponseDto | ||||
|  */ | ||||
| export interface PartnerResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {UserAvatarColor} | ||||
|      * @memberof PartnerResponseDto | ||||
|      */ | ||||
|     'avatarColor': UserAvatarColor; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -2440,6 +2446,8 @@ export interface PartnerResponseDto { | ||||
|      */ | ||||
|     'updatedAt': string; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @ -4344,6 +4352,12 @@ export interface UpdateTagDto { | ||||
|  * @interface UpdateUserDto | ||||
|  */ | ||||
| export interface UpdateUserDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {UserAvatarColor} | ||||
|      * @memberof UpdateUserDto | ||||
|      */ | ||||
|     'avatarColor'?: UserAvatarColor; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -4399,6 +4413,8 @@ export interface UpdateUserDto { | ||||
|      */ | ||||
|     'storageLabel'?: string; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @ -4436,12 +4452,40 @@ export interface UsageByUserDto { | ||||
|      */ | ||||
|     'videos': number; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @enum {string} | ||||
|  */ | ||||
| 
 | ||||
| export const UserAvatarColor = { | ||||
|     Primary: 'primary', | ||||
|     Pink: 'pink', | ||||
|     Red: 'red', | ||||
|     Yellow: 'yellow', | ||||
|     Blue: 'blue', | ||||
|     Green: 'green', | ||||
|     Purple: 'purple', | ||||
|     Orange: 'orange', | ||||
|     Gray: 'gray', | ||||
|     Amber: 'amber' | ||||
| } as const; | ||||
| 
 | ||||
| export type UserAvatarColor = typeof UserAvatarColor[keyof typeof UserAvatarColor]; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface UserDto | ||||
|  */ | ||||
| export interface UserDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {UserAvatarColor} | ||||
|      * @memberof UserDto | ||||
|      */ | ||||
|     'avatarColor': UserAvatarColor; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -4467,12 +4511,20 @@ export interface UserDto { | ||||
|      */ | ||||
|     'profileImagePath': string; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface UserResponseDto | ||||
|  */ | ||||
| export interface UserResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {UserAvatarColor} | ||||
|      * @memberof UserResponseDto | ||||
|      */ | ||||
|     'avatarColor': UserAvatarColor; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {string} | ||||
| @ -4552,6 +4604,8 @@ export interface UserResponseDto { | ||||
|      */ | ||||
|     'updatedAt': string; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @ -16477,6 +16531,44 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration) | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         deleteProfileImage: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             const localVarPath = `/user/profile-image`; | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs.
 | ||||
|             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||
|             let baseOptions; | ||||
|             if (configuration) { | ||||
|                 baseOptions = configuration.baseOptions; | ||||
|             } | ||||
| 
 | ||||
|             const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; | ||||
|             const localVarHeaderParameter = {} as any; | ||||
|             const localVarQueryParameter = {} as any; | ||||
| 
 | ||||
|             // authentication cookie required
 | ||||
| 
 | ||||
|             // authentication api_key required
 | ||||
|             await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) | ||||
| 
 | ||||
|             // authentication bearer required
 | ||||
|             // http bearer authentication required
 | ||||
|             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||
| 
 | ||||
| 
 | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
| 
 | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
| @ -16802,6 +16894,15 @@ export const UserApiFp = function(configuration?: Configuration) { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.createUser(createUserDto, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async deleteProfileImage(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.deleteProfileImage(options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} id  | ||||
| @ -16899,6 +17000,14 @@ export const UserApiFactory = function (configuration?: Configuration, basePath? | ||||
|         createUser(requestParameters: UserApiCreateUserRequest, options?: AxiosRequestConfig): AxiosPromise<UserResponseDto> { | ||||
|             return localVarFp.createUser(requestParameters.createUserDto, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         deleteProfileImage(options?: AxiosRequestConfig): AxiosPromise<void> { | ||||
|             return localVarFp.deleteProfileImage(options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {UserApiDeleteUserRequest} requestParameters Request parameters. | ||||
| @ -17105,6 +17214,16 @@ export class UserApi extends BaseAPI { | ||||
|         return UserApiFp(this.configuration).createUser(requestParameters.createUserDto, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param {*} [options] Override http request option. | ||||
|      * @throws {RequiredError} | ||||
|      * @memberof UserApi | ||||
|      */ | ||||
|     public deleteProfileImage(options?: AxiosRequestConfig) { | ||||
|         return UserApiFp(this.configuration).deleteProfileImage(options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      * @param {UserApiDeleteUserRequest} requestParameters Request parameters. | ||||
|  | ||||
| @ -77,7 +77,7 @@ | ||||
|     <section class="immich-scrollbar max-h-[400px] overflow-y-auto pb-4"> | ||||
|       <div class="flex w-full place-items-center justify-between gap-4 p-5"> | ||||
|         <div class="flex place-items-center gap-4"> | ||||
|           <UserAvatar user={album.owner} size="md" autoColor /> | ||||
|           <UserAvatar user={album.owner} size="md" /> | ||||
|           <p class="text-sm font-medium">{album.owner.name}</p> | ||||
|         </div> | ||||
| 
 | ||||
| @ -90,7 +90,7 @@ | ||||
|           class="flex w-full place-items-center justify-between gap-4 p-5 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700" | ||||
|         > | ||||
|           <div class="flex place-items-center gap-4"> | ||||
|             <UserAvatar {user} size="md" autoColor /> | ||||
|             <UserAvatar {user} size="md" /> | ||||
|             <p class="text-sm font-medium">{user.name}</p> | ||||
|           </div> | ||||
| 
 | ||||
|  | ||||
| @ -71,7 +71,7 @@ | ||||
|               on:click={() => handleUnselect(user)} | ||||
|               class="flex place-items-center gap-1 rounded-full border border-gray-400 p-1 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700" | ||||
|             > | ||||
|               <UserAvatar {user} size="sm" autoColor /> | ||||
|               <UserAvatar {user} size="sm" /> | ||||
|               <p class="text-xs font-medium">{user.name}</p> | ||||
|             </button> | ||||
|           {/key} | ||||
| @ -94,7 +94,7 @@ | ||||
|                 >✓</span | ||||
|               > | ||||
|             {:else} | ||||
|               <UserAvatar {user} size="md" autoColor /> | ||||
|               <UserAvatar {user} size="md" /> | ||||
|             {/if} | ||||
| 
 | ||||
|             <div class="text-left"> | ||||
|  | ||||
| @ -333,7 +333,7 @@ | ||||
|     <p class="text-sm">SHARED BY</p> | ||||
|     <div class="flex gap-4 pt-4"> | ||||
|       <div> | ||||
|         <UserAvatar user={asset.owner} size="md" autoColor /> | ||||
|         <UserAvatar user={asset.owner} size="md" /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="mb-auto mt-auto"> | ||||
|  | ||||
| @ -1,16 +1,48 @@ | ||||
| <script lang="ts"> | ||||
|   import Button from '$lib/components/elements/buttons/button.svelte'; | ||||
|   import { AppRoute } from '$lib/constants'; | ||||
|   import type { UserResponseDto } from '@api'; | ||||
|   import { api, UserAvatarColor, type UserResponseDto } from '@api'; | ||||
|   import { createEventDispatcher } from 'svelte'; | ||||
|   import Icon from '$lib/components/elements/icon.svelte'; | ||||
|   import { fade } from 'svelte/transition'; | ||||
|   import UserAvatar from '../user-avatar.svelte'; | ||||
|   import { mdiCog, mdiLogout } from '@mdi/js'; | ||||
|   import { mdiCog, mdiLogout, mdiPencil } from '@mdi/js'; | ||||
|   import { notificationController, NotificationType } from '../notification/notification'; | ||||
|   import { handleError } from '$lib/utils/handle-error'; | ||||
|   import AvatarSelector from './avatar-selector.svelte'; | ||||
| 
 | ||||
|   export let user: UserResponseDto; | ||||
| 
 | ||||
|   let isShowSelectAvatar = false; | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher(); | ||||
| 
 | ||||
|   const handleSaveProfile = async (color: UserAvatarColor) => { | ||||
|     try { | ||||
|       if (user.profileImagePath !== '') { | ||||
|         await api.userApi.deleteProfileImage(); | ||||
|       } | ||||
| 
 | ||||
|       const { data } = await api.userApi.updateUser({ | ||||
|         updateUserDto: { | ||||
|           id: user.id, | ||||
|           email: user.email, | ||||
|           name: user.name, | ||||
|           avatarColor: color, | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|       user = data; | ||||
|       isShowSelectAvatar = false; | ||||
| 
 | ||||
|       notificationController.show({ | ||||
|         message: 'Saved profile', | ||||
|         type: NotificationType.Info, | ||||
|       }); | ||||
|     } catch (error) { | ||||
|       handleError(error, 'Unable to save profile'); | ||||
|     } | ||||
|   }; | ||||
| </script> | ||||
| 
 | ||||
| <div | ||||
| @ -22,8 +54,22 @@ | ||||
|   <div | ||||
|     class="mx-4 mt-4 flex flex-col items-center justify-center gap-4 rounded-3xl bg-white p-4 dark:bg-immich-dark-primary/10" | ||||
|   > | ||||
|     <UserAvatar size="xl" {user} /> | ||||
|     <div class="relative"> | ||||
|       {#key user} | ||||
|         <UserAvatar {user} size="xl" /> | ||||
| 
 | ||||
|         <div | ||||
|           class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6 border dark:border-immich-dark-primary bg-immich-primary" | ||||
|         > | ||||
|           <button | ||||
|             class="flex items-center justify-center w-full h-full text-white" | ||||
|             on:click={() => (isShowSelectAvatar = true)} | ||||
|           > | ||||
|             <Icon path={mdiPencil} /> | ||||
|           </button> | ||||
|         </div> | ||||
|       {/key} | ||||
|     </div> | ||||
|     <div> | ||||
|       <p class="text-center text-lg font-medium text-immich-primary dark:text-immich-dark-primary"> | ||||
|         {user.name} | ||||
| @ -51,3 +97,10 @@ | ||||
|     > | ||||
|   </div> | ||||
| </div> | ||||
| {#if isShowSelectAvatar} | ||||
|   <AvatarSelector | ||||
|     {user} | ||||
|     on:close={() => (isShowSelectAvatar = false)} | ||||
|     on:choose={({ detail: color }) => handleSaveProfile(color)} | ||||
|   /> | ||||
| {/if} | ||||
|  | ||||
| @ -0,0 +1,39 @@ | ||||
| <script lang="ts"> | ||||
|   import { mdiClose } from '@mdi/js'; | ||||
|   import { createEventDispatcher } from 'svelte'; | ||||
|   import { UserAvatarColor, UserResponseDto } from '@api'; | ||||
|   import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; | ||||
|   import FullScreenModal from '../full-screen-modal.svelte'; | ||||
|   import UserAvatar from '../user-avatar.svelte'; | ||||
| 
 | ||||
|   export let user: UserResponseDto; | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher(); | ||||
|   const colors: UserAvatarColor[] = Object.values(UserAvatarColor); | ||||
| </script> | ||||
| 
 | ||||
| <FullScreenModal on:clickOutside={() => dispatch('close')} on:escape={() => dispatch('close')}> | ||||
|   <div class="flex h-full w-full place-content-center place-items-center overflow-hidden"> | ||||
|     <div | ||||
|       class=" rounded-3xl border bg-immich-bg shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg p-4" | ||||
|     > | ||||
|       <div class="flex items-center"> | ||||
|         <h1 class="px-4 w-full self-center font-medium text-immich-primary dark:text-immich-dark-primary text-sm"> | ||||
|           SELECT AVATAR COLOR | ||||
|         </h1> | ||||
|         <div> | ||||
|           <CircleIconButton icon={mdiClose} on:click={() => dispatch('close')} /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="flex items-center justify-center p-4 mt-4"> | ||||
|         <div class="grid grid-cols-2 md:grid-cols-5 gap-4"> | ||||
|           {#each colors as color} | ||||
|             <button on:click={() => dispatch('choose', color)}> | ||||
|               <UserAvatar {user} {color} size="xl" showProfileImage={false} /> | ||||
|             </button> | ||||
|           {/each} | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </FullScreenModal> | ||||
| @ -124,7 +124,9 @@ | ||||
|             on:mouseleave={() => (shouldShowAccountInfo = false)} | ||||
|             on:click={() => (shouldShowAccountInfoPanel = !shouldShowAccountInfoPanel)} | ||||
|           > | ||||
|             <UserAvatar {user} size="lg" showTitle={false} interactive /> | ||||
|             {#key user} | ||||
|               <UserAvatar {user} size="lg" showTitle={false} interactive /> | ||||
|             {/key} | ||||
|           </button> | ||||
| 
 | ||||
|           {#if shouldShowAccountInfo && !shouldShowAccountInfoPanel} | ||||
| @ -139,7 +141,7 @@ | ||||
|           {/if} | ||||
| 
 | ||||
|           {#if shouldShowAccountInfoPanel} | ||||
|             <AccountInfoPanel {user} on:logout={logOut} /> | ||||
|             <AccountInfoPanel bind:user on:logout={logOut} /> | ||||
|           {/if} | ||||
|         </div> | ||||
|       </section> | ||||
|  | ||||
| @ -1,35 +1,40 @@ | ||||
| <script lang="ts" context="module"> | ||||
|   export type Color = 'primary' | 'pink' | 'red' | 'yellow' | 'blue' | 'green'; | ||||
|   export type Size = 'full' | 'sm' | 'md' | 'lg' | 'xl'; | ||||
|   export type Size = 'full' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl'; | ||||
| </script> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
|   import { imageLoad } from '$lib/utils/image-load'; | ||||
|   import { api } from '@api'; | ||||
|   import { UserAvatarColor, api } from '@api'; | ||||
| 
 | ||||
|   interface User { | ||||
|     id: string; | ||||
|     name: string; | ||||
|     email: string; | ||||
|     profileImagePath: string; | ||||
|     avatarColor: UserAvatarColor; | ||||
|   } | ||||
| 
 | ||||
|   export let user: User; | ||||
|   export let color: Color = 'primary'; | ||||
|   export let color: UserAvatarColor = user.avatarColor; | ||||
|   export let size: Size = 'full'; | ||||
|   export let rounded = true; | ||||
|   export let interactive = false; | ||||
|   export let showTitle = true; | ||||
|   export let autoColor = false; | ||||
|   export let showProfileImage = true; | ||||
| 
 | ||||
|   let showFallback = true; | ||||
| 
 | ||||
|   const colorClasses: Record<Color, string> = { | ||||
|   const colorClasses: Record<UserAvatarColor, string> = { | ||||
|     primary: 'bg-immich-primary dark:bg-immich-dark-primary text-immich-dark-fg dark:text-immich-fg', | ||||
|     pink: 'bg-pink-400 text-immich-bg', | ||||
|     red: 'bg-red-500 text-immich-bg', | ||||
|     yellow: 'bg-yellow-500 text-immich-bg', | ||||
|     blue: 'bg-blue-500 text-immich-bg', | ||||
|     green: 'bg-green-600 text-immich-bg', | ||||
|     purple: 'bg-purple-600 text-immich-bg', | ||||
|     orange: 'bg-orange-600 text-immich-bg', | ||||
|     gray: 'bg-gray-600 text-immich-bg', | ||||
|     amber: 'bg-amber-600 text-immich-bg', | ||||
|   }; | ||||
| 
 | ||||
|   const sizeClasses: Record<Size, string> = { | ||||
| @ -37,18 +42,12 @@ | ||||
|     sm: 'w-7 h-7', | ||||
|     md: 'w-10 h-10', | ||||
|     lg: 'w-12 h-12', | ||||
|     xl: 'w-20 h-20', | ||||
|     xl: 'w-16 h-16', | ||||
|     xxl: 'w-24 h-24', | ||||
|     xxxl: 'w-28 h-28', | ||||
|   }; | ||||
| 
 | ||||
|   // Get color based on the user UUID. | ||||
|   function getUserColor() { | ||||
|     const seed = parseInt(user.id.split('-')[0], 16); | ||||
|     const colors = Object.keys(colorClasses).filter((color) => color !== 'primary') as Color[]; | ||||
|     const randomIndex = seed % colors.length; | ||||
|     return colors[randomIndex]; | ||||
|   } | ||||
| 
 | ||||
|   $: colorClass = colorClasses[autoColor ? getUserColor() : color]; | ||||
|   $: colorClass = colorClasses[color]; | ||||
|   $: sizeClass = sizeClasses[size]; | ||||
|   $: title = `${user.name} (${user.email})`; | ||||
|   $: interactiveClass = interactive | ||||
| @ -61,7 +60,7 @@ | ||||
|   class:rounded-full={rounded} | ||||
|   title={showTitle ? title : undefined} | ||||
| > | ||||
|   {#if user.profileImagePath} | ||||
|   {#if showProfileImage && user.profileImagePath} | ||||
|     <img | ||||
|       src={api.getProfileImageUrl(user.id)} | ||||
|       alt="Profile image of {title}" | ||||
| @ -74,12 +73,12 @@ | ||||
|   {/if} | ||||
|   {#if showFallback} | ||||
|     <span | ||||
|       class="flex h-full w-full select-none items-center justify-center" | ||||
|       class="flex h-full w-full select-none items-center justify-center font-medium" | ||||
|       class:text-xs={size === 'sm'} | ||||
|       class:text-lg={size === 'lg'} | ||||
|       class:text-xl={size === 'xl'} | ||||
|       class:font-medium={!autoColor} | ||||
|       class:font-semibold={autoColor} | ||||
|       class:text-2xl={size === 'xxl'} | ||||
|       class:text-3xl={size === 'xxxl'} | ||||
|     > | ||||
|       {(user.name[0] || '').toUpperCase()} | ||||
|     </span> | ||||
|  | ||||
| @ -56,7 +56,7 @@ | ||||
|               >✓</span | ||||
|             > | ||||
|           {:else} | ||||
|             <UserAvatar {user} size="lg" autoColor /> | ||||
|             <UserAvatar {user} size="lg" /> | ||||
|           {/if} | ||||
| 
 | ||||
|           <div class="text-left"> | ||||
|  | ||||
| @ -113,7 +113,7 @@ | ||||
|       <div class="rounded-2xl border border-gray-200 dark:border-gray-800 mt-6 bg-slate-50 dark:bg-gray-900 p-5"> | ||||
|         <div class="flex gap-4 rounded-lg pb-4 transition-all justify-between"> | ||||
|           <div class="flex gap-4"> | ||||
|             <UserAvatar user={partner.user} size="md" autoColor /> | ||||
|             <UserAvatar user={partner.user} size="md" /> | ||||
|             <div class="text-left"> | ||||
|               <p class="text-immich-fg dark:text-immich-dark-fg"> | ||||
|                 {partner.user.name} | ||||
|  | ||||
| @ -603,13 +603,13 @@ | ||||
| 
 | ||||
|                   <!-- owner --> | ||||
|                   <button on:click={() => (viewMode = ViewMode.VIEW_USERS)}> | ||||
|                     <UserAvatar user={album.owner} size="md" autoColor /> | ||||
|                     <UserAvatar user={album.owner} size="md" /> | ||||
|                   </button> | ||||
| 
 | ||||
|                   <!-- users --> | ||||
|                   {#each album.sharedUsers as user (user.id)} | ||||
|                     <button on:click={() => (viewMode = ViewMode.VIEW_USERS)}> | ||||
|                       <UserAvatar {user} size="md" autoColor /> | ||||
|                       <UserAvatar {user} size="md" /> | ||||
|                     </button> | ||||
|                   {/each} | ||||
| 
 | ||||
|  | ||||
| @ -69,7 +69,7 @@ | ||||
|               href="/partners/{partner.id}" | ||||
|               class="flex gap-4 rounded-lg px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700" | ||||
|             > | ||||
|               <UserAvatar user={partner} size="lg" autoColor /> | ||||
|               <UserAvatar user={partner} size="lg" /> | ||||
|               <div class="text-left"> | ||||
|                 <p class="text-immich-fg dark:text-immich-dark-fg"> | ||||
|                   {partner.name} | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import type { UserResponseDto } from '@api'; | ||||
| import { UserAvatarColor, type UserResponseDto } from '@api'; | ||||
| import { faker } from '@faker-js/faker'; | ||||
| import { Sync } from 'factory.ts'; | ||||
| 
 | ||||
| @ -16,4 +16,5 @@ export const userFactory = Sync.makeFactory<UserResponseDto>({ | ||||
|   updatedAt: Sync.each(() => faker.date.past().toISOString()), | ||||
|   memoriesEnabled: true, | ||||
|   oauthId: '', | ||||
|   avatarColor: UserAvatarColor.Primary, | ||||
| }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user