From 92a03ce5855527cb40e9c117bc63301b520932c7 Mon Sep 17 00:00:00 2001 From: eYdr1en <54525514+eYdr1en@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:37:15 +0200 Subject: [PATCH 01/28] [auth] fix Copied to Clipboard text behind clipboard --- mobile/apps/auth/lib/utils/toast_util.dart | 61 ++++++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/mobile/apps/auth/lib/utils/toast_util.dart b/mobile/apps/auth/lib/utils/toast_util.dart index 942274bc85..8d53d42723 100644 --- a/mobile/apps/auth/lib/utils/toast_util.dart +++ b/mobile/apps/auth/lib/utils/toast_util.dart @@ -1,4 +1,5 @@ import 'package:ente_auth/ente_theme_data.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -9,6 +10,58 @@ void showToast( toastLength = Toast.LENGTH_LONG, iOSDismissOnTap = true, }) async { + // If on mobile and keyboard is visible, render toast above the keyboard using FToast. + final bool isMobile = PlatformUtil.isMobile(); + + if (isMobile) { + final baseToast = Container( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25.0), + color: Theme.of(context).colorScheme.toastBackgroundColor, + ), + child: Text( + message, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.toastTextColor, + fontSize: 16.0, + ), + ), + ); + + final fToast = FToast()..init(context); + + Widget toastChild = baseToast; + if (iOSDismissOnTap == true) { + toastChild = GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + fToast.removeCustomToast(); + fToast.removeQueuedCustomToasts(); + }, + child: baseToast, + ); + } + + fToast.showToast( + child: toastChild, + gravity: ToastGravity.BOTTOM, + toastDuration: const Duration(seconds: 2), + positionedToastBuilder: (context, child) { + final double currentInset = MediaQuery.of(context).viewInsets.bottom; + return Positioned( + left: 16, + right: 16, + bottom: currentInset + 16, + child: child, + ); + }, + ); + return; + } + + // Default path (Android, desktop, or when keyboard isn't visible) try { await Fluttertoast.cancel(); await Fluttertoast.showToast( @@ -21,7 +74,8 @@ void showToast( fontSize: 16.0, ); } on MissingPluginException catch (_) { - Widget toast = Container( + final fToast = FToast()..init(context); + final Widget baseToast = Container( padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0), decoration: BoxDecoration( borderRadius: BorderRadius.circular(25.0), @@ -36,11 +90,8 @@ void showToast( ), ); - final fToast = FToast(); - fToast.init(context); - fToast.showToast( - child: toast, + child: baseToast, gravity: ToastGravity.BOTTOM, toastDuration: const Duration(seconds: 2), ); From d4bfbbf04c0592aef0835c9c7456c828e1997d15 Mon Sep 17 00:00:00 2001 From: eYdr1en <54525514+eYdr1en@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:40:51 +0200 Subject: [PATCH 02/28] fix comments --- mobile/apps/auth/lib/utils/toast_util.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/apps/auth/lib/utils/toast_util.dart b/mobile/apps/auth/lib/utils/toast_util.dart index 8d53d42723..b8d2f73e89 100644 --- a/mobile/apps/auth/lib/utils/toast_util.dart +++ b/mobile/apps/auth/lib/utils/toast_util.dart @@ -10,7 +10,7 @@ void showToast( toastLength = Toast.LENGTH_LONG, iOSDismissOnTap = true, }) async { - // If on mobile and keyboard is visible, render toast above the keyboard using FToast. + // If on mobile render toast above the keyboard using FToast. final bool isMobile = PlatformUtil.isMobile(); if (isMobile) { @@ -61,7 +61,7 @@ void showToast( return; } - // Default path (Android, desktop, or when keyboard isn't visible) + // Default path (desktop) try { await Fluttertoast.cancel(); await Fluttertoast.showToast( From 0b73388b0b11def45b25ee30ba60d1ded041d46f Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Sat, 9 Aug 2025 11:35:00 +0530 Subject: [PATCH 03/28] fix search view background color --- .../lib/ui/tools/editor/image_editor/image_editor_page.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart index e6bc90dbab..4f962d1357 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart @@ -227,6 +227,12 @@ class _ImageEditorPageState extends State { background: colorScheme.backgroundBase, ), emojiEditor: EmojiEditorTheme( + bottomActionBarConfig: BottomActionBarConfig( + showSearchViewButton: true, + buttonColor: colorScheme.backgroundBase, + buttonIconColor: colorScheme.tabIcon, + backgroundColor: colorScheme.backgroundBase, + ), backgroundColor: colorScheme.backgroundBase, ), ), From e32698e4be77e0c8e00bdfd92843c589ed22b23f Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Sat, 9 Aug 2025 23:33:36 +0530 Subject: [PATCH 04/28] update editor background color across image and video editor components --- mobile/apps/photos/lib/ente_theme_data.dart | 10 ++--- .../image_editor/circular_icon_button.dart | 8 ++-- .../image_editor_color_picker.dart | 5 +-- .../image_editor_crop_rotate.dart | 3 +- .../image_editor/image_editor_text_bar.dart | 13 ++++--- .../image_editor/image_editor_tune_bar.dart | 39 ++++++++++++++----- .../video_editor_bottom_action.dart | 2 +- .../video_editor_player_control.dart | 2 +- .../ui/tools/editor/video_editor_page.dart | 2 +- 9 files changed, 52 insertions(+), 32 deletions(-) diff --git a/mobile/apps/photos/lib/ente_theme_data.dart b/mobile/apps/photos/lib/ente_theme_data.dart index 6434e3ffe1..09fcb4a2e5 100644 --- a/mobile/apps/photos/lib/ente_theme_data.dart +++ b/mobile/apps/photos/lib/ente_theme_data.dart @@ -224,17 +224,17 @@ extension CustomColorScheme on ColorScheme { Color get videoPlayerPrimaryColor => brightness == Brightness.light ? const Color.fromRGBO(0, 179, 60, 1) : const Color.fromRGBO(1, 222, 77, 1); - - Color get videoPlayerBackgroundColor => brightness == Brightness.light - ? const Color(0xFFF5F5F5) - : const Color(0xFF252525); - + Color get videoPlayerBorderColor => brightness == Brightness.light ? const Color(0xFF424242) : const Color(0xFFFFFFFF); Color get imageEditorPrimaryColor => const Color.fromRGBO(8, 194, 37, 1); + Color get editorBackgroundColor => brightness == Brightness.light + ? const Color(0xFFF5F5F5) + : const Color(0xFF252525); + Color get defaultBackgroundColor => brightness == Brightness.light ? backgroundBaseLight : backgroundBaseDark; diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/circular_icon_button.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/circular_icon_button.dart index 7a025fe81f..1ecc52a7b7 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/circular_icon_button.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/circular_icon_button.dart @@ -3,7 +3,7 @@ import "package:flutter_svg/svg.dart"; import "package:photos/ente_theme_data.dart"; import "package:photos/theme/ente_theme.dart"; -class CircularIconButton extends StatelessWidget { +class CircularIconButton extends StatelessWidget { final String label; final VoidCallback onTap; final String? svgPath; @@ -11,7 +11,7 @@ class CircularIconButton extends StatelessWidget { final bool isSelected; const CircularIconButton({ - super.key, + super.key, required this.label, required this.onTap, this.svgPath, @@ -40,12 +40,12 @@ class CircularIconButton extends StatelessWidget { .colorScheme .imageEditorPrimaryColor .withOpacity(0.24) - : colorScheme.backgroundElevated2, + : Theme.of(context).colorScheme.editorBackgroundColor, shape: BoxShape.circle, border: Border.all( color: isSelected ? Theme.of(context).colorScheme.imageEditorPrimaryColor - : colorScheme.backgroundElevated2, + : Theme.of(context).colorScheme.editorBackgroundColor, width: 2, ), ), diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_color_picker.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_color_picker.dart index b87175d757..7aaa5f8880 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_color_picker.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_color_picker.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "package:photos/theme/ente_theme.dart"; +import "package:photos/ente_theme_data.dart"; class ImageEditorColorPicker extends StatefulWidget { final double value; @@ -23,7 +23,6 @@ class ColorSliderState extends State { @override Widget build(BuildContext context) { - final colorScheme = getEnteColorScheme(context); return Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: SizedBox( @@ -55,7 +54,7 @@ class ColorSliderState extends State { end: Alignment.centerRight, ), border: Border.all( - color: colorScheme.backgroundElevated2, + color: Theme.of(context).colorScheme.editorBackgroundColor, width: 6, ), ), diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_crop_rotate.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_crop_rotate.dart index 10333cd3ce..31251e27ef 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_crop_rotate.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_crop_rotate.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import "package:flutter_svg/svg.dart"; +import "package:photos/ente_theme_data.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/tools/editor/image_editor/circular_icon_button.dart"; @@ -191,7 +192,7 @@ class CropAspectChip extends StatelessWidget { decoration: BoxDecoration( color: isSelected ? colorScheme.fillBasePressed - : colorScheme.backgroundElevated2, + : Theme.of(context).colorScheme.editorBackgroundColor, borderRadius: BorderRadius.circular(25), ), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_text_bar.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_text_bar.dart index d4327fb136..14a21e00f1 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_text_bar.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_text_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import "package:flutter_svg/svg.dart"; +import "package:photos/ente_theme_data.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/tools/editor/image_editor/circular_icon_button.dart"; @@ -174,7 +175,7 @@ class _FontPickerWidget extends StatelessWidget { decoration: BoxDecoration( color: isSelected ? colorScheme.fillBasePressed - : colorScheme.backgroundElevated2, + : Theme.of(context).colorScheme.editorBackgroundColor, borderRadius: BorderRadius.circular(25), ), child: Center( @@ -209,7 +210,7 @@ class _BackgroundPickerWidget extends StatelessWidget { 'text': 'Aa', 'selectedBackgroundColor': isLightMode ? colorScheme.fillFaint : Colors.white, - 'backgroundColor': colorScheme.backgroundElevated2, + 'backgroundColor': Theme.of(context).colorScheme.editorBackgroundColor, 'border': null, 'textColor': Colors.white, 'selectedInnerBackgroundColor': Colors.black, @@ -219,7 +220,7 @@ class _BackgroundPickerWidget extends StatelessWidget { 'text': 'Aa', 'selectedBackgroundColor': isLightMode ? colorScheme.fillFaint : Colors.white, - 'backgroundColor': colorScheme.backgroundElevated2, + 'backgroundColor': Theme.of(context).colorScheme.editorBackgroundColor, 'border': null, 'textColor': Colors.black, 'selectedInnerBackgroundColor': Colors.transparent, @@ -229,7 +230,7 @@ class _BackgroundPickerWidget extends StatelessWidget { 'text': 'Aa', 'selectedBackgroundColor': isLightMode ? colorScheme.fillFaint : Colors.white, - 'backgroundColor': colorScheme.backgroundElevated2, + 'backgroundColor': Theme.of(context).colorScheme.editorBackgroundColor, 'border': null, 'textColor': Colors.black, 'selectedInnerBackgroundColor': Colors.black.withOpacity(0.11), @@ -241,7 +242,7 @@ class _BackgroundPickerWidget extends StatelessWidget { 'text': 'Aa', 'selectedBackgroundColor': isLightMode ? colorScheme.fillFaint : Colors.black, - 'backgroundColor': colorScheme.backgroundElevated2, + 'backgroundColor': Theme.of(context).colorScheme.editorBackgroundColor, 'border': isLightMode ? null : Border.all(color: Colors.white, width: 2), 'textColor': Colors.black, @@ -354,7 +355,7 @@ class _AlignPickerWidget extends StatelessWidget { decoration: BoxDecoration( color: isSelected ? colorScheme.fillBasePressed - : colorScheme.backgroundElevated2, + : Theme.of(context).colorScheme.editorBackgroundColor, borderRadius: BorderRadius.circular(25), border: isSelected ? Border.all(color: Colors.black, width: 2) diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart index a7b5c69da4..d3cafa0bc5 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import "package:flutter/services.dart"; import "package:flutter_svg/svg.dart"; import "package:photos/ente_theme_data.dart"; import "package:photos/theme/ente_theme.dart"; @@ -36,6 +37,24 @@ class _ImageEditorTuneBarState extends State with ImageEditorConvertedConfigs, SimpleConfigsAccessState { TuneEditorState get tuneEditor => widget.editor; + final Map _lastValues = {}; + + void _handleTuneItemTap(int index) { + if (tuneEditor.selectedIndex == index) { + final currentValue = tuneEditor.tuneAdjustmentMatrix[index].value; + if (currentValue != 0) { + _lastValues[index] = currentValue; + tuneEditor.onChanged(0); + } else if (_lastValues.containsKey(index)) { + tuneEditor.onChanged(_lastValues[index]!); + } + } else { + tuneEditor.setState(() { + tuneEditor.selectedIndex = index; + }); + } + } + @override Widget build(BuildContext context) { return LayoutBuilder( @@ -77,11 +96,7 @@ class _ImageEditorTuneBarState extends State value: tuneEditor.tuneAdjustmentMatrix[index].value, max: item.max, min: item.min, - onTap: () { - tuneEditor.setState(() { - tuneEditor.selectedIndex = index; - }); - }, + onTap: () => _handleTuneItemTap(index), ); }), ), @@ -224,7 +239,10 @@ class _CircularProgressWithValueState extends State @override void didUpdateWidget(CircularProgressWithValue oldWidget) { super.didUpdateWidget(oldWidget); - + if ((oldWidget.value < 0 && widget.value >= 0) || + (oldWidget.value > 0 && widget.value <= 0)) { + HapticFeedback.mediumImpact(); + } if (oldWidget.value != widget.value) { _previousValue = oldWidget.value; _progressAnimation = Tween( @@ -303,11 +321,11 @@ class _CircularProgressWithValueState extends State shape: BoxShape.circle, color: showValue || widget.isSelected ? progressColor.withOpacity(0.2) - : colorTheme.backgroundElevated2, + : Theme.of(context).colorScheme.editorBackgroundColor, border: Border.all( color: widget.isSelected ? progressColor.withOpacity(0.4) - : colorTheme.backgroundElevated2, + : Theme.of(context).colorScheme.editorBackgroundColor, width: 2, ), ), @@ -395,7 +413,7 @@ class _TuneAdjustWidget extends StatelessWidget { margin: const EdgeInsets.symmetric(horizontal: 20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(25), - color: colorScheme.backgroundElevated2, + color: Theme.of(context).colorScheme.editorBackgroundColor, ), ), ), @@ -410,7 +428,8 @@ class _TuneAdjustWidget extends StatelessWidget { overlayShape: const RoundSliderOverlayShape(overlayRadius: 0), activeTrackColor: Theme.of(context).colorScheme.imageEditorPrimaryColor, - inactiveTrackColor: colorScheme.backgroundElevated2, + inactiveTrackColor: + Theme.of(context).colorScheme.editorBackgroundColor, trackShape: const _CenterBasedTrackShape(), trackHeight: 24, ), diff --git a/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart index ed21e96dbf..d6c0f8aef6 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart @@ -31,7 +31,7 @@ class VideoEditorBottomAction extends StatelessWidget { height: 48, width: 48, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.videoPlayerBackgroundColor, + color: Theme.of(context).colorScheme.editorBackgroundColor, shape: BoxShape.circle, border: Border.all( color: isSelected diff --git a/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_player_control.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_player_control.dart index 719b62dd60..b05fc13789 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_player_control.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_player_control.dart @@ -43,7 +43,7 @@ class VideoEditorPlayerControl extends StatelessWidget { vertical: 4, ), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.videoPlayerBackgroundColor, + color: Theme.of(context).colorScheme.editorBackgroundColor, borderRadius: BorderRadius.circular(56), ), child: Row( diff --git a/mobile/apps/photos/lib/ui/tools/editor/video_editor_page.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor_page.dart index 679cea3c23..451a3d934a 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/video_editor_page.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/video_editor_page.dart @@ -74,7 +74,7 @@ class _VideoEditorPageState extends State { trimStyle: TrimSliderStyle( onTrimmedColor: const ColorScheme.dark().videoPlayerPrimaryColor, onTrimmingColor: const ColorScheme.dark().videoPlayerPrimaryColor, - background: Theme.of(context).colorScheme.videoPlayerBackgroundColor, + background: Theme.of(context).colorScheme.editorBackgroundColor, positionLineColor: Theme.of(context).colorScheme.videoPlayerBorderColor, lineColor: Theme.of(context) From 72f9d9e75615d9e199acae0a21536a61f61262bd Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Sat, 9 Aug 2025 23:34:15 +0530 Subject: [PATCH 05/28] refactor: fix bottom navigation bar colors --- .../image_editor/image_editor_page.dart | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart index 4f962d1357..ba28d1c293 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart @@ -1,10 +1,10 @@ import "dart:async"; import "dart:io"; import "dart:math"; -import "dart:typed_data"; import 'dart:ui' as ui show Image; import 'package:flutter/material.dart'; +import "package:flutter/services.dart"; import "package:flutter_image_compress/flutter_image_compress.dart"; import "package:flutter_svg/svg.dart"; import "package:logging/logging.dart"; @@ -176,17 +176,18 @@ class _ImageEditorPageState extends State { final isLightMode = Theme.of(context).brightness == Brightness.light; final colorScheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); - return Scaffold( - resizeToAvoidBottomInset: false, - backgroundColor: colorScheme.backgroundBase, - body: PopScope( - canPop: false, - onPopInvoked: (didPop) { - if (didPop) return; - editorKey.currentState?.disablePopScope = true; - _showExitConfirmationDialog(context); - }, - child: ProImageEditor.file( + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + if (didPop) return; + editorKey.currentState?.disablePopScope = true; + _showExitConfirmationDialog(context); + }, + child: Scaffold( + extendBodyBehindAppBar: true, + resizeToAvoidBottomInset: false, + backgroundColor: colorScheme.backgroundBase, + body: ProImageEditor.file( key: editorKey, widget.file, callbacks: ProImageEditorCallbacks( @@ -205,6 +206,14 @@ class _ImageEditorPageState extends State { ), configs: ProImageEditorConfigs( imageEditorTheme: ImageEditorTheme( + uiOverlayStyle: SystemUiOverlayStyle( + systemNavigationBarContrastEnforced: true, + systemNavigationBarColor: Colors.transparent, + statusBarBrightness: + isLightMode ? Brightness.dark : Brightness.light, + statusBarIconBrightness: + isLightMode ? Brightness.dark : Brightness.light, + ), appBarBackgroundColor: colorScheme.backgroundBase, background: colorScheme.backgroundBase, bottomBarBackgroundColor: colorScheme.backgroundBase, From 4875ddf30c4558355dffd225e1631ae4e341f1ec Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 11 Aug 2025 00:44:23 +0000 Subject: [PATCH 06/28] New Crowdin translations by GitHub Action --- web/packages/base/locales/pl-PL/translation.json | 14 +++++++------- web/packages/base/locales/vi-VN/translation.json | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/web/packages/base/locales/pl-PL/translation.json b/web/packages/base/locales/pl-PL/translation.json index ecbbff918f..064add62dc 100644 --- a/web/packages/base/locales/pl-PL/translation.json +++ b/web/packages/base/locales/pl-PL/translation.json @@ -62,8 +62,8 @@ "processed_counts": "{{count, number}} / {{total, number}}", "upload_reading_metadata_files": "Czytanie plików metadanych", "upload_cancelling": "Anulowanie pozostałych przesłań", - "upload_done": "", - "upload_skipped": "", + "upload_done": "Przesłano {{count, number}}", + "upload_skipped": "Pominięto {{count, number}}", "initial_load_delay_warning": "Pierwsze ładowanie może zająć trochę czasu", "no_account": "Nie mam konta", "existing_account": "Posiadam już konto", @@ -84,12 +84,12 @@ "tap_outside_image": "Dotknij na zewnątrz obrazu", "shortcuts": "Skróty", "show_shortcuts": "Pokaż skróty", - "zoom_preset": "", - "toggle_controls": "", + "zoom_preset": "Ustawienie powiększenia", + "toggle_controls": "Przełącz kontrolki", "toggle_live": "", - "toggle_audio": "", - "toggle_favorite": "", - "toggle_archive": "", + "toggle_audio": "Przełącz dźwięk", + "toggle_favorite": "Przełącz ulubione", + "toggle_archive": "Przełącz archiwizację", "view_info": "Zobacz informacje", "copy_as_png": "Kopiuj jako PNG", "toggle_fullscreen": "Przełącz tryb pełnoekranowy", diff --git a/web/packages/base/locales/vi-VN/translation.json b/web/packages/base/locales/vi-VN/translation.json index f3dec27f76..fdd39566fb 100644 --- a/web/packages/base/locales/vi-VN/translation.json +++ b/web/packages/base/locales/vi-VN/translation.json @@ -515,7 +515,7 @@ "enter_name": "Nhập tên", "uploader_name_hint": "Thêm tên để bạn bè biết ai là người chụp những tấm ảnh tuyệt vời này!", "name_placeholder": "Tên...", - "more_details": "Thêm chi tiết", + "more_details": "Thông tin thêm", "ml_search": "Học máy", "ml_search_description": "Ente hỗ trợ học máy trên-thiết-bị nhằm nhận diện khuôn mặt, tìm kiếm vi diệu và các tính năng tìm kiếm nâng cao khác", "ml_search_footnote": "Tìm kiếm vi diệu cho phép tìm ảnh theo nội dung của chúng, ví dụ: 'xe hơi', 'xe hơi đỏ', 'Ferrari'", @@ -560,13 +560,13 @@ "delete_account_reason_placeholder": "Chọn một lý do", "delete_reason": { "missing_feature": "Thiếu một tính năng quan trọng mà tôi cần", - "behaviour": "Ứng dụng hoặc một tính năng nhất định không hoạt động như tôi muốn", + "behaviour": "Ứng dụng hoặc một tính năng không hoạt động như tôi muốn", "found_another_service": "Tôi tìm thấy một dịch vụ khác mà tôi thích hơn", "not_listed": "Lý do không có trong danh sách" }, "delete_account_feedback_label": "Chúng tôi rất tiếc khi thấy bạn ra đi. Vui lòng giải thích lý do bạn rời đi để giúp chúng tôi cải thiện.", "delete_account_feedback_placeholder": "Phản hồi", - "delete_account_confirm_checkbox_label": "Có, tôi muốn xóa vĩnh viễn tài khoản này và tất cả dữ liệu của nó", + "delete_account_confirm_checkbox_label": "Có, tôi muốn xóa vĩnh viễn tài khoản này và tất cả dữ liệu", "delete_account_confirm": "Xác nhận xóa tài khoản", "delete_account_confirm_message": "

Tài khoản này được liên kết với các ứng dụng Ente khác, nếu bạn có dùng.

Dữ liệu bạn đã tải lên, trên tất cả ứng dụng Ente, sẽ được lên lịch để xóa, và tài khoản của bạn sẽ bị xóa vĩnh viễn.

", "feedback_required": "Mong bạn giúp chúng tôi thông tin này", From 6ac19de2af9e6d27b6665070376adca8e27ea5f6 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 11 Aug 2025 01:18:24 +0000 Subject: [PATCH 07/28] New Crowdin translations by GitHub Action --- mobile/apps/auth/lib/l10n/arb/app_pl.arb | 2 +- mobile/apps/auth/lib/l10n/arb/app_sv.arb | 2 +- mobile/apps/auth/lib/l10n/arb/app_ti.arb | 14 ++++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/mobile/apps/auth/lib/l10n/arb/app_pl.arb b/mobile/apps/auth/lib/l10n/arb/app_pl.arb index a4177c8133..f17467db12 100644 --- a/mobile/apps/auth/lib/l10n/arb/app_pl.arb +++ b/mobile/apps/auth/lib/l10n/arb/app_pl.arb @@ -45,7 +45,7 @@ "timeBasedKeyType": "Oparte na czasie (TOTP)", "counterBasedKeyType": "Oparte na liczniku (HOTP)", "saveAction": "Zapisz", - "nextTotpTitle": "dalej", + "nextTotpTitle": "następny", "deleteCodeTitle": "Usunąć kod?", "deleteCodeMessage": "Czy na pewno chcesz usunąć ten kod? Ta akcja jest nieodwracalna.", "trashCode": "Przenieść kod do kosza?", diff --git a/mobile/apps/auth/lib/l10n/arb/app_sv.arb b/mobile/apps/auth/lib/l10n/arb/app_sv.arb index 4092736661..890e97ce0b 100644 --- a/mobile/apps/auth/lib/l10n/arb/app_sv.arb +++ b/mobile/apps/auth/lib/l10n/arb/app_sv.arb @@ -19,7 +19,7 @@ "pleaseVerifyDetails": "Kontrollera dina detaljer och försök igen", "codeIssuerHint": "Utfärdare", "codeSecretKeyHint": "Secret Key", - "secret": "Säkerhets nyckel", + "secret": "Säkerhetsnyckel", "all": "Alla", "notes": "Anteckningar", "notesLengthLimit": "Anteckningar kan vara högst {count} tecken långa", diff --git a/mobile/apps/auth/lib/l10n/arb/app_ti.arb b/mobile/apps/auth/lib/l10n/arb/app_ti.arb index 377dc51fff..680067d211 100644 --- a/mobile/apps/auth/lib/l10n/arb/app_ti.arb +++ b/mobile/apps/auth/lib/l10n/arb/app_ti.arb @@ -10,7 +10,7 @@ "onBoardingGetStarted": "ጀምር", "setupFirstAccount": "ናይ መጀመርታ ሕሳብካ ኣዳል", "importScanQrCode": "QR ኮድ ስካን ግበር", - "qrCode": "ኪዊኣር ስርዓት", + "qrCode": "ኪዊኣር ኮድ", "importEnterSetupKey": "ምድላው መፍትሕ ኣእቱ", "importAccountPageTitle": "ዝርዝር ሕሳብ ኣእትዉ", "secretCanNotBeEmpty": "ምስጢር ባዶ ኪኸውን ኣይክእልን እዩ", @@ -19,6 +19,8 @@ "pleaseVerifyDetails": "በጃኹም ዝርዝር-ሓበሬታ ኣረጋግጹ እሞ እንደገና ፈትኑ", "codeIssuerHint": "ኣዋጂ", "codeSecretKeyHint": "ምስጢራዊ መፍትሕ", + "all": "ኩሉ", + "notes": "መዘኻኸሪታት", "codeAccountHint": "ሕሳብ (you@domain.com)", "codeTagHint": "ልጣፍ", "accountKeyType": "ዓይነት ቁልፊ", @@ -30,12 +32,12 @@ "loggingOut": "ወጸ...", "timeBasedKeyType": "ግዜ እተመስረተ (TOTP)", "counterBasedKeyType": "ቆጻሪ እተመስረተ (TOTP)", - "saveAction": "", + "saveAction": "ዓቅብ", "nextTotpTitle": "ቀጽሊ", - "deleteCodeTitle": "ኮድ ምድምሳስ፧", - "deleteCodeMessage": "ነዚ ኮድ ክትድምስሶ ከም እትደሊ ርግጸኛ ዲኻ፧ እዚ ተግባር ንድሕሪት ዘይምለስ እዩ።", + "deleteCodeTitle": "ኮድ ይደምሰሰ፧", + "deleteCodeMessage": "ነዚ ኮድ ክትድምስሶ ከም እትደሊ ርግጸኛ ዲኻ፧ እዚ ተግባር ንድሕሪት ዘይምለስ ኣይኮነን።", "viewLogsAction": "ምዝገባታት ርአ", - "sendLogsDescription": "", + "sendLogsDescription": "This will send across logs to help us debug your issue. While we take precautions to ensure that sensitive information is not logged, we encourage you to view these logs before sharing them.", "preparingLogsTitle": "ምዝገባ ድላው...", "emailLogsTitle": "መዝገብ ኢ-መይል", "emailLogsMessage": "በጃኹም ነቲ መዝገብ ናብ {email} ስደዱሉ", @@ -67,7 +69,7 @@ "pleaseWait": "በጃኻ ተጸበ...", "generatingEncryptionKeysTitle": "ናይ ምስጢራዊ ቁልፊ ዪፍጠር...", "recreatePassword": "ቃለ-ምስጢር እንደገና ፍጠር", - "recreatePasswordMessage": "እዛ ሕጂ ዘላ ኤለክትሮኒካዊት መሳርሒት ነቲ passwordካ ንምርግጋጽ እኹል ሓይሊ ስለ ዘይብላ ምስ ኵሉ መሳርሒታት ብዚሰማማዕ መገዲ ሓንሳእ እንደገና ኸነሐድሶ ኣሎና ።\n\nበጃኻ በቲ ምሕዋይ-መፍትሕ ኣቲኻ ቃለ-ምስጢር ኣሐድሶ (እንተ ደሊኻ ነታ ቃለ-ምስጢር እንደገና ኽትጥቀመላ ትኽእል ኢኻ)።", + "recreatePasswordMessage": "እዛ ሕጂ ዘላ ኤለክትሮኒካዊት መሳርሒት ነቲ passwordካ ንምርግጋጽ እኹል ሓይሊ ስለ ዘይብላ ምስ ኵሉ መሳርሒታት ብዚሰማማዕ መገዲ ሓንሳእ እንደገና ኸነሐድሶ ኣሎና። \n\nበጃኻ በቲ ምሕዋይ-መፍትሕ ኣቲኻ ቃለ-ምስጢር ኣሐድሶ (እንተ ደሊኻ ነታ ቃለ-ምስጢር እንደገና ኽትጥቀመላ ትኽእል ኢኻ)።", "useRecoveryKey": "ምሕዋይ መፍትሕ ተጠቐም", "incorrectPasswordTitle": "ግጉይ ቃለ-ምስጢር", "welcomeBack": "እንኳዕ ብደሓን ተመለስካ!", From 1d25f23053f57b87965f7cedf91471f2b9f1e3d0 Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Mon, 11 Aug 2025 12:56:59 +0530 Subject: [PATCH 08/28] fix: update haptic feedback when reached zero --- .../lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart index d3cafa0bc5..7dcf73d4ed 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_tune_bar.dart @@ -241,7 +241,7 @@ class _CircularProgressWithValueState extends State super.didUpdateWidget(oldWidget); if ((oldWidget.value < 0 && widget.value >= 0) || (oldWidget.value > 0 && widget.value <= 0)) { - HapticFeedback.mediumImpact(); + HapticFeedback.vibrate(); } if (oldWidget.value != widget.value) { _previousValue = oldWidget.value; From 903762f28369f8247cf4be5c705efcd3ffacd7a2 Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Mon, 11 Aug 2025 15:34:02 +0530 Subject: [PATCH 09/28] fix: set initial color for painting editor --- .../lib/ui/tools/editor/image_editor/image_editor_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart index ba28d1c293..9d7fdf971a 100644 --- a/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart +++ b/mobile/apps/photos/lib/ui/tools/editor/image_editor/image_editor_page.dart @@ -221,6 +221,7 @@ class _ImageEditorPageState extends State { background: colorScheme.backgroundBase, ), paintingEditor: PaintingEditorTheme( + initialColor: const Color(0xFF00FFFF), background: colorScheme.backgroundBase, ), textEditor: const TextEditorTheme( From 4860236f66a4864faa5b4f7203b2b0adef222eb8 Mon Sep 17 00:00:00 2001 From: Kremiorspokke <153205941+Kremiorspokke@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:22:41 +0200 Subject: [PATCH 10/28] Add 1 new icon Add polish e-commerce platfrom icon allegro --- mobile/apps/auth/assets/custom-icons/icons/allegro.svg | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 mobile/apps/auth/assets/custom-icons/icons/allegro.svg diff --git a/mobile/apps/auth/assets/custom-icons/icons/allegro.svg b/mobile/apps/auth/assets/custom-icons/icons/allegro.svg new file mode 100644 index 0000000000..151c5408ec --- /dev/null +++ b/mobile/apps/auth/assets/custom-icons/icons/allegro.svg @@ -0,0 +1,8 @@ + + logo (15)-svg copy-svg + + + \ No newline at end of file From cabae4cca0181f755fa9a6ede1aad8a0f0e86486 Mon Sep 17 00:00:00 2001 From: Kremiorspokke <153205941+Kremiorspokke@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:32:39 +0200 Subject: [PATCH 11/28] Update custom-icons.json --- .../apps/auth/assets/custom-icons/_data/custom-icons.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json index 71f1fd88bd..8ee1f07db9 100644 --- a/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json +++ b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json @@ -1993,6 +1993,8 @@ { "title": "ISC2", "slug": "isc2" - } + }, + "title": "Allegro", + "slug": "allegro" ] -} \ No newline at end of file +} From 187a60ec3ac5243938b068dc4ffd6afe2605f85a Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:32:01 +0530 Subject: [PATCH 12/28] Add daily release workflow for photos --- .github/workflows/mobile-daily-internal.yml | 118 ++++++++++++++++++++ mobile/apps/photos/scripts/changes.txt | 1 + 2 files changed, 119 insertions(+) create mode 100644 .github/workflows/mobile-daily-internal.yml create mode 100644 mobile/apps/photos/scripts/changes.txt diff --git a/.github/workflows/mobile-daily-internal.yml b/.github/workflows/mobile-daily-internal.yml new file mode 100644 index 0000000000..7002bea278 --- /dev/null +++ b/.github/workflows/mobile-daily-internal.yml @@ -0,0 +1,118 @@ +name: "Internal release (photos)" + +on: + schedule: + # Runs daily at 12:30 UTC (6:00 PM IST) + - cron: "30 12 * * *" + workflow_dispatch: # Allow manual trigger + +env: + FLUTTER_VERSION: "3.24.3" + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: mobile/apps/photos + + steps: + - name: Checkout code and submodules + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 + + - name: Install Flutter ${{ env.FLUTTER_VERSION }} + uses: subosito/flutter-action@v2 + with: + channel: "stable" + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true + + - name: Increment version code for build + run: | + CURRENT_VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //') + VERSION_NAME=$(echo $CURRENT_VERSION | cut -d'+' -f1) + NEW_BUILD=$(date +%Y%m%d%H%M) + NEW_VERSION="${VERSION_NAME}+${NEW_BUILD}" + + sed -i "s/^version: .*/version: $NEW_VERSION/" pubspec.yaml + echo "Building with version ${NEW_VERSION}" + + - name: Prepare and validate changelog for Play Store + run: | + mkdir -p whatsnew + CHANGELOG_FILE="scripts/changes.txt" + OUTPUT_FILE="whatsnew/en-US.txt" + + # Use provided changelog or fallback + if [ -f "$CHANGELOG_FILE" ]; then + head -c 500 "$CHANGELOG_FILE" > "$OUTPUT_FILE" + else + echo "Bug fixes and improvements" > "$OUTPUT_FILE" + fi + + # Validate: file exists + if [ ! -s "$OUTPUT_FILE" ]; then + echo "❌ Changelog is empty." + exit 1 + fi + + # Validate: <= 500 chars + LENGTH=$(wc -m < "$OUTPUT_FILE") + if [ "$LENGTH" -gt 500 ]; then + echo "❌ Changelog exceeds 500 characters ($LENGTH)." + exit 1 + fi + + # Validate: no markdown or HTML + if grep -Eq '[\*\_\<\>\[\]\(\)]' "$OUTPUT_FILE"; then + echo "❌ Changelog contains markdown/HTML formatting." + exit 1 + fi + + echo "✅ Changelog valid:" + cat "$OUTPUT_FILE" + + - name: Setup keys + uses: timheuer/base64-to-file@v1 + with: + fileName: "keystore/ente_photos_key.jks" + encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }} + + - name: Build PlayStore AAB + run: | + flutter build appbundle --dart-define=cronetHttpNoPlay=true --release --flavor playstore + env: + SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks" + SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }} + SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD_PHOTOS }} + SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }} + + - name: Upload AAB to PlayStore + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} + packageName: io.ente.photos + releaseFiles: mobile/apps/photos/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab + track: internal + whatsNewDirectory: mobile/apps/photos/whatsnew + mappingFile: mobile/apps/photos/build/app/outputs/mapping/playstoreRelease/mapping.txt + + - name: Notify Discord + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }} + nodetail: true + title: "🏆 Daily release Photos (Branch: ${{ github.ref_name }})" + description: "[Download](https://play.google.com/store/apps/details?id=io.ente.photos) | [Changelog](https://github.com/ente-io/ente/blob/main/mobile/apps/photos/scripts/changes.txt)" + color: 0x00ff00 diff --git a/mobile/apps/photos/scripts/changes.txt b/mobile/apps/photos/scripts/changes.txt new file mode 100644 index 0000000000..965deccd99 --- /dev/null +++ b/mobile/apps/photos/scripts/changes.txt @@ -0,0 +1 @@ +- Gracefully handle heic rendering on Android \ No newline at end of file From 49c966f50c058a9320c7258fe7ae1458bce0930f Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:03:58 +0530 Subject: [PATCH 13/28] [mob] Modify daily build workflow --- .github/workflows/mobile-daily-internal.yml | 202 ++++++++++-------- .github/workflows/mobile-internal-release.yml | 2 +- mobile/apps/photos/pubspec.yaml | 2 +- 3 files changed, 111 insertions(+), 95 deletions(-) diff --git a/.github/workflows/mobile-daily-internal.yml b/.github/workflows/mobile-daily-internal.yml index 7002bea278..1cb30afe02 100644 --- a/.github/workflows/mobile-daily-internal.yml +++ b/.github/workflows/mobile-daily-internal.yml @@ -1,118 +1,134 @@ name: "Internal release (photos)" on: - schedule: - # Runs daily at 12:30 UTC (6:00 PM IST) - - cron: "30 12 * * *" - workflow_dispatch: # Allow manual trigger + schedule: + # Runs daily at 12:30 UTC (6:00 PM IST) + - cron: "30 12 * * *" + workflow_dispatch: # Allow manual trigger env: - FLUTTER_VERSION: "3.24.3" + FLUTTER_VERSION: "3.24.3" permissions: - contents: read + contents: read jobs: - build: - runs-on: ubuntu-latest + build: + runs-on: ubuntu-latest - defaults: - run: - working-directory: mobile/apps/photos + defaults: + run: + working-directory: mobile/apps/photos - steps: - - name: Checkout code and submodules - uses: actions/checkout@v4 - with: - submodules: recursive + steps: + - name: Checkout code and submodules + uses: actions/checkout@v4 + with: + submodules: recursive - - name: Setup JDK 17 - uses: actions/setup-java@v1 - with: - java-version: 17 + - name: Setup JDK 17 + uses: actions/setup-java@v1 + with: + java-version: 17 - - name: Install Flutter ${{ env.FLUTTER_VERSION }} - uses: subosito/flutter-action@v2 - with: - channel: "stable" - flutter-version: ${{ env.FLUTTER_VERSION }} - cache: true + - name: Install Flutter ${{ env.FLUTTER_VERSION }} + uses: subosito/flutter-action@v2 + with: + channel: "stable" + flutter-version: ${{ env.FLUTTER_VERSION }} + cache: true - - name: Increment version code for build - run: | - CURRENT_VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //') - VERSION_NAME=$(echo $CURRENT_VERSION | cut -d'+' -f1) - NEW_BUILD=$(date +%Y%m%d%H%M) - NEW_VERSION="${VERSION_NAME}+${NEW_BUILD}" + - name: Increment version code for build + run: | + CURRENT_VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //') + VERSION_NAME=$(echo $CURRENT_VERSION | cut -d'+' -f1) + CURRENT_BUILD=$(echo $CURRENT_VERSION | cut -d'+' -f2) + NEW_BUILD=$((CURRENT_BUILD + 1)) + NEW_VERSION="${VERSION_NAME}+${NEW_BUILD}" - sed -i "s/^version: .*/version: $NEW_VERSION/" pubspec.yaml - echo "Building with version ${NEW_VERSION}" + sed -i "s/^version: .*/version: $NEW_VERSION/" pubspec.yaml + echo "Building with version ${NEW_VERSION}" + + # Store version for later use + echo "NEW_VERSION=${NEW_VERSION}" >> $GITHUB_ENV - - name: Prepare and validate changelog for Play Store - run: | - mkdir -p whatsnew - CHANGELOG_FILE="scripts/changes.txt" - OUTPUT_FILE="whatsnew/en-US.txt" + - name: Prepare and validate changelog for Play Store + run: | + mkdir -p whatsnew + CHANGELOG_FILE="scripts/changes.txt" + OUTPUT_FILE="whatsnew/en-US.txt" - # Use provided changelog or fallback - if [ -f "$CHANGELOG_FILE" ]; then - head -c 500 "$CHANGELOG_FILE" > "$OUTPUT_FILE" - else - echo "Bug fixes and improvements" > "$OUTPUT_FILE" - fi + # Use provided changelog or fallback + if [ -f "$CHANGELOG_FILE" ]; then + head -c 500 "$CHANGELOG_FILE" > "$OUTPUT_FILE" + else + echo "Bug fixes and improvements" > "$OUTPUT_FILE" + fi - # Validate: file exists - if [ ! -s "$OUTPUT_FILE" ]; then - echo "❌ Changelog is empty." - exit 1 - fi + # Validate: file exists + if [ ! -s "$OUTPUT_FILE" ]; then + echo "❌ Changelog is empty." + exit 1 + fi - # Validate: <= 500 chars - LENGTH=$(wc -m < "$OUTPUT_FILE") - if [ "$LENGTH" -gt 500 ]; then - echo "❌ Changelog exceeds 500 characters ($LENGTH)." - exit 1 - fi + # Validate: <= 500 chars + LENGTH=$(wc -m < "$OUTPUT_FILE") + if [ "$LENGTH" -gt 500 ]; then + echo "❌ Changelog exceeds 500 characters ($LENGTH)." + exit 1 + fi - # Validate: no markdown or HTML - if grep -Eq '[\*\_\<\>\[\]\(\)]' "$OUTPUT_FILE"; then - echo "❌ Changelog contains markdown/HTML formatting." - exit 1 - fi + # Validate: no markdown or HTML + if grep -Eq '[\*\_\<\>\[\]\(\)]' "$OUTPUT_FILE"; then + echo "❌ Changelog contains markdown/HTML formatting." + exit 1 + fi - echo "✅ Changelog valid:" - cat "$OUTPUT_FILE" + echo "✅ Changelog valid:" + cat "$OUTPUT_FILE" + + # Store changelog for later use + echo "CHANGELOG<> $GITHUB_ENV + cat "$OUTPUT_FILE" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV - - name: Setup keys - uses: timheuer/base64-to-file@v1 - with: - fileName: "keystore/ente_photos_key.jks" - encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }} + - name: Setup keys + uses: timheuer/base64-to-file@v1 + with: + fileName: "keystore/ente_photos_key.jks" + encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }} - - name: Build PlayStore AAB - run: | - flutter build appbundle --dart-define=cronetHttpNoPlay=true --release --flavor playstore - env: - SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks" - SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }} - SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD_PHOTOS }} - SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }} + - name: Build PlayStore AAB + run: | + flutter build appbundle --dart-define=cronetHttpNoPlay=true --release --flavor playstore + env: + SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks" + SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }} + SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD_PHOTOS }} + SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }} - - name: Upload AAB to PlayStore - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} - packageName: io.ente.photos - releaseFiles: mobile/apps/photos/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab - track: internal - whatsNewDirectory: mobile/apps/photos/whatsnew - mappingFile: mobile/apps/photos/build/app/outputs/mapping/playstoreRelease/mapping.txt + - name: Upload AAB to PlayStore + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} + packageName: io.ente.photos + releaseFiles: mobile/apps/photos/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab + track: internal + whatsNewDirectory: mobile/apps/photos/whatsnew + mappingFile: mobile/apps/photos/build/app/outputs/mapping/playstoreRelease/mapping.txt - - name: Notify Discord - uses: sarisia/actions-status-discord@v1 - with: - webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }} - nodetail: true - title: "🏆 Daily release Photos (Branch: ${{ github.ref_name }})" - description: "[Download](https://play.google.com/store/apps/details?id=io.ente.photos) | [Changelog](https://github.com/ente-io/ente/blob/main/mobile/apps/photos/scripts/changes.txt)" - color: 0x00ff00 + - name: Notify Discord + uses: sarisia/actions-status-discord@v1 + with: + webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }} + nodetail: true + title: "🏆 Daily release Photos v${{ env.NEW_VERSION }} (Branch: ${{ github.ref_name }})" + description: | + **Version:** ${{ env.NEW_VERSION }} + **Flutter:** ${{ env.FLUTTER_VERSION }} + **Commit:** [${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) + **Download:** [Play Store](https://play.google.com/store/apps/details?id=io.ente.photos) + + **Changes:** + ${{ env.CHANGELOG }} + color: 0x00ff00 diff --git a/.github/workflows/mobile-internal-release.yml b/.github/workflows/mobile-internal-release.yml index 1a689d3785..5da51828ca 100644 --- a/.github/workflows/mobile-internal-release.yml +++ b/.github/workflows/mobile-internal-release.yml @@ -1,4 +1,4 @@ -name: "Internal release (photos)" +name: "Old Internal release (photos)" on: workflow_dispatch: # Allow manually running the action diff --git a/mobile/apps/photos/pubspec.yaml b/mobile/apps/photos/pubspec.yaml index 245f2f4e48..7c6e5392f9 100644 --- a/mobile/apps/photos/pubspec.yaml +++ b/mobile/apps/photos/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.2.0+1203 +version: 1.2.0+1205 publish_to: none environment: From e8dde486889bd41ab0c9c5cb3db0e044a6438f41 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:08:07 +0530 Subject: [PATCH 14/28] Update workflow --- .github/workflows/mobile-daily-internal.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/mobile-daily-internal.yml b/.github/workflows/mobile-daily-internal.yml index 1cb30afe02..961ab91362 100644 --- a/.github/workflows/mobile-daily-internal.yml +++ b/.github/workflows/mobile-daily-internal.yml @@ -87,10 +87,9 @@ jobs: echo "✅ Changelog valid:" cat "$OUTPUT_FILE" - # Store changelog for later use - echo "CHANGELOG<> $GITHUB_ENV - cat "$OUTPUT_FILE" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV + # Store changelog for later use - Simple and reliable method + CHANGELOG=$(cat "$OUTPUT_FILE" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g') + echo "CHANGELOG=${CHANGELOG}" >> $GITHUB_ENV - name: Setup keys uses: timheuer/base64-to-file@v1 @@ -131,4 +130,4 @@ jobs: **Changes:** ${{ env.CHANGELOG }} - color: 0x00ff00 + color: 0x00ff00 \ No newline at end of file From 229bfdc7ec51886a870e2e774f3a7377af687216 Mon Sep 17 00:00:00 2001 From: Aman Raj Singh Mourya <146618155+AmanRajSinghMourya@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:28:20 +0530 Subject: [PATCH 15/28] Fix formatting --- mobile/apps/auth/assets/custom-icons/_data/custom-icons.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json index 8ee1f07db9..ce6f1efea6 100644 --- a/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json +++ b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json @@ -1994,7 +1994,9 @@ "title": "ISC2", "slug": "isc2" }, + { "title": "Allegro", "slug": "allegro" + } ] } From 22c60fb83e807460193b67e41c744a61303e6de1 Mon Sep 17 00:00:00 2001 From: Sven Date: Mon, 11 Aug 2025 19:12:32 +0200 Subject: [PATCH 16/28] Remove hex from multi-color icons --- .../custom-icons/_data/custom-icons.json | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json index ce6f1efea6..a764df70a1 100644 --- a/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json +++ b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json @@ -17,8 +17,7 @@ "uidai", "UIDAI", "Unique Identification Authority of India" - ], - "hex": "FBB401" + ] }, { "title": "Accredible", @@ -57,8 +56,7 @@ }, { "title": "Amtrak", - "slug": "amtrak", - "hex": "003A5D" + "slug": "amtrak" }, { "title": "Animal Crossing", @@ -321,8 +319,7 @@ }, { "title": "Caltrain", - "slug": "caltrain", - "hex": "E31837" + "slug": "caltrain" }, { "title": "Canva" @@ -387,8 +384,7 @@ "ClipperCard", "clipper-card", "Clipper Card" - ], - "hex": "006298" + ] }, { "title": "CloudAMQP" @@ -433,8 +429,7 @@ }, { "title": "Coolify", - "slug": "coolify", - "hex": "8C52FF" + "slug": "coolify" }, { "title": "Crowdpear" @@ -547,8 +542,7 @@ "altNames": [ "Domino's", "Domino's Pizza" - ], - "hex": "0B648F" + ] }, { "title": "Doppler" @@ -571,8 +565,7 @@ "Dunkin'", "Dunkin", "Dunkin Donuts" - ], - "hex": "C63663" + ] }, { "title": "eBay" @@ -638,8 +631,7 @@ }, { "title": "Experian", - "slug": "experian", - "hex": "AF1685" + "slug": "experian" }, { "title": "Fanatical", @@ -763,8 +755,7 @@ "altNames": [ "green man gaming", "gmg" - ], - "hex": "00E205" + ] }, { "title": "Guideline" @@ -891,8 +882,7 @@ }, { "title": "Kayak", - "slug": "kayak", - "hex": "FF6900" + "slug": "kayak" }, { "title": "Keygen", @@ -1193,8 +1183,7 @@ "slug": "njtransit", "altNames": [ "NJ Transit" - ], - "hex": "1A2B57" + ] }, { "title": "nordvpn", @@ -1299,8 +1288,7 @@ "slug": "pcpartpicker", "altNames": [ "PC Part Picker" - ], - "hex": "EDA920" + ] }, { "title": "Peerberry" @@ -1510,8 +1498,7 @@ "altNames": [ "onlinesbi", "State Bank of India" - ], - "hex": "12A8E0" + ] }, { "title": "SEI", @@ -1612,8 +1599,7 @@ }, { "title": "Supercell", - "slug": "supercell", - "hex": "000000" + "slug": "supercell" }, { "title": "Surfshark" @@ -1696,8 +1682,7 @@ "altNames": [ "StoryGraph", "TheStoryGraph" - ], - "hex": "15919B" + ] }, { "title": "tianyiyun", @@ -1872,8 +1857,7 @@ "Washington Metro", "DC Metro", "Washington Metropolitan Area Transit Authority" - ], - "hex": "2D2D2D" + ] }, { "title": "Wolvesville" From 4ff211868eb225b0f84f2b81eb335ca007d8c871 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 12 Aug 2025 08:54:56 +0530 Subject: [PATCH 17/28] Update changes --- mobile/apps/photos/scripts/changes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/apps/photos/scripts/changes.txt b/mobile/apps/photos/scripts/changes.txt index 965deccd99..6abb45295c 100644 --- a/mobile/apps/photos/scripts/changes.txt +++ b/mobile/apps/photos/scripts/changes.txt @@ -1 +1,2 @@ +- Aman: Fixed bottom nav bar color in light theme, resolved paint editor's initial color, and added tap-to-reset with haptics for tune adjustments (brightness/exposure) - Gracefully handle heic rendering on Android \ No newline at end of file From b98294c07379f354c4fe514ef5946c7378430261 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:01:48 +0530 Subject: [PATCH 18/28] Update workflow --- .github/workflows/mobile-daily-internal.yml | 2 +- .github/workflows/mobile-internal-release-rust.yml | 2 +- mobile/apps/photos/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mobile-daily-internal.yml b/.github/workflows/mobile-daily-internal.yml index 961ab91362..0ca74c1bb0 100644 --- a/.github/workflows/mobile-daily-internal.yml +++ b/.github/workflows/mobile-daily-internal.yml @@ -43,7 +43,7 @@ jobs: CURRENT_VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //') VERSION_NAME=$(echo $CURRENT_VERSION | cut -d'+' -f1) CURRENT_BUILD=$(echo $CURRENT_VERSION | cut -d'+' -f2) - NEW_BUILD=$((CURRENT_BUILD + 1)) + NEW_BUILD=$((CURRENT_BUILD + ${{ github.run_number }})) NEW_VERSION="${VERSION_NAME}+${NEW_BUILD}" sed -i "s/^version: .*/version: $NEW_VERSION/" pubspec.yaml diff --git a/.github/workflows/mobile-internal-release-rust.yml b/.github/workflows/mobile-internal-release-rust.yml index 28c93d8618..e4a0121866 100644 --- a/.github/workflows/mobile-internal-release-rust.yml +++ b/.github/workflows/mobile-internal-release-rust.yml @@ -1,4 +1,4 @@ -name: "Internal release (photos)" +name: "Internal release (photos with rust)" on: workflow_dispatch: # Allow manually running the action diff --git a/mobile/apps/photos/pubspec.yaml b/mobile/apps/photos/pubspec.yaml index 7c6e5392f9..eabe908229 100644 --- a/mobile/apps/photos/pubspec.yaml +++ b/mobile/apps/photos/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.2.0+1205 +version: 1.2.1+1205 publish_to: none environment: From 46c588c5120d5fca1a897c2d6d88a03082ca0477 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 8 Aug 2025 11:50:49 +0530 Subject: [PATCH 19/28] Button --- web/apps/photos/src/components/Sidebar.tsx | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 8c9769d0c5..8fdf4c1697 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -51,6 +51,7 @@ import { import { useBaseContext } from "ente-base/context"; import { getLocaleInUse, + pt, setLocaleInUse, supportedLocales, ut, @@ -816,6 +817,26 @@ const Preferences: React.FC = ({ /> )} + + + + + } + onClick={showMapSettings} + /> } label={t("map")} From b099d16a32aa178a178ae4a22bd694db238b58a0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 8 Aug 2025 13:07:36 +0530 Subject: [PATCH 20/28] Screen outline --- web/apps/photos/src/components/Sidebar.tsx | 91 +++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 8fdf4c1697..a1b799a15f 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -777,6 +777,8 @@ const Preferences: React.FC = ({ onClose, onRootClose, }) => { + const { show: showDomainSettings, props: domainSettingsVisibilityProps } = + useModalVisibility(); const { show: showMapSettings, props: mapSettingsVisibilityProps } = useModalVisibility(); const { @@ -835,7 +837,7 @@ const Preferences: React.FC = ({ } - onClick={showMapSettings} + onClick={showDomainSettings} /> } @@ -857,6 +859,10 @@ const Preferences: React.FC = ({ )} + { ); }; +const DomainSettings: React.FC = ({ + open, + onClose, + onRootClose, +}) => { + // const { showMiniDialog } = useBaseContext(); + + // const { mapEnabled } = useSettingsSnapshot(); + + // const confirmToggle = useCallback( + // () => + // showMiniDialog( + // mapEnabled + // ? confirmDisableMapsDialogAttributes(() => + // updateMapEnabled(false), + // ) + // : confirmEnableMapsDialogAttributes(() => + // updateMapEnabled(true), + // ), + // ), + // [showMiniDialog, mapEnabled], + // ); + + const handleRootClose = () => { + onClose(); + onRootClose(); + }; + + return ( + + + + + + + + + + + + + + + + ); +}; + +interface DomainSectionProps { + title: string; + ordinal: string; +} + +const DomainItem: React.FC> = ({ + title, + ordinal, + children, +}) => ( + + + {title} + + {ordinal} + + + {children} + +); + const MapSettings: React.FC = ({ open, onClose, From 18e7a52848cd927c1908d102379aec5eab54cbcb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 8 Aug 2025 14:30:00 +0530 Subject: [PATCH 21/28] Sketch 1 --- web/apps/photos/src/components/Sidebar.tsx | 62 ++++++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index a1b799a15f..1d7665521f 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -1,3 +1,4 @@ +import { SingleInputForm } from "ente-base/components/SingleInputForm"; import ArchiveOutlinedIcon from "@mui/icons-material/ArchiveOutlined"; import CategoryIcon from "@mui/icons-material/Category"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; @@ -990,8 +991,9 @@ const DomainSettings: React.FC = ({ // const { mapEnabled } = useSettingsSnapshot(); - // const confirmToggle = useCallback( - // () => + const handleSubmit = useCallback(() => { + console.log("test"); + }, []); // showMiniDialog( // mapEnabled // ? confirmDisableMapsDialogAttributes(() => @@ -1009,24 +1011,62 @@ const DomainSettings: React.FC = ({ onRootClose(); }; + const customDomain = ""; + return ( - + - + + // {`.${extension}`} + // + // ), + }, + }} + /> - - - + + + + On your DNS provider, add a CNAME from your domain to{" "} + my.ente.io + - + - + + Within 1 hour, your public albums will be accessible via + your domain! + + + For more information, see + + {" help "} + + From 440818f1af3be015246660224b0f9958a13e9c2c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 8 Aug 2025 18:06:10 +0530 Subject: [PATCH 22/28] Read from remote store --- web/apps/photos/src/components/Sidebar.tsx | 55 +++++++++----------- web/packages/new/photos/services/settings.ts | 24 +++++++++ 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 1d7665521f..11562c26c0 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -989,29 +989,35 @@ const DomainSettings: React.FC = ({ }) => { // const { showMiniDialog } = useBaseContext(); - // const { mapEnabled } = useSettingsSnapshot(); + const { customDomain, customDomainCNAME } = useSettingsSnapshot(); + // const handleSubmit = useCallback(async (newName: string) => { + // const newFileName = [newName, extension].filter((x) => !!x).join("."); + // if (newFileName != fileName) { + // await onRename(newFileName); + // } + // onClose(); + // }; const handleSubmit = useCallback(() => { - console.log("test"); + // customDomain + // showMiniDialog( + // mapEnabled + // ? confirmDisableMapsDialogAttributes(() => + // updateMapEnabled(false), + // ) + // : confirmEnableMapsDialogAttributes(() => + // updateMapEnabled(true), + // ), + // ), + // [showMiniDialog, mapEnabled], }, []); - // showMiniDialog( - // mapEnabled - // ? confirmDisableMapsDialogAttributes(() => - // updateMapEnabled(false), - // ) - // : confirmEnableMapsDialogAttributes(() => - // updateMapEnabled(true), - // ), - // ), - // [showMiniDialog, mapEnabled], - // ); const handleRootClose = () => { onClose(); onRootClose(); }; - const customDomain = ""; + // const customDomain = ""; return ( = ({ placeholder={ut("photos.example.org")} initialValue={customDomain} submitButtonColor="accent" - submitButtonTitle={pt("Set")} + submitButtonTitle={ + customDomain ? pt("Update") : pt("Set") + } onSubmit={handleSubmit} - // onCancel={onClose} - slotProps={{ - input: { - // Align the adornment text to the input text. - sx: { alignItems: "baseline" }, - - // endAdornment: extension && ( - // - // {`.${extension}`} - // - // ), - }, - }} /> - + On your DNS provider, add a CNAME from your domain to{" "} - my.ente.io + {customDomainCNAME} diff --git a/web/packages/new/photos/services/settings.ts b/web/packages/new/photos/services/settings.ts index aedda42384..fc988edbdb 100644 --- a/web/packages/new/photos/services/settings.ts +++ b/web/packages/new/photos/services/settings.ts @@ -66,6 +66,24 @@ export interface Settings { * Default: "https://cast.ente.io" */ castURL: string; + + /** + * Set to the domain (host, e.g. "photos.example.org") that the user wishes + * to use for sharing their public albums. + * + * An empty string is treated as `undefined`. + */ + customDomain?: string; + + /** + * The URL we should ask the user to CNAME their {@link customDomain} to + * for wiring up their domain to the public albums app. + * + * See also `apps.custom-domain.cname` in `server/local.yaml`. + * + * Default: "my.ente.io" + */ + customDomainCNAME: string; } const createDefaultSettings = (): Settings => ({ @@ -73,6 +91,7 @@ const createDefaultSettings = (): Settings => ({ mapEnabled: false, cfUploadProxyDisabled: false, castURL: "https://cast.ente.io", + customDomainCNAME: "my.ente.io", }); /** @@ -147,6 +166,8 @@ const FeatureFlags = z.object({ betaUser: z.boolean().nullish().transform(nullToUndefined), mapEnabled: z.boolean().nullish().transform(nullToUndefined), castUrl: z.string().nullish().transform(nullToUndefined), + customDomain: z.string().nullish().transform(nullToUndefined), + customDomainCNAME: z.string().nullish().transform(nullToUndefined), }); type FeatureFlags = z.infer; @@ -158,6 +179,9 @@ const syncSettingsSnapshotWithLocalStorage = () => { settings.mapEnabled = flags?.mapEnabled || false; settings.cfUploadProxyDisabled = savedCFProxyDisabled(); if (flags?.castUrl) settings.castURL = flags.castUrl; + if (flags?.customDomain) settings.customDomain = flags.customDomain; + if (flags?.customDomainCNAME) + settings.customDomainCNAME = flags.customDomainCNAME; setSettingsSnapshot(settings); }; From 3d12812671a887c17a71bada0ad479dd2458a79e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 8 Aug 2025 19:19:23 +0530 Subject: [PATCH 23/28] Use --- .../Collections/CollectionShare.tsx | 21 ++++++++++++-- web/apps/photos/src/components/Sidebar.tsx | 28 ++----------------- web/packages/new/photos/services/settings.ts | 17 ++++++++++- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index e22740e1eb..b23778551f 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -58,6 +58,7 @@ import type { import { type CollectionUser } from "ente-media/collection"; import type { RemotePullOpts } from "ente-new/photos/components/gallery"; import { PublicLinkCreated } from "ente-new/photos/components/share/PublicLinkCreated"; +import { useSettingsSnapshot } from "ente-new/photos/components/utils/use-snapshot"; import { avatarTextColor } from "ente-new/photos/services/avatar"; import { createPublicURL, @@ -1105,6 +1106,8 @@ const PublicShare: React.FC = ({ setBlockingLoad, onRemotePull, }) => { + const { customDomain } = useSettingsSnapshot(); + const { show: showPublicLinkCreated, props: publicLinkCreatedVisibilityProps, @@ -1126,11 +1129,15 @@ const PublicShare: React.FC = ({ void appendCollectionKeyToShareURL( publicURL.url, collection.key, - ).then((url) => setResolvedURL(url)); + ).then((url) => + setResolvedURL( + substituteCustomDomainIfNeeded(url, customDomain), + ), + ); } else { setResolvedURL(undefined); } - }, [collection.key, publicURL]); + }, [collection.key, publicURL, customDomain]); const handleCopyLink = () => { if (resolvedURL) void navigator.clipboard.writeText(resolvedURL); @@ -1164,6 +1171,16 @@ const PublicShare: React.FC = ({ ); }; +const substituteCustomDomainIfNeeded = ( + url: string, + customDomain: string | undefined, +) => { + if (!customDomain) return url; + const u = new URL(url); + u.host = customDomain; + return u.href; +}; + type EnablePublicShareOptionsProps = { setPublicURL: (value: PublicURL) => void; onLinkCreated: () => void; diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 11562c26c0..537299588c 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -90,6 +90,7 @@ import { isDevBuildAndUser, pullSettings, updateCFProxyDisabledPreference, + updateCustomDomain, updateMapEnabled, } from "ente-new/photos/services/settings"; import { @@ -987,38 +988,13 @@ const DomainSettings: React.FC = ({ onClose, onRootClose, }) => { - // const { showMiniDialog } = useBaseContext(); - const { customDomain, customDomainCNAME } = useSettingsSnapshot(); - // const handleSubmit = useCallback(async (newName: string) => { - // const newFileName = [newName, extension].filter((x) => !!x).join("."); - // if (newFileName != fileName) { - // await onRename(newFileName); - // } - // onClose(); - // }; - const handleSubmit = useCallback(() => { - // customDomain - // showMiniDialog( - // mapEnabled - // ? confirmDisableMapsDialogAttributes(() => - // updateMapEnabled(false), - // ) - // : confirmEnableMapsDialogAttributes(() => - // updateMapEnabled(true), - // ), - // ), - // [showMiniDialog, mapEnabled], - }, []); - const handleRootClose = () => { onClose(); onRootClose(); }; - // const customDomain = ""; - return ( = ({ submitButtonTitle={ customDomain ? pt("Update") : pt("Set") } - onSubmit={handleSubmit} + onSubmit={updateCustomDomain} /> diff --git a/web/packages/new/photos/services/settings.ts b/web/packages/new/photos/services/settings.ts index fc988edbdb..e09844ce7b 100644 --- a/web/packages/new/photos/services/settings.ts +++ b/web/packages/new/photos/services/settings.ts @@ -8,7 +8,11 @@ import log from "ente-base/log"; import { updateShouldDisableCFUploadProxy } from "ente-gallery/services/upload"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; -import { fetchFeatureFlags, updateRemoteFlag } from "./remote-store"; +import { + fetchFeatureFlags, + updateRemoteFlag, + updateRemoteValue, +} from "./remote-store"; /** * In-memory flags that tracks various settings. @@ -222,6 +226,17 @@ export const isDevBuildAndUser = () => isDevBuild && isDevUserViaEmail(); const isDevUserViaEmail = () => !!savedPartialLocalUser()?.email?.endsWith("@ente.io"); +/** + * Persist the user's custom domain preference both locally and on remote. + * + * Setting the value to a blank string is equivalent to deleting the custom + * domain value altogether. + */ +export const updateCustomDomain = async (customDomain: string) => { + await updateRemoteValue("customDomain", customDomain); + return pullSettings(); +}; + /** * Persist the user's map enabled preference both locally and on remote. */ From 4c57c6b30f1d7b26ae26175ef11738b8e0bdcec0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 8 Aug 2025 20:02:22 +0530 Subject: [PATCH 24/28] Tweak --- web/apps/photos/src/components/Sidebar.tsx | 41 +++++++++++++++++----- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 537299588c..18bb82ce5f 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -1,4 +1,3 @@ -import { SingleInputForm } from "ente-base/components/SingleInputForm"; import ArchiveOutlinedIcon from "@mui/icons-material/ArchiveOutlined"; import CategoryIcon from "@mui/icons-material/Category"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; @@ -36,6 +35,7 @@ import { RowButtonGroupHint, RowSwitch, } from "ente-base/components/RowButton"; +import { SingleInputForm } from "ente-base/components/SingleInputForm"; import { SpacedRow } from "ente-base/components/containers"; import { DialogCloseIconButton } from "ente-base/components/mui/DialogCloseIconButton"; import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton"; @@ -831,8 +831,25 @@ const Preferences: React.FC = ({ + + @@ -999,28 +1016,36 @@ const DomainSettings: React.FC = ({ - + + + Any domain or subdomain you own + - + On your DNS provider, add a CNAME from your domain to{" "} - {customDomainCNAME} + + {customDomainCNAME} + @@ -1029,7 +1054,7 @@ const DomainSettings: React.FC = ({ Within 1 hour, your public albums will be accessible via your domain! - + For more information, see Date: Tue, 12 Aug 2025 10:44:46 +0530 Subject: [PATCH 25/28] Not required --- web/apps/photos/src/components/Sidebar.tsx | 62 +++++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 18bb82ce5f..4acac1929e 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -17,6 +17,7 @@ import { Skeleton, Stack, styled, + TextField, Tooltip, useColorScheme, } from "@mui/material"; @@ -35,10 +36,10 @@ import { RowButtonGroupHint, RowSwitch, } from "ente-base/components/RowButton"; -import { SingleInputForm } from "ente-base/components/SingleInputForm"; import { SpacedRow } from "ente-base/components/containers"; import { DialogCloseIconButton } from "ente-base/components/mui/DialogCloseIconButton"; import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton"; +import { LoadingButton } from "ente-base/components/mui/LoadingButton"; import { SidebarDrawer, TitledNestedSidebarDrawer, @@ -113,6 +114,7 @@ import { import { usePhotosAppContext } from "ente-new/photos/types/context"; import { initiateEmail, openURL } from "ente-new/photos/utils/web"; import { wait } from "ente-utils/promise"; +import { useFormik } from "formik"; import { t } from "i18next"; import { useRouter } from "next/router"; import React, { @@ -1007,6 +1009,22 @@ const DomainSettings: React.FC = ({ }) => { const { customDomain, customDomainCNAME } = useSettingsSnapshot(); + const formik = useFormik({ + initialValues: { domain: customDomain ?? "" }, + onSubmit: async (values, { setFieldError }) => { + const domain = values.domain; + const setValueFieldError = (message: string) => + setFieldError("domain", message); + + try { + await updateCustomDomain(domain); + } catch (e) { + log.error(`Failed to submit input ${domain}`, e); + setValueFieldError(t("generic_error")); + } + }, + }); + const handleRootClose = () => { onClose(); onRootClose(); @@ -1023,18 +1041,32 @@ const DomainSettings: React.FC = ({ - Any domain or subdomain you own + {pt("Any domain or subdomain you own")} - +
+ + + {customDomain ? pt("Update") : pt("Save")} + +
@@ -1049,7 +1081,7 @@ const DomainSettings: React.FC = ({
- + Within 1 hour, your public albums will be accessible via your domain! @@ -1072,11 +1104,13 @@ const DomainSettings: React.FC = ({ interface DomainSectionProps { title: string; ordinal: string; + isEmoji?: boolean; } const DomainItem: React.FC> = ({ title, ordinal, + isEmoji, children, }) => ( @@ -1084,7 +1118,7 @@ const DomainItem: React.FC> = ({ direction="row" sx={{ alignItems: "center", justifyContent: "space-between" }} > - {title} + {title} Date: Tue, 12 Aug 2025 11:03:37 +0530 Subject: [PATCH 26/28] Handle errors --- web/apps/photos/src/components/Sidebar.tsx | 26 ++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 4acac1929e..d52cc92578 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -51,6 +51,7 @@ import { type ModalVisibilityProps, } from "ente-base/components/utils/modal"; import { useBaseContext } from "ente-base/context"; +import { isHTTPErrorWithStatus } from "ente-base/http"; import { getLocaleInUse, pt, @@ -1020,7 +1021,13 @@ const DomainSettings: React.FC = ({ await updateCustomDomain(domain); } catch (e) { log.error(`Failed to submit input ${domain}`, e); - setValueFieldError(t("generic_error")); + if (isHTTPErrorWithStatus(e, 400)) { + setValueFieldError(pt("Invalid domain")); + } else if (isHTTPErrorWithStatus(e, 409)) { + setValueFieldError(pt("Domain already linked by a user")); + } else { + setValueFieldError(t("generic_error")); + } } }, }); @@ -1040,9 +1047,6 @@ const DomainSettings: React.FC = ({ > - - {pt("Any domain or subdomain you own")} -
= ({ type={"text"} fullWidth autoFocus={true} - margin="normal" + margin="dense" disabled={formik.isSubmitting} error={!!formik.errors.domain} - helperText={formik.errors.domain ?? " "} + helperText={ + formik.errors.domain ?? + pt("Any domain or subdomain you own") + } label={t("Domain")} placeholder={ut("photos.example.org")} + sx={{ mb: 2 }} /> = ({
- + On your DNS provider, add a CNAME from your domain to{" "} @@ -1080,9 +1088,9 @@ const DomainSettings: React.FC = ({
- + - + Within 1 hour, your public albums will be accessible via your domain! From 95ed8d23de1a630e892f7f9e7859323635e71a14 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 12 Aug 2025 11:30:03 +0530 Subject: [PATCH 27/28] Prep for merge --- web/apps/photos/src/components/Sidebar.tsx | 228 +++++++++++---------- 1 file changed, 121 insertions(+), 107 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index d52cc92578..27f6aca66c 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -824,43 +824,50 @@ const Preferences: React.FC = ({ /> )} - - - - - -
- } - onClick={showDomainSettings} - /> + { + /* TODO: CD */ process.env.NEXT_PUBLIC_ENTE_WIP_CD && ( + + + + + + + } + onClick={showDomainSettings} + /> + ) + } } label={t("map")} @@ -1008,6 +1015,27 @@ const DomainSettings: React.FC = ({ onClose, onRootClose, }) => { + const handleRootClose = () => { + onClose(); + onRootClose(); + }; + + return ( + + + + ); +}; + +// Separate component to reset state on back. +const DomainSettingsContents: React.FC = () => { const { customDomain, customDomainCNAME } = useSettingsSnapshot(); const formik = useFormik({ @@ -1032,80 +1060,66 @@ const DomainSettings: React.FC = ({ }, }); - const handleRootClose = () => { - onClose(); - onRootClose(); - }; + // TODO: CD: help return ( - - - -
- - - {customDomain ? pt("Update") : pt("Save")} - - -
- - - - On your DNS provider, add a CNAME from your domain to{" "} - - {customDomainCNAME} - + + +
+ + + {customDomain ? pt("Update") : pt("Save")} + + +
+ + + + On your DNS provider, add a CNAME from your domain to{" "} + + {customDomainCNAME} - - - - - Within 1 hour, your public albums will be accessible via - your domain! + + + + + + Within 1 hour, your public albums will be accessible via + your domain! + + + For more information, see + + {" help "} - - For more information, see - - {" help "} - - - -
-
+ + + ); }; From 633caa7883686831ffe12f373898f926f4fdb40b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 12 Aug 2025 12:15:30 +0530 Subject: [PATCH 28/28] Custom albums build for custom domains --- .github/workflows/web-deploy.yml | 12 ++++++++++++ web/apps/photos/src/pages/index.tsx | 9 +++++++-- web/packages/base/origins.ts | 6 ++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/web-deploy.yml b/.github/workflows/web-deploy.yml index 6c2691ca35..ee6e1d6e7b 100644 --- a/.github/workflows/web-deploy.yml +++ b/.github/workflows/web-deploy.yml @@ -54,6 +54,18 @@ jobs: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} command: pages deploy --project-name=ente --commit-dirty=true --branch=deploy/photos web/apps/photos/out + - name: Build custom-albums + run: yarn build:photos + env: + NEXT_PUBLIC_ENTE_ONLY_SERVE_ALBUMS_APP: 1 + + - name: Publish custom-albums + uses: cloudflare/wrangler-action@v3 + with: + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + command: pages deploy --project-name=ente --commit-dirty=true --branch=deploy/custom-albums web/apps/photos/out + - name: Build accounts run: yarn build:accounts diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index d276aad67b..1796318bca 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -7,7 +7,11 @@ import { EnteLogo } from "ente-base/components/EnteLogo"; import { ActivityIndicator } from "ente-base/components/mui/ActivityIndicator"; import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton"; import { useBaseContext } from "ente-base/context"; -import { albumsAppOrigin, customAPIHost } from "ente-base/origins"; +import { + albumsAppOrigin, + customAPIHost, + shouldOnlyServeAlbumsApp, +} from "ente-base/origins"; import { masterKeyFromSession, updateSessionFromElectronSafeStorageIfNeeded, @@ -41,7 +45,8 @@ const Page: React.FC = () => { const albumsURL = new URL(albumsAppOrigin()); currentURL.pathname = router.pathname; if ( - currentURL.host == albumsURL.host && + (shouldOnlyServeAlbumsApp || + currentURL.host == albumsURL.host) && currentURL.pathname != "/shared-albums" ) { const end = currentURL.hash.lastIndexOf("&"); diff --git a/web/packages/base/origins.ts b/web/packages/base/origins.ts index 8e1ef1ddac..e6c8b89ebd 100644 --- a/web/packages/base/origins.ts +++ b/web/packages/base/origins.ts @@ -97,3 +97,9 @@ export const isCustomAlbumsAppOrigin = */ export const albumsAppOrigin = () => process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT ?? "https://albums.ente.io"; + +/** + * Return true if this build is meant to only serve public albums. + */ +export const shouldOnlyServeAlbumsApp = + !!process.env.NEXT_PUBLIC_ENTE_ONLY_SERVE_ALBUMS_APP;