mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	Internationalization (German) of the mobile app. (#246)
* Add i18n framework to mobile app and write simple translation generator * Replace all texts in login_form with i18n keys * Localization of sharing section * Localization of asset viewer section * Use JSON as base translation format * Add check for missing/unused translation keys * Add localizely * Remove i18n directory in favour of localizely * Backup Translation * More translations * Translate home page * Translation of search page * Translate new server version announcement * Reformat code * Fix typo in german translation * Update englisch translations * Change translation keys to match dart filenames * Add /api to translated endpoint_urls * Update localizely.yml * Add languages to ios plist * Remove unused keys * Added script to check outdated key in other translations * Add download key to localizely.yml Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
		
							parent
							
								
									f3032f74a4
								
							
						
					
					
						commit
						2b5cef156c
					
				
							
								
								
									
										15
									
								
								localizely.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								localizely.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					config_version: 1.0
 | 
				
			||||||
 | 
					project_id: ead34689-ec52-41d9-b675-09bc85a6cbd7
 | 
				
			||||||
 | 
					file_type: flutter_arb
 | 
				
			||||||
 | 
					upload:
 | 
				
			||||||
 | 
					  files:
 | 
				
			||||||
 | 
					    - file: mobile/assets/i18n/en-US.json
 | 
				
			||||||
 | 
					      locale_code: en
 | 
				
			||||||
 | 
					    - file: mobile/assets/i18n/de-DE.json
 | 
				
			||||||
 | 
					      locale_code: de
 | 
				
			||||||
 | 
					download:
 | 
				
			||||||
 | 
					  files:
 | 
				
			||||||
 | 
					    - file: mobile/assets/i18n/en-US.json
 | 
				
			||||||
 | 
					      locale_code: en
 | 
				
			||||||
 | 
					    - file: mobile/assets/i18n/de-DE.json
 | 
				
			||||||
 | 
					      locale_code: de
 | 
				
			||||||
							
								
								
									
										98
									
								
								mobile/assets/i18n/de-DE.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								mobile/assets/i18n/de-DE.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "date_format": "E d. LLL y \u2022 hh:mm",
 | 
				
			||||||
 | 
					  "daily_title_text_date": "E, dd MMM",
 | 
				
			||||||
 | 
					  "daily_title_text_date_year": "E, dd MMM, yyyy",
 | 
				
			||||||
 | 
					  "monthly_title_text_date_format": "MMMM y",
 | 
				
			||||||
 | 
					  "login_form_button_text": "Anmelden",
 | 
				
			||||||
 | 
					  "login_form_save_login": "Angemeldet bleiben",
 | 
				
			||||||
 | 
					  "login_form_endpoint_url": "Server URL",
 | 
				
			||||||
 | 
					  "login_form_endpoint_hint": "http://deine-server-ip:port/api",
 | 
				
			||||||
 | 
					  "login_form_err_trailing_whitespace": "Folgendes Leerzeichen",
 | 
				
			||||||
 | 
					  "login_form_err_leading_whitespace": "Führendes Leerzichen",
 | 
				
			||||||
 | 
					  "login_form_err_invalid_email": "Ungültige E-Mail",
 | 
				
			||||||
 | 
					  "login_form_err_http": "Bitte gebe http:// oder https:// an",
 | 
				
			||||||
 | 
					  "login_form_label_email": "E-Mail",
 | 
				
			||||||
 | 
					  "login_form_email_hint": "deine@email.de",
 | 
				
			||||||
 | 
					  "login_form_label_password": "Passwort",
 | 
				
			||||||
 | 
					  "login_form_password_hint": "password",
 | 
				
			||||||
 | 
					  "share_add_title": "Titel hinzufügen",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_err_delete": "Album konnte nicht gelöscht werden",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_err_leave": "Album konnte nicht verlassen werden",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_err_remove": "Beim Löschen von Elementen aus dem Album ist ein Problem aufgetreten",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_err_title": "Der Titel konnte nicht geändert werden",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_remove": "Entferne vom Album",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_delete": "Album löschen",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_leave": "Album verlassen",
 | 
				
			||||||
 | 
					  "sharing_silver_appbar_create_shared_album": "Neues geteiltes Album",
 | 
				
			||||||
 | 
					  "sharing_silver_appbar_share_partner": "Teile mit Partner",
 | 
				
			||||||
 | 
					  "share_add_photos": "Fotos hinzufügen",
 | 
				
			||||||
 | 
					  "album_viewer_page_share_add_users": "Nutzer hinzufügen",
 | 
				
			||||||
 | 
					  "share_add": "Hinzufügen",
 | 
				
			||||||
 | 
					  "create_shared_album_page_share_add_assets": "ELEMENTE HINZUFÜGEN",
 | 
				
			||||||
 | 
					  "create_shared_album_page_share_select_photos": "Fotos auswählen",
 | 
				
			||||||
 | 
					  "share_create_album": "Album erstellen",
 | 
				
			||||||
 | 
					  "create_shared_album_page_share": "Teilen",
 | 
				
			||||||
 | 
					  "select_additional_user_for_sharing_page_suggestions": "Vorschläge",
 | 
				
			||||||
 | 
					  "share_invite": "Zum Album einladen",
 | 
				
			||||||
 | 
					  "select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
 | 
				
			||||||
 | 
					  "sharing_page_empty_list": "LEERE LISTE",
 | 
				
			||||||
 | 
					  "sharing_page_description": "Erstelle ein geteiltes Album um Fotos und Videos mit Personen in deinem Netzwerk zu teilen.",
 | 
				
			||||||
 | 
					  "sharing_page_album": "Geteilte Alben",
 | 
				
			||||||
 | 
					  "exif_bottom_sheet_description": "Beschreibung hinzufügen...",
 | 
				
			||||||
 | 
					  "exif_bottom_sheet_location": "STANDORT",
 | 
				
			||||||
 | 
					  "exif_bottom_sheet_details": "DETAILS",
 | 
				
			||||||
 | 
					  "backup_err_only_album": "Das einzige Album kann nicht entfernt werden",
 | 
				
			||||||
 | 
					  "backup_controller_page_server_storage": "Server Speicher",
 | 
				
			||||||
 | 
					  "backup_controller_page_status_on": "Sicherung ist aktiv",
 | 
				
			||||||
 | 
					  "backup_controller_page_status_off": "Sicherung ist inaktiv",
 | 
				
			||||||
 | 
					  "backup_controller_page_turn_off": "Sicherung ausschalten",
 | 
				
			||||||
 | 
					  "backup_controller_page_turn_on": "Sicherung einschalten",
 | 
				
			||||||
 | 
					  "backup_controller_page_desc_backup": "Aktiviere die Sicherung um Elemente automatisch auf den Server zu laden.",
 | 
				
			||||||
 | 
					  "backup_controller_page_backup_selected": "Ausgewählt: ",
 | 
				
			||||||
 | 
					  "backup_all": "Alle",
 | 
				
			||||||
 | 
					  "backup_controller_page_none_selected": "Keine ausgewählt",
 | 
				
			||||||
 | 
					  "backup_controller_page_excluded": "Ausgeschlossen: ",
 | 
				
			||||||
 | 
					  "backup_controller_page_albums": "Gesicherte Alben",
 | 
				
			||||||
 | 
					  "backup_controller_page_to_backup": "Zu sichernde Alben",
 | 
				
			||||||
 | 
					  "backup_controller_page_select": "Auswählen",
 | 
				
			||||||
 | 
					  "backup_controller_page_backup": "Sicherung",
 | 
				
			||||||
 | 
					  "backup_controller_page_info": "Informationen zur Sicherung",
 | 
				
			||||||
 | 
					  "backup_controller_page_total": "Gesamt",
 | 
				
			||||||
 | 
					  "backup_controller_page_total_sub": "Alle Fotos und Videos",
 | 
				
			||||||
 | 
					  "backup_controller_page_backup_sub": "Gesicherte Fotos und Videos",
 | 
				
			||||||
 | 
					  "backup_controller_page_remainder": "Übrig",
 | 
				
			||||||
 | 
					  "backup_controller_page_remainder_sub": "Noch zu sichernde Fotos und Videos",
 | 
				
			||||||
 | 
					  "backup_controller_page_cancel": "Abbrechen",
 | 
				
			||||||
 | 
					  "backup_controller_page_start_backup": "Sicherung starten",
 | 
				
			||||||
 | 
					  "album_info_card_backup_album_included": "EINGESCHLOSSEN",
 | 
				
			||||||
 | 
					  "album_info_card_backup_album_excluded": "AUSGESCHLOSSEN",
 | 
				
			||||||
 | 
					  "backup_info_card_assets": "Elemente",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_select_albums": "Alben auswählen",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_selection_info": "Auswahl",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_total_assets": "Elemente",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_albums_tap": "Tippen um einzuschließen, doppelt tippen um zu entfernen",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
 | 
				
			||||||
 | 
					  "backup_controller_page_storage_format": "{} von {} genutzt",
 | 
				
			||||||
 | 
					  "tab_controller_nav_photos": "Fotos",
 | 
				
			||||||
 | 
					  "tab_controller_nav_search": "Suche",
 | 
				
			||||||
 | 
					  "tab_controller_nav_sharing": "Teilen",
 | 
				
			||||||
 | 
					  "control_bottom_app_bar_delete": "Löschen",
 | 
				
			||||||
 | 
					  "delete_dialog_title": "Für immer löschen",
 | 
				
			||||||
 | 
					  "delete_dialog_alert": "Diese Elemente werden unwiderruflich von Immich und dem Gerät entfernt",
 | 
				
			||||||
 | 
					  "delete_dialog_cancel": "Abbrechen",
 | 
				
			||||||
 | 
					  "delete_dialog_ok": "Löschen",
 | 
				
			||||||
 | 
					  "profile_drawer_sign_out": "Abmelden",
 | 
				
			||||||
 | 
					  "profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
 | 
				
			||||||
 | 
					  "search_bar_hint": "Durchsuche deine Fotos",
 | 
				
			||||||
 | 
					  "search_page_places": "Orte",
 | 
				
			||||||
 | 
					  "search_page_things": "Dinge",
 | 
				
			||||||
 | 
					  "search_result_page_new_search_hint": "Neue Suche",
 | 
				
			||||||
 | 
					  "search_page_no_places": "Keine Informationen über Orte verfügbar",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_text_1": "Hallo mein Freund! Es gibt eine neue Version von",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_release_notes": "Änderungsprotokoll",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_ack": "Ich habe verstanden"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										98
									
								
								mobile/assets/i18n/en-US.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								mobile/assets/i18n/en-US.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "date_format": "E, LLL d, y \u2022 h:mm a",
 | 
				
			||||||
 | 
					  "daily_title_text_date": "E, MMM dd",
 | 
				
			||||||
 | 
					  "daily_title_text_date_year": "E, MMM dd, yyyy",
 | 
				
			||||||
 | 
					  "monthly_title_text_date_format": "MMMM y",
 | 
				
			||||||
 | 
					  "login_form_button_text": "Login",
 | 
				
			||||||
 | 
					  "login_form_save_login": "Stay logged in",
 | 
				
			||||||
 | 
					  "login_form_endpoint_url": "Server Endpoint URL",
 | 
				
			||||||
 | 
					  "login_form_endpoint_hint": "http://your-server-ip:port/api",
 | 
				
			||||||
 | 
					  "login_form_err_trailing_whitespace": "Trailing whitespace",
 | 
				
			||||||
 | 
					  "login_form_err_leading_whitespace": "Leading whitespace",
 | 
				
			||||||
 | 
					  "login_form_err_invalid_email": "Invalid Email",
 | 
				
			||||||
 | 
					  "login_form_err_http": "Please specify http:// or https://",
 | 
				
			||||||
 | 
					  "login_form_label_email": "Email",
 | 
				
			||||||
 | 
					  "login_form_email_hint": "youremail@email.com",
 | 
				
			||||||
 | 
					  "login_form_label_password": "Password",
 | 
				
			||||||
 | 
					  "login_form_password_hint": "password",
 | 
				
			||||||
 | 
					  "share_add_title": "Add a title",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_err_delete": "Failed to delete album",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_err_leave": "Failed to leave album",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_err_remove": "There are problems in removing assets from album",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_err_title": "Failed to change album title",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_remove": "Remove from album",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_delete": "Delete album",
 | 
				
			||||||
 | 
					  "album_viewer_appbar_share_leave": "Leave album",
 | 
				
			||||||
 | 
					  "sharing_silver_appbar_create_shared_album": "Create shared album",
 | 
				
			||||||
 | 
					  "sharing_silver_appbar_share_partner": "Share with partner",
 | 
				
			||||||
 | 
					  "share_add_photos": "Add photos",
 | 
				
			||||||
 | 
					  "album_viewer_page_share_add_users": "Add users",
 | 
				
			||||||
 | 
					  "share_add": "Add",
 | 
				
			||||||
 | 
					  "create_shared_album_page_share_add_assets": "ADD ASSETS",
 | 
				
			||||||
 | 
					  "create_shared_album_page_share_select_photos": "Select Photos",
 | 
				
			||||||
 | 
					  "share_create_album": "Create album",
 | 
				
			||||||
 | 
					  "create_shared_album_page_share": "Share",
 | 
				
			||||||
 | 
					  "select_additional_user_for_sharing_page_suggestions": "Suggestions",
 | 
				
			||||||
 | 
					  "share_invite": "Invite to album",
 | 
				
			||||||
 | 
					  "select_user_for_sharing_page_err_album": "Failed to create album",
 | 
				
			||||||
 | 
					  "sharing_page_empty_list": "EMPTY LIST",
 | 
				
			||||||
 | 
					  "sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
 | 
				
			||||||
 | 
					  "sharing_page_album": "Shared albums",
 | 
				
			||||||
 | 
					  "exif_bottom_sheet_description": "Add Description...",
 | 
				
			||||||
 | 
					  "exif_bottom_sheet_location": "LOCATION",
 | 
				
			||||||
 | 
					  "exif_bottom_sheet_details": "DETAILS",
 | 
				
			||||||
 | 
					  "backup_err_only_album": "Cannot remove the only album",
 | 
				
			||||||
 | 
					  "backup_controller_page_server_storage": "Server Storage",
 | 
				
			||||||
 | 
					  "backup_controller_page_status_on": "Backup is on",
 | 
				
			||||||
 | 
					  "backup_controller_page_status_off": "Backup is off",
 | 
				
			||||||
 | 
					  "backup_controller_page_turn_off": "Turn off Backup",
 | 
				
			||||||
 | 
					  "backup_controller_page_turn_on": "Turn on Backup",
 | 
				
			||||||
 | 
					  "backup_controller_page_desc_backup": "Turn on backup to automatically upload new assets to the server.",
 | 
				
			||||||
 | 
					  "backup_controller_page_backup_selected": "Selected: ",
 | 
				
			||||||
 | 
					  "backup_all": "All",
 | 
				
			||||||
 | 
					  "backup_controller_page_none_selected": "None selected",
 | 
				
			||||||
 | 
					  "backup_controller_page_excluded": "Excluded: ",
 | 
				
			||||||
 | 
					  "backup_controller_page_albums": "Backup Albums",
 | 
				
			||||||
 | 
					  "backup_controller_page_to_backup": "Albums to be backup",
 | 
				
			||||||
 | 
					  "backup_controller_page_select": "Select",
 | 
				
			||||||
 | 
					  "backup_controller_page_backup": "Backup",
 | 
				
			||||||
 | 
					  "backup_controller_page_info": "Backup Information",
 | 
				
			||||||
 | 
					  "backup_controller_page_total": "Total",
 | 
				
			||||||
 | 
					  "backup_controller_page_total_sub": "All unique photos and videos from selected albums",
 | 
				
			||||||
 | 
					  "backup_controller_page_backup_sub": "Backed up photos and videos",
 | 
				
			||||||
 | 
					  "backup_controller_page_remainder": "Remainder",
 | 
				
			||||||
 | 
					  "backup_controller_page_remainder_sub": "Remaining photos and albums to back up from selection",
 | 
				
			||||||
 | 
					  "backup_controller_page_cancel": "Cancel",
 | 
				
			||||||
 | 
					  "backup_controller_page_start_backup": "Start Backup",
 | 
				
			||||||
 | 
					  "album_info_card_backup_album_included": "INCLUDED",
 | 
				
			||||||
 | 
					  "album_info_card_backup_album_excluded": "EXCLUDED",
 | 
				
			||||||
 | 
					  "backup_info_card_assets": "assets",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_select_albums": "Select Albums",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_selection_info": "Selection Info",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_total_assets": "Total unique assets",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_albums_device": "Albums on device ({})",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
 | 
				
			||||||
 | 
					  "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
 | 
				
			||||||
 | 
					  "backup_controller_page_storage_format": "{} of {} used",
 | 
				
			||||||
 | 
					  "tab_controller_nav_photos": "Photos",
 | 
				
			||||||
 | 
					  "tab_controller_nav_search": "Search",
 | 
				
			||||||
 | 
					  "tab_controller_nav_sharing": "Sharing",
 | 
				
			||||||
 | 
					  "control_bottom_app_bar_delete": "Delete",
 | 
				
			||||||
 | 
					  "delete_dialog_title": "Delete Permanently",
 | 
				
			||||||
 | 
					  "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
 | 
				
			||||||
 | 
					  "delete_dialog_cancel": "Cancel",
 | 
				
			||||||
 | 
					  "delete_dialog_ok": "Delete",
 | 
				
			||||||
 | 
					  "profile_drawer_sign_out": "Sign Out",
 | 
				
			||||||
 | 
					  "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
 | 
				
			||||||
 | 
					  "search_bar_hint": "Search your photos",
 | 
				
			||||||
 | 
					  "search_page_places": "Places",
 | 
				
			||||||
 | 
					  "search_page_things": "Things",
 | 
				
			||||||
 | 
					  "search_result_page_new_search_hint": "New Search",
 | 
				
			||||||
 | 
					  "search_page_no_places": "No Places Info Available",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_text_1": "Hi friend, there is a new release of",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_text_2": "please take your time to visit the ",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_release_notes": "release notes",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
 | 
				
			||||||
 | 
					  "version_announcement_overlay_ack": "Acknowledge"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -82,5 +82,11 @@
 | 
				
			|||||||
    <array>
 | 
					    <array>
 | 
				
			||||||
      <string>https</string>
 | 
					      <string>https</string>
 | 
				
			||||||
    </array>
 | 
					    </array>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <key>CFBundleLocalizations</key>
 | 
				
			||||||
 | 
					    <array>
 | 
				
			||||||
 | 
					      <string>en</string>
 | 
				
			||||||
 | 
					      <string>de</string>
 | 
				
			||||||
 | 
					    </array>
 | 
				
			||||||
  </dict>
 | 
					  </dict>
 | 
				
			||||||
</plist>
 | 
					</plist>
 | 
				
			||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
import 'package:hive_flutter/hive_flutter.dart';
 | 
					import 'package:hive_flutter/hive_flutter.dart';
 | 
				
			||||||
@ -36,7 +37,21 @@ void main() async {
 | 
				
			|||||||
    ),
 | 
					    ),
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  runApp(const ProviderScope(child: ImmichApp()));
 | 
					  await EasyLocalization.ensureInitialized();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var locales = const [
 | 
				
			||||||
 | 
					    // Default locale
 | 
				
			||||||
 | 
					    Locale('en', 'US'),
 | 
				
			||||||
 | 
					    // Additional locales
 | 
				
			||||||
 | 
					    Locale('de', 'DE')
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  runApp(EasyLocalization(
 | 
				
			||||||
 | 
					      supportedLocales: locales,
 | 
				
			||||||
 | 
					      path: 'assets/i18n',
 | 
				
			||||||
 | 
					      useFallbackTranslations: true,
 | 
				
			||||||
 | 
					      fallbackLocale: locales.first,
 | 
				
			||||||
 | 
					      child: const ProviderScope(child: ImmichApp())));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ImmichApp extends ConsumerStatefulWidget {
 | 
					class ImmichApp extends ConsumerStatefulWidget {
 | 
				
			||||||
@ -112,6 +127,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
 | 
				
			|||||||
    ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
 | 
					    ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return MaterialApp(
 | 
					    return MaterialApp(
 | 
				
			||||||
 | 
					      localizationsDelegates: context.localizationDelegates,
 | 
				
			||||||
 | 
					      supportedLocales: context.supportedLocales,
 | 
				
			||||||
 | 
					      locale: context.locale,
 | 
				
			||||||
      debugShowCheckedModeBanner: false,
 | 
					      debugShowCheckedModeBanner: false,
 | 
				
			||||||
      home: Stack(
 | 
					      home: Stack(
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_map/flutter_map.dart';
 | 
					import 'package:flutter_map/flutter_map.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -72,7 +73,7 @@ class ExifBottomSheet extends ConsumerWidget {
 | 
				
			|||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          if (assetDetail.exifInfo?.dateTimeOriginal != null)
 | 
					          if (assetDetail.exifInfo?.dateTimeOriginal != null)
 | 
				
			||||||
            Text(
 | 
					            Text(
 | 
				
			||||||
              DateFormat('E, LLL d, y • h:mm a').format(
 | 
					              DateFormat('date_format'.tr()).format(
 | 
				
			||||||
                DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!),
 | 
					                DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              style: TextStyle(
 | 
					              style: TextStyle(
 | 
				
			||||||
@ -84,12 +85,12 @@ class ExifBottomSheet extends ConsumerWidget {
 | 
				
			|||||||
          Padding(
 | 
					          Padding(
 | 
				
			||||||
            padding: const EdgeInsets.only(top: 16.0),
 | 
					            padding: const EdgeInsets.only(top: 16.0),
 | 
				
			||||||
            child: Text(
 | 
					            child: Text(
 | 
				
			||||||
              "Add Description...",
 | 
					              "exif_bottom_sheet_description",
 | 
				
			||||||
              style: TextStyle(
 | 
					              style: TextStyle(
 | 
				
			||||||
                color: Colors.grey[500],
 | 
					                color: Colors.grey[500],
 | 
				
			||||||
                fontSize: 11,
 | 
					                fontSize: 11,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ).tr(),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Location
 | 
					          // Location
 | 
				
			||||||
@ -104,9 +105,9 @@ class ExifBottomSheet extends ConsumerWidget {
 | 
				
			|||||||
                    color: Colors.grey[600],
 | 
					                    color: Colors.grey[600],
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  Text(
 | 
					                  Text(
 | 
				
			||||||
                    "LOCATION",
 | 
					                    "exif_bottom_sheet_location",
 | 
				
			||||||
                    style: TextStyle(fontSize: 11, color: Colors.grey[400]),
 | 
					                    style: TextStyle(fontSize: 11, color: Colors.grey[400]),
 | 
				
			||||||
                  ),
 | 
					                  ).tr(),
 | 
				
			||||||
                  if (assetDetail.exifInfo?.latitude != null &&
 | 
					                  if (assetDetail.exifInfo?.latitude != null &&
 | 
				
			||||||
                      assetDetail.exifInfo?.longitude != null)
 | 
					                      assetDetail.exifInfo?.longitude != null)
 | 
				
			||||||
                    _buildMap(),
 | 
					                    _buildMap(),
 | 
				
			||||||
@ -134,9 +135,9 @@ class ExifBottomSheet extends ConsumerWidget {
 | 
				
			|||||||
                  Padding(
 | 
					                  Padding(
 | 
				
			||||||
                    padding: const EdgeInsets.only(bottom: 8.0),
 | 
					                    padding: const EdgeInsets.only(bottom: 8.0),
 | 
				
			||||||
                    child: Text(
 | 
					                    child: Text(
 | 
				
			||||||
                      "DETAILS",
 | 
					                      "exif_bottom_sheet_details",
 | 
				
			||||||
                      style: TextStyle(fontSize: 11, color: Colors.grey[400]),
 | 
					                      style: TextStyle(fontSize: 11, color: Colors.grey[400]),
 | 
				
			||||||
                    ),
 | 
					                    ).tr(),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  ListTile(
 | 
					                  ListTile(
 | 
				
			||||||
                    contentPadding: const EdgeInsets.all(0),
 | 
					                    contentPadding: const EdgeInsets.all(0),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import 'dart:typed_data';
 | 
					import 'dart:typed_data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
import 'package:fluttertoast/fluttertoast.dart';
 | 
					import 'package:fluttertoast/fluttertoast.dart';
 | 
				
			||||||
@ -37,10 +38,10 @@ class AlbumInfoCard extends HookConsumerWidget {
 | 
				
			|||||||
          visualDensity: VisualDensity.compact,
 | 
					          visualDensity: VisualDensity.compact,
 | 
				
			||||||
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
 | 
					          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
 | 
				
			||||||
          label: const Text(
 | 
					          label: const Text(
 | 
				
			||||||
            "INCLUDED",
 | 
					            "album_info_card_backup_album_included",
 | 
				
			||||||
            style: TextStyle(
 | 
					            style: TextStyle(
 | 
				
			||||||
                fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
 | 
					                fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
 | 
				
			||||||
          ),
 | 
					          ).tr(),
 | 
				
			||||||
          backgroundColor: Theme.of(context).primaryColor,
 | 
					          backgroundColor: Theme.of(context).primaryColor,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      } else if (isExcluded) {
 | 
					      } else if (isExcluded) {
 | 
				
			||||||
@ -48,10 +49,10 @@ class AlbumInfoCard extends HookConsumerWidget {
 | 
				
			|||||||
          visualDensity: VisualDensity.compact,
 | 
					          visualDensity: VisualDensity.compact,
 | 
				
			||||||
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
 | 
					          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
 | 
				
			||||||
          label: const Text(
 | 
					          label: const Text(
 | 
				
			||||||
            "EXCLUDED",
 | 
					            "album_info_card_backup_album_excluded",
 | 
				
			||||||
            style: TextStyle(
 | 
					            style: TextStyle(
 | 
				
			||||||
                fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
 | 
					                fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
 | 
				
			||||||
          ),
 | 
					          ).tr(),
 | 
				
			||||||
          backgroundColor: Colors.red[300],
 | 
					          backgroundColor: Colors.red[300],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -77,7 +78,7 @@ class AlbumInfoCard extends HookConsumerWidget {
 | 
				
			|||||||
          if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
 | 
					          if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
 | 
				
			||||||
            ImmichToast.show(
 | 
					            ImmichToast.show(
 | 
				
			||||||
              context: context,
 | 
					              context: context,
 | 
				
			||||||
              msg: "Cannot remove the only album",
 | 
					              msg: "backup_err_only_album".tr(),
 | 
				
			||||||
              toastType: ToastType.error,
 | 
					              toastType: ToastType.error,
 | 
				
			||||||
              gravity: ToastGravity.BOTTOM,
 | 
					              gravity: ToastGravity.BOTTOM,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
@ -104,7 +105,7 @@ class AlbumInfoCard extends HookConsumerWidget {
 | 
				
			|||||||
                  .contains(albumInfo)) {
 | 
					                  .contains(albumInfo)) {
 | 
				
			||||||
            ImmichToast.show(
 | 
					            ImmichToast.show(
 | 
				
			||||||
              context: context,
 | 
					              context: context,
 | 
				
			||||||
              msg: "Cannot exclude the only album",
 | 
					              msg: "backup_err_only_album".tr(),
 | 
				
			||||||
              toastType: ToastType.error,
 | 
					              toastType: ToastType.error,
 | 
				
			||||||
              gravity: ToastGravity.BOTTOM,
 | 
					              gravity: ToastGravity.BOTTOM,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
@ -180,7 +181,10 @@ class AlbumInfoCard extends HookConsumerWidget {
 | 
				
			|||||||
                          Padding(
 | 
					                          Padding(
 | 
				
			||||||
                            padding: const EdgeInsets.only(top: 2.0),
 | 
					                            padding: const EdgeInsets.only(top: 2.0),
 | 
				
			||||||
                            child: Text(
 | 
					                            child: Text(
 | 
				
			||||||
                              '${albumInfo.assetCount} ${(albumInfo.isAll ? " (ALL)" : "")}',
 | 
					                              albumInfo.assetCount.toString() +
 | 
				
			||||||
 | 
					                                  (albumInfo.isAll
 | 
				
			||||||
 | 
					                                      ? " (${'backup_all'.tr()})"
 | 
				
			||||||
 | 
					                                      : ""),
 | 
				
			||||||
                              style: TextStyle(
 | 
					                              style: TextStyle(
 | 
				
			||||||
                                  fontSize: 12, color: Colors.grey[600]),
 | 
					                                  fontSize: 12, color: Colors.grey[600]),
 | 
				
			||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BackupInfoCard extends StatelessWidget {
 | 
					class BackupInfoCard extends StatelessWidget {
 | 
				
			||||||
@ -44,7 +45,7 @@ class BackupInfoCard extends StatelessWidget {
 | 
				
			|||||||
              info,
 | 
					              info,
 | 
				
			||||||
              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
 | 
					              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            const Text("assets"),
 | 
					            const Text("backup_info_card_assets").tr(),
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:fluttertoast/fluttertoast.dart';
 | 
					import 'package:fluttertoast/fluttertoast.dart';
 | 
				
			||||||
@ -55,7 +56,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
 | 
				
			|||||||
          if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
 | 
					          if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
 | 
				
			||||||
            ImmichToast.show(
 | 
					            ImmichToast.show(
 | 
				
			||||||
              context: context,
 | 
					              context: context,
 | 
				
			||||||
              msg: "Cannot remove the only album",
 | 
					              msg: "backup_err_only_album".tr(),
 | 
				
			||||||
              toastType: ToastType.error,
 | 
					              toastType: ToastType.error,
 | 
				
			||||||
              gravity: ToastGravity.BOTTOM,
 | 
					              gravity: ToastGravity.BOTTOM,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
@ -136,20 +137,21 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
 | 
				
			|||||||
          icon: const Icon(Icons.arrow_back_ios_rounded),
 | 
					          icon: const Icon(Icons.arrow_back_ios_rounded),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        title: const Text(
 | 
					        title: const Text(
 | 
				
			||||||
          "Select Albums",
 | 
					          "backup_album_selection_page_select_albums",
 | 
				
			||||||
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
 | 
					          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
 | 
				
			||||||
        ),
 | 
					        ).tr(),
 | 
				
			||||||
        elevation: 0,
 | 
					        elevation: 0,
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      body: ListView(
 | 
					      body: ListView(
 | 
				
			||||||
        physics: const ClampingScrollPhysics(),
 | 
					        physics: const ClampingScrollPhysics(),
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          const Padding(
 | 
					          Padding(
 | 
				
			||||||
            padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
 | 
					            padding:
 | 
				
			||||||
            child: Text(
 | 
					                const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
 | 
				
			||||||
              "Selection Info",
 | 
					            child: const Text(
 | 
				
			||||||
 | 
					              "backup_album_selection_page_selection_info",
 | 
				
			||||||
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
 | 
					              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
 | 
				
			||||||
            ),
 | 
					            ).tr(),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          // Selected Album Chips
 | 
					          // Selected Album Chips
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -181,14 +183,18 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
 | 
				
			|||||||
                  ListTile(
 | 
					                  ListTile(
 | 
				
			||||||
                    visualDensity: VisualDensity.compact,
 | 
					                    visualDensity: VisualDensity.compact,
 | 
				
			||||||
                    title: Text(
 | 
					                    title: Text(
 | 
				
			||||||
                      "Total unique assets",
 | 
					                      "backup_album_selection_page_total_assets",
 | 
				
			||||||
                      style: TextStyle(
 | 
					                      style: TextStyle(
 | 
				
			||||||
                          fontWeight: FontWeight.bold,
 | 
					                          fontWeight: FontWeight.bold,
 | 
				
			||||||
                          fontSize: 14,
 | 
					                          fontSize: 14,
 | 
				
			||||||
                          color: Colors.grey[700]),
 | 
					                          color: Colors.grey[700]),
 | 
				
			||||||
                    ),
 | 
					                    ).tr(),
 | 
				
			||||||
                    trailing: Text(
 | 
					                    trailing: Text(
 | 
				
			||||||
                      '${ref.watch(backupProvider).allUniqueAssets.length}',
 | 
					                      ref
 | 
				
			||||||
 | 
					                          .watch(backupProvider)
 | 
				
			||||||
 | 
					                          .allUniqueAssets
 | 
				
			||||||
 | 
					                          .length
 | 
				
			||||||
 | 
					                          .toString(),
 | 
				
			||||||
                      style: const TextStyle(fontWeight: FontWeight.bold),
 | 
					                      style: const TextStyle(fontWeight: FontWeight.bold),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
@ -199,19 +205,20 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
          ListTile(
 | 
					          ListTile(
 | 
				
			||||||
            title: Text(
 | 
					            title: Text(
 | 
				
			||||||
              "Albums on device (${availableAlbums.length})",
 | 
					              "backup_album_selection_page_albums_device"
 | 
				
			||||||
 | 
					                  .tr(args: [availableAlbums.length.toString()]),
 | 
				
			||||||
              style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
 | 
					              style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            subtitle: Padding(
 | 
					            subtitle: Padding(
 | 
				
			||||||
              padding: const EdgeInsets.symmetric(vertical: 8.0),
 | 
					              padding: const EdgeInsets.symmetric(vertical: 8.0),
 | 
				
			||||||
              child: Text(
 | 
					              child: Text(
 | 
				
			||||||
                "Tap to include, double tap to exclude",
 | 
					                "backup_album_selection_page_albums_tap",
 | 
				
			||||||
                style: TextStyle(
 | 
					                style: TextStyle(
 | 
				
			||||||
                  fontSize: 12,
 | 
					                  fontSize: 12,
 | 
				
			||||||
                  color: Theme.of(context).primaryColor,
 | 
					                  color: Theme.of(context).primaryColor,
 | 
				
			||||||
                  fontWeight: FontWeight.bold,
 | 
					                  fontWeight: FontWeight.bold,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ).tr(),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            trailing: IconButton(
 | 
					            trailing: IconButton(
 | 
				
			||||||
              splashRadius: 16,
 | 
					              splashRadius: 16,
 | 
				
			||||||
@ -230,21 +237,21 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
 | 
				
			|||||||
                          borderRadius: BorderRadius.circular(12)),
 | 
					                          borderRadius: BorderRadius.circular(12)),
 | 
				
			||||||
                      elevation: 5,
 | 
					                      elevation: 5,
 | 
				
			||||||
                      title: Text(
 | 
					                      title: Text(
 | 
				
			||||||
                        'Selection Info',
 | 
					                        'backup_album_selection_page_selection_info',
 | 
				
			||||||
                        style: TextStyle(
 | 
					                        style: TextStyle(
 | 
				
			||||||
                          fontSize: 16,
 | 
					                          fontSize: 16,
 | 
				
			||||||
                          fontWeight: FontWeight.bold,
 | 
					                          fontWeight: FontWeight.bold,
 | 
				
			||||||
                          color: Theme.of(context).primaryColor,
 | 
					                          color: Theme.of(context).primaryColor,
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ).tr(),
 | 
				
			||||||
                      content: SingleChildScrollView(
 | 
					                      content: SingleChildScrollView(
 | 
				
			||||||
                        child: ListBody(
 | 
					                        child: ListBody(
 | 
				
			||||||
                          children: [
 | 
					                          children: [
 | 
				
			||||||
                            Text(
 | 
					                            Text(
 | 
				
			||||||
                              'Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.',
 | 
					                              'backup_album_selection_page_assets_scatter',
 | 
				
			||||||
                              style: TextStyle(
 | 
					                              style: TextStyle(
 | 
				
			||||||
                                  fontSize: 14, color: Colors.grey[700]),
 | 
					                                  fontSize: 14, color: Colors.grey[700]),
 | 
				
			||||||
                            ),
 | 
					                            ).tr(),
 | 
				
			||||||
                          ],
 | 
					                          ],
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -44,9 +45,9 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
          color: Theme.of(context).primaryColor,
 | 
					          color: Theme.of(context).primaryColor,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        title: const Text(
 | 
					        title: const Text(
 | 
				
			||||||
          "Server storage",
 | 
					          "backup_controller_page_server_storage",
 | 
				
			||||||
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
 | 
					          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
 | 
				
			||||||
        ),
 | 
					        ).tr(),
 | 
				
			||||||
        subtitle: Padding(
 | 
					        subtitle: Padding(
 | 
				
			||||||
          padding: const EdgeInsets.only(top: 8.0),
 | 
					          padding: const EdgeInsets.only(top: 8.0),
 | 
				
			||||||
          child: Column(
 | 
					          child: Column(
 | 
				
			||||||
@ -66,8 +67,11 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
              ),
 | 
					              ),
 | 
				
			||||||
              Padding(
 | 
					              Padding(
 | 
				
			||||||
                padding: const EdgeInsets.only(top: 12.0),
 | 
					                padding: const EdgeInsets.only(top: 12.0),
 | 
				
			||||||
                child: Text(
 | 
					                child: const Text('backup_controller_page_storage_format').tr(
 | 
				
			||||||
                    '${backupState.serverInfo.diskUse} of ${backupState.serverInfo.diskSize} used'),
 | 
					                    args: [
 | 
				
			||||||
 | 
					                      backupState.serverInfo.diskUse,
 | 
				
			||||||
 | 
					                      backupState.serverInfo.diskSize
 | 
				
			||||||
 | 
					                    ]),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
@ -76,11 +80,13 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ListTile _buildBackupController() {
 | 
					    ListTile _buildBackupController() {
 | 
				
			||||||
      var backUpOption =
 | 
					      var backUpOption = authenticationState.deviceInfo.isAutoBackup
 | 
				
			||||||
          authenticationState.deviceInfo.isAutoBackup ? "on" : "off";
 | 
					          ? "backup_controller_page_status_on".tr()
 | 
				
			||||||
 | 
					          : "backup_controller_page_status_off".tr();
 | 
				
			||||||
      var isAutoBackup = authenticationState.deviceInfo.isAutoBackup;
 | 
					      var isAutoBackup = authenticationState.deviceInfo.isAutoBackup;
 | 
				
			||||||
      var backupBtnText =
 | 
					      var backupBtnText = authenticationState.deviceInfo.isAutoBackup
 | 
				
			||||||
          authenticationState.deviceInfo.isAutoBackup ? "off" : "on";
 | 
					          ? "backup_controller_page_turn_off".tr()
 | 
				
			||||||
 | 
					          : "backup_controller_page_turn_on".tr();
 | 
				
			||||||
      return ListTile(
 | 
					      return ListTile(
 | 
				
			||||||
        isThreeLine: true,
 | 
					        isThreeLine: true,
 | 
				
			||||||
        leading: isAutoBackup
 | 
					        leading: isAutoBackup
 | 
				
			||||||
@ -90,7 +96,7 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
              )
 | 
					              )
 | 
				
			||||||
            : const Icon(Icons.cloud_off_rounded),
 | 
					            : const Icon(Icons.cloud_off_rounded),
 | 
				
			||||||
        title: Text(
 | 
					        title: Text(
 | 
				
			||||||
          "Back up is $backUpOption",
 | 
					          backUpOption,
 | 
				
			||||||
          style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
 | 
					          style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        subtitle: Padding(
 | 
					        subtitle: Padding(
 | 
				
			||||||
@ -100,9 +106,9 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
            children: [
 | 
					            children: [
 | 
				
			||||||
              if (!isAutoBackup)
 | 
					              if (!isAutoBackup)
 | 
				
			||||||
                const Text(
 | 
					                const Text(
 | 
				
			||||||
                  "Turn on backup to automatically upload new assets to the server.",
 | 
					                  "backup_controller_page_desc_backup",
 | 
				
			||||||
                  style: TextStyle(fontSize: 14),
 | 
					                  style: TextStyle(fontSize: 14),
 | 
				
			||||||
                ),
 | 
					                ).tr(),
 | 
				
			||||||
              Padding(
 | 
					              Padding(
 | 
				
			||||||
                padding: const EdgeInsets.only(top: 8.0),
 | 
					                padding: const EdgeInsets.only(top: 8.0),
 | 
				
			||||||
                child: OutlinedButton(
 | 
					                child: OutlinedButton(
 | 
				
			||||||
@ -123,7 +129,7 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
                          .setAutoBackup(true);
 | 
					                          .setAutoBackup(true);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  child: Text("Turn $backupBtnText Backup",
 | 
					                  child: Text(backupBtnText,
 | 
				
			||||||
                      style: const TextStyle(fontWeight: FontWeight.bold)),
 | 
					                      style: const TextStyle(fontWeight: FontWeight.bold)),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              )
 | 
					              )
 | 
				
			||||||
@ -134,13 +140,13 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Widget _buildSelectedAlbumName() {
 | 
					    Widget _buildSelectedAlbumName() {
 | 
				
			||||||
      var text = "Selected: ";
 | 
					      var text = "backup_controller_page_backup_selected".tr();
 | 
				
			||||||
      var albums = ref.watch(backupProvider).selectedBackupAlbums;
 | 
					      var albums = ref.watch(backupProvider).selectedBackupAlbums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (albums.isNotEmpty) {
 | 
					      if (albums.isNotEmpty) {
 | 
				
			||||||
        for (var album in albums) {
 | 
					        for (var album in albums) {
 | 
				
			||||||
          if (album.name == "Recent" || album.name == "Recents") {
 | 
					          if (album.name == "Recent" || album.name == "Recents") {
 | 
				
			||||||
            text += "${album.name} (All), ";
 | 
					            text += "${album.name} (${'backup_all'.tr()}), ";
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            text += "${album.name}, ";
 | 
					            text += "${album.name}, ";
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -160,7 +166,7 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
        return Padding(
 | 
					        return Padding(
 | 
				
			||||||
          padding: const EdgeInsets.only(top: 8.0),
 | 
					          padding: const EdgeInsets.only(top: 8.0),
 | 
				
			||||||
          child: Text(
 | 
					          child: Text(
 | 
				
			||||||
            "None selected",
 | 
					            "backup_controller_page_none_selected".tr(),
 | 
				
			||||||
            style: TextStyle(
 | 
					            style: TextStyle(
 | 
				
			||||||
                color: Theme.of(context).primaryColor,
 | 
					                color: Theme.of(context).primaryColor,
 | 
				
			||||||
                fontSize: 12,
 | 
					                fontSize: 12,
 | 
				
			||||||
@ -171,7 +177,7 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Widget _buildExcludedAlbumName() {
 | 
					    Widget _buildExcludedAlbumName() {
 | 
				
			||||||
      var text = "Excluded: ";
 | 
					      var text = "backup_controller_page_excluded".tr();
 | 
				
			||||||
      var albums = ref.watch(backupProvider).excludedBackupAlbums;
 | 
					      var albums = ref.watch(backupProvider).excludedBackupAlbums;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (albums.isNotEmpty) {
 | 
					      if (albums.isNotEmpty) {
 | 
				
			||||||
@ -207,17 +213,18 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
        borderOnForeground: false,
 | 
					        borderOnForeground: false,
 | 
				
			||||||
        child: ListTile(
 | 
					        child: ListTile(
 | 
				
			||||||
          minVerticalPadding: 15,
 | 
					          minVerticalPadding: 15,
 | 
				
			||||||
          title: const Text("Backup Albums",
 | 
					          title: const Text("backup_controller_page_albums",
 | 
				
			||||||
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
 | 
					                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20))
 | 
				
			||||||
 | 
					              .tr(),
 | 
				
			||||||
          subtitle: Padding(
 | 
					          subtitle: Padding(
 | 
				
			||||||
            padding: const EdgeInsets.only(top: 8.0),
 | 
					            padding: const EdgeInsets.only(top: 8.0),
 | 
				
			||||||
            child: Column(
 | 
					            child: Column(
 | 
				
			||||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
					              crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
                const Text(
 | 
					                const Text(
 | 
				
			||||||
                  "Albums to be backed up",
 | 
					                  "backup_controller_page_to_backup",
 | 
				
			||||||
                  style: TextStyle(color: Color(0xFF808080), fontSize: 12),
 | 
					                  style: TextStyle(color: Color(0xFF808080), fontSize: 12),
 | 
				
			||||||
                ),
 | 
					                ).tr(),
 | 
				
			||||||
                _buildSelectedAlbumName(),
 | 
					                _buildSelectedAlbumName(),
 | 
				
			||||||
                _buildExcludedAlbumName()
 | 
					                _buildExcludedAlbumName()
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
@ -234,14 +241,14 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
            onPressed: () {
 | 
					            onPressed: () {
 | 
				
			||||||
              AutoRouter.of(context).push(const BackupAlbumSelectionRoute());
 | 
					              AutoRouter.of(context).push(const BackupAlbumSelectionRoute());
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            child: const Padding(
 | 
					            child: Padding(
 | 
				
			||||||
              padding: EdgeInsets.symmetric(
 | 
					              padding: const EdgeInsets.symmetric(
 | 
				
			||||||
                vertical: 16.0,
 | 
					                vertical: 16.0,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              child: Text(
 | 
					              child: const Text(
 | 
				
			||||||
                "Select",
 | 
					                "backup_controller_page_select",
 | 
				
			||||||
                style: TextStyle(fontWeight: FontWeight.bold),
 | 
					                style: TextStyle(fontWeight: FontWeight.bold),
 | 
				
			||||||
              ),
 | 
					              ).tr(),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
@ -387,9 +394,9 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
      appBar: AppBar(
 | 
					      appBar: AppBar(
 | 
				
			||||||
        elevation: 0,
 | 
					        elevation: 0,
 | 
				
			||||||
        title: const Text(
 | 
					        title: const Text(
 | 
				
			||||||
          "Backup",
 | 
					          "backup_controller_page_backup",
 | 
				
			||||||
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
 | 
					          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
 | 
				
			||||||
        ),
 | 
					        ).tr(),
 | 
				
			||||||
        leading: IconButton(
 | 
					        leading: IconButton(
 | 
				
			||||||
            onPressed: () {
 | 
					            onPressed: () {
 | 
				
			||||||
              ref.watch(websocketProvider.notifier).listenUploadEvent();
 | 
					              ref.watch(websocketProvider.notifier).listenUploadEvent();
 | 
				
			||||||
@ -405,27 +412,27 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
        child: ListView(
 | 
					        child: ListView(
 | 
				
			||||||
          // crossAxisAlignment: CrossAxisAlignment.start,
 | 
					          // crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
          children: [
 | 
					          children: [
 | 
				
			||||||
            const Padding(
 | 
					            Padding(
 | 
				
			||||||
              padding: EdgeInsets.all(8.0),
 | 
					              padding: const EdgeInsets.all(8.0),
 | 
				
			||||||
              child: Text(
 | 
					              child: const Text(
 | 
				
			||||||
                "Backup Information",
 | 
					                "backup_controller_page_info",
 | 
				
			||||||
                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
 | 
					                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
 | 
				
			||||||
              ),
 | 
					              ).tr(),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            _buildFolderSelectionTile(),
 | 
					            _buildFolderSelectionTile(),
 | 
				
			||||||
            BackupInfoCard(
 | 
					            BackupInfoCard(
 | 
				
			||||||
              title: "Total",
 | 
					              title: "backup_controller_page_total".tr(),
 | 
				
			||||||
              subtitle: "All unique photos and videos from selected albums",
 | 
					              subtitle: "backup_controller_page_total_sub".tr(),
 | 
				
			||||||
              info: "${backupState.allUniqueAssets.length}",
 | 
					              info: "${backupState.allUniqueAssets.length}",
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            BackupInfoCard(
 | 
					            BackupInfoCard(
 | 
				
			||||||
              title: "Backup",
 | 
					              title: "backup_controller_page_backup".tr(),
 | 
				
			||||||
              subtitle: "Backed up photos and videos",
 | 
					              subtitle: "backup_controller_page_backup_sub".tr(),
 | 
				
			||||||
              info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
 | 
					              info: "${backupState.selectedAlbumsBackupAssetsIds.length}",
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            BackupInfoCard(
 | 
					            BackupInfoCard(
 | 
				
			||||||
              title: "Remainder",
 | 
					              title: "backup_controller_page_remainder".tr(),
 | 
				
			||||||
              subtitle: "Remaining photos and albums to back up from selection",
 | 
					              subtitle: "backup_controller_page_remainder_sub".tr(),
 | 
				
			||||||
              info:
 | 
					              info:
 | 
				
			||||||
                  "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
 | 
					                  "${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@ -452,12 +459,12 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
                              ref.read(backupProvider.notifier).cancelBackup();
 | 
					                              ref.read(backupProvider.notifier).cancelBackup();
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                            child: const Text(
 | 
					                            child: const Text(
 | 
				
			||||||
                              "CANCEL",
 | 
					                              "backup_controller_page_cancel",
 | 
				
			||||||
                              style: TextStyle(
 | 
					                              style: TextStyle(
 | 
				
			||||||
                                fontSize: 14,
 | 
					                                fontSize: 14,
 | 
				
			||||||
                                fontWeight: FontWeight.bold,
 | 
					                                fontWeight: FontWeight.bold,
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                            ),
 | 
					                            ).tr(),
 | 
				
			||||||
                          )
 | 
					                          )
 | 
				
			||||||
                        : ElevatedButton(
 | 
					                        : ElevatedButton(
 | 
				
			||||||
                            style: ElevatedButton.styleFrom(
 | 
					                            style: ElevatedButton.styleFrom(
 | 
				
			||||||
@ -467,12 +474,12 @@ class BackupControllerPage extends HookConsumerWidget {
 | 
				
			|||||||
                            ),
 | 
					                            ),
 | 
				
			||||||
                            onPressed: shouldBackup ? startBackup : null,
 | 
					                            onPressed: shouldBackup ? startBackup : null,
 | 
				
			||||||
                            child: const Text(
 | 
					                            child: const Text(
 | 
				
			||||||
                              "START BACKUP",
 | 
					                              "backup_controller_page_start_backup",
 | 
				
			||||||
                              style: TextStyle(
 | 
					                              style: TextStyle(
 | 
				
			||||||
                                fontSize: 14,
 | 
					                                fontSize: 14,
 | 
				
			||||||
                                fontWeight: FontWeight.bold,
 | 
					                                fontWeight: FontWeight.bold,
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                            ),
 | 
					                            ).tr(),
 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
 | 
					import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -26,7 +27,7 @@ class ControlBottomAppBar extends StatelessWidget {
 | 
				
			|||||||
                children: [
 | 
					                children: [
 | 
				
			||||||
                  ControlBoxButton(
 | 
					                  ControlBoxButton(
 | 
				
			||||||
                    iconData: Icons.delete_forever_rounded,
 | 
					                    iconData: Icons.delete_forever_rounded,
 | 
				
			||||||
                    label: "Delete",
 | 
					                    label: "control_bottom_app_bar_delete".tr(),
 | 
				
			||||||
                    onPressed: () {
 | 
					                    onPressed: () {
 | 
				
			||||||
                      showDialog(
 | 
					                      showDialog(
 | 
				
			||||||
                        context: context,
 | 
					                        context: context,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
 | 
					import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
 | 
				
			||||||
@ -19,7 +20,7 @@ class DailyTitleText extends ConsumerWidget {
 | 
				
			|||||||
    var currentYear = DateTime.now().year;
 | 
					    var currentYear = DateTime.now().year;
 | 
				
			||||||
    var groupYear = DateTime.parse(isoDate).year;
 | 
					    var groupYear = DateTime.parse(isoDate).year;
 | 
				
			||||||
    var formatDateTemplate =
 | 
					    var formatDateTemplate =
 | 
				
			||||||
        currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy';
 | 
					        currentYear == groupYear ? "daily_title_text_date".tr() : "daily_title_text_date_year".tr();
 | 
				
			||||||
    var dateText =
 | 
					    var dateText =
 | 
				
			||||||
        DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
 | 
					        DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
 | 
				
			||||||
    var isMultiSelectEnable =
 | 
					    var isMultiSelectEnable =
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
 | 
					import 'package:immich_mobile/shared/providers/asset.provider.dart';
 | 
				
			||||||
@ -13,18 +14,17 @@ class DeleteDialog extends ConsumerWidget {
 | 
				
			|||||||
    return AlertDialog(
 | 
					    return AlertDialog(
 | 
				
			||||||
      backgroundColor: Colors.grey[200],
 | 
					      backgroundColor: Colors.grey[200],
 | 
				
			||||||
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
 | 
					      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
 | 
				
			||||||
      title: const Text("Delete Permanently"),
 | 
					      title: const Text("delete_dialog_title").tr(),
 | 
				
			||||||
      content: const Text(
 | 
					      content: const Text("delete_dialog_alert").tr(),
 | 
				
			||||||
          "These items will be permanently deleted from Immich and from your device"),
 | 
					 | 
				
			||||||
      actions: [
 | 
					      actions: [
 | 
				
			||||||
        TextButton(
 | 
					        TextButton(
 | 
				
			||||||
          onPressed: () {
 | 
					          onPressed: () {
 | 
				
			||||||
            Navigator.of(context).pop();
 | 
					            Navigator.of(context).pop();
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          child: const Text(
 | 
					          child: const Text(
 | 
				
			||||||
            "Cancel",
 | 
					            "delete_dialog_cancel",
 | 
				
			||||||
            style: TextStyle(color: Colors.blueGrey),
 | 
					            style: TextStyle(color: Colors.blueGrey),
 | 
				
			||||||
          ),
 | 
					          ).tr(),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        TextButton(
 | 
					        TextButton(
 | 
				
			||||||
          onPressed: () {
 | 
					          onPressed: () {
 | 
				
			||||||
@ -36,9 +36,9 @@ class DeleteDialog extends ConsumerWidget {
 | 
				
			|||||||
            Navigator.of(context).pop();
 | 
					            Navigator.of(context).pop();
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          child: Text(
 | 
					          child: Text(
 | 
				
			||||||
            "Delete",
 | 
					            "delete_dialog_ok",
 | 
				
			||||||
            style: TextStyle(color: Colors.red[400]),
 | 
					            style: TextStyle(color: Colors.red[400]),
 | 
				
			||||||
          ),
 | 
					          ).tr(),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:intl/intl.dart';
 | 
					import 'package:intl/intl.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -11,7 +12,7 @@ class MonthlyTitleText extends StatelessWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    var monthTitleText = DateFormat('MMMM y').format(DateTime.parse(isoDate));
 | 
					    var monthTitleText = DateFormat("monthly_title_text_date_format".tr()).format(DateTime.parse(isoDate));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return SliverToBoxAdapter(
 | 
					    return SliverToBoxAdapter(
 | 
				
			||||||
      child: Padding(
 | 
					      child: Padding(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hive_flutter/hive_flutter.dart';
 | 
					import 'package:hive_flutter/hive_flutter.dart';
 | 
				
			||||||
@ -183,12 +184,12 @@ class ProfileDrawer extends HookConsumerWidget {
 | 
				
			|||||||
                  color: Colors.black54,
 | 
					                  color: Colors.black54,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                title: const Text(
 | 
					                title: const Text(
 | 
				
			||||||
                  "Sign Out",
 | 
					                  "profile_drawer_sign_out",
 | 
				
			||||||
                  style: TextStyle(
 | 
					                  style: TextStyle(
 | 
				
			||||||
                      color: Colors.black54,
 | 
					                      color: Colors.black54,
 | 
				
			||||||
                      fontSize: 14,
 | 
					                      fontSize: 14,
 | 
				
			||||||
                      fontWeight: FontWeight.bold),
 | 
					                      fontWeight: FontWeight.bold),
 | 
				
			||||||
                ),
 | 
					                ).tr(),
 | 
				
			||||||
                onTap: () async {
 | 
					                onTap: () async {
 | 
				
			||||||
                  bool res =
 | 
					                  bool res =
 | 
				
			||||||
                      await ref.watch(authenticationProvider.notifier).logout();
 | 
					                      await ref.watch(authenticationProvider.notifier).logout();
 | 
				
			||||||
@ -227,7 +228,7 @@ class ProfileDrawer extends HookConsumerWidget {
 | 
				
			|||||||
                      child: Text(
 | 
					                      child: Text(
 | 
				
			||||||
                        serverInfoState.isVersionMismatch
 | 
					                        serverInfoState.isVersionMismatch
 | 
				
			||||||
                            ? serverInfoState.versionMismatchErrorMessage
 | 
					                            ? serverInfoState.versionMismatchErrorMessage
 | 
				
			||||||
                            : "Client and Server are up-to-date",
 | 
					                            : "profile_drawer_client_server_up_to_date".tr(),
 | 
				
			||||||
                        textAlign: TextAlign.center,
 | 
					                        textAlign: TextAlign.center,
 | 
				
			||||||
                        style: TextStyle(
 | 
					                        style: TextStyle(
 | 
				
			||||||
                            fontSize: 11,
 | 
					                            fontSize: 11,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hive/hive.dart';
 | 
					import 'package:hive/hive.dart';
 | 
				
			||||||
@ -21,7 +22,7 @@ class LoginForm extends HookConsumerWidget {
 | 
				
			|||||||
    final passwordController =
 | 
					    final passwordController =
 | 
				
			||||||
        useTextEditingController.fromValue(TextEditingValue.empty);
 | 
					        useTextEditingController.fromValue(TextEditingValue.empty);
 | 
				
			||||||
    final serverEndpointController =
 | 
					    final serverEndpointController =
 | 
				
			||||||
        useTextEditingController(text: 'http://your-server-ip:2283/api');
 | 
					        useTextEditingController(text: 'login_endpoint_hint'.tr());
 | 
				
			||||||
    final isSaveLoginInfo = useState<bool>(false);
 | 
					    final isSaveLoginInfo = useState<bool>(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() {
 | 
					    useEffect(() {
 | 
				
			||||||
@ -73,12 +74,12 @@ class LoginForm extends HookConsumerWidget {
 | 
				
			|||||||
                    borderRadius: BorderRadius.circular(5)),
 | 
					                    borderRadius: BorderRadius.circular(5)),
 | 
				
			||||||
                enableFeedback: true,
 | 
					                enableFeedback: true,
 | 
				
			||||||
                title: const Text(
 | 
					                title: const Text(
 | 
				
			||||||
                  "Stay logged in",
 | 
					                  "login_form_save_login",
 | 
				
			||||||
                  style: TextStyle(
 | 
					                  style: TextStyle(
 | 
				
			||||||
                      fontSize: 16,
 | 
					                      fontSize: 16,
 | 
				
			||||||
                      fontWeight: FontWeight.bold,
 | 
					                      fontWeight: FontWeight.bold,
 | 
				
			||||||
                      color: Colors.grey),
 | 
					                      color: Colors.grey),
 | 
				
			||||||
                ),
 | 
					                ).tr(),
 | 
				
			||||||
                value: isSaveLoginInfo.value,
 | 
					                value: isSaveLoginInfo.value,
 | 
				
			||||||
                onChanged: (switchValue) {
 | 
					                onChanged: (switchValue) {
 | 
				
			||||||
                  if (switchValue != null) {
 | 
					                  if (switchValue != null) {
 | 
				
			||||||
@ -111,7 +112,7 @@ class ServerEndpointInput extends StatelessWidget {
 | 
				
			|||||||
    if (url?.startsWith(RegExp(r'https?://')) == true) {
 | 
					    if (url?.startsWith(RegExp(r'https?://')) == true) {
 | 
				
			||||||
      return null;
 | 
					      return null;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return 'Please specify http:// or https://';
 | 
					      return 'login_form_err_http'.tr();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -119,10 +120,10 @@ class ServerEndpointInput extends StatelessWidget {
 | 
				
			|||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return TextFormField(
 | 
					    return TextFormField(
 | 
				
			||||||
      controller: controller,
 | 
					      controller: controller,
 | 
				
			||||||
      decoration: const InputDecoration(
 | 
					      decoration: InputDecoration(
 | 
				
			||||||
        labelText: 'Server Endpoint URL',
 | 
					        labelText: 'login_form_endpoint_url'.tr(),
 | 
				
			||||||
        border: OutlineInputBorder(),
 | 
					        border: OutlineInputBorder(),
 | 
				
			||||||
        hintText: 'http://your-server-ip:port',
 | 
					        hintText: 'login_form_endpoint_hint'.tr(),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      validator: _validateInput,
 | 
					      validator: _validateInput,
 | 
				
			||||||
      autovalidateMode: AutovalidateMode.always,
 | 
					      autovalidateMode: AutovalidateMode.always,
 | 
				
			||||||
@ -137,9 +138,10 @@ class EmailInput extends StatelessWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  String? _validateInput(String? email) {
 | 
					  String? _validateInput(String? email) {
 | 
				
			||||||
    if (email == null || email == '') return null;
 | 
					    if (email == null || email == '') return null;
 | 
				
			||||||
    if (email.endsWith(' ')) return 'Trailing whitespace';
 | 
					    if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr();
 | 
				
			||||||
    if (email.startsWith(' ')) return 'Leading whitespace';
 | 
					    if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr();
 | 
				
			||||||
    if (email.contains(' ') || !email.contains('@')) return 'Invalid Email';
 | 
					    if (email.contains(' ') || !email.contains('@'))
 | 
				
			||||||
 | 
					      return 'login_form_err_invalid_email'.tr();
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -147,10 +149,10 @@ class EmailInput extends StatelessWidget {
 | 
				
			|||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return TextFormField(
 | 
					    return TextFormField(
 | 
				
			||||||
      controller: controller,
 | 
					      controller: controller,
 | 
				
			||||||
      decoration: const InputDecoration(
 | 
					      decoration: InputDecoration(
 | 
				
			||||||
        labelText: 'Email',
 | 
					        labelText: 'login_form_label_email'.tr(),
 | 
				
			||||||
        border: OutlineInputBorder(),
 | 
					        border: OutlineInputBorder(),
 | 
				
			||||||
        hintText: 'youremail@email.com',
 | 
					        hintText: 'login_form_email_hint'.tr(),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      validator: _validateInput,
 | 
					      validator: _validateInput,
 | 
				
			||||||
      autovalidateMode: AutovalidateMode.always,
 | 
					      autovalidateMode: AutovalidateMode.always,
 | 
				
			||||||
@ -168,10 +170,10 @@ class PasswordInput extends StatelessWidget {
 | 
				
			|||||||
    return TextFormField(
 | 
					    return TextFormField(
 | 
				
			||||||
      obscureText: true,
 | 
					      obscureText: true,
 | 
				
			||||||
      controller: controller,
 | 
					      controller: controller,
 | 
				
			||||||
      decoration: const InputDecoration(
 | 
					      decoration: InputDecoration(
 | 
				
			||||||
          labelText: 'Password',
 | 
					          labelText: 'login_form_label_password'.tr(),
 | 
				
			||||||
          border: OutlineInputBorder(),
 | 
					          border: OutlineInputBorder(),
 | 
				
			||||||
          hintText: 'password'),
 | 
					          hintText: 'login_form_password_hint'.tr()),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -222,15 +224,14 @@ class LoginButton extends ConsumerWidget {
 | 
				
			|||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            ImmichToast.show(
 | 
					            ImmichToast.show(
 | 
				
			||||||
              context: context,
 | 
					              context: context,
 | 
				
			||||||
              msg:
 | 
					              msg: "login_failed".tr(),
 | 
				
			||||||
                  "Error logging you in, check server url, email and password!",
 | 
					 | 
				
			||||||
              toastType: ToastType.error,
 | 
					              toastType: ToastType.error,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        child: const Text(
 | 
					        child: const Text(
 | 
				
			||||||
          "Login",
 | 
					          "login_form_button_text",
 | 
				
			||||||
          style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
 | 
					          style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
 | 
				
			||||||
        ));
 | 
					        ).tr());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -47,8 +48,8 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
 | 
				
			|||||||
        onChanged: (value) {
 | 
					        onChanged: (value) {
 | 
				
			||||||
          ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
 | 
					          ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        decoration: const InputDecoration(
 | 
					        decoration: InputDecoration(
 | 
				
			||||||
          hintText: 'Search your photos',
 | 
					          hintText: 'search_bar_hint'.tr(),
 | 
				
			||||||
          enabledBorder: UnderlineInputBorder(
 | 
					          enabledBorder: UnderlineInputBorder(
 | 
				
			||||||
            borderSide: BorderSide(color: Colors.transparent),
 | 
					            borderSide: BorderSide(color: Colors.transparent),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
				
			|||||||
@ -55,7 +55,7 @@ class ThumbnailWithInfo extends StatelessWidget {
 | 
				
			|||||||
                child: SizedBox(
 | 
					                child: SizedBox(
 | 
				
			||||||
                  width: MediaQuery.of(context).size.width / 3,
 | 
					                  width: MediaQuery.of(context).size.width / 3,
 | 
				
			||||||
                  child: Text(
 | 
					                  child: Text(
 | 
				
			||||||
                    textInfo.capitalizeFirstLetter(),
 | 
					                    textInfo,
 | 
				
			||||||
                    style: const TextStyle(
 | 
					                    style: const TextStyle(
 | 
				
			||||||
                      color: Colors.white,
 | 
					                      color: Colors.white,
 | 
				
			||||||
                      fontWeight: FontWeight.bold,
 | 
					                      fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hive_flutter/hive_flutter.dart';
 | 
					import 'package:hive_flutter/hive_flutter.dart';
 | 
				
			||||||
@ -82,7 +83,7 @@ class SearchPage extends HookConsumerWidget {
 | 
				
			|||||||
                      return ThumbnailWithInfo(
 | 
					                      return ThumbnailWithInfo(
 | 
				
			||||||
                        imageUrl:
 | 
					                        imageUrl:
 | 
				
			||||||
                            'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60',
 | 
					                            'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60',
 | 
				
			||||||
                        textInfo: 'No Places Info Available',
 | 
					                        textInfo: 'search_page_no_places'.tr(),
 | 
				
			||||||
                        onTap: () {},
 | 
					                        onTap: () {},
 | 
				
			||||||
                      );
 | 
					                      );
 | 
				
			||||||
                    }),
 | 
					                    }),
 | 
				
			||||||
@ -134,7 +135,7 @@ class SearchPage extends HookConsumerWidget {
 | 
				
			|||||||
                      return ThumbnailWithInfo(
 | 
					                      return ThumbnailWithInfo(
 | 
				
			||||||
                        imageUrl:
 | 
					                        imageUrl:
 | 
				
			||||||
                            'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60',
 | 
					                            'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60',
 | 
				
			||||||
                        textInfo: 'No Object Info Available',
 | 
					                        textInfo: 'sarch_no_objects'.tr(),
 | 
				
			||||||
                        onTap: () {},
 | 
					                        onTap: () {},
 | 
				
			||||||
                      );
 | 
					                      );
 | 
				
			||||||
                    }),
 | 
					                    }),
 | 
				
			||||||
@ -158,20 +159,20 @@ class SearchPage extends HookConsumerWidget {
 | 
				
			|||||||
          children: [
 | 
					          children: [
 | 
				
			||||||
            ListView(
 | 
					            ListView(
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
                const Padding(
 | 
					                Padding(
 | 
				
			||||||
                  padding: EdgeInsets.all(16.0),
 | 
					                  padding: EdgeInsets.all(16.0),
 | 
				
			||||||
                  child: Text(
 | 
					                  child: const Text(
 | 
				
			||||||
                    "Places",
 | 
					                    "search_page_places",
 | 
				
			||||||
                    style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
 | 
					                    style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
 | 
				
			||||||
                  ),
 | 
					                  ).tr(),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                _buildPlaces(),
 | 
					                _buildPlaces(),
 | 
				
			||||||
                const Padding(
 | 
					                Padding(
 | 
				
			||||||
                  padding: EdgeInsets.all(16.0),
 | 
					                  padding: EdgeInsets.all(16.0),
 | 
				
			||||||
                  child: Text(
 | 
					                  child: const  Text(
 | 
				
			||||||
                    "Things",
 | 
					                    "search_page_things",
 | 
				
			||||||
                    style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
 | 
					                    style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
 | 
				
			||||||
                  ),
 | 
					                  ).tr(),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                _buildThings()
 | 
					                _buildThings()
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
 | 
					import 'package:flutter_spinkit/flutter_spinkit.dart';
 | 
				
			||||||
@ -66,8 +67,8 @@ class SearchResultPage extends HookConsumerWidget {
 | 
				
			|||||||
        onChanged: (value) {
 | 
					        onChanged: (value) {
 | 
				
			||||||
          ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
 | 
					          ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        decoration: const InputDecoration(
 | 
					        decoration: InputDecoration(
 | 
				
			||||||
          hintText: 'New Search',
 | 
					          hintText: 'search_result_page_new_search_hint'.tr(),
 | 
				
			||||||
          enabledBorder: UnderlineInputBorder(
 | 
					          enabledBorder: UnderlineInputBorder(
 | 
				
			||||||
            borderSide: BorderSide(color: Colors.transparent),
 | 
					            borderSide: BorderSide(color: Colors.transparent),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart';
 | 
					import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart';
 | 
				
			||||||
@ -59,7 +60,7 @@ class AlbumTitleTextField extends ConsumerWidget {
 | 
				
			|||||||
          borderSide: const BorderSide(color: Colors.transparent),
 | 
					          borderSide: const BorderSide(color: Colors.transparent),
 | 
				
			||||||
          borderRadius: BorderRadius.circular(10),
 | 
					          borderRadius: BorderRadius.circular(10),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        hintText: 'Add a title',
 | 
					        hintText: 'share_add_title'.tr(),
 | 
				
			||||||
        focusColor: Colors.grey[300],
 | 
					        focusColor: Colors.grey[300],
 | 
				
			||||||
        fillColor: Colors.grey[200],
 | 
					        fillColor: Colors.grey[200],
 | 
				
			||||||
        filled: isAlbumTitleTextFieldFocus.value,
 | 
					        filled: isAlbumTitleTextFieldFocus.value,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:fluttertoast/fluttertoast.dart';
 | 
					import 'package:fluttertoast/fluttertoast.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -45,7 +46,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
 | 
				
			|||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        ImmichToast.show(
 | 
					        ImmichToast.show(
 | 
				
			||||||
          context: context,
 | 
					          context: context,
 | 
				
			||||||
          msg: "Failed to delete album",
 | 
					          msg: "album_viewer_appbar_share_err_delete".tr(),
 | 
				
			||||||
          toastType: ToastType.error,
 | 
					          toastType: ToastType.error,
 | 
				
			||||||
          gravity: ToastGravity.BOTTOM,
 | 
					          gravity: ToastGravity.BOTTOM,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -67,7 +68,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
 | 
				
			|||||||
        Navigator.pop(context);
 | 
					        Navigator.pop(context);
 | 
				
			||||||
        ImmichToast.show(
 | 
					        ImmichToast.show(
 | 
				
			||||||
          context: context,
 | 
					          context: context,
 | 
				
			||||||
          msg: "Failed to leave album",
 | 
					          msg: "album_viewer_appbar_share_err_leave".tr(),
 | 
				
			||||||
          toastType: ToastType.error,
 | 
					          toastType: ToastType.error,
 | 
				
			||||||
          gravity: ToastGravity.BOTTOM,
 | 
					          gravity: ToastGravity.BOTTOM,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -93,7 +94,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
 | 
				
			|||||||
        Navigator.pop(context);
 | 
					        Navigator.pop(context);
 | 
				
			||||||
        ImmichToast.show(
 | 
					        ImmichToast.show(
 | 
				
			||||||
          context: context,
 | 
					          context: context,
 | 
				
			||||||
          msg: "There are problems in removing assets from album",
 | 
					          msg: "album_viewer_appbar_share_err_remove".tr(),
 | 
				
			||||||
          toastType: ToastType.error,
 | 
					          toastType: ToastType.error,
 | 
				
			||||||
          gravity: ToastGravity.BOTTOM,
 | 
					          gravity: ToastGravity.BOTTOM,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -108,9 +109,9 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
 | 
				
			|||||||
          return ListTile(
 | 
					          return ListTile(
 | 
				
			||||||
            leading: const Icon(Icons.delete_sweep_rounded),
 | 
					            leading: const Icon(Icons.delete_sweep_rounded),
 | 
				
			||||||
            title: const Text(
 | 
					            title: const Text(
 | 
				
			||||||
              'Remove from album',
 | 
					              'album_viewer_appbar_share_remove',
 | 
				
			||||||
              style: TextStyle(fontWeight: FontWeight.bold),
 | 
					              style: TextStyle(fontWeight: FontWeight.bold),
 | 
				
			||||||
            ),
 | 
					            ).tr(),
 | 
				
			||||||
            onTap: () => _onRemoveFromAlbumPressed(albumId),
 | 
					            onTap: () => _onRemoveFromAlbumPressed(albumId),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
@ -121,18 +122,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
 | 
				
			|||||||
          return ListTile(
 | 
					          return ListTile(
 | 
				
			||||||
            leading: const Icon(Icons.delete_forever_rounded),
 | 
					            leading: const Icon(Icons.delete_forever_rounded),
 | 
				
			||||||
            title: const Text(
 | 
					            title: const Text(
 | 
				
			||||||
              'Delete album',
 | 
					              'album_viewer_appbar_share_delete',
 | 
				
			||||||
              style: TextStyle(fontWeight: FontWeight.bold),
 | 
					              style: TextStyle(fontWeight: FontWeight.bold),
 | 
				
			||||||
            ),
 | 
					            ).tr(),
 | 
				
			||||||
            onTap: () => _onDeleteAlbumPressed(albumId),
 | 
					            onTap: () => _onDeleteAlbumPressed(albumId),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          return ListTile(
 | 
					          return ListTile(
 | 
				
			||||||
            leading: const Icon(Icons.person_remove_rounded),
 | 
					            leading: const Icon(Icons.person_remove_rounded),
 | 
				
			||||||
            title: const Text(
 | 
					            title: const Text(
 | 
				
			||||||
              'Leave album',
 | 
					              'album_viewer_appbar_share_leave',
 | 
				
			||||||
              style: TextStyle(fontWeight: FontWeight.bold),
 | 
					              style: TextStyle(fontWeight: FontWeight.bold),
 | 
				
			||||||
            ),
 | 
					            ).tr(),
 | 
				
			||||||
            onTap: () => _onLeaveAlbumPressed(albumId),
 | 
					            onTap: () => _onLeaveAlbumPressed(albumId),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -176,7 +177,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
 | 
				
			|||||||
            if (!isSuccess) {
 | 
					            if (!isSuccess) {
 | 
				
			||||||
              ImmichToast.show(
 | 
					              ImmichToast.show(
 | 
				
			||||||
                context: context,
 | 
					                context: context,
 | 
				
			||||||
                msg: "Failed to change album title",
 | 
					                msg: "album_viewer_appbar_share_err_title".tr(),
 | 
				
			||||||
                gravity: ToastGravity.BOTTOM,
 | 
					                gravity: ToastGravity.BOTTOM,
 | 
				
			||||||
                toastType: ToastType.error,
 | 
					                toastType: ToastType.error,
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -74,7 +75,7 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
 | 
				
			|||||||
        focusColor: Colors.grey[300],
 | 
					        focusColor: Colors.grey[300],
 | 
				
			||||||
        fillColor: Colors.grey[200],
 | 
					        fillColor: Colors.grey[200],
 | 
				
			||||||
        filled: titleFocusNode.hasFocus,
 | 
					        filled: titleFocusNode.hasFocus,
 | 
				
			||||||
        hintText: 'Add a title',
 | 
					        hintText: 'share_add_title'.tr(),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:immich_mobile/routing/router.dart';
 | 
					import 'package:immich_mobile/routing/router.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,10 +52,10 @@ class SharingSliverAppBar extends StatelessWidget {
 | 
				
			|||||||
                      size: 20,
 | 
					                      size: 20,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    label: const Text(
 | 
					                    label: const Text(
 | 
				
			||||||
                      "Create shared album",
 | 
					                      "sharing_silver_appbar_create_shared_album",
 | 
				
			||||||
                      style:
 | 
					                      style:
 | 
				
			||||||
                          TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
 | 
					                          TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
 | 
				
			||||||
                    ),
 | 
					                    ).tr(),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
@ -73,10 +74,10 @@ class SharingSliverAppBar extends StatelessWidget {
 | 
				
			|||||||
                      size: 20,
 | 
					                      size: 20,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                    label: const Text(
 | 
					                    label: const Text(
 | 
				
			||||||
                      "Share with partner",
 | 
					                      "sharing_silver_appbar_share_partner",
 | 
				
			||||||
                      style:
 | 
					                      style:
 | 
				
			||||||
                          TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
 | 
					                          TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
 | 
				
			||||||
                    ),
 | 
					                    ).tr(),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              )
 | 
					              )
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -204,13 +205,13 @@ class AlbumViewerPage extends HookConsumerWidget {
 | 
				
			|||||||
              AlbumActionOutlinedButton(
 | 
					              AlbumActionOutlinedButton(
 | 
				
			||||||
                iconData: Icons.add_photo_alternate_outlined,
 | 
					                iconData: Icons.add_photo_alternate_outlined,
 | 
				
			||||||
                onPressed: () => _onAddPhotosPressed(albumInfo),
 | 
					                onPressed: () => _onAddPhotosPressed(albumInfo),
 | 
				
			||||||
                labelText: "Add photos",
 | 
					                labelText: "share_add_photos".tr(),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              if (userId == albumInfo.ownerId)
 | 
					              if (userId == albumInfo.ownerId)
 | 
				
			||||||
                AlbumActionOutlinedButton(
 | 
					                AlbumActionOutlinedButton(
 | 
				
			||||||
                  iconData: Icons.person_add_alt_rounded,
 | 
					                  iconData: Icons.person_add_alt_rounded,
 | 
				
			||||||
                  onPressed: () => _onAddUsersPressed(albumInfo),
 | 
					                  onPressed: () => _onAddUsersPressed(albumInfo),
 | 
				
			||||||
                  labelText: "Add users",
 | 
					                  labelText: "album_viewer_page_share_add_users".tr(),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -65,9 +66,9 @@ class AssetSelectionPage extends HookConsumerWidget {
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
        title: selectedAssets.isEmpty
 | 
					        title: selectedAssets.isEmpty
 | 
				
			||||||
            ? const Text(
 | 
					            ? const Text(
 | 
				
			||||||
                'Add photos',
 | 
					                'share_add_photos',
 | 
				
			||||||
                style: TextStyle(fontSize: 18),
 | 
					                style: TextStyle(fontSize: 18),
 | 
				
			||||||
              )
 | 
					              ).tr()
 | 
				
			||||||
            : Text(
 | 
					            : Text(
 | 
				
			||||||
                _buildAssetCountText(),
 | 
					                _buildAssetCountText(),
 | 
				
			||||||
                style: const TextStyle(fontSize: 18),
 | 
					                style: const TextStyle(fontSize: 18),
 | 
				
			||||||
@ -86,9 +87,9 @@ class AssetSelectionPage extends HookConsumerWidget {
 | 
				
			|||||||
                AutoRouter.of(context).pop(payload);
 | 
					                AutoRouter.of(context).pop(payload);
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
              child: const Text(
 | 
					              child: const Text(
 | 
				
			||||||
                "Add",
 | 
					                "share_add",
 | 
				
			||||||
                style: TextStyle(fontWeight: FontWeight.bold),
 | 
					                style: TextStyle(fontWeight: FontWeight.bold),
 | 
				
			||||||
              ),
 | 
					              ).tr(),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -64,13 +65,13 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    _buildTitle() {
 | 
					    _buildTitle() {
 | 
				
			||||||
      if (selectedAssets.isEmpty) {
 | 
					      if (selectedAssets.isEmpty) {
 | 
				
			||||||
        return const SliverToBoxAdapter(
 | 
					        return SliverToBoxAdapter(
 | 
				
			||||||
          child: Padding(
 | 
					          child: Padding(
 | 
				
			||||||
            padding: EdgeInsets.only(top: 200, left: 18),
 | 
					            padding: EdgeInsets.only(top: 200, left: 18),
 | 
				
			||||||
            child: Text(
 | 
					            child: Text(
 | 
				
			||||||
              'ADD ASSETS',
 | 
					              'create_shared_album_page_share_add_assets',
 | 
				
			||||||
              style: TextStyle(fontSize: 12),
 | 
					              style: TextStyle(fontSize: 12),
 | 
				
			||||||
            ),
 | 
					            ).tr(),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -97,12 +98,12 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
 | 
				
			|||||||
              label: Padding(
 | 
					              label: Padding(
 | 
				
			||||||
                padding: const EdgeInsets.only(left: 8.0),
 | 
					                padding: const EdgeInsets.only(left: 8.0),
 | 
				
			||||||
                child: Text(
 | 
					                child: Text(
 | 
				
			||||||
                  'Select Photos',
 | 
					                  'create_shared_album_page_share_select_photos',
 | 
				
			||||||
                  style: TextStyle(
 | 
					                  style: TextStyle(
 | 
				
			||||||
                      fontSize: 16,
 | 
					                      fontSize: 16,
 | 
				
			||||||
                      color: Colors.grey[700],
 | 
					                      color: Colors.grey[700],
 | 
				
			||||||
                      fontWeight: FontWeight.bold),
 | 
					                      fontWeight: FontWeight.bold),
 | 
				
			||||||
                ),
 | 
					                ).tr(),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
@ -123,7 +124,7 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
 | 
				
			|||||||
              AlbumActionOutlinedButton(
 | 
					              AlbumActionOutlinedButton(
 | 
				
			||||||
                iconData: Icons.add_photo_alternate_outlined,
 | 
					                iconData: Icons.add_photo_alternate_outlined,
 | 
				
			||||||
                onPressed: _onSelectPhotosButtonPressed,
 | 
					                onPressed: _onSelectPhotosButtonPressed,
 | 
				
			||||||
                labelText: "Add photos",
 | 
					                labelText: "share_add_photos".tr(),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
@ -169,16 +170,16 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
 | 
				
			|||||||
              },
 | 
					              },
 | 
				
			||||||
              icon: const Icon(Icons.close_rounded)),
 | 
					              icon: const Icon(Icons.close_rounded)),
 | 
				
			||||||
          title: const Text(
 | 
					          title: const Text(
 | 
				
			||||||
            'Create album',
 | 
					            'share_create_album',
 | 
				
			||||||
            style: TextStyle(color: Colors.black),
 | 
					            style: TextStyle(color: Colors.black),
 | 
				
			||||||
          ),
 | 
					          ).tr(),
 | 
				
			||||||
          actions: [
 | 
					          actions: [
 | 
				
			||||||
            TextButton(
 | 
					            TextButton(
 | 
				
			||||||
              onPressed: albumTitleController.text.isNotEmpty
 | 
					              onPressed: albumTitleController.text.isNotEmpty
 | 
				
			||||||
                  ? _showSelectUserPage
 | 
					                  ? _showSelectUserPage
 | 
				
			||||||
                  : null,
 | 
					                  : null,
 | 
				
			||||||
              child: const Text(
 | 
					              child: Text(
 | 
				
			||||||
                'Share',
 | 
					                'create_shared_album_page_share'.tr(),
 | 
				
			||||||
                style: TextStyle(
 | 
					                style: TextStyle(
 | 
				
			||||||
                  fontWeight: FontWeight.bold,
 | 
					                  fontWeight: FontWeight.bold,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -68,10 +69,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
 | 
				
			|||||||
          Wrap(
 | 
					          Wrap(
 | 
				
			||||||
            children: [...usersChip],
 | 
					            children: [...usersChip],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          const Padding(
 | 
					          Padding(
 | 
				
			||||||
            padding: EdgeInsets.all(16.0),
 | 
					            padding: EdgeInsets.all(16.0),
 | 
				
			||||||
            child: Text(
 | 
					            child: Text(
 | 
				
			||||||
              'Suggestions',
 | 
					              'select_additional_user_for_sharing_page_suggestions'.tr(),
 | 
				
			||||||
              style: TextStyle(
 | 
					              style: TextStyle(
 | 
				
			||||||
                  fontSize: 14,
 | 
					                  fontSize: 14,
 | 
				
			||||||
                  color: Colors.grey,
 | 
					                  color: Colors.grey,
 | 
				
			||||||
@ -112,9 +113,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
 | 
				
			|||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      appBar: AppBar(
 | 
					      appBar: AppBar(
 | 
				
			||||||
        title: const Text(
 | 
					        title: const Text(
 | 
				
			||||||
          'Invite to album',
 | 
					          'share_invite',
 | 
				
			||||||
          style: TextStyle(color: Colors.black),
 | 
					          style: TextStyle(color: Colors.black),
 | 
				
			||||||
        ),
 | 
					        ).tr(),
 | 
				
			||||||
        elevation: 0,
 | 
					        elevation: 0,
 | 
				
			||||||
        centerTitle: false,
 | 
					        centerTitle: false,
 | 
				
			||||||
        leading: IconButton(
 | 
					        leading: IconButton(
 | 
				
			||||||
@ -128,9 +129,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
 | 
				
			|||||||
            onPressed:
 | 
					            onPressed:
 | 
				
			||||||
                sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
 | 
					                sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
 | 
				
			||||||
            child: const Text(
 | 
					            child: const Text(
 | 
				
			||||||
              "Add",
 | 
					              "share_add",
 | 
				
			||||||
              style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
 | 
					              style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
 | 
				
			||||||
            ),
 | 
					            ).tr(),
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -36,8 +37,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
 | 
				
			|||||||
            .navigate(const TabControllerRoute(children: [SharingRoute()]));
 | 
					            .navigate(const TabControllerRoute(children: [SharingRoute()]));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const ScaffoldMessenger(
 | 
					      ScaffoldMessenger(child: SnackBar(content: Text('select_user_for_sharing_page_err_album').tr()));
 | 
				
			||||||
          child: SnackBar(content: Text('Failed to create album')));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _buildTileIcon(User user) {
 | 
					    _buildTileIcon(User user) {
 | 
				
			||||||
@ -84,15 +84,15 @@ class SelectUserForSharingPage extends HookConsumerWidget {
 | 
				
			|||||||
          Wrap(
 | 
					          Wrap(
 | 
				
			||||||
            children: [...usersChip],
 | 
					            children: [...usersChip],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          const Padding(
 | 
					          Padding(
 | 
				
			||||||
            padding: EdgeInsets.all(16.0),
 | 
					            padding: EdgeInsets.all(16.0),
 | 
				
			||||||
            child: Text(
 | 
					            child: Text(
 | 
				
			||||||
              'Suggestions',
 | 
					              'share_suggestions',
 | 
				
			||||||
              style: TextStyle(
 | 
					              style: TextStyle(
 | 
				
			||||||
                  fontSize: 14,
 | 
					                  fontSize: 14,
 | 
				
			||||||
                  color: Colors.grey,
 | 
					                  color: Colors.grey,
 | 
				
			||||||
                  fontWeight: FontWeight.bold),
 | 
					                  fontWeight: FontWeight.bold),
 | 
				
			||||||
            ),
 | 
					            ).tr(),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          ListView.builder(
 | 
					          ListView.builder(
 | 
				
			||||||
            shrinkWrap: true,
 | 
					            shrinkWrap: true,
 | 
				
			||||||
@ -128,9 +128,9 @@ class SelectUserForSharingPage extends HookConsumerWidget {
 | 
				
			|||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      appBar: AppBar(
 | 
					      appBar: AppBar(
 | 
				
			||||||
        title: const Text(
 | 
					        title: const Text(
 | 
				
			||||||
          'Invite to album',
 | 
					          'share_invite',
 | 
				
			||||||
          style: TextStyle(color: Colors.black),
 | 
					          style: TextStyle(color: Colors.black),
 | 
				
			||||||
        ),
 | 
					        ).tr(),
 | 
				
			||||||
        elevation: 0,
 | 
					        elevation: 0,
 | 
				
			||||||
        centerTitle: false,
 | 
					        centerTitle: false,
 | 
				
			||||||
        leading: IconButton(
 | 
					        leading: IconButton(
 | 
				
			||||||
@ -144,9 +144,9 @@ class SelectUserForSharingPage extends HookConsumerWidget {
 | 
				
			|||||||
              onPressed:
 | 
					              onPressed:
 | 
				
			||||||
                  sharedUsersList.value.isEmpty ? null : _createSharedAlbum,
 | 
					                  sharedUsersList.value.isEmpty ? null : _createSharedAlbum,
 | 
				
			||||||
              child: const Text(
 | 
					              child: const Text(
 | 
				
			||||||
                "Create Album",
 | 
					                "share_create_album",
 | 
				
			||||||
                style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
 | 
					                style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
 | 
				
			||||||
              ))
 | 
					              ).tr())
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      body: suggestedShareUsers.when(
 | 
					      body: suggestedShareUsers.when(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hive/hive.dart';
 | 
					import 'package:hive/hive.dart';
 | 
				
			||||||
@ -104,20 +105,20 @@ class SharingPage extends HookConsumerWidget {
 | 
				
			|||||||
                  Padding(
 | 
					                  Padding(
 | 
				
			||||||
                    padding: const EdgeInsets.all(8.0),
 | 
					                    padding: const EdgeInsets.all(8.0),
 | 
				
			||||||
                    child: Text(
 | 
					                    child: Text(
 | 
				
			||||||
                      'EMPTY LIST',
 | 
					                      'sharing_page_empty_list',
 | 
				
			||||||
                      style: TextStyle(
 | 
					                      style: TextStyle(
 | 
				
			||||||
                        fontSize: 12,
 | 
					                        fontSize: 12,
 | 
				
			||||||
                        color: Theme.of(context).primaryColor,
 | 
					                        color: Theme.of(context).primaryColor,
 | 
				
			||||||
                        fontWeight: FontWeight.bold,
 | 
					                        fontWeight: FontWeight.bold,
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ).tr(),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  Padding(
 | 
					                  Padding(
 | 
				
			||||||
                    padding: const EdgeInsets.all(8.0),
 | 
					                    padding: const EdgeInsets.all(8.0),
 | 
				
			||||||
                    child: Text(
 | 
					                    child: Text(
 | 
				
			||||||
                      'Create shared albums to share photos and videos with people in your network.',
 | 
					                      'sharing_page_description',
 | 
				
			||||||
                      style: TextStyle(fontSize: 12, color: Colors.grey[700]),
 | 
					                      style: TextStyle(fontSize: 12, color: Colors.grey[700]),
 | 
				
			||||||
                    ),
 | 
					                    ).tr(),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
@ -131,15 +132,15 @@ class SharingPage extends HookConsumerWidget {
 | 
				
			|||||||
      body: CustomScrollView(
 | 
					      body: CustomScrollView(
 | 
				
			||||||
        slivers: [
 | 
					        slivers: [
 | 
				
			||||||
          const SharingSliverAppBar(),
 | 
					          const SharingSliverAppBar(),
 | 
				
			||||||
          const SliverPadding(
 | 
					          SliverPadding(
 | 
				
			||||||
            padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
 | 
					            padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
 | 
				
			||||||
            sliver: SliverToBoxAdapter(
 | 
					            sliver: SliverToBoxAdapter(
 | 
				
			||||||
              child: Text(
 | 
					              child: Text(
 | 
				
			||||||
                "Shared albums",
 | 
					                "sharing_page_album",
 | 
				
			||||||
                style: TextStyle(
 | 
					                style: TextStyle(
 | 
				
			||||||
                  fontWeight: FontWeight.bold,
 | 
					                  fontWeight: FontWeight.bold,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ).tr(),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          sharedAlbums.isNotEmpty
 | 
					          sharedAlbums.isNotEmpty
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
import 'package:auto_route/auto_route.dart';
 | 
					import 'package:auto_route/auto_route.dart';
 | 
				
			||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
 | 
					import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
 | 
				
			||||||
@ -41,13 +42,16 @@ class TabControllerPage extends ConsumerWidget {
 | 
				
			|||||||
                    onTap: (index) {
 | 
					                    onTap: (index) {
 | 
				
			||||||
                      tabsRouter.setActiveIndex(index);
 | 
					                      tabsRouter.setActiveIndex(index);
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    items: const [
 | 
					                    items: [
 | 
				
			||||||
                      BottomNavigationBarItem(
 | 
					                      BottomNavigationBarItem(
 | 
				
			||||||
                          label: 'Photos', icon: Icon(Icons.photo)),
 | 
					                          label: 'tab_controller_nav_photos'.tr(),
 | 
				
			||||||
 | 
					                          icon: const Icon(Icons.photo)),
 | 
				
			||||||
                      BottomNavigationBarItem(
 | 
					                      BottomNavigationBarItem(
 | 
				
			||||||
                          label: 'Search', icon: Icon(Icons.search)),
 | 
					                          label: 'tab_controller_nav_search'.tr(),
 | 
				
			||||||
 | 
					                          icon: const Icon(Icons.search)),
 | 
				
			||||||
                      BottomNavigationBarItem(
 | 
					                      BottomNavigationBarItem(
 | 
				
			||||||
                          label: 'Sharing', icon: Icon(Icons.group_outlined)),
 | 
					                          label: 'tab_controller_nav_sharing'.tr(),
 | 
				
			||||||
 | 
					                          icon: const Icon(Icons.group_outlined)),
 | 
				
			||||||
                    ],
 | 
					                    ],
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/gestures.dart';
 | 
					import 'package:flutter/gestures.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
@ -40,14 +41,14 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
 | 
				
			|||||||
                          crossAxisAlignment: CrossAxisAlignment.start,
 | 
					                          crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
                          children: [
 | 
					                          children: [
 | 
				
			||||||
                            const Text(
 | 
					                            const Text(
 | 
				
			||||||
                              "New Server Version Available 🎉",
 | 
					                              "version_announcement_overlay_title",
 | 
				
			||||||
                              style: TextStyle(
 | 
					                              style: TextStyle(
 | 
				
			||||||
                                fontSize: 16,
 | 
					                                fontSize: 16,
 | 
				
			||||||
                                fontFamily: 'WorkSans',
 | 
					                                fontFamily: 'WorkSans',
 | 
				
			||||||
                                fontWeight: FontWeight.bold,
 | 
					                                fontWeight: FontWeight.bold,
 | 
				
			||||||
                                color: Colors.indigo,
 | 
					                                color: Colors.indigo,
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                            ),
 | 
					                            ).tr(),
 | 
				
			||||||
                            Padding(
 | 
					                            Padding(
 | 
				
			||||||
                              padding: const EdgeInsets.only(top: 16.0),
 | 
					                              padding: const EdgeInsets.only(top: 16.0),
 | 
				
			||||||
                              child: RichText(
 | 
					                              child: RichText(
 | 
				
			||||||
@ -58,9 +59,8 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
 | 
				
			|||||||
                                      color: Colors.black87,
 | 
					                                      color: Colors.black87,
 | 
				
			||||||
                                      height: 1.2),
 | 
					                                      height: 1.2),
 | 
				
			||||||
                                  children: <TextSpan>[
 | 
					                                  children: <TextSpan>[
 | 
				
			||||||
                                    const TextSpan(
 | 
					                                    TextSpan(
 | 
				
			||||||
                                      text:
 | 
					                                      text: 'version_announcement_overlay_text_1'.tr(),
 | 
				
			||||||
                                          'Hi friend, there is a new release of',
 | 
					 | 
				
			||||||
                                    ),
 | 
					                                    ),
 | 
				
			||||||
                                    const TextSpan(
 | 
					                                    const TextSpan(
 | 
				
			||||||
                                      text: ' Immich ',
 | 
					                                      text: ' Immich ',
 | 
				
			||||||
@ -70,22 +70,21 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
 | 
				
			|||||||
                                        fontWeight: FontWeight.bold,
 | 
					                                        fontWeight: FontWeight.bold,
 | 
				
			||||||
                                      ),
 | 
					                                      ),
 | 
				
			||||||
                                    ),
 | 
					                                    ),
 | 
				
			||||||
                                    const TextSpan(
 | 
					                                    TextSpan(
 | 
				
			||||||
                                      text:
 | 
					                                      text: "version_announcement_overlay_text_2".tr(),
 | 
				
			||||||
                                          "please take your time to visit the ",
 | 
					 | 
				
			||||||
                                    ),
 | 
					                                    ),
 | 
				
			||||||
                                    TextSpan(
 | 
					                                    TextSpan(
 | 
				
			||||||
                                      text: "release note",
 | 
					                                      text: "version_announcement_overlay_release_notes"
 | 
				
			||||||
 | 
					                                          .tr(),
 | 
				
			||||||
                                      style: const TextStyle(
 | 
					                                      style: const TextStyle(
 | 
				
			||||||
                                        decoration: TextDecoration.underline,
 | 
					                                        decoration: TextDecoration.underline,
 | 
				
			||||||
                                      ),
 | 
					                                      ),
 | 
				
			||||||
                                      recognizer: TapGestureRecognizer()
 | 
					                                      recognizer: TapGestureRecognizer()
 | 
				
			||||||
                                        ..onTap = goToReleaseNote,
 | 
					                                        ..onTap = goToReleaseNote,
 | 
				
			||||||
                                    ),
 | 
					                                    ),
 | 
				
			||||||
                                    const TextSpan(
 | 
					                                    TextSpan(
 | 
				
			||||||
                                      text:
 | 
					                                      text: "version_announcement_overlay_text_3".tr(),
 | 
				
			||||||
                                          " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
 | 
					                                    )
 | 
				
			||||||
                                    ),
 | 
					 | 
				
			||||||
                                  ],
 | 
					                                  ],
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
@ -104,13 +103,12 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
 | 
				
			|||||||
                                  ),
 | 
					                                  ),
 | 
				
			||||||
                                  onPressed: onAcknowledgeTapped,
 | 
					                                  onPressed: onAcknowledgeTapped,
 | 
				
			||||||
                                  child: const Text(
 | 
					                                  child: const Text(
 | 
				
			||||||
                                  "Acknowledge",
 | 
					                                    "version_announcement_overlay_ack",
 | 
				
			||||||
                                    style: TextStyle(
 | 
					                                    style: TextStyle(
 | 
				
			||||||
                                      fontSize: 14,
 | 
					                                      fontSize: 14,
 | 
				
			||||||
                                    ),
 | 
					                                    ),
 | 
				
			||||||
                                ),
 | 
					                                  ).tr()),
 | 
				
			||||||
                              ),
 | 
					                            )
 | 
				
			||||||
                            ),
 | 
					 | 
				
			||||||
                          ],
 | 
					                          ],
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
 | 
				
			|||||||
@ -253,6 +253,20 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "4.0.6"
 | 
					    version: "4.0.6"
 | 
				
			||||||
 | 
					  easy_localization:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: easy_localization
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "3.0.1"
 | 
				
			||||||
 | 
					  easy_logger:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: easy_logger
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "0.0.2"
 | 
				
			||||||
  equatable:
 | 
					  equatable:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -335,6 +349,11 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "2.0.1"
 | 
					    version: "2.0.1"
 | 
				
			||||||
 | 
					  flutter_localizations:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description: flutter
 | 
				
			||||||
 | 
					    source: sdk
 | 
				
			||||||
 | 
					    version: "0.0.0"
 | 
				
			||||||
  flutter_map:
 | 
					  flutter_map:
 | 
				
			||||||
    dependency: "direct main"
 | 
					    dependency: "direct main"
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@ -842,6 +861,62 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dartlang.org"
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.27.3"
 | 
					    version: "0.27.3"
 | 
				
			||||||
 | 
					  shared_preferences:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: shared_preferences
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.0.15"
 | 
				
			||||||
 | 
					  shared_preferences_android:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: shared_preferences_android
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.0.12"
 | 
				
			||||||
 | 
					  shared_preferences_ios:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: shared_preferences_ios
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.1.1"
 | 
				
			||||||
 | 
					  shared_preferences_linux:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: shared_preferences_linux
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.1.1"
 | 
				
			||||||
 | 
					  shared_preferences_macos:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: shared_preferences_macos
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.0.4"
 | 
				
			||||||
 | 
					  shared_preferences_platform_interface:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: shared_preferences_platform_interface
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.0.0"
 | 
				
			||||||
 | 
					  shared_preferences_web:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: shared_preferences_web
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.0.4"
 | 
				
			||||||
 | 
					  shared_preferences_windows:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: shared_preferences_windows
 | 
				
			||||||
 | 
					      url: "https://pub.dartlang.org"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "2.1.1"
 | 
				
			||||||
  shelf:
 | 
					  shelf:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,7 @@ dependencies:
 | 
				
			|||||||
  url_launcher: ^6.1.3
 | 
					  url_launcher: ^6.1.3
 | 
				
			||||||
  http: 0.13.4
 | 
					  http: 0.13.4
 | 
				
			||||||
  cancellation_token_http: ^1.1.0
 | 
					  cancellation_token_http: ^1.1.0
 | 
				
			||||||
 | 
					  easy_localization: ^3.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  path: ^1.8.1
 | 
					  path: ^1.8.1
 | 
				
			||||||
  path_provider: ^2.0.11
 | 
					  path_provider: ^2.0.11
 | 
				
			||||||
@ -59,6 +60,7 @@ flutter:
 | 
				
			|||||||
  uses-material-design: true
 | 
					  uses-material-design: true
 | 
				
			||||||
  assets:
 | 
					  assets:
 | 
				
			||||||
    - assets/
 | 
					    - assets/
 | 
				
			||||||
 | 
					    - assets/i18n/
 | 
				
			||||||
  fonts:
 | 
					  fonts:
 | 
				
			||||||
    - family: WorkSans
 | 
					    - family: WorkSans
 | 
				
			||||||
      fonts:
 | 
					      fonts:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								mobile/scripts/check_i18n_keys.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								mobile/scripts/check_i18n_keys.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    with open('assets/i18n/en-US.json', 'r') as f:
 | 
				
			||||||
 | 
					        data = json.load(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for k in data.keys():
 | 
				
			||||||
 | 
					            print(k)
 | 
				
			||||||
 | 
					            sp = subprocess.run(['sh', '-c', f'grep -r --include="*.dart" "{k}"'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if sp.returncode != 0:
 | 
				
			||||||
 | 
					                print("Not found in source code!")
 | 
				
			||||||
 | 
					                return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
							
								
								
									
										19
									
								
								mobile/scripts/check_key_uniform.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								mobile/scripts/check_key_uniform.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    print("CHECK GERMAN TRANSLATIONS")
 | 
				
			||||||
 | 
					    with open('assets/i18n/de-DE.json', 'r') as f:
 | 
				
			||||||
 | 
					        data = json.load(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for k in data.keys():
 | 
				
			||||||
 | 
					            print(k)
 | 
				
			||||||
 | 
					            sp = subprocess.run(['sh', '-c', f'grep -r --include="./assets/i18n/en-US.json" "{k}"'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if sp.returncode != 0:
 | 
				
			||||||
 | 
					                print(f"Outdated Key! {k}")
 | 
				
			||||||
 | 
					                return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user