Merge branch 'main' into fix_auto_scaling_on_loading_final_image
This commit is contained in:
commit
454f5cdead
114 changed files with 1261 additions and 872 deletions
3
.github/workflows/cli-release.yml
vendored
3
.github/workflows/cli-release.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
3
auth/lib/events/endpoint_updated_event.dart
Normal file
3
auth/lib/events/endpoint_updated_event.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class EndpointUpdatedEvent extends Event {}
|
|
@ -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;
|
||||
|
|
|
@ -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}"
|
||||
}
|
|
@ -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."
|
||||
}
|
||||
}
|
|
@ -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": "通行密钥"
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -54,6 +54,7 @@ class LocalAuthenticationService {
|
|||
.setEnabled(Configuration.instance.shouldShowLockScreen());
|
||||
}
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
errorDialogTitle,
|
||||
|
|
|
@ -27,6 +27,7 @@ class NotificationService {
|
|||
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
if (implementation != null) {
|
||||
// ignore: unawaited_futures
|
||||
implementation.requestPermission();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -236,7 +236,7 @@ class DeleteAccountPage extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
);
|
||||
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -146,6 +146,7 @@ class ProgressDialog {
|
|||
try {
|
||||
if (!_isShowing) {
|
||||
_dialog = _Body();
|
||||
// ignore: unawaited_futures
|
||||
showDialog<dynamic>(
|
||||
context: _context!,
|
||||
barrierDismissible: _barrierDismissible,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -46,6 +46,7 @@ class DangerSectionWidget extends StatelessWidget {
|
|||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, const DeleteAccountPage());
|
||||
},
|
||||
),
|
||||
|
|
|
@ -35,6 +35,7 @@ class DataSectionWidget extends StatelessWidget {
|
|||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, ImportCodePage());
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
90
auth/lib/ui/settings/developer_settings_page.dart
Normal file
90
auth/lib/ui/settings/developer_settings_page.dart
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
27
auth/lib/ui/settings/developer_settings_widget.dart
Normal file
27
auth/lib/ui/settings/developer_settings_widget.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ class _AdvancedSectionWidgetState extends State<AdvancedSectionWidget> {
|
|||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -81,6 +81,7 @@ class SocialsMenuItemWidget extends StatelessWidget {
|
|||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
launchUrlString(
|
||||
url,
|
||||
mode: launchInExternalApp
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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");
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -119,7 +119,7 @@ class _TwoFactorAuthenticationPageState
|
|||
child: OutlinedButton(
|
||||
onPressed: _code.length == 6
|
||||
? () async {
|
||||
_verifyTwoFactorCode(_code);
|
||||
await _verifyTwoFactorCode(_code);
|
||||
}
|
||||
: null,
|
||||
child: Text(l10n.verify),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -203,7 +203,7 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
21
cli/docs/release.md
Normal 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
|
||||
```
|
|
@ -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."
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
|
|
15
docs/docs/about/contribute.md
Normal file
15
docs/docs/about/contribute.md
Normal 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).
|
|
@ -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)
|
||||
|
|
|
@ -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).
|
||||
|
|
9
docs/docs/auth/migration-guides/index.md
Normal file
9
docs/docs/auth/migration-guides/index.md
Normal 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.
|
|
@ -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)
|
|
@ -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
16
docs/docs/index.md
Normal 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).
|
9
docs/docs/photos/faq/index.md
Normal file
9
docs/docs/photos/faq/index.md
Normal 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.
|
|
@ -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_
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||

|
||||
|
||||
4. Choose how you want to pair your device with your large screen device.
|
||||

|
||||
|
||||
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.
|
||||

|
||||
|
||||
6. If you entered your PIN correctly, you'll see a screen on your TV with a
|
||||
green checkmark confirming the connection.
|
65
docs/docs/photos/features/cast/index.md
Normal file
65
docs/docs/photos/features/cast/index.md
Normal 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">
|
||||
|
||||
{width=300px}
|
||||
|
||||
</div>
|
||||
|
||||
4. Choose how you want to pair your device with your large screen device.
|
||||
|
||||
<div align="center">
|
||||
|
||||
{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">
|
||||
|
||||

|
||||
|
||||
</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.
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
@ -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!
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
43
docs/docs/photos/features/location-tags.md
Normal file
43
docs/docs/photos/features/location-tags.md
Normal 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.
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 can’t 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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
## Report Bug
|
||||
|
||||
Guide to help the user in sharing logs.
|
||||
|
||||
### Mobile
|
||||
|
||||
Placeholder
|
||||
|
||||
### Desktop
|
||||
|
||||
Placeholder
|
||||
|
||||
### Web
|
||||
|
||||
Placeholder
|
26
docs/docs/photos/troubleshooting/sharing-logs.md
Normal file
26
docs/docs/photos/troubleshooting/sharing-logs.md
Normal 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
|
|
@ -1,5 +0,0 @@
|
|||
## Video Playback Issue
|
||||
|
||||
### Web
|
||||
|
||||
### Desktop / Mobile
|
|
@ -9,6 +9,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3",
|
||||
"vitepress": "^1.0.0-rc.44"
|
||||
"vitepress": "^1.0.0-rc.45"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue