Merge branch 'main' into fix_auto_scaling_on_loading_final_image

This commit is contained in:
ashilkn 2024-03-08 12:17:04 +05:30
commit 454f5cdead
114 changed files with 1261 additions and 872 deletions

View file

@ -47,5 +47,8 @@ jobs:
release_name: ${{ github.ref_name }}
goversion: "1.20"
project_path: "./cli"
pre_command: export CGO_ENABLED=0
build_flags: "-trimpath"
ldflags: "-s -w"
md5sum: false
sha256sum: true

View file

@ -27,6 +27,7 @@ linter:
- use_rethrow_when_possible
- directives_ordering
- always_use_package_imports
- unawaited_futures
analyzer:
errors:
@ -45,6 +46,8 @@ analyzer:
prefer_const_declarations: warning
prefer_const_constructors_in_immutables: ignore # too many warnings
unawaited_futures: warning # convert to warning after fixing existing issues
avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides
exclude:

@ -1 +1 @@
Subproject commit 367f9ea16bfae1ca451b9cc27c1366870b187ae2
Subproject commit 41456452f29d64e8deb623a3c927524bcf9f111b

View file

@ -214,7 +214,7 @@ SPEC CHECKSUMS:
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743

View file

@ -6,6 +6,7 @@ import 'dart:typed_data';
import 'package:bip39/bip39.dart' as bip39;
import 'package:ente_auth/core/constants.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/events/endpoint_updated_event.dart';
import 'package:ente_auth/events/signed_in_event.dart';
import 'package:ente_auth/events/signed_out_event.dart';
import 'package:ente_auth/models/key_attributes.dart';
@ -42,6 +43,7 @@ class Configuration {
static const userIDKey = "user_id";
static const hasMigratedSecureStorageKey = "has_migrated_secure_storage";
static const hasOptedForOfflineModeKey = "has_opted_for_offline_mode";
static const endPointKey = "endpoint";
final List<String> onlineSecureKeys = [
keyKey,
secretKeyKey,
@ -317,7 +319,12 @@ class Configuration {
}
String getHttpEndpoint() {
return endpoint;
return _preferences.getString(endPointKey) ?? endpoint;
}
Future<void> setHttpEndpoint(String endpoint) async {
await _preferences.setString(endPointKey, endpoint);
Bus.instance.fire(EndpointUpdatedEvent());
}
String? getToken() {

View file

@ -167,7 +167,7 @@ class SuperLogging {
await setupLogDir();
}
if (sentryIsEnabled) {
setupSentry();
await setupSentry();
}
Logger.root.level = Level.ALL;
@ -250,7 +250,7 @@ class SuperLogging {
// add error to sentry queue
if (sentryIsEnabled && rec.error != null) {
_sendErrorToSentry(rec.error!, null);
await _sendErrorToSentry(rec.error!, null);
}
}
@ -289,7 +289,7 @@ class SuperLogging {
SuperLogging.setUserID(await _getOrCreateAnonymousUserID());
await for (final error in sentryQueueControl.stream.asBroadcastStream()) {
try {
Sentry.captureException(
await Sentry.captureException(
error,
);
} catch (e) {

View file

@ -2,28 +2,24 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/constants.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/events/endpoint_updated_event.dart';
import 'package:fk_user_agent/fk_user_agent.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
int kConnectTimeout = 15000;
class Network {
// apiEndpoint points to the Ente server's API endpoint
static const apiEndpoint = String.fromEnvironment(
"endpoint",
defaultValue: kDefaultProductionEndpoint,
);
late Dio _dio;
late Dio _enteDio;
Future<void> init() async {
await FkUserAgent.init();
final packageInfo = await PackageInfo.fromPlatform();
final preferences = await SharedPreferences.getInstance();
final endpoint = Configuration.instance.getHttpEndpoint();
_dio = Dio(
BaseOptions(
connectTimeout: kConnectTimeout,
@ -34,10 +30,10 @@ class Network {
},
),
);
_dio.interceptors.add(RequestIdInterceptor());
_enteDio = Dio(
BaseOptions(
baseUrl: apiEndpoint,
baseUrl: endpoint,
connectTimeout: kConnectTimeout,
headers: {
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
@ -46,7 +42,13 @@ class Network {
},
),
);
_enteDio.interceptors.add(EnteRequestInterceptor(preferences, apiEndpoint));
_setupInterceptors(endpoint);
Bus.instance.on<EndpointUpdatedEvent>().listen((event) {
final endpoint = Configuration.instance.getHttpEndpoint();
_enteDio.options.baseUrl = endpoint;
_setupInterceptors(endpoint);
});
}
Network._privateConstructor();
@ -55,34 +57,41 @@ class Network {
Dio getDio() => _dio;
Dio get enteDio => _enteDio;
void _setupInterceptors(String endpoint) {
_dio.interceptors.clear();
_dio.interceptors.add(RequestIdInterceptor());
_enteDio.interceptors.clear();
_enteDio.interceptors.add(EnteRequestInterceptor(endpoint));
}
}
class RequestIdInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// ignore: prefer_const_constructors
options.headers.putIfAbsent("x-request-id", () => Uuid().v4().toString());
options.headers
.putIfAbsent("x-request-id", () => const Uuid().v4().toString());
return super.onRequest(options, handler);
}
}
class EnteRequestInterceptor extends Interceptor {
final SharedPreferences _preferences;
final String enteEndpoint;
final String endpoint;
EnteRequestInterceptor(this._preferences, this.enteEndpoint);
EnteRequestInterceptor(this.endpoint);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (kDebugMode) {
assert(
options.baseUrl == enteEndpoint,
options.baseUrl == endpoint,
"interceptor should only be used for API endpoint",
);
}
// ignore: prefer_const_constructors
options.headers.putIfAbsent("x-request-id", () => Uuid().v4().toString());
final String? tokenValue = _preferences.getString(Configuration.tokenKey);
options.headers
.putIfAbsent("x-request-id", () => const Uuid().v4().toString());
final String? tokenValue = Configuration.instance.getToken();
if (tokenValue != null) {
options.headers.putIfAbsent("X-Auth-Token", () => tokenValue);
}

View file

@ -0,0 +1,3 @@
import 'package:ente_auth/events/event.dart';
class EndpointUpdatedEvent extends Event {}

View file

@ -1,43 +1,29 @@
import 'package:dio/dio.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/errors.dart';
import 'package:ente_auth/core/network.dart';
import 'package:ente_auth/models/authenticator/auth_entity.dart';
import 'package:ente_auth/models/authenticator/auth_key.dart';
class AuthenticatorGateway {
final Dio _dio;
final Configuration _config;
late String _basedEndpoint;
late Dio _enteDio;
AuthenticatorGateway(this._dio, this._config) {
_basedEndpoint = _config.getHttpEndpoint() + "/authenticator";
AuthenticatorGateway() {
_enteDio = Network.instance.enteDio;
}
Future<void> createKey(String encKey, String header) async {
await _dio.post(
_basedEndpoint + "/key",
await _enteDio.post(
"/authenticator/key",
data: {
"encryptedKey": encKey,
"header": header,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
}
Future<AuthKey> getKey() async {
try {
final response = await _dio.get(
_basedEndpoint + "/key",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final response = await _enteDio.get("/authenticator/key");
return AuthKey.fromMap(response.data);
} on DioError catch (e) {
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
@ -51,17 +37,12 @@ class AuthenticatorGateway {
}
Future<AuthEntity> createEntity(String encryptedData, String header) async {
final response = await _dio.post(
_basedEndpoint + "/entity",
final response = await _enteDio.post(
"/authenticator/entity",
data: {
"encryptedData": encryptedData,
"header": header,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
return AuthEntity.fromMap(response.data);
}
@ -71,50 +52,35 @@ class AuthenticatorGateway {
String encryptedData,
String header,
) async {
await _dio.put(
_basedEndpoint + "/entity",
await _enteDio.put(
"/authenticator/entity",
data: {
"id": id,
"encryptedData": encryptedData,
"header": header,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
}
Future<void> deleteEntity(
String id,
) async {
await _dio.delete(
_basedEndpoint + "/entity",
await _enteDio.delete(
"/authenticator/entity",
queryParameters: {
"id": id,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
}
Future<List<AuthEntity>> getDiff(int sinceTime, {int limit = 500}) async {
try {
final response = await _dio.get(
_basedEndpoint + "/entity/diff",
final response = await _enteDio.get(
"/authenticator/entity/diff",
queryParameters: {
"sinceTime": sinceTime,
"limit": limit,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
final List<AuthEntity> authEntities = <AuthEntity>[];
final diff = response.data["diff"] as List;

View file

@ -408,5 +408,12 @@
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
"waitingForBrowserRequest": "Waiting for browser request...",
"launchPasskeyUrlAgain": "Launch passkey URL again",
"passkey": "Passkey"
"passkey": "Passkey",
"developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
"developerSettings": "Developer settings",
"serverEndpoint": "Server endpoint",
"invalidEndpoint": "Invalid endpoint",
"invalidEndpointMessage": "Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.",
"endpointUpdatedMessage": "Endpoint updated successfully",
"customEndpoint": "Connected to {endpoint}"
}

View file

@ -59,6 +59,12 @@
"recreatePassword": "Återskapa lösenord",
"useRecoveryKey": "Använd återställningsnyckel",
"incorrectPasswordTitle": "Felaktigt lösenord",
"welcomeBack": "Välkommen tillbaka!",
"changePassword": "Ändra lösenord",
"cancel": "Avbryt",
"yes": "Ja",
"no": "Nej",
"settings": "Inställningar",
"pleaseTryAgain": "Försök igen",
"existingUser": "Befintlig användare",
"delete": "Radera",
@ -68,9 +74,23 @@
"suggestFeatures": "Föreslå funktionalitet",
"faq": "FAQ",
"faq_q_1": "Hur säkert är ente Auth?",
"scan": "Skanna",
"twoFactorAuthTitle": "Tvåfaktorsautentisering",
"enterRecoveryKeyHint": "Ange din återställningsnyckel",
"noRecoveryKeyTitle": "Ingen återställningsnyckel?",
"enterEmailHint": "Ange din e-postadress",
"invalidEmailTitle": "Ogiltig e-postadress",
"invalidEmailMessage": "Ange en giltig e-postadress.",
"deleteAccount": "Radera konto",
"yesSendFeedbackAction": "Ja, skicka feedback",
"noDeleteAccountAction": "Nej, radera konto",
"createNewAccount": "Skapa nytt konto",
"weakStrength": "Svag",
"strongStrength": "Stark",
"moderateStrength": "Måttligt",
"confirmPassword": "Bekräfta lösenord",
"close": "Stäng",
"language": "Språk",
"searchHint": "Sök...",
"search": "Sök",
"sorryUnableToGenCode": "Tyvärr, det gick inte att generera en kod för {issuerName}",
@ -83,5 +103,45 @@
"copiedNextToClipboard": "Kopierade nästa kod till urklipp",
"error": "Fel",
"recoveryKeyCopiedToClipboard": "Återställningsnyckel kopierad till urklipp",
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel."
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel.",
"saveKey": "Spara nyckel",
"back": "Tillbaka",
"createAccount": "Skapa konto",
"password": "Lösenord",
"privacyPolicyTitle": "Integritetspolicy",
"termsOfServicesTitle": "Villkor",
"encryption": "Kryptering",
"changePasswordTitle": "Ändra lösenord",
"resetPasswordTitle": "Återställ lösenord",
"encryptionKeys": "Krypteringsnycklar",
"continueLabel": "Fortsätt",
"logInLabel": "Logga in",
"logout": "Logga ut",
"areYouSureYouWantToLogout": "Är du säker på att du vill logga ut?",
"yesLogout": "Ja, logga ut",
"invalidKey": "Ogiltig nyckel",
"tryAgain": "Försök igen",
"viewRecoveryKey": "Visa återställningsnyckel",
"confirmRecoveryKey": "Bekräfta återställningsnyckel",
"confirmYourRecoveryKey": "Bekräfta din återställningsnyckel",
"confirm": "Bekräfta",
"copyEmailAddress": "Kopiera e-postadress",
"exportLogs": "Exportera loggar",
"enterYourRecoveryKey": "Ange din återställningsnyckel",
"about": "Om",
"terms": "Villkor",
"warning": "Varning",
"pendingSyncs": "Varning",
"activeSessions": "Aktiva sessioner",
"enterPassword": "Ange lösenord",
"export": "Exportera",
"singIn": "Logga in",
"androidCancelButton": "Avbryt",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"iOSOkButton": "OK",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
}
}

View file

@ -144,6 +144,7 @@
"enterCodeHint": "从你的身份验证器应用中\n输入6位数字代码",
"lostDeviceTitle": "丢失了设备吗?",
"twoFactorAuthTitle": "双因素认证",
"passkeyAuthTitle": "通行密钥认证",
"recoverAccount": "恢复账户",
"enterRecoveryKeyHint": "输入您的恢复密钥",
"recover": "恢复",
@ -404,5 +405,8 @@
"signOutOtherDevices": "登出其他设备",
"doNotSignOut": "不要退登",
"hearUsWhereTitle": "您是如何知道Ente的 (可选的)",
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!"
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
"waitingForBrowserRequest": "正在等待浏览器请求...",
"launchPasskeyUrlAgain": "再次启动 通行密钥 URL",
"passkey": "通行密钥"
}

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
@ -46,7 +47,7 @@ Future<void> _runInForeground() async {
_logger.info("Starting app in foreground");
await _init(false, via: 'mainMethod');
final Locale locale = await getLocale();
UpdateService.instance.showUpdateNotification();
unawaited(UpdateService.instance.showUpdateNotification());
runApp(
AppLock(
builder: (args) => App(locale: locale),
@ -83,7 +84,7 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
Future<void> _init(bool bool, {String? via}) async {
// Start workers asynchronously. No need to wait for them to start
Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode);
Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode).ignore();
CryptoUtil.init();
await PreferenceService.instance.init();
await CodeStore.instance.init();

View file

@ -17,6 +17,8 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/models/button_result.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/ui/settings/developer_settings_page.dart';
import 'package:ente_auth/ui/settings/developer_settings_widget.dart';
import 'package:ente_auth/ui/settings/language_picker.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
@ -33,8 +35,12 @@ class OnboardingPage extends StatefulWidget {
}
class _OnboardingPageState extends State<OnboardingPage> {
static const kDeveloperModeTapCountThreshold = 7;
late StreamSubscription<TriggerLogoutEvent> _triggerLogoutEvent;
int _developerModeTapCount = 0;
@override
void initState() {
_triggerLogoutEvent =
@ -56,114 +62,143 @@ class _OnboardingPageState extends State<OnboardingPage> {
final l10n = context.l10n;
return Scaffold(
body: SafeArea(
child: Center(
child: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Column(
children: [
Column(
children: [
kDebugMode
? GestureDetector(
child: const Align(
alignment: Alignment.topRight,
child: Text("Lang"),
),
onTap: () async {
final locale = await getLocale();
routeToPage(
context,
LanguageSelectorPage(
appSupportedLocales,
(locale) async {
await setLocale(locale);
App.setLocale(context, locale);
},
locale,
child: GestureDetector(
onTap: () async {
_developerModeTapCount++;
if (_developerModeTapCount >= kDeveloperModeTapCountThreshold) {
_developerModeTapCount = 0;
final result = await showChoiceDialog(
context,
title: l10n.developerSettings,
firstButtonLabel: l10n.yes,
body: l10n.developerSettingsWarning,
isDismissible: false,
);
if (result?.action == ButtonAction.first) {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return const DeveloperSettingsPage();
},
),
);
setState(() {});
}
}
},
child: Center(
child: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Column(
children: [
Column(
children: [
kDebugMode
? GestureDetector(
child: const Align(
alignment: Alignment.topRight,
child: Text("Lang"),
),
onTap: () async {
final locale = await getLocale();
// ignore: unawaited_futures
routeToPage(
context,
LanguageSelectorPage(
appSupportedLocales,
(locale) async {
await setLocale(locale);
App.setLocale(context, locale);
},
locale,
),
).then((value) {
setState(() {});
});
},
)
: const SizedBox(),
Image.asset(
"assets/sheild-front-gradient.png",
width: 200,
height: 200,
),
const SizedBox(height: 12),
const Text(
"ente",
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
fontSize: 42,
),
),
const SizedBox(height: 4),
Text(
"Authenticator",
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 32),
Text(
l10n.onBoardingBody,
textAlign: TextAlign.center,
style:
Theme.of(context).textTheme.titleLarge!.copyWith(
color: Colors.white38,
),
).then((value) {
setState(() {});
});
},
)
: const SizedBox(),
Image.asset(
"assets/sheild-front-gradient.png",
width: 200,
height: 200,
),
const SizedBox(height: 12),
const Text(
"ente",
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
fontSize: 42,
),
],
),
const SizedBox(height: 100),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: GradientButton(
onTap: _navigateToSignUpPage,
text: l10n.newUser,
),
const SizedBox(height: 4),
Text(
"Authenticator",
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 32),
Text(
l10n.onBoardingBody,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Colors.white38,
),
const SizedBox(height: 4),
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
child: Hero(
tag: "log_in",
child: ElevatedButton(
style: Theme.of(context)
.colorScheme
.optionalActionButtonStyle,
onPressed: _navigateToSignInPage,
child: Text(
l10n.existingUser,
style: const TextStyle(
color: Colors.black, // same for both themes
),
),
],
),
const SizedBox(height: 100),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: GradientButton(
onTap: _navigateToSignUpPage,
text: l10n.newUser,
),
),
const SizedBox(height: 4),
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
child: Hero(
tag: "log_in",
child: ElevatedButton(
style: Theme.of(context)
.colorScheme
.optionalActionButtonStyle,
onPressed: _navigateToSignInPage,
child: Text(
l10n.existingUser,
style: const TextStyle(
color: Colors.black, // same for both themes
),
),
),
),
),
const SizedBox(height: 4),
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 20, bottom: 20),
child: GestureDetector(
onTap: _optForOfflineMode,
child: Center(
child: Text(
l10n.useOffline,
style: body.copyWith(
color: Theme.of(context).colorScheme.mutedTextColor,
const SizedBox(height: 4),
Container(
width: double.infinity,
padding: const EdgeInsets.only(top: 20, bottom: 20),
child: GestureDetector(
onTap: _optForOfflineMode,
child: Center(
child: Text(
l10n.useOffline,
style: body.copyWith(
color:
Theme.of(context).colorScheme.mutedTextColor,
),
),
),
),
),
),
],
const DeveloperSettingsWidget(),
],
),
),
),
),
@ -194,6 +229,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
}
if (hasOptedBefore || result?.action == ButtonAction.first) {
await Configuration.instance.optForOfflineMode();
// ignore: unawaited_futures
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {

View file

@ -5,7 +5,6 @@ import 'dart:math';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/errors.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/core/network.dart';
import 'package:ente_auth/events/codes_updated_event.dart';
import 'package:ente_auth/events/signed_in_event.dart';
import 'package:ente_auth/events/trigger_logout_event.dart';
@ -26,6 +25,7 @@ enum AccountMode {
online,
offline,
}
extension on AccountMode {
bool get isOnline => this == AccountMode.online;
bool get isOffline => this == AccountMode.offline;
@ -56,7 +56,7 @@ class AuthenticatorService {
_prefs = await SharedPreferences.getInstance();
_db = AuthenticatorDB.instance;
_offlineDb = OfflineAuthenticatorDB.instance;
_gateway = AuthenticatorGateway(Network.instance.getDio(), _config);
_gateway = AuthenticatorGateway();
if (Configuration.instance.hasConfiguredAccount()) {
unawaited(onlineSync());
}
@ -154,7 +154,7 @@ class AuthenticatorService {
} else {
debugPrint("Skipping delete since account mode is offline");
}
if(accountMode.isOnline) {
if (accountMode.isOnline) {
await _db.deleteByIDs(generatedIDs: [genID]);
} else {
await _offlineDb.deleteByIDs(generatedIDs: [genID]);
@ -163,7 +163,7 @@ class AuthenticatorService {
Future<bool> onlineSync() async {
try {
if(getAccountMode().isOffline) {
if (getAccountMode().isOffline) {
debugPrint("Skipping sync since account mode is offline");
return false;
}
@ -208,7 +208,7 @@ class AuthenticatorService {
if (deletedIDs.isNotEmpty) {
await _db.deleteByIDs(ids: deletedIDs);
}
_prefs.setInt(_lastEntitySyncTime, maxSyncTime);
await _prefs.setInt(_lastEntitySyncTime, maxSyncTime);
_logger.info("Setting synctime to " + maxSyncTime.toString());
if (result.length == fetchLimit) {
_logger.info("Diff limit reached, pulling again");
@ -253,7 +253,7 @@ class AuthenticatorService {
}
Future<Uint8List> getOrCreateAuthDataKey(AccountMode mode) async {
if(mode.isOffline) {
if (mode.isOffline) {
return _config.getOfflineSecretKey()!;
}
if (_config.getAuthSecretKey() != null) {

View file

@ -54,6 +54,7 @@ class LocalAuthenticationService {
.setEnabled(Configuration.instance.shouldShowLockScreen());
}
} else {
// ignore: unawaited_futures
showErrorDialog(
context,
errorDialogTitle,

View file

@ -27,6 +27,7 @@ class NotificationService {
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
if (implementation != null) {
// ignore: unawaited_futures
implementation.requestPermission();
}
}

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:ente_auth/core/constants.dart';
@ -70,9 +71,11 @@ class UpdateService {
if (shouldUpdate &&
hasBeen3DaysSinceLastNotification &&
_latestVersion!.shouldNotify!) {
NotificationService.instance.showNotification(
"Update available",
"Click to install our best version yet",
unawaited(
NotificationService.instance.showNotification(
"Update available",
"Click to install our best version yet",
),
);
await _prefs.setInt(kUpdateAvailableShownTimeKey, now);
} else {

View file

@ -147,18 +147,18 @@ class UserService {
final userDetails = UserDetails.fromMap(response.data);
if (shouldCache) {
if (userDetails.profileData != null) {
_preferences.setBool(
await _preferences.setBool(
kIsEmailMFAEnabled,
userDetails.profileData!.isEmailMFAEnabled,
);
_preferences.setBool(
await _preferences.setBool(
kCanDisableEmailMFA,
userDetails.profileData!.canDisableEmailMFA,
);
}
// handle email change from different client
if (userDetails.email != _config.getEmail()) {
setEmail(userDetails.email);
await setEmail(userDetails.email);
}
}
return userDetails;
@ -282,6 +282,7 @@ class UserService {
throw Exception("unexpected response during passkey verification");
}
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -331,6 +332,7 @@ class UserService {
);
}
}
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -354,6 +356,7 @@ class UserService {
);
Navigator.of(context).pop();
} else {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.incorrectCode,
@ -363,6 +366,7 @@ class UserService {
} catch (e) {
await dialog.hide();
_logger.severe(e);
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -399,6 +403,7 @@ class UserService {
Bus.instance.fire(UserDetailsChangedEvent());
return;
}
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -407,12 +412,14 @@ class UserService {
} on DioError catch (e) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 403) {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
context.l10n.thisEmailIsAlreadyInUse,
);
} else {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.incorrectCode,
@ -422,6 +429,7 @@ class UserService {
} catch (e) {
await dialog.hide();
_logger.severe(e);
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -632,6 +640,7 @@ class UserService {
}
}
await dialog.hide();
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -709,6 +718,7 @@ class UserService {
if (response.statusCode == 200) {
showShortToast(context, context.l10n.authenticationSuccessful);
await _saveConfiguration(response);
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -723,6 +733,7 @@ class UserService {
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
showToast(context, "Session expired");
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -732,6 +743,7 @@ class UserService {
(route) => route.isFirst,
);
} else {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.incorrectCode,
@ -741,6 +753,7 @@ class UserService {
} catch (e) {
await dialog.hide();
_logger.severe(e);
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -760,6 +773,7 @@ class UserService {
},
);
if (response.statusCode == 200) {
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -777,6 +791,7 @@ class UserService {
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
showToast(context, context.l10n.sessionExpired);
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -786,6 +801,7 @@ class UserService {
(route) => route.isFirst,
);
} else {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -794,6 +810,7 @@ class UserService {
}
} catch (e) {
_logger.severe(e);
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -853,6 +870,7 @@ class UserService {
context.l10n.twofactorAuthenticationSuccessfullyReset,
);
await _saveConfiguration(response);
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -866,6 +884,7 @@ class UserService {
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
showToast(context, "Session expired");
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -875,6 +894,7 @@ class UserService {
(route) => route.isFirst,
);
} else {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -883,6 +903,7 @@ class UserService {
}
} catch (e) {
_logger.severe(e);
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -925,7 +946,7 @@ class UserService {
"isEnabled": isEnabled,
},
);
_preferences.setBool(kIsEmailMFAEnabled, isEnabled);
await _preferences.setBool(kIsEmailMFAEnabled, isEnabled);
} catch (e) {
_logger.severe("Failed to update email mfa", e);
rethrow;

View file

@ -7,10 +7,6 @@ class UserStore {
late SharedPreferences _preferences;
static final UserStore instance = UserStore._privateConstructor();
static const endpoint = String.fromEnvironment(
"endpoint",
defaultValue: "https://api.ente.io",
);
Future<void> init() async {
_preferences = await SharedPreferences.getInstance();

View file

@ -236,7 +236,7 @@ class DeleteAccountPage extends StatelessWidget {
),
],
);
// ignore: unawaited_futures
showDialog(
context: context,
builder: (BuildContext context) {

View file

@ -61,7 +61,7 @@ class _LoginPageState extends State<LoginPage> {
isFormValid: _emailIsValid,
buttonText: context.l10n.logInLabel,
onPressedFunction: () async {
UserService.instance.setEmail(_email!);
await UserService.instance.setEmail(_email!);
Configuration.instance.resetVolatilePassword();
SrpAttributes? attr;
bool isEmailVerificationEnabled = true;
@ -74,6 +74,7 @@ class _LoginPageState extends State<LoginPage> {
}
}
if (attr != null && !isEmailVerificationEnabled) {
// ignore: unawaited_futures
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {

View file

@ -23,6 +23,7 @@ Future<void> autoLogoutAlert(BuildContext context) async {
int pendingSyncCount =
await AuthenticatorDB.instance.getNeedSyncCount();
if (pendingSyncCount > 0) {
// ignore: unawaited_futures
showChoiceActionSheet(
context,
title: l10n.pendingSyncs,

View file

@ -399,6 +399,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
} catch (e, s) {
_logger.severe(e, s);
await dialog.hide();
// ignore: unawaited_futures
showGenericErrorDialog(context: context);
}
}
@ -446,6 +447,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
await UserService.instance.setAttributes(result);
await dialog.hide();
Configuration.instance.resetVolatilePassword();
// ignore: unawaited_futures
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
@ -457,10 +459,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
} catch (e, s) {
_logger.severe(e, s);
await dialog.hide();
// ignore: unawaited_futures
showGenericErrorDialog(context: context);
}
}
// ignore: unawaited_futures
routeToPage(
context,
RecoveryKeyPage(
@ -476,12 +479,14 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
_logger.severe(e);
await dialog.hide();
if (e is UnsupportedError) {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.insecureDevice,
context.l10n.sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease,
);
} else {
// ignore: unawaited_futures
showGenericErrorDialog(context: context);
}
}

View file

@ -116,6 +116,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
firstButtonLabel: context.l10n.useRecoveryKey,
);
if (dialogChoice!.action == ButtonAction.first) {
// ignore: unawaited_futures
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {

View file

@ -54,6 +54,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
await Configuration.instance.recover(_recoveryKey.text.trim());
await dialog.hide();
showToast(context, "Recovery successful!");
// ignore: unawaited_futures
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) {
@ -72,6 +73,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
if (e is AssertionError) {
errMessage = '$errMessage : ${e.message}';
}
// ignore: unawaited_futures
showErrorDialog(context, "Incorrect recovery key", errMessage);
}
},

View file

@ -78,7 +78,7 @@ class _RequestPasswordVerificationPageState
onPressedFunction: () async {
FocusScope.of(context).unfocus();
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
dialog.show();
await dialog.show();
try {
final attributes = Configuration.instance.getKeyAttributes()!;
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
@ -92,17 +92,18 @@ class _RequestPasswordVerificationPageState
keyEncryptionKey,
Sodium.base642bin(attributes.keyDecryptionNonce),
);
dialog.show();
await dialog.show();
// pop
await widget.onPasswordVerified(keyEncryptionKey);
dialog.hide();
await dialog.hide();
Navigator.of(context).pop(true);
} catch (e, s) {
_logger.severe("Error while verifying password", e, s);
dialog.hide();
await dialog.hide();
if (widget.onPasswordError != null) {
widget.onPasswordError!();
} else {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.incorrectPasswordTitle,

View file

@ -121,6 +121,7 @@ class _SessionsPageState extends State<SessionsPage> {
} catch (e) {
await dialog.hide();
_logger.severe('failed to terminate');
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.oops,
@ -184,7 +185,7 @@ class _SessionsPageState extends State<SessionsPage> {
if (isLoggingOutFromThisDevice) {
await UserService.instance.logout(context);
} else {
_terminateSession(session);
await _terminateSession(session);
}
},
),

View file

@ -92,6 +92,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
String recoveryKey;
try {
recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
// ignore: unawaited_futures
routeToPage(
context,
RecoveryKeyPage(
@ -104,6 +105,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
),
);
} catch (e) {
// ignore: unawaited_futures
showGenericErrorDialog(context: context);
return;
}

View file

@ -356,6 +356,7 @@ class _CodeWidgetState extends State<CodeWidget> {
await FlutterClipboard.copy(content);
showToast(context, confirmationMessage);
if (Platform.isAndroid && shouldMinimizeOnCopy) {
// ignore: unawaited_futures
MoveToBackground.moveTaskToBack();
}
}
@ -385,7 +386,7 @@ class _CodeWidgetState extends State<CodeWidget> {
),
);
if (code != null) {
CodeStore.instance.addCode(code);
await CodeStore.instance.addCode(code);
}
}

View file

@ -146,6 +146,7 @@ class ProgressDialog {
try {
if (!_isShowing) {
_dialog = _Body();
// ignore: unawaited_futures
showDialog<dynamic>(
context: _context!,
barrierDismissible: _barrierDismissible,

View file

@ -120,7 +120,7 @@ class _HomePageState extends State<HomePage> {
),
);
if (code != null) {
CodeStore.instance.addCode(code);
await CodeStore.instance.addCode(code);
// Focus the new code by searching
if (_codes.length > 2) {
_focusNewCode(code);
@ -137,7 +137,7 @@ class _HomePageState extends State<HomePage> {
),
);
if (code != null) {
CodeStore.instance.addCode(code);
await CodeStore.instance.addCode(code);
}
}
@ -151,6 +151,7 @@ class _HomePageState extends State<HomePage> {
return false;
}
if (Platform.isAndroid) {
// ignore: unawaited_futures
MoveToBackground.moveTaskToBack();
return false;
} else {

View file

@ -4,6 +4,7 @@ import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:uni_links/uni_links.dart';
@ -49,14 +50,27 @@ class _PasskeyPageState extends State<PasskeyPage> {
if (!context.mounted ||
Configuration.instance.hasConfiguredAccount() ||
link == null) {
_logger.warning(
'ignored deeplink: contextMounted ${context.mounted} hasConfiguredAccount ${Configuration.instance.hasConfiguredAccount()}',
);
return;
}
if (mounted && link.toLowerCase().startsWith("enteauth://passkey")) {
final uri = Uri.parse(link).queryParameters['response'];
// response to json
final res = utf8.decode(base64.decode(uri!));
final json = jsonDecode(res) as Map<String, dynamic>;
await UserService.instance.onPassKeyVerified(context, json);
try {
if (mounted && link.toLowerCase().startsWith("enteauth://passkey")) {
final String? uri = Uri.parse(link).queryParameters['response'];
String base64String = uri!.toString();
while (base64String.length % 4 != 0) {
base64String += '=';
}
final res = utf8.decode(base64.decode(base64String));
final json = jsonDecode(res) as Map<String, dynamic>;
await UserService.instance.onPassKeyVerified(context, json);
} else {
_logger.info('ignored deeplink: $link mounted $mounted');
}
} catch (e, s) {
_logger.severe('passKey: failed to handle deeplink', e, s);
showGenericErrorDialog(context: context).ignore();
}
}

View file

@ -36,6 +36,7 @@ class AboutSectionWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
launchUrl(Uri.parse("https://github.com/ente-io/ente"));
},
),
@ -68,6 +69,7 @@ class AboutSectionWidget extends StatelessWidget {
await UpdateService.instance.shouldUpdate();
await dialog.hide();
if (shouldUpdate) {
// ignore: unawaited_futures
showDialog(
context: context,
builder: (BuildContext context) {
@ -115,6 +117,7 @@ class AboutMenuItemWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {

View file

@ -48,6 +48,7 @@ class AccountSectionWidget extends StatelessWidget {
l10n.authToChangeYourEmail,
);
if (hasAuthenticated) {
// ignore: unawaited_futures
showDialog(
context: context,
builder: (BuildContext context) {
@ -74,6 +75,7 @@ class AccountSectionWidget extends StatelessWidget {
l10n.authToChangeYourPassword,
);
if (hasAuthenticated) {
// ignore: unawaited_futures
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
@ -106,9 +108,11 @@ class AccountSectionWidget extends StatelessWidget {
recoveryKey =
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
} catch (e) {
// ignore: unawaited_futures
showGenericErrorDialog(context: context);
return;
}
// ignore: unawaited_futures
routeToPage(
context,
RecoveryKeyPage(
@ -142,6 +146,7 @@ class AccountSectionWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
routeToPage(context, const DeleteAccountPage());
},
),

View file

@ -174,6 +174,7 @@ class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
);
}
Navigator.of(context, rootNavigator: true).pop('dialog');
// ignore: unawaited_futures
OpenFilex.open(_saveUrl);
} catch (e) {
Logger("ApkDownloader").severe(e);
@ -214,7 +215,7 @@ class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
),
],
);
// ignore: unawaited_futures
showDialog(
context: context,
builder: (BuildContext context) {

View file

@ -46,6 +46,7 @@ class DangerSectionWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
routeToPage(context, const DeleteAccountPage());
},
),

View file

@ -35,6 +35,7 @@ class DataSectionWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
routeToPage(context, ImportCodePage());
},
),

View file

@ -98,7 +98,7 @@ Future<void> _requestForEncryptionPassword(
),
);
// get json value of data
_exportCodes(context, jsonEncode(data.toJson()));
await _exportCodes(context, jsonEncode(data.toJson()));
} catch (e, s) {
Logger("ExportWidget").severe(e, s);
showToast(context, "Error while exporting codes.");

View file

@ -56,6 +56,7 @@ Future<void> showGoogleAuthInstruction(BuildContext context) async {
await CodeStore.instance.addCode(code, shouldSync: false);
}
unawaited(AuthenticatorService.instance.onlineSync());
// ignore: unawaited_futures
importSuccessDialog(context, codes.length);
}
}

View file

@ -19,29 +19,29 @@ class ImportService {
Future<void> initiateImport(BuildContext context, ImportType type) async {
switch (type) {
case ImportType.plainText:
showImportInstructionDialog(context);
await showImportInstructionDialog(context);
break;
case ImportType.encrypted:
showEncryptedImportInstruction(context);
await showEncryptedImportInstruction(context);
break;
case ImportType.ravio:
showRaivoImportInstruction(context);
await showRaivoImportInstruction(context);
break;
case ImportType.googleAuthenticator:
showGoogleAuthInstruction(context);
await showGoogleAuthInstruction(context);
// showToast(context, 'coming soon');
break;
case ImportType.aegis:
showAegisImportInstruction(context);
await showAegisImportInstruction(context);
break;
case ImportType.twoFas:
show2FasImportInstruction(context);
await show2FasImportInstruction(context);
break;
case ImportType.bitwarden:
showBitwardenImportInstruction(context);
await showBitwardenImportInstruction(context);
break;
case ImportType.lastpass:
showLastpassImportInstruction(context);
await showLastpassImportInstruction(context);
break;
}
}

View file

@ -105,7 +105,7 @@ class ImportCodePage extends StatelessWidget {
index != importOptions.length - 1,
isTopBorderRadiusRemoved: index != 0,
onTap: () async {
ImportService().initiateImport(context, type);
await ImportService().initiateImport(context, type);
// routeToPage(context, ImportCodePage());
// _showImportInstructionDialog(context);
},

View file

@ -0,0 +1,90 @@
import 'package:dio/dio.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class DeveloperSettingsPage extends StatefulWidget {
const DeveloperSettingsPage({super.key});
@override
_DeveloperSettingsPageState createState() => _DeveloperSettingsPageState();
}
class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
final _logger = Logger('DeveloperSettingsPage');
final _urlController = TextEditingController();
@override
void dispose() {
_urlController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_logger.info(
"Current endpoint is: " + Configuration.instance.getHttpEndpoint(),
);
return Scaffold(
appBar: AppBar(
title: Text(context.l10n.developerSettings),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _urlController,
decoration: InputDecoration(
labelText: context.l10n.serverEndpoint,
hintText: Configuration.instance.getHttpEndpoint(),
),
autofocus: true,
),
const SizedBox(height: 40),
GradientButton(
onTap: () async {
String url = _urlController.text;
_logger.info("Entered endpoint: " + url);
try {
final uri = Uri.parse(url);
if ((uri.scheme == "http" || uri.scheme == "https")) {
await _ping(url);
await Configuration.instance.setHttpEndpoint(url);
showToast(context, context.l10n.endpointUpdatedMessage);
Navigator.of(context).pop();
} else {
throw const FormatException();
}
} catch (e) {
// ignore: unawaited_futures
showErrorDialog(
context,
context.l10n.invalidEndpoint,
context.l10n.invalidEndpointMessage,
);
}
},
text: context.l10n.saveAction,
),
],
),
),
);
}
Future<void> _ping(String endpoint) async {
try {
final response = await Dio().get(endpoint + '/ping');
if (response.data['message'] != 'pong') {
throw Exception('Invalid response');
}
} catch (e) {
throw Exception('Error occurred: $e');
}
}
}

View file

@ -0,0 +1,27 @@
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/constants.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:flutter/material.dart';
class DeveloperSettingsWidget extends StatelessWidget {
const DeveloperSettingsWidget({super.key});
@override
Widget build(BuildContext context) {
final endpoint = Configuration.instance.getHttpEndpoint();
if (endpoint != kDefaultProductionEndpoint) {
final endpointURI = Uri.parse(endpoint);
return Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Text(
context.l10n.customEndpoint(
endpointURI.host + ":" + endpointURI.port.toString(),
),
style: Theme.of(context).textTheme.bodySmall,
),
);
} else {
return const SizedBox.shrink();
}
}
}

View file

@ -48,6 +48,7 @@ class _AdvancedSectionWidgetState extends State<AdvancedSectionWidget> {
trailingIconIsMuted: true,
onTap: () async {
final locale = await getLocale();
// ignore: unawaited_futures
routeToPage(
context,
LanguageSelectorPage(

View file

@ -116,6 +116,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
context.l10n.authToViewYourActiveSessions,
);
if (hasAuthenticated) {
// ignore: unawaited_futures
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {

View file

@ -81,6 +81,7 @@ class SocialsMenuItemWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
launchUrlString(
url,
mode: launchInExternalApp

View file

@ -42,6 +42,7 @@ class _SupportSectionWidgetState extends State<SupportSectionWidget> {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
showModalBottomSheet<void>(
backgroundColor: Theme.of(context).colorScheme.background,
barrierColor: Colors.black87,
@ -61,6 +62,7 @@ class _SupportSectionWidgetState extends State<SupportSectionWidget> {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
launchUrlString(
githubDiscussionsUrl,
mode: LaunchMode.externalApplication,

View file

@ -16,6 +16,7 @@ import 'package:ente_auth/ui/settings/account_section_widget.dart';
import 'package:ente_auth/ui/settings/app_version_widget.dart';
import 'package:ente_auth/ui/settings/data/data_section_widget.dart';
import 'package:ente_auth/ui/settings/data/export_widget.dart';
import 'package:ente_auth/ui/settings/developer_settings_widget.dart';
import 'package:ente_auth/ui/settings/general_section_widget.dart';
import 'package:ente_auth/ui/settings/security_section_widget.dart';
import 'package:ente_auth/ui/settings/social_section_widget.dart';
@ -149,6 +150,7 @@ class SettingsPage extends StatelessWidget {
sectionSpacing,
const AboutSectionWidget(),
const AppVersionWidget(),
const DeveloperSettingsWidget(),
const SupportDevWidget(),
const Padding(
padding: EdgeInsets.only(bottom: 60),

View file

@ -56,7 +56,7 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
text: context.l10n.unlock,
iconData: Icons.lock_open_outlined,
onTap: () async {
_showLockScreen(source: "tapUnlock");
await _showLockScreen(source: "tapUnlock");
},
),
),

View file

@ -119,7 +119,7 @@ class _TwoFactorAuthenticationPageState
child: OutlinedButton(
onPressed: _code.length == 6
? () async {
_verifyTwoFactorCode(_code);
await _verifyTwoFactorCode(_code);
}
: null,
child: Text(l10n.verify),

View file

@ -62,6 +62,7 @@ Future<void> sendLogs(
],
),
onPressed: () async {
// ignore: unawaited_futures
showDialog(
context: context,
builder: (BuildContext context) {
@ -118,6 +119,7 @@ Future<void> sendLogs(
),
),
);
// ignore: unawaited_futures
showDialog(
context: context,
builder: (_) {
@ -159,7 +161,7 @@ Future<String> getZippedLogsFile(BuildContext context) async {
tempPath + "/logs-${Configuration.instance.getUserID() ?? 0}.zip";
final encoder = ZipFileEncoder();
encoder.create(zipFilePath);
encoder.addDirectory(logsDirectory);
await encoder.addDirectory(logsDirectory);
encoder.close();
await dialog.hide();
return zipFilePath;

View file

@ -2,14 +2,14 @@ import 'package:ente_auth/ente_theme_data.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
Future showToast(
void showToast(
BuildContext context,
String message, {
toastLength = Toast.LENGTH_LONG,
iOSDismissOnTap = true,
}) async {
await Fluttertoast.cancel();
return Fluttertoast.showToast(
await Fluttertoast.showToast(
msg: message,
toastLength: toastLength,
gravity: ToastGravity.BOTTOM,
@ -20,6 +20,6 @@ Future showToast(
);
}
Future<void> showShortToast(context, String message) {
return showToast(context, message, toastLength: Toast.LENGTH_SHORT);
void showShortToast(context, String message) {
showToast(context, message, toastLength: Toast.LENGTH_SHORT);
}

View file

@ -203,7 +203,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -262,10 +262,10 @@ packages:
dependency: transitive
description:
name: coverage
sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76"
sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb"
url: "https://pub.dev"
source: hosted
version: "1.7.2"
version: "1.6.4"
cross_file:
dependency: transitive
description:
@ -751,30 +751,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.6.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints:
dependency: "direct dev"
description:
@ -835,26 +811,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.5.0"
meta:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.10.0"
mime:
dependency: transitive
description:
@ -955,10 +931,10 @@ packages:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
version: "1.8.3"
path_drawing:
dependency: transitive
description:
@ -1584,10 +1560,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583
url: "https://pub.dev"
source: hosted
version: "13.0.0"
version: "11.10.0"
watcher:
dependency: transitive
description:
@ -1596,6 +1572,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.2"
web:
dependency: transitive
description:
name: web
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.3.0"
web_socket_channel:
dependency: transitive
description:
@ -1653,5 +1637,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.2.0-0 <4.0.0"
dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=3.10.0"

View file

@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 2.0.34+234
version: 2.0.35+235
publish_to: none
environment:

View file

@ -1,44 +1,65 @@
# Command Line Utility for exporting data from [Ente](https://ente.io)
# Ente CLI
The Ente CLI is a Command Line Utility for exporting data from
[Ente](https://ente.io). It also does a few more things, for example, you can
use it to decrypting the export from Ente Auth.
## Install
You can either download the binary from the [GitHub releases
page](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0&expanded=true) or
build it yourself.
The easiest way is to download a pre-built binary from the [GitHub
releases](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0&expanded=true).
### Build from source
You can also build these binaries yourself
```shell
./release.sh
```
Or you can build from source
```shell
go build -o "bin/ente" main.go
```
### Getting Started
The generated binaries are standalone, static binaries with no dependencies. You
can run them directly, or put them somewhere in your PATH.
There is also an option to use [Docker](#docker).
## Usage
Run the help command to see all available commands.
```shell
ente --help
```
#### Accounts
### Accounts
If you wish, you can add multiple accounts (your own and that of your family members) and export all data using this tool.
##### Add an account
#### Add an account
```shell
ente account add
```
##### List accounts
#### List accounts
```shell
ente account list
```
##### Change export directory
#### Change export directory
```shell
ente account update --email email@domain.com --dir ~/photos
```
### Export
##### Start export
#### Start export
```shell
ente export
```
@ -51,16 +72,22 @@ If you fancy Docker, you can also run the CLI within a container.
### Configure
Modify the `docker-compose.yml` and add volume.
``cli-data`` volume is mandatory, you can add more volumes for your export directory.
Modify the `docker-compose.yml` and add volume. ``cli-data`` volume is
mandatory, you can add more volumes for your export directory.
Build the docker image
```shell
docker build -t ente:latest .
```
Note that [BuildKit](https://docs.docker.com/go/buildkit/) is needed to build
this image. If you face this issue, a quick fix is to add `DOCKER_BUILDKIT=1` in
front of the build command.
Start the container in detached mode
```bash
```shell
docker-compose up -d
```
@ -69,20 +96,8 @@ docker-compose up -d
docker-compose exec ente /bin/sh
```
#### Directly executing commands
```shell
docker run -it --rm ente:latest ls
```
---
## Releases
Run the release script to build the binary and run it.
```shell
./release.sh
```

21
cli/docs/release.md Normal file
View file

@ -0,0 +1,21 @@
# Releasing
Tag main, and push the tag.
```sh
git tag cli-v1.2.3
git push origin cli-v1.2.3
```
This'll trigger a [GitHub workflow](../../.github/workflows/cli-release.yml)
that creates a new draft GitHub release and attaches all the build artifacts to
it (zipped up binaries for various OS and architecture combinations).
## Local release builds
Run the release script to build the binaries for the various OS and architecture
cominations
```shell
./release.sh
```

View file

@ -9,6 +9,7 @@ OS_TARGETS=("windows" "linux" "darwin")
# Corresponding architectures for each OS
ARCH_TARGETS=("386 amd64" "386 amd64 arm arm64" "amd64 arm64")
export CGO_ENABLED=0
# Loop through each OS target
for index in "${!OS_TARGETS[@]}"
do
@ -28,16 +29,12 @@ do
fi
# Build the binary and place it in the "bin" directory
go build -o "bin/$BINARY_NAME" main.go
go build -ldflags="-s -w" -trimpath -o "bin/$BINARY_NAME" main.go
# Print a message indicating the build is complete for the current OS and architecture
echo "Built for $OS ($ARCH) as bin/$BINARY_NAME"
done
done
# Clean up any environment variables
unset GOOS
unset GOARCH
# Print a message indicating the build process is complete
echo "Build process completed for all platforms and architectures. Binaries are in the 'bin' directory."

View file

@ -18,10 +18,6 @@ export default defineConfig({
pattern:
"https://github.com/ente-io/ente/edit/main/docs/docs/:path",
},
// nav: [
// { text: "Photos", link: "/photos/index" },
// { text: "Authenticator", link: "/authenticator/index" },
// ],
search: {
provider: "local",
options: {

View file

@ -1,27 +1,94 @@
// When adding new pages, they need to manually inserted into their appropriate
// place here if you wish them to also appear in the sidebar.
// When adding new pages, they need to be manually inserted into their
// appropriate place here.
export const sidebar = [];
export const sidebar = [
{
text: "Photos",
items: [
{ text: "Introduction", link: "/photos/" },
{
text: "Features",
collapsed: true,
items: [
{ text: "Albums", link: "/photos/features/albums" },
{ text: "Archiving", link: "/photos/features/archive" },
{ text: "Cast", link: "/photos/features/cast/" },
{
text: "Collecting photos",
link: "/photos/features/collect",
},
{
text: "Family plans",
link: "/photos/features/family-plans",
},
{ text: "Hidden photos", link: "/photos/features/hide" },
{
text: "Location tags",
link: "/photos/features/location-tags",
},
{ text: "Map", link: "/photos/features/map" },
{
text: "Public link",
link: "/photos/features/public-link",
},
{ text: "Quick link", link: "/photos/features/quick-link" },
{ text: "Referrals", link: "/photos/features/referrals" },
{ text: "Trash", link: "/photos/features/trash" },
{
text: "Uncategorized",
link: "/photos/features/uncategorized",
},
{
text: "Watch folder",
link: "/photos/features/watch-folder",
},
],
},
{ text: "FAQ", link: "/photos/faq/" },
{
text: "Troubleshooting",
collapsed: true,
items: [
{
text: "Files not uploading",
link: "/photos/troubleshooting/files-not-uploading",
},
{
text: "Sharing debug logs",
link: "/photos/troubleshooting/sharing-logs",
},
],
},
],
},
{
text: "Auth",
items: [
{ text: "Introduction", link: "/auth/" },
{
text: "Migration guides",
collapsed: true,
items: [
{ text: "Introduction", link: "/auth/migration-guides/" },
],
},
],
},
{
text: "About",
link: "/about/",
},
{
text: "Contribute",
link: "/about/contribute",
},
];
function sidebarOld() {
return [
{
text: "Welcome",
items: [
{
text: "About",
collapsed: true,
link: "/about/company",
items: [
{ text: "Company", link: "/about/company" },
{ text: "Products", link: "/about/products" },
{ text: "Plans", link: "/about/plans" },
{ text: "Support", link: "/about/support" },
{ text: "Community", link: "/about/community" },
{ text: "Open source", link: "/about/open-source" },
{ text: "Contribute", link: "/about/contribute" },
],
},
{
text: "Features",
collapsed: true,

View file

@ -0,0 +1,15 @@
---
title: Contribute
description: Details about how to contribute to Ente's docs
---
# Contributing
To contribute to these docs, you can use the "Edit this page" button at the
bottom of each page. This will allow you to directly edit the markdown file that
is used to generate this documentation and open a quick pull request directly
from GitHub's UI.
If you're more comfortable in contributing with your text editor, see the
`docs/` folder of our GitHub repository,
[github.com/ente-io/ente](https://github.com/ente-io/ente).

View file

@ -5,19 +5,26 @@ description: >
that we make.
---
Ente is a platform for privately, reliably, and securely storing your data on
the cloud. On top of this platform, Ente offers two products currently:
# About
* Ente Photos - An alternative to Google Photos and Apple Photos
Ente is a end-to-end encrypted platform for privately, reliably, and securely
storing your data on the cloud. On top of this platform, Ente offers two
products:
* Ente Auth - A (free!) app for storing your 2FA codes.
* **Ente Photos** - An alternative to Google Photos and Apple Photos
and more products are in the pipeline.
* **Ente Auth** - A free 2FA alternative to Authy
Both these apps are available for all desktop (Linux, Mac, Windows) and mobile
(Android, iOS and F-Droid) platforms. They also work directly in your web
browser without you needing to install anything.
More products are in the pipeline.
## History
Ente was the founded by Vishnu Mohandas, who is also the Ente's CEO, in response
to privacy concerns with major tech companies. The underlying motivation was the
Ente was the founded by Vishnu Mohandas (he's also Ente's CEO) in response to
privacy concerns with major tech companies. The underlying motivation was the
understanding that big tech had no incentive to fix their act, but with
end-to-end encrypted cross platform apps, there was a way for people to take
back control over their own data without sacrificing on features.
@ -29,9 +36,9 @@ has the literal meaning "my photos".
This was a good name, but still Vishnu looked around for better ones. But one
day, he discovered that "ente" means "duck" in German. This unexpected
connection sealed the deal! We should ask him why he likes ducks so much, but
connection sealed the deal. We should ask him why he likes ducks so much, but
apparently he does, so this dual meaning ("mine" / "duck") led him to finalize
the name, and also led to the adoption of the duck as Ente's mascot, "Ducky":
the name, and also led to the adoption of "Ducky", Ente's mascot:
<div align="center">
@ -43,80 +50,20 @@ the name, and also led to the adoption of the duck as Ente's mascot, "Ducky":
en-_tay_. Like ca<i>fe</i>.
---
## Get in touch
# Products
If you have a support query that is not answered by these docs, please reach out
to our Customer Support by sending an email to support@ente.io
Ente currently offers Photo and Auth. Additionally, there are some other
products (Lockers and Legacy) that are being considered.
To stay up to date with new product launches, and behind the scenes details of
how we're building Ente, you can read our [blog](https://ente.io/blog) (or
subscribe to it via [RSS](https://ente.io/blog/rss.xml))
## Ente Photos
Ente Photos goes beyond traditional cloud storage, prioritizing your privacy and
the safety of your cherished memories. All your photos, along with their
metadata, are stored end-to-end encrypted, ensuring that only you have access to
your data.
Ente preserves your encrypted photos across three different clouds in three
separate locations, including an underground fallout shelter. This multi-layered
backup strategy provides a high level of reliability.
Ente photos is available for Android, iOS, Linux, Mac, Windows and the web.
# Ente Auth
Ente auth is ust an authenticator app; it's an open-source initiative
dedicated to securing your 2FA secrets. Now, you can backup and view your
two-factor authentication secrets seamlessly. find more information about the
project on GitHub at github.com/ente-io/auth.
As a token of gratitude to the community that has supported us, Ente Auth is
offered as a free service. If in the future we convert this to a paid service,
existing users will be grandfathered in.
Ente auth is available on Android, iOS, and the web
# Connect with Ente
## Customer support
Connect with our support team for swift assistance and expert guidance email us
@support@ente.io.
Reach out to our dev team @team@ente.io, even our CEO and CTO personally
responds here.
## Blog
As Ente continues to evolve, So does our story. Follow our blog @
https://ente.io/blog As Ente undergoes continuous growth and development, our
narrative unfolds. Explore our blog for exclusive company updates that offer an
insider's perspective. Regularly visit the Ente blog to maintain your
connection, stay well informed, and draw insipration.
## Roadmap
You plays a pivotal role in shaping the future direction of Ente's product, and
we invite you to be an integral part of it.
Take a look at our roadmap to see where we're headed
https://roadmap.ente.io/roadmap/
# Community
#### Join our vibrant community and stay updated on all things on Ente! Follow us on various platforms for the latest news, updates, and engaging content
#### Discord
Join our Discord for real-time discussions, solutions to queries, and a thriving
camaraderie. Stay updated on all things on Ente!
🐦 Twitter: https://twitter.com/enteio
🔗 Reddit: https://www.reddit.com/r/enteio
📸 Instagram: https://www.instagram.com/ente.app/
🐘 Mastodon: https://mstdn.social/@ente
🔗 LinkedIn: https://www.linkedin.com/company/ente-io/
To suggest new features and/or offer your perspective on how we should design
planned and upcoming features, use our [GitHub
discussions](https://github.com/ente-io/ente/discussions)
Or if you'd just like to hang out, join our
[Discord](https://discord.gg/z2YVKkycX3), follow us on
[Twitter](https://twitter.com/enteio) or give us a shout out on
[Mastodon](https://mstdn.social/@ente)

View file

@ -3,13 +3,13 @@ title: Ente Auth
description: User guide for Ente Auth
---
# Welcome to the Ente Auth's User Guide!
# Ente Auth
Ente Auth is a free, cross-platform, end-to-end encrypted authenticator app for
everyone. You can use it to safely store your 2FA codes (second-factor
authentication codes).
Ente Auth is a free, cross-platform, end-to-end encrypted authenticator app. You
can use it to safely store your 2FA codes (second-factor authentication codes).
> [!CAUTION]
>
> These docs are still incomplete. If you feel like contributing though, help us
> [fill them in](https://github.com/ente-io/ente/docs).
> These docs are still incomplete. If you feel like documenting an issue you ran
> into and then found a solution to, help us [fill them
> in](/about/contributing).

View file

@ -0,0 +1,9 @@
---
title: Migrating to Ente Auth
description: Guides for migrating your existing 2FA tokens from other products into Ente Auth
---
# Migrating to Ente Auth
_Coming soon_. This section will contain guides for migrating your existing 2FA
tokens from other products into Ente Auth.

View file

@ -1,8 +0,0 @@
## Translation
## Icons
## Support Development
If you wish to support the development of the project, please consider switching
to paid plan of [Ente Photos](https://ente.io)

View file

@ -3,11 +3,11 @@ title: Ente Auth
description: Ente Auth-Benutzerhandbuch
---
# Willkommen beim Ente Auth-Benutzerhandbuch!
# Ente Auth
Ente Authenticator ist eine kostenlose, plattformübergreifende,
Ende-zu-Ende-verschlüsselte Authenticator-App für jedermann. Wir sind froh, dass
du hier bist!
**Please note that this German translation is currently just a placeholder.**
Know German? [Help us fill this in!](https://github.com/ente-io/ente/docs).
Know German? [Help us fill this in!](/about/contributing).

16
docs/docs/index.md Normal file
View file

@ -0,0 +1,16 @@
---
title: Home
---
# Welcome!
This site contains documentation and help for Ente Photos and Ente Auth.
It describes various features, and also offers various troubleshooting
suggestions.
Use the **sidebar** menu to navigate to information about the product (Photos or
Auth) you'd like to know more about. Or use the **search** at the top to try and
jump directly to page that might contain the information you need.
To know more about Ente, see [about](/about/) or visit our website
[ente.io](https://ente.io).

View file

@ -0,0 +1,9 @@
---
title: FAQ
description: Frequently asked questions about Ente Photos
---
# FAQ
_Coming soon_. On this page we'll document some help items in a question and
answer format.

View file

@ -1,67 +1,81 @@
---
title: Albums
description: Using albums in Ente Photos
---
# Albums
This guide will show you how to make the most of your albums with simple yet
effective features. Below are the features that allow you to personailze your
albums according to your preferences:
Make the most of your albums and personalize them to your preferences with these
simple yet effective features.
## 1. Rename album: Personalize your albums by giving them a meaningful name
## Rename album
### How to Rename an album on your mobile?
Personalize your albums by giving them a meaningful name.
### How to rename an album on your mobile?
- Open the album
- Tap the three dots button in the top right corner of the screen
- Tap rename album, then type in a new name
- Tap on Rename button
- Tap _Rename album_, then type in a new name
- Tap on _Rename_ button
### How to Rename an album on your Desktop?
### How to rename an album on your web / desktop?
- Open the album
- Click on the overflow menu in the top right corner
- Click the Rename album
- Click the _Rename album_ button
- Type in a new name
- Click on Rename or Press enter
- Click on _Rename_ or press enter
## 2. Set cover: Select any photo you want to use as the cover for your album.
## Set album cover
Select any photo you want to use as the cover for your album.
### How to set an album cover on mobile?
- Open the album you want to change
- Tap the three dots button in the top right corner
- From the menu options, select Set cover
- From the menu options, select _Set cover_
- A new screen will appear, propmpting you to select the cover photo
- Browse through your photos and tap on the image you want to set as the album
cover
- Then tap on Use selected photo
- Then tap on _Use selected photo_
## 3. Map: Explore the memories based on their location
## View your photos on a map
### How to explore the album's photo in map view?
Explore your memories based on their location.
### How to explore the album's photos in map view?
- Open the album
- Tap on the three dots button in the top right corner
- Select map
- View all photos of the album in map view
- Select _Map_
- This will show all photos of the album in a map view
## 4. Sort by: Maintain order in your albums by arranging them from the newest to the oldest
## Sort albums
### How to sort by on mobile?
Maintain order in your albums by arranging them from the newest to the oldest.
### How to change the sort order on mobile?
- Open the album
- Tap on the three dots button in the top right corner
- Select sort by
- Tap on the Newst first for the latest, Or Oldest first for the oldest
- Select _Sort by_
- Tap on the _Newest first_ for the latest, Or _Oldest first_ for the oldest
### How to sort by on desktop?
### How to change the sort order on web / desktop?
- Open the album
- Click on the three dots button in the top right corner
- Click sort by
- Click on the Newest first for the latest, Or oldest first for the oldest
- Click _Sort by_
- Tap on the _Newest first_ for the latest, Or _Oldest first_ for the oldest
## 5. Pin album: Keep your favorite album at the top by pinning them for quick access.
## Pin albums
### How to Pin/Unpin an album on Mobile?
Keep your favorite album at the top by pinning them for quick access.
### How to pin/unpin an album on mobile?
- Open the album
- Tap on the three dots button in the top right corner
- Tap on Pin album/Unpin album
- Tap on _Pin album_ / _Unpin album_

View file

@ -1,3 +1,10 @@
---
title: Archive
description: |
Archiving photos and albums in Ente Photos to remove them from your home
timeline
---
# Archive
You can remove a photo (owned by you) or an album from your **home timeline** by
@ -8,30 +15,30 @@ mobile app.
when some of the photos are also present in a non-archived album.
- You can archive albums that are owned by you or shared with you.
- Search results will include archived photos. If you want to hide photos from
search result, use [Hide](./hidden.md) feature.
search result, use [Hide](./hide) feature.
### How to
## How to
#### Archive Album
### Archive Album
- Open the album
- Click on the overflow menu
- Click on Archive album
#### Archive Photo
### Archive Photo
- Long press to select the photo
- Select Archive option from the bottom menu.
#### View Archived Photos and Albums
### View Archived Photos and Albums
**Mobile**
#### Mobile
- Go to Albums tab
- Scroll down to bottom
- Click on Archive button.
**Desktop**
#### Web / Desktop
- Click on the topleft hamburger menu
- Click on Archive
@ -39,8 +46,8 @@ mobile app.
### Metadata Privacy
Both Ente and the person with whom you are sharing an album or photo have no
information about whether you have
information about whether you have:
- Archived a photo
- Archived an album
- Archived a shared album.
- Archived a shared album

View file

@ -1,36 +0,0 @@
# Cast
With ente Cast, you can play a slideshow of your favourite albums on your Google
Chromecast TVs or other Internet-connected large screen devices.
## Get Started
1. Open ente on the web or on your mobile device.
2. Select the album you want to play on your large screen device.
3. Click "Play album on TV" in the album menu.
On the web, you can find this menu in the balls menu on the right hand side.
![Balls menu](/assets/cast/web-play-album-on-tv.webp)
4. Choose how you want to pair your device with your large screen device.
![Pairing options](/assets/cast/web-pairing-options.webp)
On Chromium browsers, you will see a button labeled "Auto Pair". This option
will prompt you to select a Chromecast supported device on your local network.
Note: this option requires connecting to Google servers to load necessary
dependencies. This option does not transmit any sensitive data through Google
servers, such as your photos. Once your Chromecast device is connected, you're
all set.
On all browsers, you'll see the option to "Pair with PIN". This option works
with all devices, Chromecast-enabled or not. You'll be required to load up
[cast.ente.io](https://cast.ente.io) on your large screen device.
5. Enter the PIN displayed on your large screen device into the input field on
your mobile or web device.
On your large screen device, you'll see the following screen.
![Pairing screen](/assets/cast/tv-pairing-screen.webp)
6. If you entered your PIN correctly, you'll see a screen on your TV with a
green checkmark confirming the connection.

View file

@ -0,0 +1,65 @@
---
title: Archive
description: |
Archiving photos and albums in Ente Photos to remove them from your home
timeline
---
> [!CAUTION]
>
> This is preview documentation for an upcoming feature. This feature has not
> yet been released yet, so the steps below will not work currently.
# Cast
With Ente Cast, you can play a slideshow of your favourite albums on your Google
Chromecast TVs or other Internet-connected large screen devices.
## Get Started
1. Open ente on the web or on your mobile device.
2. Select the album you want to play on your large screen device.
3. Click "Play album on TV" in the album menu.
On the web, you can find this option in the three dots menu on the right
hand side.
<div align="center">
![Album options menu](web-play-album-on-tv.webp){width=300px}
</div>
4. Choose how you want to pair your device with your large screen device.
<div align="center">
![Pairing options](web-pairing-options.webp){width=300px}
</div>
On Google Chrome and other Chromium browsers, you will see a button labeled
"Auto Pair". This option will prompt you to select a Chromecast supported device
on your local network. Note: this option requires connecting to Google servers
to load necessary dependencies. This option does not transmit any sensitive data
through Google servers, such as your photos. Once your Chromecast device is
connected, you're all set.
On all browsers, you'll see the option to "Pair with PIN". This option works
with all devices, Chromecast-enabled or not. You'll be required to load up
[cast.ente.io](https://cast.ente.io) on your large screen device.
5. Enter the PIN displayed on your large screen device into the input field on
your mobile or web device.
On your large screen device, you'll see the following screen.
<div align="center">
![Pairing screen](tv-pairing-screen.webp)
</div>
6. Once you enter the correct PIN, you will see a screen on your TV with a green
checkmark confirming the connection. Your photos will start showing up in a
bit.

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,4 +1,11 @@
# Collect photos: Collecting memories from events is now a breeze!
---
title: Collect
description: Collecting photos from others using Ente Photos
---
# Collect photos
Collecting memories from events is now a breeze!
- Whether it's a birthday party, vacation trip or wedding, easily share your
album using a unique, secure, end-to-end encrypted link.
@ -6,23 +13,21 @@
contribute without an ente account.
- This allows them to effortlessly add, view, and download photos from the
link without an ente account.
- Also preserves metadata and photo quality
- Also preserves metadata and photo quality.
## How to Collect photos on mobile?
## How to collect photos on mobile?
- Open the album you want to share with
- Tap on the Share album icon in the top right corner of the screen
- Select 'Collect photos'
- Tap 'Copy link'
- The link has been copied to your clipboard. Now, feel free to share it
- Select _Collect photos_
- Tap _Copy link_
- The link will get copied to your clipboard. Now, feel free to share it
## How to Collect photos on Web/Desktop?
To collect photos on the web/desktop:
## How to Collect photos on web / desktop?
- Open the album
- Click on the share album icon
- Select Collect photos
- Click on Copy link
- The link has been copied to your clipboard. Share it effortlessly with
- Select _Collect photos_
- Click on _Copy link_
- The link will get copied to your clipboard. Share it effortlessly with
others!

View file

@ -1,9 +1,14 @@
---
title: Family plans
description: Share your Ente Photos plan with your family members with no extra cost
---
# Family plans
Paid subscribers of Ente can share the storage with their family, **at no
additional cost** as you have already paid for the storage.
In breif,
In brief,
- Your family members can use storage space from your plan without paying
extra.

View file

@ -1,59 +1,66 @@
# Hidden
---
title: Hiding photos and albums
description: Hide photos and albums in Ente Photos
---
# Hiding photos and albums
You can further protect extra sensitive photos or albums by hiding them. Hidden
photos or albums will **only be viewable** after an extra layer of
authentication inside the app. Hidden differs from [Archive](./archive.md) in
the fact that hidden photos won't be surfaced anywhere in the app without
explicit authentication, whereas Archive only removes them from the home
timeline and memories sections.
authentication inside the app. Hiding differs from [Archiving](./archive.md) in
that the hidden photos won't be surfaced anywhere in the app without explicit
authentication, whereas archiving only removes them from the home timeline and
memories sections.
- Hidden photos and albums will be removed from home timelines, memories,
albums tab, search results, and any other visable place in the app.
- Hidden photos will be removed from all regular albums. If you want to unhide
again you will have to specify the album to move it to.
- You cannot hide photos or albums shared with you. You can archive shared
albums instead.
### How to
## How to
#### Hide album
### Hide album
- Open the album
- Click on the overflow menu
- Click on Hide album
- Click on _Hide album_
#### Hide photo
### Hide photo
- Select the photo
- Click on Hide option from the select menu
- Click on _Hide_ option from the select menu
#### View hidden photos and albums
### View hidden photos and albums
_Mobile_:
#### Mobile
- Go to Albums tab
- Scroll down to bottom
- Click on Hidden button
- Click on _Hidden_ button
- Authenticate in app
_Desktop_:
#### Web / Desktop
- Click on the topleft hamburger menu
- Click on Hidden
- Click on _Hidden_
- Authenticate in app
#### Unhide album
### Unhide album
- Open the hidden album
- Click on the overflow menu
- Click on Unhide album
- Click on _Unhide album_
#### Unhide photo
### Unhide photo
- Select the hidden photo
- Click on Unhide option from the select menu
- Click on _Unhide_ option from the select menu
- Click on the album the photo should be restored to
### Metadata Privacy
## Metadata Privacy
Ente has no information about whether you have hidden a photo or album.

View file

@ -0,0 +1,43 @@
---
title: Location tags
description: Search and organize your photos by location in Ente Photos
---
# Location Tags
_Search and organize your photos by their location_
Location Tags allow you to search and organize your photos based on their
geographical location. Instead of sending your queries to our servers, we've
implemented a privacy-friendly solution that runs searches locally on your
device.
## How to use Location Tags
### Method 1: Adding Location Tags from a Photo
1. Open a photo
2. Click on _Info_
3. Select _Add Location_"
4. Enter the location name and define a radius
### Method 2: Adding Location Tags from the Search Tab
1. Go to the search tab
2. Click on _Add new_ at the end of the location tags section
3. Select a photo as the center point for the location tag
4. Enter the location name and define a radius
## Tips
- The app will automatically cluster photos falling within a radius under a
specified location.
- Location Tags provide a seamless way to filter photos without compromising
your privacy.
- Location tags are stored end-to-end encrypted, ensuring the security of your
data.
- Enjoy a more organized photo library by tagging important places like home,
office, and vacation spots.

View file

@ -1,35 +0,0 @@
# Location Tags: Search and Organize Your Photos
## Overview:
The Location Tags feature allows you to efficiently search and organize your
photos based on their geographical location. Instead of sending your queries to
our servers, we've implemented a privacy-friendly solution that runs searches
locally on your device.
## How to Use Location Tags:
### Method 1: Adding Location Tags from a Photo
1. Open a photo.
2. Click on "Info."
3. Select "Add Location."
4. Enter the location name and define a radius.
### Method 2: Adding Location Tags from the Search Tab
1. Go to the search tab.
2. Click on "Add new" at the end of the location tags section.
3. Select a photo as the center point for the location tag.
4. Enter the location name and define a radius.
## Note:
- The app will automatically cluster photos falling within a radius under a
specified location.
- Location Tags provide a seamless way to filter photos without compromising
your privacy.
- Location tags are stored end-to-end encrypted, ensuring the security of your
data.
- Enjoy a more organized photo library by tagging important places like home,
office, and vacation spots.

View file

@ -1,34 +1,39 @@
# Map : View and explore your photos on the map
---
title: Maps
description: View and explore your photos on a map within Ente Photos
---
# Map
_View and explore your photos on the map_
## How can I view photos on the map on mobile?
- Step 1. Find the search icon located at the bottom right corner of your
screen.
- Step 2. Tap on the globe icon (Your map) withing the location
- Step 3. Enter the map view and start exploring your photos from around the
world.
- Find the search icon located at the bottom right corner of your screen.
- Tap on the globe icon (Your map) withing the location
- Enter the map view and start exploring your photos from around the world.
## How to enable map on your mobile app?
- Step 1. Tap the three horizontal lines located at the top left corner of
your home screen or swipe left on the home screen.
- Step 2. Select "General" settings.
- Step 3. Enter the "Advanced" settings.
- Step 4. Use the toggle switch to turn the map feature on or off
- Tap the three horizontal lines located at the top left corner of your home
screen or swipe left on the home screen.
- Select _General_ settings.
- Enter the _Advanced_ settings.
- Use the toggle switch to turn the map feature on or off.
## How to view Album photos on the map?
- Step 1. Open the album containing the photos you want to view
- Step 2. Tap on the three horizontal lines located in the top right corner of
the screen.
- Step 3. Select "Map" from the options.
- Step 4. View and explore your photos on the map.
- Open the album containing the photos you want to view
- Tap on the three horizontal lines located in the top right corner of the
screen.
- Select _Map_ from the options.
- View and explore your photos on the map.
## How to enable map on desktop?
- Step 1. Click on the three horizontal lines located in the top left corner
of the app.
- Step 2. Navigate to "preferences" from the menu.
- Step 3. Select "Advanced" in the preference menu.
- Step 4. Click on "Map" to access map settings.
- Step 5. Toggle the map settings on and off based on your preferences.
- Click on the three horizontal lines located in the top left corner of the
app.
- Navigate to _Preferences_ from the menu.
- Select _Advanced_ in the preferences menu.
- Click on _Map_ to access map settings.
- Toggle the map settings on and off based on your preferences.

View file

@ -1,3 +1,8 @@
---
title: Public links
description: Share photos with your friends and family without them needing to install Ente Photos
---
# Public Links
Ente lets you share your photos via links, that can be accessed by anyone,

View file

@ -1,12 +1,22 @@
---
title: Quick links
description: Share photos with your friends and family without creating albums
---
# Quick Links
Quick links allows you to select single or multiple photos & create a link.
Behind the scene, ente creates a special album and put all the selected files in
that special album.
Quick links allows you to select single or multiple photos and create a link
that you can then share. You don't need to create an album first.
> Behind the scene, Ente creates a special album and put all the selected files
> in that special album.
- You can view all quick links created by you in the sharing tab, under Quick
links section.
- Quick links can be converted to regular album.
- Remove link will not delete the photos that are present in that link.
- Similar to [public-links](./public-links.md), you can set link expirty, set
- Removing a link will not delete the photos that are present in that link.
- Similar to [public-links](./public-links), you can set link expiry,
passwords or device limits.

View file

@ -1,6 +1,11 @@
# Referral plan: Earn and Expand Your Storage
---
title: Referral plan
description: Earn and expand your storage by referring Ente Photos to your friends and family
---
## Overview:
# Referral plan
_Earn and Expand Your Storage_
Did you know you can boost your storage on Ente simply by referring your
friends? Our referral program lets you earn 10 GB of free storage for each
@ -13,29 +18,29 @@ On the Home Page:
- Click on the hamburger menu in the top left corner
- Open the sidebar
- Tap on 'General'
- Select Referrals
- Tap on _General_
- Select _Referrals_
- Share the code with your friend or family
Note:
- Once your friend upgrades to a paid plan, both you and your friend receive
an additional 10 GB of storage
- Keep track of your earned storage and referral details on Claim free storage
screen
an additional 10 GB of storage.
- You can keep track of your earned storage and referral details on _Claim
free storage_ screen.
- If you refer more friends than your plan allows, the extra storage earned
will be reserved until you upgrade your plan
- Earned storage remains accessible as long as your subscription is active
will be reserved until you upgrade your plan.
- Earned storage remains accessible as long as your subscription is active.
## How to apply Refferal code of a friend?
## How to apply referral code given by a friend?
On the Home Page:
- Click on the hamburger menu inthe top left corner
- Tap on 'General' from the options
- Select 'Referrals' from the menu
- Find and tap on 'Apply Code.'
- Tap on _General_ from the options
- Select _Referrals_ from the menu
- Find and tap on _Apply Code_
- Enter the referral code provided by your friend.
Note: Please note that referral codes should be applied within one month of
account creation to claim free storage.
Please note that referral codes should be applied within one month of account
creation to claim the free storage.

View file

@ -1,7 +1,12 @@
---
title: Trash
description: Deleting items and trash
---
# Trash
Whenever you delete an item from ente, it is moved to Trash. These items will be
automatically deleted from Trash after 30 days. You can manaully select or
completely empty the trash, if you wish.
Whenever you delete an item from Ente, it is moved to Trash. These items will be
automatically deleted from Trash after 30 days. You can manaully select photos
to permanently delete or completely empty the trash if you wish.
Items in trash are included in your used storage calculation.

View file

@ -1,18 +1,24 @@
## Uncategoried
---
title: Uncategorized
description: Uncategorized items in Ente Photos
---
"Uncategorized" is a special album type where photos are automatically added
# Uncategorized
_Uncategorized_ is a special album type where photos are automatically added
under the following circumstances:
- When you remove a photo from the last album, it is relocated to
"Uncategorized."
_Uncategorized_ section.
- During album deletion, if you choose to keep photos but delete the album,
all photos exclusive to the current album are moved to the "Uncategorized"
all photos exclusive to the current album are moved to the _Uncategorized_
section.
Note: This does not include photos uploaded by others.
### Clean up Uncategorized
### Cleaning up Uncategorized items
In the mobile app, you can click on the overflow menu and click
`Clean Uncategorized` option. All files that are also present in another album,
that is owned by the user, will be removed from the Uncategorized section.
In the mobile app, you can click on the overflow menu and click _Clean
Uncategorized_ option. All files that are also present in another album, that is
owned by the user, will be removed from the _Uncategorized_ section.

View file

@ -1,30 +1,36 @@
# Watched Folders: Effortless Syncing
---
title: Watch folder
description: Automatic syncing of certain folders in the Ente Photos desktop app
---
## Overview:
# Watch folder
The ente desktop app now allows you to "watch" a folder on your computer for any
changes, creating a one-way sync from your device to the Ente cloud. This will
make photo management and backup a breeze.
_Automatic syncing_
The Ente desktop app allows you to "watch" a folder on your computer for any
changes, creating a one-way sync from your device to the Ente cloud. This is
intended to automate your photo management and backup.
## How to add Watch folders?
- Click on the hamburger menu in the top left corner
- Open the sidebar
- Select "Watch Folders"
- Choose "Add Watch Folders"
- Pick the folder from your system that you want to add as Watch folder
- Select _Watch Folders_
- Choose _Add Watch Folders_
- Pick the folder from your system that you want to add as a watched folder
## How to remove Watch folders?
- Click on the hamburger menu in the top left corner
- Open the sidebar
- Select "Watch Folders"
- Select _Watch Folders_
- Click on the three dots menu next to the folders on the right side
- Choose "Stop Watching" from the menu
- Choose _Stop Watching_ from the menu
# Note:
# Tips:
- You will get an option to choose whether to sync nested folders to a single
album or separate albums.
- Option to choose whether to sync nested folders to a single album or
separate albums.
- The app continuously monitors changes in the watched folder, such as the
addition or removal of files
addition or removal of files.

View file

@ -1,42 +1,18 @@
# Welcome to Help!
---
title: Ente Photos
description: User guide for Ente Photos
---
Welcome to Ente Photos Help! If you're new to Ente Photos, our
[Quick Start](./getting-started/index.md) and [FAQs](./faq/faq.md) are great
places to begin.
# Ente Photos
If you cant find an answer, please [ask the community](https://ente.io/discord)
or write to **support@ente.io**.
Ente Photos is an end-to-end encrypted alternative to Google Photos and Apple
Photos. You can use it to safely and securely store your photos on the cloud.
To send a bug report or a feature suggestion, you can use
[Github Issues](https://github.com/ente-io/photos-app/issues).
While security and privacy form the bedrock of Ente Photos, it is not at the
cost of usability. The user interface is simple, and we are continuously working
to make it even simpler. The goal is a product that can be used by people with
all sorts of technical ability and background.
Feedback about this documentation can be shared on our
[Discord Server](https://ente.io/discord) in the **\#docs** channel. We would
love to hear your thoughts on anything we can fix or improve.
## About Ente Photos
[Ente Photos](https://ente.io) is a safe home for your photos and videos.
You can store, share, and re-discover your moments with absolute privacy.
## About Ente Auth
[Ente Auth](https://ente.io/auth) is a secure, end-to-end encrypted 2FA app with
multi-platform sync.
Learn more about Auth [here](../authenticator/).
## Contributing
The source for this documentation is available at
[github.com/ente-io/docs](https://github.com/ente-io/docs).
Please see our
[contribution guide](https://github.com/ente-io/docs#contributing). We'd be
grateful for any fixes and improvements!
Once your contributions are accepted, please add yourself to the list of
[Contributors](./misc/contributors.md).
Thank you!
These help docs are divided into three sections: Features, FAQ and
Troubleshooting. Choose the relevant page from the sidebar menu, or use the
search at the top.

View file

@ -1,13 +1,19 @@
---
title: Files not uploading
description: Troubleshooting when files are not uploading from your Ente Photos app
---
# Files not uploading
## Network Issue
If you are using VPN, please try disabling the VPN or switching provider.
## Web/Desktop
## Web / Desktop
**Certain file types are not uploading**
### Certain file types are not uploading
The desktop/web app tries to detect if a particular file is video or image. If
the detection fails, then the app skips the upload. Please share either the
sample file or logs with us @support.ente.io
## Mobile
the detection fails, then the app skips the upload. Please contact our
[support](support.ente.io) if you find that a valid file did not get detected
and uploaded.

View file

@ -1,15 +0,0 @@
## Report Bug
Guide to help the user in sharing logs.
### Mobile
Placeholder
### Desktop
Placeholder
### Web
Placeholder

View file

@ -0,0 +1,26 @@
---
title: Sharing logs with support
description: How to report bugs and share the logs from your Ente Photos app
---
# Sharing debug logs
In some cases when you report a bug, our customer support might request you to
share debug logs from your app to help our developers find the issue.
Note that the debug logs contain potentially sensitive information like the file
names, so please feel free to not share them if you have any hesitation or want
to keep these private. We will try to diagnose the issue even without the logs,
the logs just make the process a bit faster and easier.
### Mobile
Placeholder
### Desktop
Placeholder
### Web
Placeholder

View file

@ -1,5 +0,0 @@
## Video Playback Issue
### Web
### Desktop / Mobile

View file

@ -9,6 +9,6 @@
},
"devDependencies": {
"prettier": "^3",
"vitepress": "^1.0.0-rc.44"
"vitepress": "^1.0.0-rc.45"
}
}

View file

@ -723,10 +723,10 @@ vite@^5.1.3:
optionalDependencies:
fsevents "~2.3.3"
vitepress@^1.0.0-rc.44:
version "1.0.0-rc.44"
resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.0.0-rc.44.tgz#01bce883761c22de42b9869a95f04bd02cbb8cdb"
integrity sha512-tO5taxGI7fSpBK1D8zrZTyJJERlyU9nnt0jHSt3fywfq3VKn977Hg0wUuTkEmwXlFYwuW26+6+3xorf4nD3XvA==
vitepress@^1.0.0-rc.45:
version "1.0.0-rc.45"
resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.0.0-rc.45.tgz#1cb41f53fa084c224dd2d910137ef7b2e8c0c191"
integrity sha512-/OiYsu5UKpQKA2c0BAZkfyywjfauDjvXyv6Mo4Ra57m5n4Bxg1HgUGoth1CLH2vwUbR/BHvDA9zOM0RDvgeSVQ==
dependencies:
"@docsearch/css" "^3.5.2"
"@docsearch/js" "^3.5.2"

Some files were not shown because too many files have changed in this diff Show more