mirror of
https://github.com/authpass/authpass.git
synced 2025-08-06 17:21:47 +08:00
update file history when changing database name #57
This commit is contained in:
@ -11,12 +11,15 @@ import 'package:built_value/serializer.dart';
|
||||
import 'package:built_value/standard_json_plugin.dart';
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart' show Color;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:simple_json_persistence/simple_json_persistence.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'app_data.g.dart';
|
||||
|
||||
final _logger = Logger('app_data');
|
||||
|
||||
enum OpenedFilesSourceType { Local, Url, CloudStorage }
|
||||
|
||||
class SimpleEnumSerializer<T> extends PrimitiveSerializer<T> {
|
||||
@ -127,10 +130,14 @@ abstract class OpenedFile implements Built<OpenedFile, OpenedFileBuilder> {
|
||||
File(sourcePath),
|
||||
macOsSecureBookmark: macOsSecureBookmark,
|
||||
uuid: uuid ?? AppDataBloc.createUuid(),
|
||||
databaseName: name,
|
||||
);
|
||||
case OpenedFilesSourceType.Url:
|
||||
return FileSourceUrl(Uri.parse(sourcePath),
|
||||
uuid: uuid ?? AppDataBloc.createUuid());
|
||||
return FileSourceUrl(
|
||||
Uri.parse(sourcePath),
|
||||
uuid: uuid ?? AppDataBloc.createUuid(),
|
||||
databaseName: name,
|
||||
);
|
||||
case OpenedFilesSourceType.CloudStorage:
|
||||
final sourceInfo = json.decode(sourcePath) as Map<String, dynamic>;
|
||||
final storageId = sourceInfo[SOURCE_CLOUD_STORAGE_ID] as String;
|
||||
@ -139,9 +146,10 @@ abstract class OpenedFile implements Built<OpenedFile, OpenedFileBuilder> {
|
||||
throw StateError('Invalid cloud storage provider id $storageId');
|
||||
}
|
||||
return provider.toFileSource(
|
||||
(sourceInfo[SOURCE_CLOUD_STORAGE_DATA] as Map)
|
||||
.cast<String, String>(),
|
||||
uuid: uuid ?? AppDataBloc.createUuid());
|
||||
(sourceInfo[SOURCE_CLOUD_STORAGE_DATA] as Map).cast<String, String>(),
|
||||
uuid: uuid ?? AppDataBloc.createUuid(),
|
||||
databaseName: name,
|
||||
);
|
||||
}
|
||||
throw ArgumentError.value(sourceType, 'sourceType', 'Unsupported value.');
|
||||
}
|
||||
@ -237,6 +245,7 @@ class AppDataBloc {
|
||||
final colorCode = recentFile?.colorCode;
|
||||
final openedFile = OpenedFile.fromFileSource(
|
||||
file, name, (b) => b..colorCode = colorCode);
|
||||
_logger.finest('openedFile: $openedFile');
|
||||
// TODO remove potential old storages?
|
||||
b.previousFiles.removeWhere((file) => file.isSameFileAs(openedFile));
|
||||
b.previousFiles.add(openedFile);
|
||||
|
@ -39,11 +39,11 @@ class FileContent {
|
||||
}
|
||||
|
||||
abstract class FileSource {
|
||||
FileSource(
|
||||
{@required this.databaseName,
|
||||
@required this.uuid,
|
||||
FileContent initialCachedContent})
|
||||
: _cached = initialCachedContent;
|
||||
FileSource({
|
||||
@required this.databaseName,
|
||||
@required this.uuid,
|
||||
FileContent initialCachedContent,
|
||||
}) : _cached = initialCachedContent;
|
||||
|
||||
FileContent _cached;
|
||||
|
||||
@ -74,6 +74,8 @@ abstract class FileSource {
|
||||
|
||||
String get typeDebug => runtimeType.toString();
|
||||
|
||||
FileSource copyWithDatabaseName(String databaseName);
|
||||
|
||||
@protected
|
||||
Future<FileContent> load();
|
||||
|
||||
@ -182,6 +184,14 @@ class FileSourceLocal extends FileSource {
|
||||
|
||||
@override
|
||||
IconData get displayIcon => FontAwesomeIcons.hdd;
|
||||
|
||||
@override
|
||||
FileSource copyWithDatabaseName(String databaseName) => FileSourceLocal(
|
||||
file,
|
||||
databaseName: databaseName,
|
||||
uuid: uuid,
|
||||
macOsSecureBookmark: macOsSecureBookmark,
|
||||
);
|
||||
}
|
||||
|
||||
class FileSourceUrl extends FileSource {
|
||||
@ -217,6 +227,13 @@ class FileSourceUrl extends FileSource {
|
||||
|
||||
@override
|
||||
IconData get displayIcon => FontAwesomeIcons.externalLinkAlt;
|
||||
|
||||
@override
|
||||
FileSource copyWithDatabaseName(String databaseName) => FileSourceUrl(
|
||||
url,
|
||||
uuid: uuid,
|
||||
databaseName: databaseName,
|
||||
);
|
||||
}
|
||||
|
||||
class FileSourceCloudStorage extends FileSource {
|
||||
@ -258,6 +275,16 @@ class FileSourceCloudStorage extends FileSource {
|
||||
|
||||
@override
|
||||
IconData get displayIcon => provider.displayIcon;
|
||||
|
||||
@override
|
||||
FileSource copyWithDatabaseName(String databaseName) =>
|
||||
FileSourceCloudStorage(
|
||||
provider: provider,
|
||||
fileInfo: fileInfo,
|
||||
uuid: uuid,
|
||||
databaseName: databaseName,
|
||||
initialCachedContent: _cached,
|
||||
);
|
||||
}
|
||||
|
||||
class FileExistsException extends KdbxException {}
|
||||
@ -359,6 +386,26 @@ class KdbxOpenedFile {
|
||||
final KdbxFile kdbxFile;
|
||||
}
|
||||
|
||||
class OpenedKdbxFiles {
|
||||
OpenedKdbxFiles(Map<FileSource, KdbxOpenedFile> files)
|
||||
: _files = Map.unmodifiable(files);
|
||||
final Map<FileSource, KdbxOpenedFile> _files;
|
||||
|
||||
int get length => _files.length;
|
||||
|
||||
// bool get isNotEmpty => _files.isNotEmpty;
|
||||
|
||||
KdbxOpenedFile operator [](FileSource fileSource) => _files[fileSource];
|
||||
Iterable<MapEntry<FileSource, KdbxOpenedFile>> get entries => _files.entries;
|
||||
Iterable<KdbxOpenedFile> get values => _files.values;
|
||||
|
||||
bool containsKey(FileSource file) => _files.containsKey(file);
|
||||
|
||||
// Map<K2, V2> map<K2, V2>(
|
||||
// MapEntry<K2, V2> Function(FileSource key, KdbxOpenedFile value) f) =>
|
||||
// _files.map(f);
|
||||
}
|
||||
|
||||
class KdbxBloc {
|
||||
KdbxBloc({
|
||||
@required this.env,
|
||||
@ -383,7 +430,7 @@ class KdbxBloc {
|
||||
final KdbxFormat kdbxFormat = KdbxFormat(FlutterArgon2());
|
||||
|
||||
final _openedFiles =
|
||||
BehaviorSubject<Map<FileSource, KdbxOpenedFile>>.seeded({});
|
||||
BehaviorSubject<OpenedKdbxFiles>.seeded(OpenedKdbxFiles({}));
|
||||
Map<KdbxFile, KdbxOpenedFile> _openedFilesByKdbxFile;
|
||||
final _openedFilesQuickUnlock = <FileSource>{};
|
||||
|
||||
@ -391,11 +438,10 @@ class KdbxBloc {
|
||||
_openedFiles.value.entries
|
||||
.map((entry) => MapEntry(entry.key, entry.value.kdbxFile));
|
||||
|
||||
Map<FileSource, KdbxOpenedFile> get openedFiles => _openedFiles.value;
|
||||
OpenedKdbxFiles get openedFiles => _openedFiles.value;
|
||||
List<KdbxFile> get openedFilesKdbx =>
|
||||
_openedFiles.value.values.map((value) => value.kdbxFile).toList();
|
||||
ValueStream<Map<FileSource, KdbxOpenedFile>> get openedFilesChanged =>
|
||||
_openedFiles.stream;
|
||||
ValueStream<OpenedKdbxFiles> get openedFilesChanged => _openedFiles.stream;
|
||||
|
||||
Future<int> _quickUnlockCheckRunning;
|
||||
|
||||
@ -422,10 +468,10 @@ class KdbxBloc {
|
||||
openedFile: updatedFile,
|
||||
kdbxFile: file.kdbxFile,
|
||||
);
|
||||
_openedFiles.value = {
|
||||
..._openedFiles.value,
|
||||
_openedFiles.value = OpenedKdbxFiles({
|
||||
..._openedFiles.value._files,
|
||||
file.fileSource: newFile,
|
||||
};
|
||||
});
|
||||
_logger.info('new values: ${_openedFiles.value}');
|
||||
return newFile;
|
||||
}
|
||||
@ -452,14 +498,14 @@ class KdbxBloc {
|
||||
final kdbxFile = kdbxReadFile.file;
|
||||
final openedFile = await appDataBloc.openedFile(file,
|
||||
name: kdbxFile.body.meta.databaseName.get());
|
||||
_openedFiles.value = {
|
||||
..._openedFiles.value,
|
||||
_openedFiles.value = OpenedKdbxFiles({
|
||||
..._openedFiles.value._files,
|
||||
file: KdbxOpenedFile(
|
||||
fileSource: file,
|
||||
openedFile: openedFile,
|
||||
kdbxFile: kdbxFile,
|
||||
)
|
||||
};
|
||||
});
|
||||
analytics.events.trackOpenFile(type: file.typeDebug);
|
||||
analytics.events.trackOpenFile2(
|
||||
generator: kdbxFile.body.meta.generator.get() ?? 'NULL',
|
||||
@ -535,7 +581,8 @@ class KdbxBloc {
|
||||
_logger.fine('Close file.');
|
||||
analytics.events.trackCloseFile();
|
||||
final fileSource = fileForKdbxFile(file).fileSource;
|
||||
_openedFiles.value = Map.from(_openedFiles.value)..remove(fileSource);
|
||||
_openedFiles.value = OpenedKdbxFiles(
|
||||
Map.from(_openedFiles.value._files)..remove(fileSource));
|
||||
if (_openedFilesQuickUnlock.remove(fileSource)) {
|
||||
_logger.fine('file was in quick unlock. need to persist it.');
|
||||
await _updateQuickUnlockStore();
|
||||
@ -547,7 +594,7 @@ class KdbxBloc {
|
||||
void closeAllFiles() {
|
||||
_logger.finer('Closing all files, clearing quick unlock.');
|
||||
analytics.events.trackCloseAllFiles(count: _openedFiles.value?.length);
|
||||
_openedFiles.value = {};
|
||||
_openedFiles.value = OpenedKdbxFiles({});
|
||||
// clear all quick unlock data.
|
||||
_openedFilesQuickUnlock.clear();
|
||||
quickUnlockStorage.updateQuickUnlockFile({});
|
||||
@ -676,10 +723,11 @@ class KdbxBloc {
|
||||
openedFile: newOpenedFile,
|
||||
kdbxFile: oldFile.kdbxFile,
|
||||
);
|
||||
_openedFiles.value = {
|
||||
..._openedFiles.value,
|
||||
_openedFiles.value = OpenedKdbxFiles({
|
||||
...Map.fromEntries(_openedFiles.value._files.entries
|
||||
.where((entry) => entry.key != oldSource)),
|
||||
newFile.fileSource: newFile,
|
||||
}..removeWhere((key, value) => key == oldSource);
|
||||
});
|
||||
await _updateQuickUnlockStore();
|
||||
return newFile;
|
||||
}
|
||||
|
@ -206,13 +206,19 @@ abstract class CloudStorageProvider {
|
||||
String displayPath(Map<String, String> fileInfo) =>
|
||||
CloudStorageEntity.fromSimpleFileInfo(fileInfo).pathOrBaseName;
|
||||
|
||||
FileSource toFileSource(Map<String, String> fileInfo,
|
||||
{@required String uuid, FileContent initialCachedContent}) =>
|
||||
FileSource toFileSource(
|
||||
Map<String, String> fileInfo, {
|
||||
@required String uuid,
|
||||
FileContent initialCachedContent,
|
||||
String databaseName,
|
||||
}) =>
|
||||
FileSourceCloudStorage(
|
||||
provider: this,
|
||||
fileInfo: fileInfo,
|
||||
uuid: uuid,
|
||||
initialCachedContent: initialCachedContent);
|
||||
provider: this,
|
||||
fileInfo: fileInfo,
|
||||
uuid: uuid,
|
||||
databaseName: databaseName,
|
||||
initialCachedContent: initialCachedContent,
|
||||
);
|
||||
|
||||
Future<FileContent> loadFile(Map<String, String> fileInfo) =>
|
||||
loadEntity(CloudStorageEntity.fromSimpleFileInfo(fileInfo));
|
||||
|
@ -133,6 +133,10 @@ class _AuthPassAppState extends State<AuthPassApp> with StreamSubscriberMixin {
|
||||
updateShouldNotify: (a, b) => true,
|
||||
initialData: _deps.kdbxBloc,
|
||||
),
|
||||
StreamProvider<OpenedKdbxFiles>.value(
|
||||
value: _deps.kdbxBloc.openedFilesChanged,
|
||||
initialData: _deps.kdbxBloc.openedFilesChanged.value,
|
||||
)
|
||||
],
|
||||
child: MaterialApp(
|
||||
navigatorObservers: [AnalyticsNavigatorObserver(_deps.analytics)],
|
||||
|
@ -89,7 +89,7 @@ class AuthPassAboutDialog extends StatelessWidget {
|
||||
static Iterable<PopupMenuEntry<VoidCallback>> createDefaultPopupMenuItems(
|
||||
BuildContext context) {
|
||||
final openedFiles =
|
||||
Provider.of<KdbxBloc>(context, listen: false)?.openedFiles?.values;
|
||||
Provider.of<OpenedKdbxFiles>(context, listen: false).values;
|
||||
return [
|
||||
PopupMenuItem(
|
||||
child: const ListTile(
|
||||
|
@ -38,9 +38,12 @@ class ManageFileScreen extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// when changing the database name, we have to refresh the file source.
|
||||
final kdbxBloc = Provider.of<KdbxBloc>(context);
|
||||
final currentFileSource = kdbxBloc.fileForFileSource(fileSource).fileSource;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(fileSource.displayName),
|
||||
title: Text(currentFileSource.displayName),
|
||||
),
|
||||
body: ManageFile(fileSource: fileSource),
|
||||
);
|
||||
@ -106,7 +109,7 @@ class _ManageFileState extends State<ManageFile> with FutureTaskStateMixin {
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
title: Text(databaseName),
|
||||
trailing: Icon(Icons.edit),
|
||||
trailing: const Icon(Icons.edit),
|
||||
onTap: () async {
|
||||
final newName = await SimplePromptDialog(
|
||||
title: 'Enter database name',
|
||||
@ -115,6 +118,14 @@ class _ManageFileState extends State<ManageFile> with FutureTaskStateMixin {
|
||||
setState(() {
|
||||
_file.kdbxFile.body.meta.databaseName.set(newName);
|
||||
});
|
||||
await asyncRunTask((progress) async {
|
||||
await Future<int>.delayed(
|
||||
const Duration(milliseconds: 100));
|
||||
await _kdbxBloc.saveAs(
|
||||
_file,
|
||||
_file.fileSource.copyWithDatabaseName(newName),
|
||||
);
|
||||
}, label: 'Saving');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
|
@ -354,9 +354,11 @@ class _PasswordListContentState extends State<PasswordListContent>
|
||||
...AuthPassAboutDialog.createDefaultPopupMenuItems(context),
|
||||
PopupMenuItem(
|
||||
value: () {
|
||||
Provider.of<KdbxBloc>(context).closeAllFiles();
|
||||
Navigator.of(context)
|
||||
.pushAndRemoveUntil(SelectFileScreen.route(), (_) => false);
|
||||
Provider.of<KdbxBloc>(context, listen: false).closeAllFiles();
|
||||
Navigator.of(context, rootNavigator: true).pushAndRemoveUntil(
|
||||
SelectFileScreen.route(skipQuickUnlock: true),
|
||||
(_) => false,
|
||||
);
|
||||
},
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.exit_to_app),
|
||||
|
@ -33,11 +33,20 @@ import '../../theme.dart';
|
||||
final _logger = Logger('authpass.select_file_screen');
|
||||
|
||||
class SelectFileScreen extends StatelessWidget {
|
||||
static Route<Object> route() => MaterialPageRoute(
|
||||
const SelectFileScreen({Key key, this.skipQuickUnlock = false})
|
||||
: assert(skipQuickUnlock != null),
|
||||
super(key: key);
|
||||
|
||||
static Route<Object> route({bool skipQuickUnlock = false}) =>
|
||||
MaterialPageRoute(
|
||||
settings: const RouteSettings(name: '/selectFile'),
|
||||
builder: (context) => SelectFileScreen(),
|
||||
builder: (context) => SelectFileScreen(
|
||||
skipQuickUnlock: skipQuickUnlock,
|
||||
),
|
||||
);
|
||||
|
||||
final bool skipQuickUnlock;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Provider.of<Analytics>(context).events.trackLaunch();
|
||||
@ -53,7 +62,9 @@ class SelectFileScreen extends StatelessWidget {
|
||||
value: cloudBloc,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
child: const SelectFileWidget(),
|
||||
child: SelectFileWidget(
|
||||
skipQuickUnlock: skipQuickUnlock,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -128,8 +139,11 @@ class ProgressOverlay extends StatelessWidget {
|
||||
class SelectFileWidget extends StatefulWidget {
|
||||
const SelectFileWidget({
|
||||
Key key,
|
||||
this.skipQuickUnlock = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final bool skipQuickUnlock;
|
||||
|
||||
@override
|
||||
_SelectFileWidgetState createState() => _SelectFileWidgetState();
|
||||
}
|
||||
@ -142,10 +156,12 @@ class _SelectFileWidgetState extends State<SelectFileWidget>
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_logger.finer('didChangeDependencies');
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_checkQuickUnlock();
|
||||
});
|
||||
_logger.finer('didChangeDependencies ${widget.skipQuickUnlock}');
|
||||
if (!widget.skipQuickUnlock) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_checkQuickUnlock();
|
||||
});
|
||||
}
|
||||
// Future<int>.delayed(const Duration(seconds: 5))
|
||||
// .then((value) => _checkQuickUnlock());
|
||||
// _checkQuickUnlock();
|
||||
@ -155,7 +171,7 @@ class _SelectFileWidgetState extends State<SelectFileWidget>
|
||||
void didUpdateWidget(covariant SelectFileWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_logger.finer('didUpdateWidget --- ${oldWidget != widget}');
|
||||
if (oldWidget != widget) {
|
||||
if (oldWidget != widget && !widget.skipQuickUnlock) {
|
||||
_checkQuickUnlock();
|
||||
}
|
||||
}
|
||||
@ -210,147 +226,151 @@ class _SelectFileWidgetState extends State<SelectFileWidget>
|
||||
final cloudStorageBloc = Provider.of<CloudStorageBloc>(context);
|
||||
return ProgressOverlay(
|
||||
task: task,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
const Text('Please select a KeePass (.kdbx) file.'),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 16,
|
||||
runSpacing: 16,
|
||||
children: <Widget>[
|
||||
SelectFileAction(
|
||||
icon: FontAwesomeIcons.hdd,
|
||||
label: 'Open\nLocal File',
|
||||
onPressed: () async {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
final path =
|
||||
await FilePicker.getFilePath(type: FileType.any);
|
||||
if (path != null) {
|
||||
await Navigator.of(context).push(CredentialsScreen.route(
|
||||
FileSourceLocal(File(path),
|
||||
uuid: AppDataBloc.createUuid())));
|
||||
}
|
||||
} else {
|
||||
showOpenPanel((result, paths) async {
|
||||
if (result == FileChooserResult.ok) {
|
||||
String macOsBookmark;
|
||||
if (Platform.isMacOS) {
|
||||
macOsBookmark =
|
||||
await SecureBookmarks().bookmark(File(paths[0]));
|
||||
}
|
||||
await Navigator.of(context)
|
||||
.push(CredentialsScreen.route(FileSourceLocal(
|
||||
File(paths[0]),
|
||||
uuid: AppDataBloc.createUuid(),
|
||||
macOsSecureBookmark: macOsBookmark,
|
||||
)));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
...cloudStorageBloc.availableCloudStorage.map(
|
||||
(cs) => SelectFileAction(
|
||||
icon: cs.displayIcon,
|
||||
label: 'Load from ${cs.displayName}',
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 16),
|
||||
const Text('Please select a KeePass (.kdbx) file.'),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 16,
|
||||
runSpacing: 16,
|
||||
children: <Widget>[
|
||||
SelectFileAction(
|
||||
icon: FontAwesomeIcons.hdd,
|
||||
label: 'Open\nLocal File',
|
||||
onPressed: () async {
|
||||
final source = await Navigator.of(context).push(
|
||||
CloudStorageSelector.route(
|
||||
cs, CloudStorageOpenConfig()));
|
||||
if (source != null) {
|
||||
await Navigator.of(context)
|
||||
.push(CredentialsScreen.route(source.fileSource));
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
final path =
|
||||
await FilePicker.getFilePath(type: FileType.any);
|
||||
if (path != null) {
|
||||
await Navigator.of(context).push(
|
||||
CredentialsScreen.route(FileSourceLocal(File(path),
|
||||
uuid: AppDataBloc.createUuid())));
|
||||
}
|
||||
} else {
|
||||
showOpenPanel((result, paths) async {
|
||||
if (result == FileChooserResult.ok) {
|
||||
String macOsBookmark;
|
||||
if (Platform.isMacOS) {
|
||||
macOsBookmark = await SecureBookmarks()
|
||||
.bookmark(File(paths[0]));
|
||||
}
|
||||
await Navigator.of(context)
|
||||
.push(CredentialsScreen.route(FileSourceLocal(
|
||||
File(paths[0]),
|
||||
uuid: AppDataBloc.createUuid(),
|
||||
macOsSecureBookmark: macOsBookmark,
|
||||
)));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: LinkButton(
|
||||
onPressed: () async {
|
||||
final source = await showDialog<FileSourceUrl>(
|
||||
context: context,
|
||||
builder: (context) => SelectUrlDialog());
|
||||
if (source != null) {
|
||||
_loadAndGoToCredentials(source);
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
'Download from URL',
|
||||
textAlign: TextAlign.right,
|
||||
...cloudStorageBloc.availableCloudStorage.map(
|
||||
(cs) => SelectFileAction(
|
||||
icon: cs.displayIcon,
|
||||
label: 'Load from ${cs.displayName}',
|
||||
onPressed: () async {
|
||||
final source = await Navigator.of(context).push(
|
||||
CloudStorageSelector.route(
|
||||
cs, CloudStorageOpenConfig()));
|
||||
if (source != null) {
|
||||
await Navigator.of(context)
|
||||
.push(CredentialsScreen.route(source.fileSource));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: LinkButton(
|
||||
onPressed: () async {
|
||||
final source = await showDialog<FileSourceUrl>(
|
||||
context: context,
|
||||
builder: (context) => SelectUrlDialog());
|
||||
if (source != null) {
|
||||
_loadAndGoToCredentials(source);
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
'Download from URL',
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
VerticalDivider(
|
||||
indent: 8,
|
||||
endIndent: 8,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
Expanded(
|
||||
child: LinkButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(CreateFile.route());
|
||||
},
|
||||
icon: Icon(Icons.create_new_folder),
|
||||
child: const Expanded(
|
||||
child: Text(
|
||||
'New to KeePass?\nCreate New Password Database',
|
||||
softWrap: true)),
|
||||
VerticalDivider(
|
||||
indent: 8,
|
||||
endIndent: 8,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
IntrinsicWidth(
|
||||
stepWidth: 100,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Last opened files:',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText2
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
...ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: appData?.previousFiles?.reversed?.take(5)?.map(
|
||||
(f) => OpenedFileTile(
|
||||
openedFile: f.toFileSource(cloudStorageBloc),
|
||||
onPressed: () {
|
||||
final source =
|
||||
f.toFileSource(cloudStorageBloc);
|
||||
_loadAndGoToCredentials(source);
|
||||
},
|
||||
),
|
||||
) ??
|
||||
[const Text('No files have been opened yet.')]),
|
||||
Expanded(
|
||||
child: LinkButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(CreateFile.route());
|
||||
},
|
||||
icon: Icon(Icons.create_new_folder),
|
||||
child: const Expanded(
|
||||
child: Text(
|
||||
'New to KeePass?\nCreate New Password Database',
|
||||
softWrap: true)),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
IntrinsicWidth(
|
||||
stepWidth: 100,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Last opened files:',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText2
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
...ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: appData?.previousFiles?.reversed?.take(5)?.map(
|
||||
(f) => OpenedFileTile(
|
||||
openedFile:
|
||||
f.toFileSource(cloudStorageBloc),
|
||||
onPressed: () {
|
||||
final source =
|
||||
f.toFileSource(cloudStorageBloc);
|
||||
_loadAndGoToCredentials(source);
|
||||
},
|
||||
),
|
||||
) ??
|
||||
[const Text('No files have been opened yet.')]),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -713,7 +733,7 @@ class _CredentialsScreenState extends State<CredentialsScreen> {
|
||||
|
||||
Future<void> _tryUnlock() async {
|
||||
if (_formKey.currentState.validate()) {
|
||||
final kdbxBloc = Provider.of<Deps>(context).kdbxBloc;
|
||||
final kdbxBloc = Provider.of<Deps>(context, listen: false).kdbxBloc;
|
||||
final pw = _controller.text;
|
||||
final keyFileContents = await _keyFile?.readAsBytes();
|
||||
try {
|
||||
|
Reference in New Issue
Block a user