ソースを参照

[mob][photos] Merge main

ashilkn 1 年間 前
コミット
b494c308b1
45 ファイル変更670 行追加267 行削除
  1. 0 1
      auth/lib/l10n/arb/app_en.arb
  2. 1 0
      auth/lib/main.dart
  3. 22 7
      auth/lib/models/code.dart
  4. 10 3
      auth/lib/onboarding/view/setup_enter_secret_key_page.dart
  5. 5 5
      auth/lib/ui/code_widget.dart
  6. 2 0
      auth/lib/ui/settings/data/import/bitwarden_import.dart
  7. 2 2
      auth/lib/utils/totp_util.dart
  8. 1 1
      auth/pubspec.yaml
  9. 6 13
      desktop/.github/workflows/desktop-release.yml
  10. 2 2
      desktop/docs/release.md
  11. 1 0
      desktop/electron-builder.yml
  12. 2 1
      desktop/package.json
  13. 482 87
      desktop/yarn.lock
  14. 3 3
      mobile/lib/generated/intl/messages_en.dart
  15. 6 6
      mobile/lib/generated/l10n.dart
  16. 2 2
      mobile/lib/l10n/intl_en.arb
  17. 1 1
      mobile/lib/services/update_service.dart
  18. 1 1
      mobile/lib/ui/cast/choose.dart
  19. 24 29
      mobile/lib/ui/notification/update/change_log_page.dart
  20. 7 1
      mobile/lib/utils/file_uploader.dart
  21. 1 1
      mobile/pubspec.yaml
  22. 1 2
      server/ente/cast/entity.go
  23. 6 6
      server/pkg/api/cast.go
  24. 1 3
      server/pkg/controller/cast/controller.go
  25. 2 29
      server/pkg/controller/storagebonus/referral.go
  26. 4 11
      server/pkg/repo/cast/repo.go
  27. 27 0
      server/pkg/utils/random/generate.go
  28. 2 4
      web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx
  29. 2 2
      web/packages/next/locales/bg-BG/translation.json
  30. 2 2
      web/packages/next/locales/de-DE/translation.json
  31. 4 4
      web/packages/next/locales/en-US/translation.json
  32. 2 2
      web/packages/next/locales/es-ES/translation.json
  33. 2 2
      web/packages/next/locales/fa-IR/translation.json
  34. 2 2
      web/packages/next/locales/fi-FI/translation.json
  35. 4 4
      web/packages/next/locales/fr-FR/translation.json
  36. 2 2
      web/packages/next/locales/it-IT/translation.json
  37. 2 2
      web/packages/next/locales/ko-KR/translation.json
  38. 4 4
      web/packages/next/locales/nl-NL/translation.json
  39. 4 4
      web/packages/next/locales/pt-BR/translation.json
  40. 2 2
      web/packages/next/locales/pt-PT/translation.json
  41. 4 4
      web/packages/next/locales/ru-RU/translation.json
  42. 2 2
      web/packages/next/locales/sv-SE/translation.json
  43. 2 2
      web/packages/next/locales/th-TH/translation.json
  44. 2 2
      web/packages/next/locales/tr-TR/translation.json
  45. 4 4
      web/packages/next/locales/zh-CN/translation.json

+ 0 - 1
auth/lib/l10n/arb/app_en.arb

@@ -20,7 +20,6 @@
   "codeIssuerHint": "Issuer",
   "codeIssuerHint": "Issuer",
   "codeSecretKeyHint": "Secret Key",
   "codeSecretKeyHint": "Secret Key",
   "codeAccountHint": "Account (you@domain.com)",
   "codeAccountHint": "Account (you@domain.com)",
-  "accountKeyType": "Type of key",
   "sessionExpired": "Session expired",
   "sessionExpired": "Session expired",
   "@sessionExpired": {
   "@sessionExpired": {
     "description": "Title of the dialog when the users current session is invalid/expired"
     "description": "Title of the dialog when the users current session is invalid/expired"

+ 1 - 0
auth/lib/main.dart

@@ -37,6 +37,7 @@ import 'package:window_manager/window_manager.dart';
 final _logger = Logger("main");
 final _logger = Logger("main");
 
 
 Future<void> initSystemTray() async {
 Future<void> initSystemTray() async {
+  if (PlatformUtil.isMobile()) return;
   String path = Platform.isWindows
   String path = Platform.isWindows
       ? 'assets/icons/auth-icon.ico'
       ? 'assets/icons/auth-icon.ico'
       : 'assets/icons/auth-icon.png';
       : 'assets/icons/auth-icon.png';

+ 22 - 7
auth/lib/models/code.dart

@@ -2,6 +2,7 @@ import 'package:ente_auth/utils/totp_util.dart';
 
 
 class Code {
 class Code {
   static const defaultDigits = 6;
   static const defaultDigits = 6;
+  static const steamDigits = 5;
   static const defaultPeriod = 30;
   static const defaultPeriod = 30;
 
 
   int? generatedID;
   int? generatedID;
@@ -57,36 +58,42 @@ class Code {
       updatedAlgo,
       updatedAlgo,
       updatedType,
       updatedType,
       updatedCounter,
       updatedCounter,
-      "otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
+      "otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}"
+      "&digits=$updatedDigits&issuer=$updateIssuer"
+      "&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
       generatedID: generatedID,
       generatedID: generatedID,
     );
     );
   }
   }
 
 
   static Code fromAccountAndSecret(
   static Code fromAccountAndSecret(
+    Type type,
     String account,
     String account,
     String issuer,
     String issuer,
     String secret,
     String secret,
+    int digits,
   ) {
   ) {
     return Code(
     return Code(
       account,
       account,
       issuer,
       issuer,
-      defaultDigits,
+      digits,
       defaultPeriod,
       defaultPeriod,
       secret,
       secret,
       Algorithm.sha1,
       Algorithm.sha1,
-      Type.totp,
+      type,
       0,
       0,
-      "otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret",
+      "otpauth://${type.name}/$issuer:$account?algorithm=SHA1&digits=$digits&issuer=$issuer&period=30&secret=$secret",
     );
     );
   }
   }
 
 
   static Code fromRawData(String rawData) {
   static Code fromRawData(String rawData) {
     Uri uri = Uri.parse(rawData);
     Uri uri = Uri.parse(rawData);
+    final issuer = _getIssuer(uri);
+
     try {
     try {
       return Code(
       return Code(
         _getAccount(uri),
         _getAccount(uri),
-        _getIssuer(uri),
-        _getDigits(uri),
+        issuer,
+        _getDigits(uri, issuer),
         _getPeriod(uri),
         _getPeriod(uri),
         getSanitizedSecret(uri.queryParameters['secret']!),
         getSanitizedSecret(uri.queryParameters['secret']!),
         _getAlgorithm(uri),
         _getAlgorithm(uri),
@@ -140,10 +147,13 @@ class Code {
     }
     }
   }
   }
 
 
-  static int _getDigits(Uri uri) {
+  static int _getDigits(Uri uri, String issuer) {
     try {
     try {
       return int.parse(uri.queryParameters['digits']!);
       return int.parse(uri.queryParameters['digits']!);
     } catch (e) {
     } catch (e) {
+      if (issuer.toLowerCase() == "steam") {
+        return steamDigits;
+      }
       return defaultDigits;
       return defaultDigits;
     }
     }
   }
   }
@@ -186,6 +196,8 @@ class Code {
   static Type _getType(Uri uri) {
   static Type _getType(Uri uri) {
     if (uri.host == "totp") {
     if (uri.host == "totp") {
       return Type.totp;
       return Type.totp;
+    } else if (uri.host == "steam") {
+      return Type.steam;
     } else if (uri.host == "hotp") {
     } else if (uri.host == "hotp") {
       return Type.hotp;
       return Type.hotp;
     }
     }
@@ -223,6 +235,9 @@ class Code {
 enum Type {
 enum Type {
   totp,
   totp,
   hotp,
   hotp,
+  steam;
+
+  bool get isTOTPCompatible => this == totp || this == steam;
 }
 }
 
 
 enum Algorithm {
 enum Algorithm {

+ 10 - 3
auth/lib/onboarding/view/setup_enter_secret_key_page.dart

@@ -61,6 +61,8 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
                   },
                   },
                   decoration: InputDecoration(
                   decoration: InputDecoration(
                     hintText: l10n.codeIssuerHint,
                     hintText: l10n.codeIssuerHint,
+                    floatingLabelBehavior: FloatingLabelBehavior.auto,
+                    labelText: l10n.codeIssuerHint,
                   ),
                   ),
                   controller: _issuerController,
                   controller: _issuerController,
                   autofocus: true,
                   autofocus: true,
@@ -78,6 +80,8 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
                   },
                   },
                   decoration: InputDecoration(
                   decoration: InputDecoration(
                     hintText: l10n.codeSecretKeyHint,
                     hintText: l10n.codeSecretKeyHint,
+                    floatingLabelBehavior: FloatingLabelBehavior.auto,
+                    labelText: l10n.codeSecretKeyHint,
                     suffixIcon: IconButton(
                     suffixIcon: IconButton(
                       onPressed: () {
                       onPressed: () {
                         setState(() {
                         setState(() {
@@ -105,12 +109,12 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
                   },
                   },
                   decoration: InputDecoration(
                   decoration: InputDecoration(
                     hintText: l10n.codeAccountHint,
                     hintText: l10n.codeAccountHint,
+                    floatingLabelBehavior: FloatingLabelBehavior.auto,
+                    labelText: l10n.codeAccountHint,
                   ),
                   ),
                   controller: _accountController,
                   controller: _accountController,
                 ),
                 ),
-                const SizedBox(
-                  height: 40,
-                ),
+                const SizedBox(height: 40),
                 SizedBox(
                 SizedBox(
                   width: 400,
                   width: 400,
                   child: OutlinedButton(
                   child: OutlinedButton(
@@ -152,6 +156,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
       final account = _accountController.text.trim();
       final account = _accountController.text.trim();
       final issuer = _issuerController.text.trim();
       final issuer = _issuerController.text.trim();
       final secret = _secretController.text.trim().replaceAll(' ', '');
       final secret = _secretController.text.trim().replaceAll(' ', '');
+      final isStreamCode = issuer.toLowerCase() == "steam";
       if (widget.code != null && widget.code!.secret != secret) {
       if (widget.code != null && widget.code!.secret != secret) {
         ButtonResult? result = await showChoiceActionSheet(
         ButtonResult? result = await showChoiceActionSheet(
           context,
           context,
@@ -168,9 +173,11 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
       }
       }
       final Code newCode = widget.code == null
       final Code newCode = widget.code == null
           ? Code.fromAccountAndSecret(
           ? Code.fromAccountAndSecret(
+              isStreamCode ? Type.steam : Type.totp,
               account,
               account,
               issuer,
               issuer,
               secret,
               secret,
+              isStreamCode ? Code.steamDigits : Code.defaultDigits,
             )
             )
           : widget.code!.copyWith(
           : widget.code!.copyWith(
               account: account,
               account: account,

+ 5 - 5
auth/lib/ui/code_widget.dart

@@ -53,7 +53,7 @@ class _CodeWidgetState extends State<CodeWidget> {
       String newCode = _getCurrentOTP();
       String newCode = _getCurrentOTP();
       if (newCode != _currentCode.value) {
       if (newCode != _currentCode.value) {
         _currentCode.value = newCode;
         _currentCode.value = newCode;
-        if (widget.code.type == Type.totp) {
+        if (widget.code.type.isTOTPCompatible) {
           _nextCode.value = _getNextTotp();
           _nextCode.value = _getNextTotp();
         }
         }
       }
       }
@@ -78,7 +78,7 @@ class _CodeWidgetState extends State<CodeWidget> {
     _shouldShowLargeIcon = PreferenceService.instance.shouldShowLargeIcons();
     _shouldShowLargeIcon = PreferenceService.instance.shouldShowLargeIcons();
     if (!_isInitialized) {
     if (!_isInitialized) {
       _currentCode.value = _getCurrentOTP();
       _currentCode.value = _getCurrentOTP();
-      if (widget.code.type == Type.totp) {
+      if (widget.code.type.isTOTPCompatible) {
         _nextCode.value = _getNextTotp();
         _nextCode.value = _getNextTotp();
       }
       }
       _isInitialized = true;
       _isInitialized = true;
@@ -213,7 +213,7 @@ class _CodeWidgetState extends State<CodeWidget> {
         crossAxisAlignment: CrossAxisAlignment.start,
         crossAxisAlignment: CrossAxisAlignment.start,
         mainAxisAlignment: MainAxisAlignment.center,
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
         children: [
-          if (widget.code.type == Type.totp)
+          if (widget.code.type.isTOTPCompatible)
             CodeTimerProgress(
             CodeTimerProgress(
               period: widget.code.period,
               period: widget.code.period,
             ),
             ),
@@ -263,7 +263,7 @@ class _CodeWidgetState extends State<CodeWidget> {
               },
               },
             ),
             ),
           ),
           ),
-          widget.code.type == Type.totp
+          widget.code.type.isTOTPCompatible
               ? GestureDetector(
               ? GestureDetector(
                   onTap: () {
                   onTap: () {
                     _copyNextToClipboard();
                     _copyNextToClipboard();
@@ -481,7 +481,7 @@ class _CodeWidgetState extends State<CodeWidget> {
 
 
   String _getNextTotp() {
   String _getNextTotp() {
     try {
     try {
-      assert(widget.code.type == Type.totp);
+      assert(widget.code.type.isTOTPCompatible);
       return getNextTotp(widget.code);
       return getNextTotp(widget.code);
     } catch (e) {
     } catch (e) {
       return context.l10n.error;
       return context.l10n.error;

+ 2 - 0
auth/lib/ui/settings/data/import/bitwarden_import.dart

@@ -92,9 +92,11 @@ Future<int?> _processBitwardenExportFile(
         var account = item['login']['username'];
         var account = item['login']['username'];
 
 
         code = Code.fromAccountAndSecret(
         code = Code.fromAccountAndSecret(
+          Type.totp,
           account,
           account,
           issuer,
           issuer,
           totp,
           totp,
+          Code.defaultDigits,
         );
         );
       }
       }
 
 

+ 2 - 2
auth/lib/utils/totp_util.dart

@@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
 import 'package:otp/otp.dart' as otp;
 import 'package:otp/otp.dart' as otp;
 
 
 String getOTP(Code code) {
 String getOTP(Code code) {
-  if(code.type == Type.hotp) {
+  if (code.type == Type.hotp) {
     return _getHOTPCode(code);
     return _getHOTPCode(code);
   }
   }
   return otp.OTP.generateTOTPCodeString(
   return otp.OTP.generateTOTPCodeString(
@@ -60,4 +60,4 @@ String safeDecode(String value) {
     debugPrint("Failed to decode $e");
     debugPrint("Failed to decode $e");
     return value;
     return value;
   }
   }
-}
+}

+ 1 - 1
auth/pubspec.yaml

@@ -1,6 +1,6 @@
 name: ente_auth
 name: ente_auth
 description: ente two-factor authenticator
 description: ente two-factor authenticator
-version: 2.0.56+256
+version: 2.0.57+257
 publish_to: none
 publish_to: none
 
 
 environment:
 environment:

+ 6 - 13
desktop/.github/workflows/desktop-release.yml

@@ -32,9 +32,7 @@ jobs:
 
 
         strategy:
         strategy:
             matrix:
             matrix:
-                os: [macos-latest]
-                # Commented for testing
-                # os: [macos-latest, ubuntu-latest, windows-latest]
+                os: [macos-latest, ubuntu-latest, windows-latest]
 
 
         steps:
         steps:
             - name: Checkout code
             - name: Checkout code
@@ -55,13 +53,6 @@ jobs:
             - name: Install dependencies
             - name: Install dependencies
               run: yarn install
               run: yarn install
 
 
-            - name: Prepare for app notarization
-              if: startsWith(matrix.os, 'macos')
-              # Import Apple API key for app notarization on macOS
-              run: |
-                  mkdir -p ~/private_keys/
-                  echo '${{ secrets.API_KEY }}' > ~/private_keys/AuthKey_${{ secrets.API_KEY_ID }}.p8
-
             - name: Install libarchive-tools for pacman build
             - name: Install libarchive-tools for pacman build
               if: startsWith(matrix.os, 'ubuntu')
               if: startsWith(matrix.os, 'ubuntu')
               # See:
               # See:
@@ -84,7 +75,9 @@ jobs:
                   mac_certs: ${{ secrets.MAC_CERTS }}
                   mac_certs: ${{ secrets.MAC_CERTS }}
                   mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }}
                   mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }}
               env:
               env:
-                  # macOS notarization API key details
-                  API_KEY_ID: ${{ secrets.API_KEY_ID }}
-                  API_KEY_ISSUER_ID: ${{ secrets.API_KEY_ISSUER_ID }}
+                  # macOS notarization credentials key details
+                  APPLE_ID: ${{ secrets.APPLE_ID }}
+                  APPLE_APP_SPECIFIC_PASSWORD:
+                      ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
+                  APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
                   USE_HARD_LINKS: false
                   USE_HARD_LINKS: false

+ 2 - 2
desktop/docs/release.md

@@ -23,10 +23,10 @@ The workflow is:
 
 
     -   Update the CHANGELOG.
     -   Update the CHANGELOG.
     -   Update the version in `package.json`
     -   Update the version in `package.json`
-    -   `git commit -m 'Release v1.x.x'`
+    -   `git commit -m "[photosd] Release v1.2.3"`
     -   Open PR, merge into main.
     -   Open PR, merge into main.
 
 
-2.  Tag this commit with a tag matching the pattern `photosd-v1.2.3`, where
+2.  Tag the merge commit with a tag matching the pattern `photosd-v1.2.3`, where
     `1.2.3` is the version in `package.json`
     `1.2.3` is the version in `package.json`
 
 
     ```sh
     ```sh

+ 1 - 0
desktop/electron-builder.yml

@@ -29,4 +29,5 @@ mac:
         arch: [universal]
         arch: [universal]
     category: public.app-category.photography
     category: public.app-category.photography
     hardenedRuntime: true
     hardenedRuntime: true
+    notarize: true
 afterSign: electron-builder-notarize
 afterSign: electron-builder-notarize

+ 2 - 1
desktop/package.json

@@ -3,6 +3,7 @@
     "version": "1.7.0-beta.0",
     "version": "1.7.0-beta.0",
     "private": true,
     "private": true,
     "description": "Desktop client for Ente Photos",
     "description": "Desktop client for Ente Photos",
+    "repository": "github:ente-io/photos-desktop",
     "author": "Ente <code@ente.io>",
     "author": "Ente <code@ente.io>",
     "main": "app/main.js",
     "main": "app/main.js",
     "scripts": {
     "scripts": {
@@ -44,7 +45,7 @@
         "@typescript-eslint/parser": "^7",
         "@typescript-eslint/parser": "^7",
         "concurrently": "^8",
         "concurrently": "^8",
         "electron": "^30",
         "electron": "^30",
-        "electron-builder": "^24",
+        "electron-builder": "25.0.0-alpha.6",
         "electron-builder-notarize": "^1.5",
         "electron-builder-notarize": "^1.5",
         "eslint": "^8",
         "eslint": "^8",
         "prettier": "^3",
         "prettier": "^3",

ファイルの差分が大きいため隠しています
+ 482 - 87
desktop/yarn.lock


+ 3 - 3
mobile/lib/generated/intl/messages_en.dart

@@ -362,8 +362,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "autoCastiOSPermission": MessageLookupByLibrary.simpleMessage(
         "autoCastiOSPermission": MessageLookupByLibrary.simpleMessage(
             "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings."),
             "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings."),
         "autoPair": MessageLookupByLibrary.simpleMessage("Auto pair"),
         "autoPair": MessageLookupByLibrary.simpleMessage("Auto pair"),
-        "autoPairGoogle": MessageLookupByLibrary.simpleMessage(
-            "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos."),
+        "autoPairDesc": MessageLookupByLibrary.simpleMessage(
+            "Auto pair works only with devices that support Chromecast."),
         "available": MessageLookupByLibrary.simpleMessage("Available"),
         "available": MessageLookupByLibrary.simpleMessage("Available"),
         "backedUpFolders":
         "backedUpFolders":
             MessageLookupByLibrary.simpleMessage("Backed up folders"),
             MessageLookupByLibrary.simpleMessage("Backed up folders"),
@@ -918,7 +918,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "manageSubscription":
         "manageSubscription":
             MessageLookupByLibrary.simpleMessage("Manage subscription"),
             MessageLookupByLibrary.simpleMessage("Manage subscription"),
         "manualPairDesc": MessageLookupByLibrary.simpleMessage(
         "manualPairDesc": MessageLookupByLibrary.simpleMessage(
-            "Pair with PIN works for any large screen device you want to play your album on."),
+            "Pair with PIN works with any screen you wish to view your album on."),
         "map": MessageLookupByLibrary.simpleMessage("Map"),
         "map": MessageLookupByLibrary.simpleMessage("Map"),
         "maps": MessageLookupByLibrary.simpleMessage("Maps"),
         "maps": MessageLookupByLibrary.simpleMessage("Maps"),
         "mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"),
         "mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"),

+ 6 - 6
mobile/lib/generated/l10n.dart

@@ -8594,20 +8594,20 @@ class S {
     );
     );
   }
   }
 
 
-  /// `Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.`
-  String get autoPairGoogle {
+  /// `Auto pair works only with devices that support Chromecast.`
+  String get autoPairDesc {
     return Intl.message(
     return Intl.message(
-      'Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.',
-      name: 'autoPairGoogle',
+      'Auto pair works only with devices that support Chromecast.',
+      name: 'autoPairDesc',
       desc: '',
       desc: '',
       args: [],
       args: [],
     );
     );
   }
   }
 
 
-  /// `Pair with PIN works for any large screen device you want to play your album on.`
+  /// `Pair with PIN works with any screen you wish to view your album on.`
   String get manualPairDesc {
   String get manualPairDesc {
     return Intl.message(
     return Intl.message(
-      'Pair with PIN works for any large screen device you want to play your album on.',
+      'Pair with PIN works with any screen you wish to view your album on.',
       name: 'manualPairDesc',
       name: 'manualPairDesc',
       desc: '',
       desc: '',
       args: [],
       args: [],

+ 2 - 2
mobile/lib/l10n/intl_en.arb

@@ -1216,8 +1216,8 @@
   "customEndpoint": "Connected to {endpoint}",
   "customEndpoint": "Connected to {endpoint}",
   "createCollaborativeLink": "Create collaborative link",
   "createCollaborativeLink": "Create collaborative link",
   "search": "Search",
   "search": "Search",
-  "autoPairGoogle": "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.",
-  "manualPairDesc": "Pair with PIN works for any large screen device you want to play your album on.",
+  "autoPairDesc": "Auto pair works only with devices that support Chromecast.",
+  "manualPairDesc": "Pair with PIN works with any screen you wish to view your album on.",
   "connectToDevice": "Connect to device",
   "connectToDevice": "Connect to device",
   "autoCastDialogBody": "You'll see available Cast devices here.",
   "autoCastDialogBody": "You'll see available Cast devices here.",
   "autoCastiOSPermission": "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.",
   "autoCastiOSPermission": "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.",

+ 1 - 1
mobile/lib/services/update_service.dart

@@ -16,7 +16,7 @@ class UpdateService {
   static final UpdateService instance = UpdateService._privateConstructor();
   static final UpdateService instance = UpdateService._privateConstructor();
   static const kUpdateAvailableShownTimeKey = "update_available_shown_time_key";
   static const kUpdateAvailableShownTimeKey = "update_available_shown_time_key";
   static const changeLogVersionKey = "update_change_log_key";
   static const changeLogVersionKey = "update_change_log_key";
-  static const currentChangeLogVersion = 18;
+  static const currentChangeLogVersion = 19;
 
 
   LatestVersionInfo? _latestVersion;
   LatestVersionInfo? _latestVersion;
   final _logger = Logger("UpdateService");
   final _logger = Logger("UpdateService");

+ 1 - 1
mobile/lib/ui/cast/choose.dart

@@ -31,7 +31,7 @@ class _CastChooseDialogState extends State<CastChooseDialog> {
         children: [
         children: [
           const SizedBox(height: 8),
           const SizedBox(height: 8),
           Text(
           Text(
-            S.of(context).autoPairGoogle,
+            S.of(context).autoPairDesc,
             style: textStyle.bodyMuted,
             style: textStyle.bodyMuted,
           ),
           ),
           const SizedBox(height: 12),
           const SizedBox(height: 12),

+ 24 - 29
mobile/lib/ui/notification/update/change_log_page.dart

@@ -1,5 +1,3 @@
-import "dart:async";
-
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import "package:photos/generated/l10n.dart";
 import "package:photos/generated/l10n.dart";
 import 'package:photos/services/update_service.dart';
 import 'package:photos/services/update_service.dart';
@@ -9,7 +7,6 @@ import 'package:photos/ui/components/divider_widget.dart';
 import 'package:photos/ui/components/models/button_type.dart';
 import 'package:photos/ui/components/models/button_type.dart';
 import 'package:photos/ui/components/title_bar_title_widget.dart';
 import 'package:photos/ui/components/title_bar_title_widget.dart';
 import 'package:photos/ui/notification/update/change_log_entry.dart';
 import 'package:photos/ui/notification/update/change_log_entry.dart';
-import "package:url_launcher/url_launcher_string.dart";
 
 
 class ChangeLogPage extends StatefulWidget {
 class ChangeLogPage extends StatefulWidget {
   const ChangeLogPage({
   const ChangeLogPage({
@@ -81,31 +78,31 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
                     const SizedBox(
                     const SizedBox(
                       height: 8,
                       height: 8,
                     ),
                     ),
-                    ButtonWidget(
-                      buttonType: ButtonType.trailingIconSecondary,
-                      buttonSize: ButtonSize.large,
-                      labelText: S.of(context).joinDiscord,
-                      icon: Icons.discord_outlined,
-                      iconColor: enteColorScheme.primary500,
-                      onTap: () async {
-                        unawaited(
-                          launchUrlString(
-                            "https://discord.com/invite/z2YVKkycX3",
-                            mode: LaunchMode.externalApplication,
-                          ),
-                        );
-                      },
-                    ),
                     // ButtonWidget(
                     // ButtonWidget(
                     //   buttonType: ButtonType.trailingIconSecondary,
                     //   buttonType: ButtonType.trailingIconSecondary,
                     //   buttonSize: ButtonSize.large,
                     //   buttonSize: ButtonSize.large,
-                    //   labelText: S.of(context).rateTheApp,
-                    //   icon: Icons.favorite_rounded,
+                    //   labelText: S.of(context).joinDiscord,
+                    //   icon: Icons.discord_outlined,
                     //   iconColor: enteColorScheme.primary500,
                     //   iconColor: enteColorScheme.primary500,
                     //   onTap: () async {
                     //   onTap: () async {
-                    //     await UpdateService.instance.launchReviewUrl();
+                    //     unawaited(
+                    //       launchUrlString(
+                    //         "https://discord.com/invite/z2YVKkycX3",
+                    //         mode: LaunchMode.externalApplication,
+                    //       ),
+                    //     );
                     //   },
                     //   },
                     // ),
                     // ),
+                    ButtonWidget(
+                      buttonType: ButtonType.trailingIconSecondary,
+                      buttonSize: ButtonSize.large,
+                      labelText: S.of(context).rateTheApp,
+                      icon: Icons.favorite_rounded,
+                      iconColor: enteColorScheme.primary500,
+                      onTap: () async {
+                        await UpdateService.instance.launchReviewUrl();
+                      },
+                    ),
                     const SizedBox(height: 8),
                     const SizedBox(height: 8),
                   ],
                   ],
                 ),
                 ),
@@ -122,18 +119,16 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
     final List<ChangeLogEntry> items = [];
     final List<ChangeLogEntry> items = [];
     items.addAll([
     items.addAll([
       ChangeLogEntry(
       ChangeLogEntry(
-        "Improved Performance for Large Galleries ✨",
-        'We\'ve made significant improvements to how quickly galleries load and'
-            ' with less stutter, especially for those with a lot of photos and videos.',
+        "Cast albums to TV ✨",
+        "View a slideshow of your albums on any big screen! Open an album and click on the Cast button to get started.",
       ),
       ),
       ChangeLogEntry(
       ChangeLogEntry(
-        "Enhanced Functionality for Video Backups",
-        'Even if video backups are disabled, you can now manually upload individual videos.',
+        "Own shared photos",
+        "You can now add shared items to your favorites to any of your personal albums. Ente will create a copy that is fully owned by you and can be organized to your liking.",
       ),
       ),
       ChangeLogEntry(
       ChangeLogEntry(
-        "Bug Fixes",
-        'Many a bugs were squashed in this release.\n'
-            '\nIf you run into any, please write to team@ente.io, or let us know on Discord! 🙏',
+        "Performance improvements",
+        "This release also brings in major changes that should improve responsiveness. If you discover room for improvement, please let us know!",
       ),
       ),
     ]);
     ]);
 
 

+ 7 - 1
mobile/lib/utils/file_uploader.dart

@@ -376,7 +376,13 @@ class FileUploader {
     if (Platform.isAndroid) {
     if (Platform.isAndroid) {
       final bool hasPermission = await Permission.accessMediaLocation.isGranted;
       final bool hasPermission = await Permission.accessMediaLocation.isGranted;
       if (!hasPermission) {
       if (!hasPermission) {
-        throw NoMediaLocationAccessError();
+        final permissionStatus = await Permission.accessMediaLocation.request();
+        if (!permissionStatus.isGranted) {
+          _logger.severe(
+            "Media location access denied with permission status: ${permissionStatus.name}",
+          );
+          throw NoMediaLocationAccessError();
+        }
       }
       }
     }
     }
   }
   }

+ 1 - 1
mobile/pubspec.yaml

@@ -12,7 +12,7 @@ description: ente photos application
 # Read more about iOS versioning at
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 
 
-version: 0.8.89+609
+version: 0.8.90+610
 publish_to: none
 publish_to: none
 
 
 environment:
 environment:

+ 1 - 2
server/ente/cast/entity.go

@@ -9,8 +9,7 @@ type CastRequest struct {
 }
 }
 
 
 type RegisterDeviceRequest struct {
 type RegisterDeviceRequest struct {
-	DeviceCode *string `json:"deviceCode"`
-	PublicKey  string  `json:"publicKey" binding:"required"`
+	PublicKey string `json:"publicKey" binding:"required"`
 }
 }
 
 
 type AuthContext struct {
 type AuthContext struct {

+ 6 - 6
server/pkg/api/cast.go

@@ -1,16 +1,16 @@
 package api
 package api
 
 
 import (
 import (
-	entity "github.com/ente-io/museum/ente/cast"
-	"github.com/ente-io/museum/pkg/controller/cast"
-	"net/http"
-	"strconv"
-
 	"github.com/ente-io/museum/ente"
 	"github.com/ente-io/museum/ente"
+	entity "github.com/ente-io/museum/ente/cast"
 	"github.com/ente-io/museum/pkg/controller"
 	"github.com/ente-io/museum/pkg/controller"
+	"github.com/ente-io/museum/pkg/controller/cast"
 	"github.com/ente-io/museum/pkg/utils/handler"
 	"github.com/ente-io/museum/pkg/utils/handler"
 	"github.com/ente-io/stacktrace"
 	"github.com/ente-io/stacktrace"
 	"github.com/gin-gonic/gin"
 	"github.com/gin-gonic/gin"
+	"net/http"
+	"strconv"
+	"strings"
 )
 )
 
 
 // CastHandler exposes request handlers for publicly accessible collections
 // CastHandler exposes request handlers for publicly accessible collections
@@ -126,7 +126,7 @@ func (h *CastHandler) GetDiff(c *gin.Context) {
 }
 }
 
 
 func getDeviceCode(c *gin.Context) string {
 func getDeviceCode(c *gin.Context) string {
-	return c.Param("deviceCode")
+	return strings.ToUpper(c.Param("deviceCode"))
 }
 }
 
 
 func (h *CastHandler) getFileForType(c *gin.Context, objectType ente.ObjectType) {
 func (h *CastHandler) getFileForType(c *gin.Context, objectType ente.ObjectType) {

+ 1 - 3
server/pkg/controller/cast/controller.go

@@ -2,7 +2,6 @@ package cast
 
 
 import (
 import (
 	"context"
 	"context"
-	"github.com/ente-io/museum/ente"
 	"github.com/ente-io/museum/ente/cast"
 	"github.com/ente-io/museum/ente/cast"
 	"github.com/ente-io/museum/pkg/controller/access"
 	"github.com/ente-io/museum/pkg/controller/access"
 	castRepo "github.com/ente-io/museum/pkg/repo/cast"
 	castRepo "github.com/ente-io/museum/pkg/repo/cast"
@@ -28,7 +27,7 @@ func NewController(castRepo *castRepo.Repository,
 }
 }
 
 
 func (c *Controller) RegisterDevice(ctx *gin.Context, request *cast.RegisterDeviceRequest) (string, error) {
 func (c *Controller) RegisterDevice(ctx *gin.Context, request *cast.RegisterDeviceRequest) (string, error) {
-	return c.CastRepo.AddCode(ctx, request.DeviceCode, request.PublicKey, network.GetClientIP(ctx))
+	return c.CastRepo.AddCode(ctx, request.PublicKey, network.GetClientIP(ctx))
 }
 }
 
 
 func (c *Controller) GetPublicKey(ctx *gin.Context, deviceCode string) (string, error) {
 func (c *Controller) GetPublicKey(ctx *gin.Context, deviceCode string) (string, error) {
@@ -42,7 +41,6 @@ func (c *Controller) GetPublicKey(ctx *gin.Context, deviceCode string) (string,
 			"ip":         ip,
 			"ip":         ip,
 			"clientIP":   network.GetClientIP(ctx),
 			"clientIP":   network.GetClientIP(ctx),
 		}).Warn("GetPublicKey: IP mismatch")
 		}).Warn("GetPublicKey: IP mismatch")
-		return "", &ente.ErrCastIPMismatch
 	}
 	}
 	return pubKey, nil
 	return pubKey, nil
 }
 }

+ 2 - 29
server/pkg/controller/storagebonus/referral.go

@@ -3,7 +3,7 @@ package storagebonus
 import (
 import (
 	"database/sql"
 	"database/sql"
 	"errors"
 	"errors"
-	"fmt"
+	"github.com/ente-io/museum/pkg/utils/random"
 
 
 	"github.com/ente-io/museum/ente"
 	"github.com/ente-io/museum/ente"
 	entity "github.com/ente-io/museum/ente/storagebonus"
 	entity "github.com/ente-io/museum/ente/storagebonus"
@@ -119,7 +119,7 @@ func (c *Controller) GetOrCreateReferralCode(ctx *gin.Context, userID int64) (*s
 		if !errors.Is(err, sql.ErrNoRows) {
 		if !errors.Is(err, sql.ErrNoRows) {
 			return nil, stacktrace.Propagate(err, "failed to get storagebonus code")
 			return nil, stacktrace.Propagate(err, "failed to get storagebonus code")
 		}
 		}
-		code, err := generateAlphaNumString(codeLength)
+		code, err := random.GenerateAlphaNumString(codeLength)
 		if err != nil {
 		if err != nil {
 			return nil, stacktrace.Propagate(err, "")
 			return nil, stacktrace.Propagate(err, "")
 		}
 		}
@@ -131,30 +131,3 @@ func (c *Controller) GetOrCreateReferralCode(ctx *gin.Context, userID int64) (*s
 	}
 	}
 	return referralCode, nil
 	return referralCode, nil
 }
 }
-
-// generateAlphaNumString returns AlphaNumeric code of given length
-// which exclude number 0 and letter O. The code always starts with an
-// alphabet
-func generateAlphaNumString(length int) (string, error) {
-	// Define the alphabet and numbers to be used in the string.
-	alphabet := "ABCDEFGHIJKLMNPQRSTUVWXYZ"
-	// Define the alphabet and numbers to be used in the string.
-	alphaNum := fmt.Sprintf("%s123456789", alphabet)
-	// Allocate a byte slice with the desired length.
-	result := make([]byte, length)
-	// Generate the first letter as an alphabet.
-	r0, err := auth.GenerateRandomInt(int64(len(alphabet)))
-	if err != nil {
-		return "", stacktrace.Propagate(err, "")
-	}
-	result[0] = alphabet[r0]
-	// Generate the remaining characters as alphanumeric.
-	for i := 1; i < length; i++ {
-		ri, err := auth.GenerateRandomInt(int64(len(alphaNum)))
-		if err != nil {
-			return "", stacktrace.Propagate(err, "")
-		}
-		result[i] = alphaNum[ri]
-	}
-	return string(result), nil
-}

+ 4 - 11
server/pkg/repo/cast/repo.go

@@ -8,23 +8,16 @@ import (
 	"github.com/ente-io/stacktrace"
 	"github.com/ente-io/stacktrace"
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	log "github.com/sirupsen/logrus"
 	log "github.com/sirupsen/logrus"
-	"strings"
 )
 )
 
 
 type Repository struct {
 type Repository struct {
 	DB *sql.DB
 	DB *sql.DB
 }
 }
 
 
-func (r *Repository) AddCode(ctx context.Context, code *string, pubKey string, ip string) (string, error) {
-	var codeValue string
-	var err error
-	if code == nil || *code == "" {
-		codeValue, err = random.GenerateSixDigitOtp()
-		if err != nil {
-			return "", stacktrace.Propagate(err, "")
-		}
-	} else {
-		codeValue = strings.TrimSpace(*code)
+func (r *Repository) AddCode(ctx context.Context, pubKey string, ip string) (string, error) {
+	codeValue, err := random.GenerateAlphaNumString(6)
+	if err != nil {
+		return "", err
 	}
 	}
 	_, err = r.DB.ExecContext(ctx, "INSERT INTO casting (code, public_key, id, ip) VALUES ($1, $2, $3, $4)", codeValue, pubKey, uuid.New(), ip)
 	_, err = r.DB.ExecContext(ctx, "INSERT INTO casting (code, public_key, id, ip) VALUES ($1, $2, $3, $4)", codeValue, pubKey, uuid.New(), ip)
 	if err != nil {
 	if err != nil {

+ 27 - 0
server/pkg/utils/random/generate.go

@@ -13,3 +13,30 @@ func GenerateSixDigitOtp() (string, error) {
 	}
 	}
 	return fmt.Sprintf("%06d", n), nil
 	return fmt.Sprintf("%06d", n), nil
 }
 }
+
+// GenerateAlphaNumString returns AlphaNumeric code of given length
+// which exclude number 0 and letter O. The code always starts with an
+// alphabet
+func GenerateAlphaNumString(length int) (string, error) {
+	// Define the alphabet and numbers to be used in the string.
+	alphabet := "ABCDEFGHIJKLMNPQRSTUVWXYZ"
+	// Define the alphabet and numbers to be used in the string.
+	alphaNum := fmt.Sprintf("%s123456789", alphabet)
+	// Allocate a byte slice with the desired length.
+	result := make([]byte, length)
+	// Generate the first letter as an alphabet.
+	r0, err := auth.GenerateRandomInt(int64(len(alphabet)))
+	if err != nil {
+		return "", stacktrace.Propagate(err, "")
+	}
+	result[0] = alphabet[r0]
+	// Generate the remaining characters as alphanumeric.
+	for i := 1; i < length; i++ {
+		ri, err := auth.GenerateRandomInt(int64(len(alphaNum)))
+		if err != nil {
+			return "", stacktrace.Propagate(err, "")
+		}
+		result[i] = alphaNum[ri]
+	}
+	return string(result), nil
+}

+ 2 - 4
web/apps/photos/src/components/Collections/CollectionOptions/AlbumCastDialog.tsx

@@ -161,9 +161,7 @@ export default function AlbumCastDialog(props: Props) {
                     {browserCanCast && (
                     {browserCanCast && (
                         <>
                         <>
                             <Typography color={"text.muted"}>
                             <Typography color={"text.muted"}>
-                                {t(
-                                    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE",
-                                )}
+                                {t("AUTO_CAST_PAIR_DESC")}
                             </Typography>
                             </Typography>
 
 
                             <EnteButton
                             <EnteButton
@@ -179,7 +177,7 @@ export default function AlbumCastDialog(props: Props) {
                         </>
                         </>
                     )}
                     )}
                     <Typography color="text.muted">
                     <Typography color="text.muted">
-                        {t("PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE")}
+                        {t("PAIR_WITH_PIN_DESC")}
                     </Typography>
                     </Typography>
 
 
                     <EnteButton
                     <EnteButton

+ 2 - 2
web/packages/next/locales/bg-BG/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 2 - 2
web/packages/next/locales/de-DE/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "Geräte koppeln",
     "PAIR_DEVICE_TO_TV": "Geräte koppeln",
     "TV_NOT_FOUND": "Fernseher nicht gefunden. Hast du die PIN korrekt eingegeben?",
     "TV_NOT_FOUND": "Fernseher nicht gefunden. Hast du die PIN korrekt eingegeben?",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "Freihand",
     "FREEHAND": "Freihand",

+ 4 - 4
web/packages/next/locales/en-US/translation.json

@@ -598,13 +598,13 @@
     "ENTER_CAST_PIN_CODE": "Enter the code you see on the TV below to pair this device.",
     "ENTER_CAST_PIN_CODE": "Enter the code you see on the TV below to pair this device.",
     "PAIR_DEVICE_TO_TV": "Pair devices",
     "PAIR_DEVICE_TO_TV": "Pair devices",
     "TV_NOT_FOUND": "TV not found. Did you enter the PIN correctly?",
     "TV_NOT_FOUND": "TV not found. Did you enter the PIN correctly?",
-    "AUTO_CAST_PAIR": "Auto Pair",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.",
+    "AUTO_CAST_PAIR": "Auto pair",
+    "AUTO_CAST_PAIR_DESC": "Auto pair works only with devices that support Chromecast.",
     "PAIR_WITH_PIN": "Pair with PIN",
     "PAIR_WITH_PIN": "Pair with PIN",
     "CHOOSE_DEVICE_FROM_BROWSER": "Choose a cast-compatible device from the browser popup.",
     "CHOOSE_DEVICE_FROM_BROWSER": "Choose a cast-compatible device from the browser popup.",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "Pair with PIN works for any large screen device you want to play your album on.",
+    "PAIR_WITH_PIN_DESC": "Pair with PIN works with any screen you wish to view your album on.",
     "VISIT_CAST_ENTE_IO": "Visit <a>{{url}}</a> on the device you want to pair.",
     "VISIT_CAST_ENTE_IO": "Visit <a>{{url}}</a> on the device you want to pair.",
-    "CAST_AUTO_PAIR_FAILED": "Chromecast Auto Pair failed. Please try again.",
+    "CAST_AUTO_PAIR_FAILED": "Chromecast auto pair failed. Please try again.",
     "FREEHAND": "Freehand",
     "FREEHAND": "Freehand",
     "APPLY_CROP": "Apply Crop",
     "APPLY_CROP": "Apply Crop",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "At least one transformation or color adjustment must be performed before saving.",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "At least one transformation or color adjustment must be performed before saving.",

+ 2 - 2
web/packages/next/locales/es-ES/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 2 - 2
web/packages/next/locales/fa-IR/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 2 - 2
web/packages/next/locales/fi-FI/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 4 - 4
web/packages/next/locales/fr-FR/translation.json

@@ -598,13 +598,13 @@
     "ENTER_CAST_PIN_CODE": "Entrez le code que vous voyez sur la TV ci-dessous pour appairer cet appareil.",
     "ENTER_CAST_PIN_CODE": "Entrez le code que vous voyez sur la TV ci-dessous pour appairer cet appareil.",
     "PAIR_DEVICE_TO_TV": "Associer les appareils",
     "PAIR_DEVICE_TO_TV": "Associer les appareils",
     "TV_NOT_FOUND": "TV introuvable. Avez-vous entré le code PIN correctement ?",
     "TV_NOT_FOUND": "TV introuvable. Avez-vous entré le code PIN correctement ?",
-    "AUTO_CAST_PAIR": "Paire automatique",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "La paire automatique nécessite la connexion aux serveurs Google et ne fonctionne qu'avec les appareils pris en charge par Chromecast. Google ne recevra pas de données sensibles, telles que vos photos.",
+    "AUTO_CAST_PAIR": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "Associer avec le code PIN",
     "PAIR_WITH_PIN": "Associer avec le code PIN",
     "CHOOSE_DEVICE_FROM_BROWSER": "Choisissez un périphérique compatible avec la caste à partir de la fenêtre pop-up du navigateur.",
     "CHOOSE_DEVICE_FROM_BROWSER": "Choisissez un périphérique compatible avec la caste à partir de la fenêtre pop-up du navigateur.",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "L'association avec le code PIN fonctionne pour tout appareil grand écran sur lequel vous voulez lire votre album.",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "Visitez <a>{{url}}</a> sur l'appareil que vous voulez associer.",
     "VISIT_CAST_ENTE_IO": "Visitez <a>{{url}}</a> sur l'appareil que vous voulez associer.",
-    "CAST_AUTO_PAIR_FAILED": "La paire automatique de Chromecast a échoué. Veuillez réessayer.",
+    "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "Main levée",
     "FREEHAND": "Main levée",
     "APPLY_CROP": "Appliquer le recadrage",
     "APPLY_CROP": "Appliquer le recadrage",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "Au moins une transformation ou un ajustement de couleur doit être effectué avant de sauvegarder.",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "Au moins une transformation ou un ajustement de couleur doit être effectué avant de sauvegarder.",

+ 2 - 2
web/packages/next/locales/it-IT/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 2 - 2
web/packages/next/locales/ko-KR/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 4 - 4
web/packages/next/locales/nl-NL/translation.json

@@ -598,13 +598,13 @@
     "ENTER_CAST_PIN_CODE": "Voer de code in die u op de TV ziet om dit apparaat te koppelen.",
     "ENTER_CAST_PIN_CODE": "Voer de code in die u op de TV ziet om dit apparaat te koppelen.",
     "PAIR_DEVICE_TO_TV": "Koppel apparaten",
     "PAIR_DEVICE_TO_TV": "Koppel apparaten",
     "TV_NOT_FOUND": "TV niet gevonden. Heeft u de pincode correct ingevoerd?",
     "TV_NOT_FOUND": "TV niet gevonden. Heeft u de pincode correct ingevoerd?",
-    "AUTO_CAST_PAIR": "Automatisch koppelen",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "Automatisch koppelen vereist verbinding met Google-servers en werkt alleen met apparaten die door Chromecast worden ondersteund. Google zal geen gevoelige gegevens ontvangen, zoals uw foto's.",
+    "AUTO_CAST_PAIR": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "Koppelen met PIN",
     "PAIR_WITH_PIN": "Koppelen met PIN",
     "CHOOSE_DEVICE_FROM_BROWSER": "Kies een compatibel apparaat uit de browser popup.",
     "CHOOSE_DEVICE_FROM_BROWSER": "Kies een compatibel apparaat uit de browser popup.",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "Koppelen met PIN werkt op elk groot schermapparaat waarop u uw album wilt afspelen.",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "Bezoek <a>{{url}}</a> op het apparaat dat je wilt koppelen.",
     "VISIT_CAST_ENTE_IO": "Bezoek <a>{{url}}</a> op het apparaat dat je wilt koppelen.",
-    "CAST_AUTO_PAIR_FAILED": "Auto koppelen van Chromecast is mislukt. Probeer het opnieuw.",
+    "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "Losse hand",
     "FREEHAND": "Losse hand",
     "APPLY_CROP": "Bijsnijden toepassen",
     "APPLY_CROP": "Bijsnijden toepassen",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "Tenminste één transformatie of kleuraanpassing moet worden uitgevoerd voordat u opslaat.",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "Tenminste één transformatie of kleuraanpassing moet worden uitgevoerd voordat u opslaat.",

+ 4 - 4
web/packages/next/locales/pt-BR/translation.json

@@ -598,13 +598,13 @@
     "ENTER_CAST_PIN_CODE": "Digite o código que você vê na TV abaixo para parear este dispositivo.",
     "ENTER_CAST_PIN_CODE": "Digite o código que você vê na TV abaixo para parear este dispositivo.",
     "PAIR_DEVICE_TO_TV": "Parear dispositivos",
     "PAIR_DEVICE_TO_TV": "Parear dispositivos",
     "TV_NOT_FOUND": "TV não encontrada. Você inseriu o PIN correto?",
     "TV_NOT_FOUND": "TV não encontrada. Você inseriu o PIN correto?",
-    "AUTO_CAST_PAIR": "Pareamento automático",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "O Auto Pair requer a conexão com servidores do Google e só funciona com dispositivos Chromecast. O Google não receberá dados confidenciais, como suas fotos.",
+    "AUTO_CAST_PAIR": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "Parear com PIN",
     "PAIR_WITH_PIN": "Parear com PIN",
     "CHOOSE_DEVICE_FROM_BROWSER": "Escolha um dispositivo compatível com casts no navegador popup.",
     "CHOOSE_DEVICE_FROM_BROWSER": "Escolha um dispositivo compatível com casts no navegador popup.",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "Parear com o PIN funciona para qualquer dispositivo de tela grande onde você deseja reproduzir seu álbum.",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "Acesse <a>{{url}}</a> no dispositivo que você deseja parear.",
     "VISIT_CAST_ENTE_IO": "Acesse <a>{{url}}</a> no dispositivo que você deseja parear.",
-    "CAST_AUTO_PAIR_FAILED": "Chromecast Auto Pair falhou. Por favor, tente novamente.",
+    "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "Mão livre",
     "FREEHAND": "Mão livre",
     "APPLY_CROP": "Aplicar Recorte",
     "APPLY_CROP": "Aplicar Recorte",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "Pelo menos uma transformação ou ajuste de cor deve ser feito antes de salvar.",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "Pelo menos uma transformação ou ajuste de cor deve ser feito antes de salvar.",

+ 2 - 2
web/packages/next/locales/pt-PT/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 4 - 4
web/packages/next/locales/ru-RU/translation.json

@@ -598,13 +598,13 @@
     "ENTER_CAST_PIN_CODE": "Введите код, который вы видите на экране телевизора ниже, чтобы выполнить сопряжение с этим устройством.",
     "ENTER_CAST_PIN_CODE": "Введите код, который вы видите на экране телевизора ниже, чтобы выполнить сопряжение с этим устройством.",
     "PAIR_DEVICE_TO_TV": "Сопряжение устройств",
     "PAIR_DEVICE_TO_TV": "Сопряжение устройств",
     "TV_NOT_FOUND": "Телевизор не найден. Вы правильно ввели PIN-код?",
     "TV_NOT_FOUND": "Телевизор не найден. Вы правильно ввели PIN-код?",
-    "AUTO_CAST_PAIR": "Автоматическое сопряжение",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "Автоматическое сопряжение требует подключения к серверам Google и работает только с устройствами, поддерживающими Chromecast. Google не будет получать конфиденциальные данные, такие как ваши фотографии.",
+    "AUTO_CAST_PAIR": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "Соединение с помощью булавки",
     "PAIR_WITH_PIN": "Соединение с помощью булавки",
     "CHOOSE_DEVICE_FROM_BROWSER": "Выберите устройство, совместимое с cast, во всплывающем окне браузера.",
     "CHOOSE_DEVICE_FROM_BROWSER": "Выберите устройство, совместимое с cast, во всплывающем окне браузера.",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "Сопряжение с помощью PIN-кода работает на любом устройстве с большим экраном, на котором вы хотите воспроизвести свой альбом.",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "Перейдите на страницу <a>{{url}}</a> на устройстве, которое вы хотите подключить.",
     "VISIT_CAST_ENTE_IO": "Перейдите на страницу <a>{{url}}</a> на устройстве, которое вы хотите подключить.",
-    "CAST_AUTO_PAIR_FAILED": "Не удалось выполнить автоматическое сопряжение Chromecast. Пожалуйста, попробуйте снова.",
+    "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "От руки",
     "FREEHAND": "От руки",
     "APPLY_CROP": "Применить обрезку",
     "APPLY_CROP": "Применить обрезку",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "Перед сохранением необходимо выполнить по крайней мере одно преобразование или корректировку цвета.",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "Перед сохранением необходимо выполнить по крайней мере одно преобразование или корректировку цвета.",

+ 2 - 2
web/packages/next/locales/sv-SE/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 2 - 2
web/packages/next/locales/th-TH/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 2 - 2
web/packages/next/locales/tr-TR/translation.json

@@ -599,10 +599,10 @@
     "PAIR_DEVICE_TO_TV": "",
     "PAIR_DEVICE_TO_TV": "",
     "TV_NOT_FOUND": "",
     "TV_NOT_FOUND": "",
     "AUTO_CAST_PAIR": "",
     "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "",
     "PAIR_WITH_PIN": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
     "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "",
     "VISIT_CAST_ENTE_IO": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "",
     "FREEHAND": "",

+ 4 - 4
web/packages/next/locales/zh-CN/translation.json

@@ -598,13 +598,13 @@
     "ENTER_CAST_PIN_CODE": "输入您在下面的电视上看到的代码来配对此设备。",
     "ENTER_CAST_PIN_CODE": "输入您在下面的电视上看到的代码来配对此设备。",
     "PAIR_DEVICE_TO_TV": "配对设备",
     "PAIR_DEVICE_TO_TV": "配对设备",
     "TV_NOT_FOUND": "未找到电视。您输入的 PIN 码正确吗?",
     "TV_NOT_FOUND": "未找到电视。您输入的 PIN 码正确吗?",
-    "AUTO_CAST_PAIR": "自动配对",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "自动配对需要连接到 Google 服务器,且仅适用于支持 Chromecast 的设备。Google 不会接收敏感数据,例如您的照片。",
+    "AUTO_CAST_PAIR": "",
+    "AUTO_CAST_PAIR_DESC": "",
     "PAIR_WITH_PIN": "用 PIN 配对",
     "PAIR_WITH_PIN": "用 PIN 配对",
     "CHOOSE_DEVICE_FROM_BROWSER": "从浏览器弹出窗口中选择兼容 Cast 的设备。",
     "CHOOSE_DEVICE_FROM_BROWSER": "从浏览器弹出窗口中选择兼容 Cast 的设备。",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "用 PIN 配对适用于任何大屏幕设备,您可以在这些设备上播放您的相册。",
+    "PAIR_WITH_PIN_DESC": "",
     "VISIT_CAST_ENTE_IO": "在您要配对的设备上访问 <a>{{url}}</a> 。",
     "VISIT_CAST_ENTE_IO": "在您要配对的设备上访问 <a>{{url}}</a> 。",
-    "CAST_AUTO_PAIR_FAILED": "Chromecast 自动配对失败。请再试一次。",
+    "CAST_AUTO_PAIR_FAILED": "",
     "FREEHAND": "手画",
     "FREEHAND": "手画",
     "APPLY_CROP": "应用裁剪",
     "APPLY_CROP": "应用裁剪",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "保存之前必须至少执行一项转换或颜色调整。",
     "PHOTO_EDIT_REQUIRED_TO_SAVE": "保存之前必须至少执行一项转换或颜色调整。",

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません