Merge remote-tracking branch 'origin' into bg_sync

This commit is contained in:
vishnukvmd 2021-11-15 21:22:20 +05:30
commit 6185579656
21 changed files with 431 additions and 270 deletions

View file

@ -48,7 +48,7 @@
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data android:name="flutterEmbedding" android:value="2" />
<meta-data android:name="asset_statements" android:resource="@string/asset_statements" />
<meta-data android:name="io.sentry.dsn" android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4" />
</application>

View file

@ -1,4 +1,10 @@
<resources>
<string name="app_name">ente</string>
<string name="backup">backup</string>
<string name="asset_statements" translatable="false">
[{
\"include\": \"https://web.ente.io/.well-known/assetlinks.json\"
}]
</string>
</resources>

View file

@ -52,6 +52,7 @@
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A78E51A260432466D4C456A9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
BB097BB5EB0EEB41344338D2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
DA8D6672273BBB59007651D4 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
F82DAEEB9A7D9FD00E0FFA1E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@ -103,6 +104,7 @@
isa = PBXGroup;
children = (
271BD99F270EE74F00D86E6F /* Runner.entitlements */,
DA8D6672273BBB59007651D4 /* Runner.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,

View file

@ -49,7 +49,7 @@
<key>NSFaceIDUsageDescription</key>
<string>Please allow ente to lock itself with FaceID or TouchID</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Please allow access to your photos so that ente can encrypt and backup them up.</string>
<string>Please allow access to your photos so that ente can encrypt and back them up.</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>

View file

@ -4,5 +4,9 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:web.ente.io</string>
</array>
</dict>
</plist>

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io' as io;
import 'dart:typed_data';
import 'package:bip39/bip39.dart' as bip39;
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
@ -29,6 +30,8 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:super_logging/super_logging.dart';
import 'package:uuid/uuid.dart';
import 'constants.dart';
class Configuration {
Configuration._privateConstructor();
@ -266,6 +269,14 @@ class Configuration {
}
Future<void> recover(String recoveryKey) async {
// check if user has entered mnemonic code
if (recoveryKey.contains(' ')) {
if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) {
throw AssertionError(
'recovery code should have $kMnemonicKeyWordCount words');
}
recoveryKey = bip39.mnemonicToEntropy(recoveryKey);
}
final attributes = getKeyAttributes();
Uint8List masterKey;
try {

View file

@ -20,3 +20,7 @@ const String kLivePhotoToastCounterKey = "show_live_photo_toast";
const kThumbnailDiskLoadDeferDuration = Duration(milliseconds: 40);
const kThumbnailServerLoadDeferDuration = Duration(milliseconds: 80);
// 256 bit key maps to 24 words
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#Generating_the_mnemonic
const kMnemonicKeyWordCount = 24;

View file

@ -208,6 +208,13 @@ class File {
}
}
String getDisplayName() {
if (pubMagicMetadata != null && pubMagicMetadata.editedName != null) {
return pubMagicMetadata.editedName;
}
return title;
}
// returns true if the file isn't available in the user's gallery
bool isRemoteFile() {
return localID == null && uploadedFileID != null;

View file

@ -6,6 +6,7 @@ const kVisibilityArchive = 1;
const kMagicKeyVisibility = 'visibility';
const kPubMagicKeyEditedTime = 'editedTime';
const kPubMagicKeyEditedName = 'editedName';
class MagicMetadata {
// 0 -> visible
@ -36,8 +37,9 @@ class MagicMetadata {
class PubMagicMetadata {
int editedTime;
String editedName;
PubMagicMetadata({this.editedTime});
PubMagicMetadata({this.editedTime, this.editedName});
factory PubMagicMetadata.fromEncodedJson(String encodedJson) =>
PubMagicMetadata.fromJson(jsonDecode(encodedJson));
@ -48,6 +50,7 @@ class PubMagicMetadata {
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map[kPubMagicKeyEditedTime] = editedTime;
map[kPubMagicKeyEditedName] = editedName;
return map;
}
@ -55,6 +58,7 @@ class PubMagicMetadata {
if (map == null) return null;
return PubMagicMetadata(
editedTime: map[kPubMagicKeyEditedTime],
editedName: map[kPubMagicKeyEditedName],
);
}
}

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_password_strength/flutter_password_strength.dart';
@ -94,139 +95,147 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
strengthColors: passwordStrengthColors,
),
Expanded(
child: ListView(
children: [
Padding(padding: EdgeInsets.all(40)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
decoration: InputDecoration(
hintText: 'email',
hintStyle: TextStyle(
color: Colors.white30,
child: AutofillGroup(
child: ListView(
children: [
Padding(padding: EdgeInsets.all(40)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
autofillHints: [AutofillHints.email],
decoration: InputDecoration(
hintText: 'email',
hintStyle: TextStyle(
color: Colors.white30,
),
contentPadding: EdgeInsets.all(12),
),
contentPadding: EdgeInsets.all(12),
onChanged: (value) {
setState(() {
_email = value.trim();
});
},
autocorrect: false,
keyboardType: TextInputType.emailAddress,
initialValue: _email,
textInputAction: TextInputAction.next,
),
onChanged: (value) {
setState(() {
_email = value.trim();
});
},
autocorrect: false,
keyboardType: TextInputType.emailAddress,
initialValue: _email,
textInputAction: TextInputAction.next,
),
),
Padding(padding: EdgeInsets.all(8)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
keyboardType: TextInputType.text,
controller: _passwordController1,
obscureText: !_password1Visible,
decoration: InputDecoration(
hintText: "password",
hintStyle: TextStyle(
color: Colors.white30,
Padding(padding: EdgeInsets.all(8)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
keyboardType: TextInputType.text,
controller: _passwordController1,
obscureText: !_password1Visible,
enableSuggestions: true,
autofillHints: [AutofillHints.newPassword],
decoration: InputDecoration(
hintText: "password",
hintStyle: TextStyle(
color: Colors.white30,
),
contentPadding: EdgeInsets.all(12),
suffixIcon: _password1InFocus
? IconButton(
icon: Icon(
_password1Visible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white.withOpacity(0.5),
size: 20,
),
onPressed: () {
setState(() {
_password1Visible = !_password1Visible;
});
},
)
: null,
),
contentPadding: EdgeInsets.all(12),
suffixIcon: _password1InFocus
? IconButton(
icon: Icon(
_password1Visible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white.withOpacity(0.5),
size: 20,
),
onPressed: () {
setState(() {
_password1Visible = !_password1Visible;
});
},
)
: null,
focusNode: _password1FocusNode,
onChanged: (_) {
setState(() {});
},
onEditingComplete: () {
_password1FocusNode.unfocus();
_password2FocusNode.requestFocus();
TextInput.finishAutofillContext();
},
),
focusNode: _password1FocusNode,
onChanged: (_) {
setState(() {});
},
onEditingComplete: () {
_password1FocusNode.unfocus();
_password2FocusNode.requestFocus();
},
),
),
Padding(padding: EdgeInsets.all(8)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
keyboardType: TextInputType.text,
controller: _passwordController2,
obscureText: !_password2Visible,
decoration: InputDecoration(
hintText: "confirm password",
hintStyle: TextStyle(
color: Colors.white30,
Padding(padding: EdgeInsets.all(8)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
keyboardType: TextInputType.visiblePassword,
controller: _passwordController2,
obscureText: !_password2Visible,
autofillHints: [AutofillHints.newPassword],
onEditingComplete: () => TextInput.finishAutofillContext(),
decoration: InputDecoration(
hintText: "confirm password",
hintStyle: TextStyle(
color: Colors.white30,
),
contentPadding: EdgeInsets.all(12),
suffixIcon: _password2InFocus
? IconButton(
icon: Icon(
_password2Visible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white.withOpacity(0.5),
size: 20,
),
onPressed: () {
setState(() {
_password2Visible = !_password2Visible;
});
},
)
: null,
),
contentPadding: EdgeInsets.all(12),
suffixIcon: _password2InFocus
? IconButton(
icon: Icon(
_password2Visible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white.withOpacity(0.5),
size: 20,
),
onPressed: () {
setState(() {
_password2Visible = !_password2Visible;
});
},
)
: null,
focusNode: _password2FocusNode,
),
focusNode: _password2FocusNode,
),
),
Padding(
padding: EdgeInsets.all(20),
),
_getAgreement(),
Padding(padding: EdgeInsets.all(20)),
Container(
width: double.infinity,
height: 64,
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
child: button(
AppLocalizations.of(context).sign_up,
onPressed: _isFormValid()
? () {
if (!isValidEmail(_email)) {
showErrorDialog(context, "invalid email",
"please enter a valid email address.");
} else if (_passwordController1.text !=
_passwordController2.text) {
showErrorDialog(context, "uhm...",
"the passwords you entered don't match");
} else if (_passwordStrength <
kPasswordStrengthThreshold) {
showErrorDialog(context, "weak password",
"the password you have chosen is too simple, please choose another one");
} else {
_config
.setVolatilePassword(_passwordController1.text);
_config.setEmail(_email);
UserService.instance.getOtt(context, _email);
Padding(
padding: EdgeInsets.all(20),
),
_getAgreement(),
Padding(padding: EdgeInsets.all(20)),
Container(
width: double.infinity,
height: 64,
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
child: button(
AppLocalizations.of(context).sign_up,
onPressed: _isFormValid()
? () {
if (!isValidEmail(_email)) {
showErrorDialog(context, "invalid email",
"please enter a valid email address.");
} else if (_passwordController1.text !=
_passwordController2.text) {
showErrorDialog(context, "uhm...",
"the passwords you entered don't match");
} else if (_passwordStrength <
kPasswordStrengthThreshold) {
showErrorDialog(context, "weak password",
"the password you have chosen is too simple, please choose another one");
} else {
_config.setVolatilePassword(
_passwordController1.text);
_config.setEmail(_email);
UserService.instance.getOtt(context, _email);
}
}
}
: null,
fontSize: 18,
: null,
fontSize: 18,
),
),
),
],
],
),
),
),
],

View file

@ -1,6 +1,7 @@
import 'package:exif/exif.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/models/file.dart';
import 'package:photos/models/file_type.dart';
import 'package:photos/services/collections_service.dart';
@ -8,6 +9,7 @@ import 'package:photos/ui/exif_info_dialog.dart';
import 'package:photos/utils/date_time_util.dart';
import 'package:photos/utils/exif_util.dart';
import 'package:photos/utils/file_util.dart';
import 'package:photos/utils/magic_util.dart';
import 'package:photos/utils/toast_util.dart';
class FileInfoWidget extends StatefulWidget {
@ -42,6 +44,7 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
@override
Widget build(BuildContext context) {
final file = widget.file;
var items = <Widget>[
Row(
children: [
@ -52,7 +55,7 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
Padding(padding: EdgeInsets.all(4)),
Text(
getFormattedTime(
DateTime.fromMicrosecondsSinceEpoch(widget.file.creationTime),
DateTime.fromMicrosecondsSinceEpoch(file.creationTime),
),
style: TextStyle(
color: Colors.white.withOpacity(0.85),
@ -69,9 +72,9 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
),
Padding(padding: EdgeInsets.all(4)),
Text(
widget.file.deviceFolder ??
file.deviceFolder ??
CollectionsService.instance
.getCollectionByID(widget.file.collectionID)
.getCollectionByID(file.collectionID)
.name,
style: TextStyle(
color: Colors.white.withOpacity(0.85),
@ -96,7 +99,7 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
Padding(padding: EdgeInsets.all(6)),
],
);
if (widget.file.localID != null && !_isImage) {
if (file.localID != null && !_isImage) {
items.addAll(
[
Row(
@ -107,7 +110,7 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
),
Padding(padding: EdgeInsets.all(4)),
FutureBuilder(
future: widget.file.getAsset(),
future: file.getAsset(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(
@ -137,7 +140,7 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
if (_isImage && _exif != null) {
items.add(_getExifWidgets(_exif));
}
if (widget.file.uploadedFileID != null && widget.file.updationTime != null) {
if (file.uploadedFileID != null && file.updationTime != null) {
items.addAll(
[
Row(
@ -148,8 +151,8 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
),
Padding(padding: EdgeInsets.all(4)),
Text(
getFormattedTime(DateTime.fromMicrosecondsSinceEpoch(
widget.file.updationTime)),
getFormattedTime(
DateTime.fromMicrosecondsSinceEpoch(file.updationTime)),
style: TextStyle(
color: Colors.white.withOpacity(0.85),
),
@ -169,8 +172,34 @@ class _FileInfoWidgetState extends State<FileInfoWidget> {
children: _getActions(),
),
);
Widget titleContent;
if (file.uploadedFileID == null ||
file.ownerID != Configuration.instance.getUserID()) {
titleContent = Text(file.getDisplayName());
} else {
titleContent = InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
file.getDisplayName(),
),
Icon(
Icons.edit,
color: Colors.white.withOpacity(0.85),
),
],
),
onTap: () async {
await editFilename(context, file);
setState(() {});
},
);
}
return AlertDialog(
title: Text(widget.file.title),
title: titleContent,
content: SingleChildScrollView(
child: ListBody(
children: items,

View file

@ -12,8 +12,8 @@ import 'package:photos/models/collection.dart';
import 'package:photos/models/magic_metadata.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/ui/change_collection_name_dialog.dart';
import 'package:photos/ui/create_collection_page.dart';
import 'package:photos/ui/rename_dialog.dart';
import 'package:photos/ui/share_collection_widget.dart';
import 'package:photos/utils/delete_file_util.dart';
import 'package:photos/utils/dialog_util.dart';
@ -122,15 +122,15 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
if (widget.type != GalleryAppBarType.owned_collection) {
return;
}
final result = await showDialog(
final result = await showDialog<String>(
context: context,
builder: (BuildContext context) {
return ChangeCollectionNameDialog(name: _appBarTitle);
return RenameDialog(_appBarTitle, 'album');
},
barrierColor: Colors.black.withOpacity(0.85),
);
// indicates user cancelled the rename request
if (result == null) {
if (result == null || result.trim() == _appBarTitle.trim()) {
return;
}

View file

@ -58,6 +58,7 @@ class _LoginPageState extends State<LoginPage> {
Padding(
padding: const EdgeInsets.fromLTRB(40, 40, 40, 0),
child: TextFormField(
autofillHints: [AutofillHints.email],
decoration: InputDecoration(
hintText: 'email',
hintStyle: TextStyle(

View file

@ -92,6 +92,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
if (_password != null) {
return Container();
}
final String email = Configuration.instance.getEmail() ?? '';
return Column(
children: [
FlutterPasswordStrength(
@ -105,118 +106,137 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(16, 36, 16, 16),
child: Column(
children: [
Padding(padding: EdgeInsets.all(12)),
Text(
"enter a" +
(widget.mode != PasswordEntryMode.set ? " new " : " ") +
"password we can use to encrypt your data",
textAlign: TextAlign.center,
style: TextStyle(
height: 1.3,
child: AutofillGroup(
child: Column(
children: [
Padding(padding: EdgeInsets.all(12)),
Text(
"enter a" +
(widget.mode != PasswordEntryMode.set ? " new " : " ") +
"password we can use to encrypt your data",
textAlign: TextAlign.center,
style: TextStyle(
height: 1.3,
),
),
),
Padding(padding: EdgeInsets.all(8)),
Text("we don't store this password, so if you forget, "),
Text.rich(
TextSpan(
text: "we cannot decrypt your data",
style: TextStyle(
decoration: TextDecoration.underline,
fontWeight: FontWeight.bold,
)),
style: TextStyle(
height: 1.3,
Padding(padding: EdgeInsets.all(8)),
Text("we don't store this password, so if you forget, "),
Text.rich(
TextSpan(
text: "we cannot decrypt your data",
style: TextStyle(
decoration: TextDecoration.underline,
fontWeight: FontWeight.bold,
)),
style: TextStyle(
height: 1.3,
),
textAlign: TextAlign.center,
),
textAlign: TextAlign.center,
),
Padding(padding: EdgeInsets.all(12)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
decoration: InputDecoration(
hintText: "password",
contentPadding: EdgeInsets.all(20),
suffixIcon: _password1InFocus
? IconButton(
icon: Icon(
_password1Visible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white.withOpacity(0.5),
size: 20,
),
onPressed: () {
setState(() {
_password1Visible = !_password1Visible;
});
},
)
Padding(padding: EdgeInsets.all(12)),
// hidden textForm for suggesting auto-fill service for saving
// password
SizedBox(
width: 0,
height: 0,
child: TextFormField(
autofillHints: [
AutofillHints.email,
],
autocorrect: false,
keyboardType: TextInputType.emailAddress,
initialValue: email,
textInputAction: TextInputAction.next,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
autofillHints: [AutofillHints.newPassword],
decoration: InputDecoration(
hintText: "password",
contentPadding: EdgeInsets.all(20),
suffixIcon: _password1InFocus
? IconButton(
icon: Icon(
_password1Visible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white.withOpacity(0.5),
size: 20,
),
onPressed: () {
setState(() {
_password1Visible = !_password1Visible;
});
},
)
: null,
),
obscureText: !_password1Visible,
controller: _passwordController1,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
onChanged: (_) {
setState(() {});
},
textInputAction: TextInputAction.next,
focusNode: _password1FocusNode,
),
),
Padding(padding: EdgeInsets.all(8)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
autofillHints: [AutofillHints.newPassword],
decoration: InputDecoration(
hintText: "password again",
contentPadding: EdgeInsets.all(20),
suffixIcon: _password2InFocus
? IconButton(
icon: Icon(
_password2Visible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white.withOpacity(0.5),
size: 20,
),
onPressed: () {
setState(() {
_password2Visible = !_password2Visible;
});
},
)
: null,
),
obscureText: !_password2Visible,
controller: _passwordController2,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
onChanged: (_) {
setState(() {});
},
focusNode: _password2FocusNode,
),
),
Padding(padding: EdgeInsets.all(20)),
Container(
width: double.infinity,
height: 64,
padding: EdgeInsets.fromLTRB(40, 0, 40, 0),
child: button(
buttonText,
fontSize: 18,
onPressed: _passwordController1.text.isNotEmpty &&
_passwordController2.text.isNotEmpty
? _onButtonPress
: null,
),
obscureText: !_password1Visible,
controller: _passwordController1,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
onChanged: (_) {
setState(() {});
},
textInputAction: TextInputAction.next,
focusNode: _password1FocusNode,
),
),
Padding(padding: EdgeInsets.all(8)),
Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 0),
child: TextFormField(
decoration: InputDecoration(
hintText: "password again",
contentPadding: EdgeInsets.all(20),
suffixIcon: _password2InFocus
? IconButton(
icon: Icon(
_password2Visible
? Icons.visibility
: Icons.visibility_off,
color: Colors.white.withOpacity(0.5),
size: 20,
),
onPressed: () {
setState(() {
_password2Visible = !_password2Visible;
});
},
)
: null,
),
obscureText: !_password2Visible,
controller: _passwordController2,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
onChanged: (_) {
setState(() {});
},
focusNode: _password2FocusNode,
),
),
Padding(padding: EdgeInsets.all(20)),
Container(
width: double.infinity,
height: 64,
padding: EdgeInsets.fromLTRB(40, 0, 40, 0),
child: button(
buttonText,
fontSize: 18,
onPressed: _passwordController1.text.isNotEmpty &&
_passwordController2.text.isNotEmpty
? _onButtonPress
: null,
),
),
],
],
),
),
),
),
@ -227,7 +247,8 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage("how it works", "https://ente.io/architecture");
return WebPage(
"how it works", "https://ente.io/architecture");
},
),
);

View file

@ -53,6 +53,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
Padding(
padding: const EdgeInsets.fromLTRB(60, 0, 60, 0),
child: TextFormField(
autofillHints: [AutofillHints.password],
decoration: InputDecoration(
hintText: "enter your password",
contentPadding: EdgeInsets.all(20),

View file

@ -1,10 +1,12 @@
import 'dart:io' as io;
import 'dart:ui';
import 'package:bip39/bip39.dart' as bip39;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:share_plus/share_plus.dart';
@ -39,7 +41,11 @@ class _RecoveryKeyDialogState extends State<RecoveryKeyDialog> {
@override
Widget build(BuildContext context) {
final recoveryKey = widget.recoveryKey;
final String recoveryKey = bip39.entropyToMnemonic(widget.recoveryKey);
if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) {
throw AssertionError(
'recovery code should have $kMnemonicKeyWordCount words');
}
List<Widget> actions = [];
if (!_hasTriedToSave) {
actions.add(TextButton(

View file

@ -86,8 +86,13 @@ class _RecoveryPageState extends State<RecoveryPage> {
);
} catch (e) {
await dialog.hide();
showErrorDialog(context, "incorrect recovery key",
"the recovery key you entered is incorrect");
String errMessage =
'the recovery key you entered is incorrect';
if (e is AssertionError) {
errMessage = '$errMessage : ${e.message}';
}
showErrorDialog(
context, "incorrect recovery key", errMessage);
}
}
: null,

View file

@ -2,24 +2,25 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:photos/utils/dialog_util.dart';
class ChangeCollectionNameDialog extends StatefulWidget {
class RenameDialog extends StatefulWidget {
final String name;
final String type;
final int maxLength;
const ChangeCollectionNameDialog({Key key, this.name}) : super(key: key);
const RenameDialog(this.name, this.type, {Key key, this.maxLength = 100})
: super(key: key);
@override
_ChangeCollectionNameDialogState createState() =>
_ChangeCollectionNameDialogState();
_RenameDialogState createState() => _RenameDialogState();
}
class _ChangeCollectionNameDialogState
extends State<ChangeCollectionNameDialog> {
String _newCollectionName;
class _RenameDialogState extends State<RenameDialog> {
String _newName;
@override
void initState() {
super.initState();
_newCollectionName = widget.name;
_newName = widget.name;
}
@override
@ -33,7 +34,7 @@ class _ChangeCollectionNameDialogState
children: [
TextFormField(
decoration: InputDecoration(
hintText: 'album name',
hintText: '${widget.type} name',
hintStyle: TextStyle(
color: Colors.white30,
),
@ -41,12 +42,12 @@ class _ChangeCollectionNameDialogState
),
onChanged: (value) {
setState(() {
_newCollectionName = value;
_newName = value;
});
},
autocorrect: false,
keyboardType: TextInputType.text,
initialValue: _newCollectionName,
initialValue: _newName,
autofocus: true,
),
],
@ -72,21 +73,17 @@ class _ChangeCollectionNameDialogState
),
),
onPressed: () {
if (_newCollectionName.trim().isEmpty) {
if (_newName.trim().isEmpty) {
showErrorDialog(
context, "empty name", "album name cannot be empty");
context, "empty name", "${widget.type} name cannot be empty");
return;
}
if (_newCollectionName.trim().length > 100) {
if (_newName.trim().length > widget.maxLength) {
showErrorDialog(context, "name too large",
"album name should be less than 100 characters");
"${widget.type} name should be less than ${widget.maxLength} characters");
return;
}
if (_newCollectionName.trim() == widget.name.trim()) {
Navigator.of(context).pop(null);
return;
}
Navigator.of(context).pop(_newCollectionName.trim());
Navigator.of(context).pop(_newName.trim());
},
),
],

View file

@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/force_reload_home_gallery_event.dart';
import 'package:photos/models/file.dart';
import 'package:photos/models/magic_metadata.dart';
import 'package:photos/services/file_magic_service.dart';
import 'package:photos/ui/rename_dialog.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
@ -41,6 +44,35 @@ Future<bool> editTime(
}
}
Future<bool> editFilename(
BuildContext context,
File file,
) async {
try {
final fileName = file.getDisplayName();
final nameWithoutExt = basenameWithoutExtension(fileName);
final extName = extension(fileName);
var result = await showDialog<String>(
context: context,
builder: (BuildContext context) {
return RenameDialog(nameWithoutExt, 'file', maxLength: 50);
},
barrierColor: Colors.black.withOpacity(0.85),
);
if (result == null || result.trim() == nameWithoutExt.trim()) {
return true;
}
result = result + extName;
await _updatePublicMetadata(
context, List.of([file]), kPubMagicKeyEditedName, result);
return true;
} catch (e, s) {
showToast('something went wrong');
return false;
}
}
Future<void> _updatePublicMetadata(
BuildContext context, List<File> files, String key, dynamic value) async {
if (files.isEmpty) {

View file

@ -50,6 +50,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.73"
bip39:
dependency: "direct main"
description:
name: bip39
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.6"
boolean_selector:
dependency: transitive
description:
@ -534,6 +541,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.5"
hex:
dependency: transitive
description:
name: hex
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
html:
dependency: transitive
description:
@ -869,6 +883,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
pointycastle:
dependency: transitive
description:
name: pointycastle
url: "https://pub.dartlang.org"
source: hosted
version: "3.4.0"
process:
dependency: transitive
description:

View file

@ -11,7 +11,7 @@ description: ente photos application
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.3.46+256
version: 0.3.48+258
environment:
sdk: ">=2.10.0 <3.0.0"
@ -21,6 +21,7 @@ dependencies:
animate_do: ^2.0.0
archive: ^3.1.2
background_fetch: ^1.0.1
bip39: ^1.0.6
cached_network_image: ^3.0.0
chewie:
path: thirdparty/chewie