[mob][photos] Merge main

This commit is contained in:
ashilkn 2024-05-03 19:19:30 +05:30
commit b494c308b1
45 changed files with 768 additions and 276 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -32,9 +32,7 @@ jobs:
strategy:
matrix:
os: [macos-latest]
# Commented for testing
# os: [macos-latest, ubuntu-latest, windows-latest]
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Checkout code
@ -55,13 +53,6 @@ jobs:
- name: Install dependencies
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
if: startsWith(matrix.os, 'ubuntu')
# See:
@ -84,7 +75,9 @@ jobs:
mac_certs: ${{ secrets.MAC_CERTS }}
mac_certs_password: ${{ secrets.MAC_CERTS_PASSWORD }}
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

View file

@ -23,10 +23,10 @@ The workflow is:
- Update the CHANGELOG.
- 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.
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`
```sh

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -362,8 +362,8 @@ class MessageLookup extends MessageLookupByLibrary {
"autoCastiOSPermission": MessageLookupByLibrary.simpleMessage(
"Make sure Local Network permissions are turned on for the Ente Photos app, in Settings."),
"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"),
"backedUpFolders":
MessageLookupByLibrary.simpleMessage("Backed up folders"),
@ -918,7 +918,7 @@ class MessageLookup extends MessageLookupByLibrary {
"manageSubscription":
MessageLookupByLibrary.simpleMessage("Manage subscription"),
"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"),
"maps": MessageLookupByLibrary.simpleMessage("Maps"),
"mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"),

View file

@ -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(
'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: '',
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 {
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',
desc: '',
args: [],

View file

@ -1216,8 +1216,8 @@
"customEndpoint": "Connected to {endpoint}",
"createCollaborativeLink": "Create collaborative link",
"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",
"autoCastDialogBody": "You'll see available Cast devices here.",
"autoCastiOSPermission": "Make sure Local Network permissions are turned on for the Ente Photos app, in Settings.",

View file

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

View file

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

View file

@ -1,5 +1,3 @@
import "dart:async";
import 'package:flutter/material.dart';
import "package:photos/generated/l10n.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/title_bar_title_widget.dart';
import 'package:photos/ui/notification/update/change_log_entry.dart';
import "package:url_launcher/url_launcher_string.dart";
class ChangeLogPage extends StatefulWidget {
const ChangeLogPage({
@ -81,31 +78,31 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
const SizedBox(
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(
// buttonType: ButtonType.trailingIconSecondary,
// buttonSize: ButtonSize.large,
// labelText: S.of(context).rateTheApp,
// icon: Icons.favorite_rounded,
// labelText: S.of(context).joinDiscord,
// icon: Icons.discord_outlined,
// iconColor: enteColorScheme.primary500,
// 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),
],
),
@ -122,18 +119,16 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
final List<ChangeLogEntry> items = [];
items.addAll([
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(
"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(
"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!",
),
]);

View file

@ -376,7 +376,13 @@ class FileUploader {
if (Platform.isAndroid) {
final bool hasPermission = await Permission.accessMediaLocation.isGranted;
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();
}
}
}
}

View file

@ -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: 0.8.89+609
version: 0.8.90+610
publish_to: none
environment:

View file

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

View file

@ -1,16 +1,16 @@
package api
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"
entity "github.com/ente-io/museum/ente/cast"
"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/stacktrace"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"strings"
)
// 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 {
return c.Param("deviceCode")
return strings.ToUpper(c.Param("deviceCode"))
}
func (h *CastHandler) getFileForType(c *gin.Context, objectType ente.ObjectType) {

View file

@ -2,7 +2,6 @@ package cast
import (
"context"
"github.com/ente-io/museum/ente"
"github.com/ente-io/museum/ente/cast"
"github.com/ente-io/museum/pkg/controller/access"
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) {
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) {
@ -42,7 +41,6 @@ func (c *Controller) GetPublicKey(ctx *gin.Context, deviceCode string) (string,
"ip": ip,
"clientIP": network.GetClientIP(ctx),
}).Warn("GetPublicKey: IP mismatch")
return "", &ente.ErrCastIPMismatch
}
return pubKey, nil
}

View file

@ -3,7 +3,7 @@ package storagebonus
import (
"database/sql"
"errors"
"fmt"
"github.com/ente-io/museum/pkg/utils/random"
"github.com/ente-io/museum/ente"
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) {
return nil, stacktrace.Propagate(err, "failed to get storagebonus code")
}
code, err := generateAlphaNumString(codeLength)
code, err := random.GenerateAlphaNumString(codeLength)
if err != nil {
return nil, stacktrace.Propagate(err, "")
}
@ -131,30 +131,3 @@ func (c *Controller) GetOrCreateReferralCode(ctx *gin.Context, userID int64) (*s
}
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
}

View file

@ -8,23 +8,16 @@ import (
"github.com/ente-io/stacktrace"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"strings"
)
type Repository struct {
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)
if err != nil {

View file

@ -13,3 +13,30 @@ func GenerateSixDigitOtp() (string, error) {
}
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
}

View file

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

View file

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

View file

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

View file

@ -598,13 +598,13 @@
"ENTER_CAST_PIN_CODE": "Enter the code you see on the TV below to pair this device.",
"PAIR_DEVICE_TO_TV": "Pair devices",
"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",
"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.",
"CAST_AUTO_PAIR_FAILED": "Chromecast Auto Pair failed. Please try again.",
"CAST_AUTO_PAIR_FAILED": "Chromecast auto pair failed. Please try again.",
"FREEHAND": "Freehand",
"APPLY_CROP": "Apply Crop",
"PHOTO_EDIT_REQUIRED_TO_SAVE": "At least one transformation or color adjustment must be performed before saving.",

View file

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

View file

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

View file

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

View file

@ -598,13 +598,13 @@
"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",
"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",
"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.",
"CAST_AUTO_PAIR_FAILED": "La paire automatique de Chromecast a échoué. Veuillez réessayer.",
"CAST_AUTO_PAIR_FAILED": "",
"FREEHAND": "Main levée",
"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.",

View file

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

View file

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

View file

@ -598,13 +598,13 @@
"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",
"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",
"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.",
"CAST_AUTO_PAIR_FAILED": "Auto koppelen van Chromecast is mislukt. Probeer het opnieuw.",
"CAST_AUTO_PAIR_FAILED": "",
"FREEHAND": "Losse hand",
"APPLY_CROP": "Bijsnijden toepassen",
"PHOTO_EDIT_REQUIRED_TO_SAVE": "Tenminste één transformatie of kleuraanpassing moet worden uitgevoerd voordat u opslaat.",

View file

@ -598,13 +598,13 @@
"ENTER_CAST_PIN_CODE": "Digite o código que você vê na TV abaixo para parear este dispositivo.",
"PAIR_DEVICE_TO_TV": "Parear dispositivos",
"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",
"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.",
"CAST_AUTO_PAIR_FAILED": "Chromecast Auto Pair falhou. Por favor, tente novamente.",
"CAST_AUTO_PAIR_FAILED": "",
"FREEHAND": "Mão livre",
"APPLY_CROP": "Aplicar Recorte",
"PHOTO_EDIT_REQUIRED_TO_SAVE": "Pelo menos uma transformação ou ajuste de cor deve ser feito antes de salvar.",

View file

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

View file

@ -598,13 +598,13 @@
"ENTER_CAST_PIN_CODE": "Введите код, который вы видите на экране телевизора ниже, чтобы выполнить сопряжение с этим устройством.",
"PAIR_DEVICE_TO_TV": "Сопряжение устройств",
"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": "Соединение с помощью булавки",
"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> на устройстве, которое вы хотите подключить.",
"CAST_AUTO_PAIR_FAILED": "Не удалось выполнить автоматическое сопряжение Chromecast. Пожалуйста, попробуйте снова.",
"CAST_AUTO_PAIR_FAILED": "",
"FREEHAND": "От руки",
"APPLY_CROP": "Применить обрезку",
"PHOTO_EDIT_REQUIRED_TO_SAVE": "Перед сохранением необходимо выполнить по крайней мере одно преобразование или корректировку цвета.",

View file

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

View file

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

View file

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

View file

@ -598,13 +598,13 @@
"ENTER_CAST_PIN_CODE": "输入您在下面的电视上看到的代码来配对此设备。",
"PAIR_DEVICE_TO_TV": "配对设备",
"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 配对",
"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> 。",
"CAST_AUTO_PAIR_FAILED": "Chromecast 自动配对失败。请再试一次。",
"CAST_AUTO_PAIR_FAILED": "",
"FREEHAND": "手画",
"APPLY_CROP": "应用裁剪",
"PHOTO_EDIT_REQUIRED_TO_SAVE": "保存之前必须至少执行一项转换或颜色调整。",