[client] Use user id and email

This commit is contained in:
Abhishek Shroff
2025-05-16 01:24:41 +05:30
parent 1d444ff943
commit 6be5fa3d5e
11 changed files with 204 additions and 156 deletions

View File

@@ -1,5 +1,6 @@
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/actions/changes/update_resource_permissions_change.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
import 'action_resource.dart';
@@ -19,7 +20,7 @@ class ResourceShareAction extends ResourceAction with JsonApiAction {
@override
Map<String, dynamic>? get requestBody => {
'path': '$resourceId:',
'username': username,
'email': userEmail,
'permission': permission,
};
@@ -27,7 +28,7 @@ class ResourceShareAction extends ResourceAction with JsonApiAction {
List<LocalChange> get localChanges => [
UpdateResourcePermissionsChange(
objectId: resourceId,
username: username,
userId: userId,
permission: permission,
timestamp: modified,
),
@@ -39,19 +40,22 @@ class ResourceShareAction extends ResourceAction with JsonApiAction {
@override
Map<String, dynamic> get props => {
'resourceId': resourceId,
'username': username,
'userId': userId,
'userEmail': userEmail,
'permission': permission,
'modified': modified.millisecondsSinceEpoch,
'description': description,
};
final String username;
final int userId;
final String userEmail;
final int permission;
final DateTime modified;
ResourceShareAction._({
required super.resourceId,
required this.username,
required this.userId,
required this.userEmail,
required this.permission,
required this.modified,
required this.description,
@@ -60,20 +64,22 @@ class ResourceShareAction extends ResourceAction with JsonApiAction {
ResourceShareAction({
required String resourceId,
required String resourceName,
required String username,
required User user,
required int permission,
}) : this._(
resourceId: resourceId,
username: username,
userId: user.id,
userEmail: user.email,
permission: permission,
modified: DateTime.now(),
description: 'Sharing $resourceName with $username',
description: 'Sharing $resourceName with ${user.displayName}',
);
static ResourceShareAction fromMap(Map<String, dynamic> map) {
return ResourceShareAction._(
resourceId: map['resourceId'],
username: map['username'],
userId: map['userId'],
userEmail: map['userEmail'],
permission: map['permission'],
modified: DateTime.fromMillisecondsSinceEpoch(map['modified']),
description: map['description'],
@@ -83,5 +89,5 @@ class ResourceShareAction extends ResourceAction with JsonApiAction {
@override
bool dependsOn(PhylumAction action) =>
(action is ResourceCreateAction && action.resourceId == resourceId) ||
(action is ResourceShareAction && action.resourceId == resourceId && action.username == username);
(action is ResourceShareAction && action.resourceId == resourceId && action.userEmail == userEmail);
}

View File

@@ -7,7 +7,7 @@ import 'package:phylum/libphylum/db/db.dart';
class UpdateResourcePermissionsChange extends LocalChange<Resource> {
@override
final String objectId;
final String username;
final int userId;
final int permission;
final DateTime timestamp;
@@ -16,7 +16,7 @@ class UpdateResourcePermissionsChange extends LocalChange<Resource> {
const UpdateResourcePermissionsChange({
required this.objectId,
required this.username,
required this.userId,
required this.permission,
required this.timestamp,
});
@@ -24,22 +24,22 @@ class UpdateResourcePermissionsChange extends LocalChange<Resource> {
@override
Resource? apply(Resource? data) {
if (data == null) return null;
var p = data.grants == null ? const <String, Map>{} : (jsonDecode(data.grants!) as Map).cast<String, Map>();
if ((p[username]?['p'] ?? 0) == permission) {
var p = data.grants == null ? const <int, Map>{} : (jsonDecode(data.grants!) as Map).cast<int, Map>();
if ((p[userId]?['p'] ?? 0) == permission) {
return data;
}
if (permission == 0) {
p.remove(username);
p.remove(userId);
} else if (p.isEmpty) {
// Empty means const map, so we can't add to it
p = {
username: <String, int>{
userId: <String, int>{
'p': permission,
't': DateTime.now().millisecondsSinceEpoch,
},
};
} else {
p[username] = <String, int>{
p[userId] = <String, int>{
'p': permission,
't': DateTime.now().millisecondsSinceEpoch,
};

View File

@@ -8,22 +8,27 @@ class Users extends Table with TableInfo<Users, User> {
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
static const VerificationMeta _usernameMeta =
const VerificationMeta('username');
late final GeneratedColumn<String> username = GeneratedColumn<String>(
'username', aliasedName, false,
static const VerificationMeta _idMeta = const VerificationMeta('id');
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL PRIMARY KEY');
static const VerificationMeta _emailMeta = const VerificationMeta('email');
late final GeneratedColumn<String> email = GeneratedColumn<String>(
'email', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL PRIMARY KEY');
static const VerificationMeta _displayMeta =
const VerificationMeta('display');
late final GeneratedColumn<String> display = GeneratedColumn<String>(
'display', aliasedName, false,
$customConstraints: 'NOT NULL');
static const VerificationMeta _displayNameMeta =
const VerificationMeta('displayName');
late final GeneratedColumn<String> displayName = GeneratedColumn<String>(
'display_name', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
@override
List<GeneratedColumn> get $columns => [username, display];
List<GeneratedColumn> get $columns => [id, email, displayName];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -34,31 +39,38 @@ class Users extends Table with TableInfo<Users, User> {
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('username')) {
context.handle(_usernameMeta,
username.isAcceptableOrUnknown(data['username']!, _usernameMeta));
} else if (isInserting) {
context.missing(_usernameMeta);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('display')) {
context.handle(_displayMeta,
display.isAcceptableOrUnknown(data['display']!, _displayMeta));
if (data.containsKey('email')) {
context.handle(
_emailMeta, email.isAcceptableOrUnknown(data['email']!, _emailMeta));
} else if (isInserting) {
context.missing(_displayMeta);
context.missing(_emailMeta);
}
if (data.containsKey('display_name')) {
context.handle(
_displayNameMeta,
displayName.isAcceptableOrUnknown(
data['display_name']!, _displayNameMeta));
} else if (isInserting) {
context.missing(_displayNameMeta);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {username};
Set<GeneratedColumn> get $primaryKey => {id};
@override
User map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return User(
username: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}username'])!,
display: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}display'])!,
id: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
email: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}email'])!,
displayName: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}display_name'])!,
);
}
@@ -72,21 +84,25 @@ class Users extends Table with TableInfo<Users, User> {
}
class User extends DataClass implements Insertable<User> {
final String username;
final String display;
const User({required this.username, required this.display});
final int id;
final String email;
final String displayName;
const User(
{required this.id, required this.email, required this.displayName});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['username'] = Variable<String>(username);
map['display'] = Variable<String>(display);
map['id'] = Variable<int>(id);
map['email'] = Variable<String>(email);
map['display_name'] = Variable<String>(displayName);
return map;
}
UsersCompanion toCompanion(bool nullToAbsent) {
return UsersCompanion(
username: Value(username),
display: Value(display),
id: Value(id),
email: Value(email),
displayName: Value(displayName),
);
}
@@ -94,96 +110,103 @@ class User extends DataClass implements Insertable<User> {
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return User(
username: serializer.fromJson<String>(json['username']),
display: serializer.fromJson<String>(json['display']),
id: serializer.fromJson<int>(json['id']),
email: serializer.fromJson<String>(json['email']),
displayName: serializer.fromJson<String>(json['display_name']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'username': serializer.toJson<String>(username),
'display': serializer.toJson<String>(display),
'id': serializer.toJson<int>(id),
'email': serializer.toJson<String>(email),
'display_name': serializer.toJson<String>(displayName),
};
}
User copyWith({String? username, String? display}) => User(
username: username ?? this.username,
display: display ?? this.display,
User copyWith({int? id, String? email, String? displayName}) => User(
id: id ?? this.id,
email: email ?? this.email,
displayName: displayName ?? this.displayName,
);
User copyWithCompanion(UsersCompanion data) {
return User(
username: data.username.present ? data.username.value : this.username,
display: data.display.present ? data.display.value : this.display,
id: data.id.present ? data.id.value : this.id,
email: data.email.present ? data.email.value : this.email,
displayName:
data.displayName.present ? data.displayName.value : this.displayName,
);
}
@override
String toString() {
return (StringBuffer('User(')
..write('username: $username, ')
..write('display: $display')
..write('id: $id, ')
..write('email: $email, ')
..write('displayName: $displayName')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(username, display);
int get hashCode => Object.hash(id, email, displayName);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is User &&
other.username == this.username &&
other.display == this.display);
other.id == this.id &&
other.email == this.email &&
other.displayName == this.displayName);
}
class UsersCompanion extends UpdateCompanion<User> {
final Value<String> username;
final Value<String> display;
final Value<int> rowid;
final Value<int> id;
final Value<String> email;
final Value<String> displayName;
const UsersCompanion({
this.username = const Value.absent(),
this.display = const Value.absent(),
this.rowid = const Value.absent(),
this.id = const Value.absent(),
this.email = const Value.absent(),
this.displayName = const Value.absent(),
});
UsersCompanion.insert({
required String username,
required String display,
this.rowid = const Value.absent(),
}) : username = Value(username),
display = Value(display);
this.id = const Value.absent(),
required String email,
required String displayName,
}) : email = Value(email),
displayName = Value(displayName);
static Insertable<User> custom({
Expression<String>? username,
Expression<String>? display,
Expression<int>? rowid,
Expression<int>? id,
Expression<String>? email,
Expression<String>? displayName,
}) {
return RawValuesInsertable({
if (username != null) 'username': username,
if (display != null) 'display': display,
if (rowid != null) 'rowid': rowid,
if (id != null) 'id': id,
if (email != null) 'email': email,
if (displayName != null) 'display_name': displayName,
});
}
UsersCompanion copyWith(
{Value<String>? username, Value<String>? display, Value<int>? rowid}) {
{Value<int>? id, Value<String>? email, Value<String>? displayName}) {
return UsersCompanion(
username: username ?? this.username,
display: display ?? this.display,
rowid: rowid ?? this.rowid,
id: id ?? this.id,
email: email ?? this.email,
displayName: displayName ?? this.displayName,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (username.present) {
map['username'] = Variable<String>(username.value);
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (display.present) {
map['display'] = Variable<String>(display.value);
if (email.present) {
map['email'] = Variable<String>(email.value);
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
if (displayName.present) {
map['display_name'] = Variable<String>(displayName.value);
}
return map;
}
@@ -191,9 +214,9 @@ class UsersCompanion extends UpdateCompanion<User> {
@override
String toString() {
return (StringBuffer('UsersCompanion(')
..write('username: $username, ')
..write('display: $display, ')
..write('rowid: $rowid')
..write('id: $id, ')
..write('email: $email, ')
..write('displayName: $displayName')
..write(')'))
.toString();
}
@@ -2325,14 +2348,14 @@ abstract class _$AppDatabase extends GeneratedDatabase {
}
typedef $UsersCreateCompanionBuilder = UsersCompanion Function({
required String username,
required String display,
Value<int> rowid,
Value<int> id,
required String email,
required String displayName,
});
typedef $UsersUpdateCompanionBuilder = UsersCompanion Function({
Value<String> username,
Value<String> display,
Value<int> rowid,
Value<int> id,
Value<String> email,
Value<String> displayName,
});
class $UsersFilterComposer extends Composer<_$AppDatabase, Users> {
@@ -2343,11 +2366,14 @@ class $UsersFilterComposer extends Composer<_$AppDatabase, Users> {
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnFilters<String> get username => $composableBuilder(
column: $table.username, builder: (column) => ColumnFilters(column));
ColumnFilters<int> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get display => $composableBuilder(
column: $table.display, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get email => $composableBuilder(
column: $table.email, builder: (column) => ColumnFilters(column));
ColumnFilters<String> get displayName => $composableBuilder(
column: $table.displayName, builder: (column) => ColumnFilters(column));
}
class $UsersOrderingComposer extends Composer<_$AppDatabase, Users> {
@@ -2358,11 +2384,14 @@ class $UsersOrderingComposer extends Composer<_$AppDatabase, Users> {
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnOrderings<String> get username => $composableBuilder(
column: $table.username, builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get id => $composableBuilder(
column: $table.id, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get display => $composableBuilder(
column: $table.display, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get email => $composableBuilder(
column: $table.email, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get displayName => $composableBuilder(
column: $table.displayName, builder: (column) => ColumnOrderings(column));
}
class $UsersAnnotationComposer extends Composer<_$AppDatabase, Users> {
@@ -2373,11 +2402,14 @@ class $UsersAnnotationComposer extends Composer<_$AppDatabase, Users> {
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
GeneratedColumn<String> get username =>
$composableBuilder(column: $table.username, builder: (column) => column);
GeneratedColumn<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get display =>
$composableBuilder(column: $table.display, builder: (column) => column);
GeneratedColumn<String> get email =>
$composableBuilder(column: $table.email, builder: (column) => column);
GeneratedColumn<String> get displayName => $composableBuilder(
column: $table.displayName, builder: (column) => column);
}
class $UsersTableManager extends RootTableManager<
@@ -2403,24 +2435,24 @@ class $UsersTableManager extends RootTableManager<
createComputedFieldComposer: () =>
$UsersAnnotationComposer($db: db, $table: table),
updateCompanionCallback: ({
Value<String> username = const Value.absent(),
Value<String> display = const Value.absent(),
Value<int> rowid = const Value.absent(),
Value<int> id = const Value.absent(),
Value<String> email = const Value.absent(),
Value<String> displayName = const Value.absent(),
}) =>
UsersCompanion(
username: username,
display: display,
rowid: rowid,
id: id,
email: email,
displayName: displayName,
),
createCompanionCallback: ({
required String username,
required String display,
Value<int> rowid = const Value.absent(),
Value<int> id = const Value.absent(),
required String email,
required String displayName,
}) =>
UsersCompanion.insert(
username: username,
display: display,
rowid: rowid,
id: id,
email: email,
displayName: displayName,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))

View File

@@ -1,4 +1,5 @@
CREATE TABLE IF NOT EXISTS users(
username TEXT NOT NULL PRIMARY KEY,
display TEXT NOT NULL
id INTEGER NOT NULL PRIMARY KEY,
email TEXT NOT NULL,
display_name TEXT NOT NULL
);

View File

@@ -13,7 +13,7 @@ import 'package:phylum/util/logging.dart';
const _persistKeyAccessToken = 'accessToken';
const _persistKeyUserId = 'userId';
const _persistKeyUserEmail = 'userEmail';
const _persistKeyUserName = 'userDisplayName';
const _persistKeyUserDisplayName = 'userDisplayName';
const _persistKeyUserHome = 'userHome';
const _persistKeyUserPermissions = 'userPermissions';
@@ -54,9 +54,9 @@ class PhylumAccount extends Account<PhylumAccount> {
persist(_persistKeyUserEmail, value);
}
String get userName => getPersisted(_persistKeyUserName);
set userName(String value) {
persist(_persistKeyUserName, value);
String get userDisplayName => getPersisted(_persistKeyUserDisplayName);
set userDisplayName(String value) {
persist(_persistKeyUserDisplayName, value);
}
String get userHome => getPersisted(_persistKeyUserHome);
@@ -121,9 +121,11 @@ class PhylumAccount extends Account<PhylumAccount> {
await account.initialized;
final userMap = response['user'];
account.userEmail = userMap['username'];
account.userName = userMap['display'];
account.userEmail = userMap['email'];
account.userDisplayName = userMap['display_name'];
account.userId = userMap['id'];
account.userHome = userMap['home'];
account.userPermissions = userMap['permissions'];
return account;
}

View File

@@ -7,8 +7,8 @@ import 'package:phylum/libphylum/responses/responses.dart';
class UserRepository {
final PhylumAccount account;
Map<String, User> _users = const {};
Map<String, User> get users => _users;
Map<int, User> _users = const {};
Map<int, User> get users => _users;
UserRepository({required this.account});
@@ -17,7 +17,7 @@ class UserRepository {
await account.db.users
.select()
.get()
.then((users) => _users = Map.unmodifiable(Map.fromIterable(users, key: (u) => u.username)));
.then((users) => _users = Map.unmodifiable(Map<int, User>.fromIterable(users, key: (u) => u.id)));
}
Future<ApiResult> refresh() async {
@@ -25,12 +25,12 @@ class UserRepository {
UsersListRequest(), (request, response) => parseJsonMapResponse(response, UserListResponse.fromResponse),
callback: (request, response) async {
if (response is UserListResponse) {
_users = Map.unmodifiable(Map.fromIterable(response.users, key: (u) => u.username));
_users = Map.unmodifiable(Map<int, User>.fromIterable(response.users, key: (u) => u.id));
}
});
}
String getUserDisplayName(String username) {
return _users[username]?.display ?? username;
String getUserDisplayName(int userId) {
return _users[userId]?.displayName ?? 'Unknown User ($userId)';
}
}

View File

@@ -40,7 +40,7 @@ class ResourceResponse extends PhylumApiSuccessResponse {
}
void _updateUserPermissions(PhylumAccount account) {
_resource = _resource.copyWith(userPermission: permissions[account.userEmail] ?? 0);
_resource = _resource.copyWith(userPermission: permissions[account.userId.toString()] ?? 0);
}
@override

View File

@@ -14,8 +14,9 @@ class UserListResponse extends PhylumApiSuccessResponse {
factory UserListResponse.fromResponse(Map<String, dynamic> data) {
final users = (data["users"] as List).cast<Map>().map((u) => User(
username: u['username'],
display: u['display'],
id: u['id'],
email: u['email'],
displayName: u['display_name'],
));
return UserListResponse(users: users);
}

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:drift/drift.dart' show TableOrViewStatements;
import 'package:flutter/material.dart';
import 'package:phylum/libphylum/actions/action_resource_share.dart';
import 'package:phylum/libphylum/db/resource_helpers.dart';
@@ -19,8 +20,8 @@ class ResourcePermissionsView extends StatefulWidget {
class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
String? resourceName;
Map<String, Permission>? permissions;
Map<String, Permission>? grants;
Map<int, Permission>? permissions;
Map<int, Permission>? grants;
Permission? userPermission;
StreamSubscription? sub;
@@ -75,11 +76,11 @@ class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
final users =
account.userRepository.users.entries.where((e) => !grants.containsKey(e.key)).map((e) => e.value);
final user = await showOptionsDialogBuilder(
context, users.toList(growable: false), (user) => ListTile(title: Text(user.display)),
context, users.toList(growable: false), (user) => ListTile(title: Text(user.displayName)),
filterList: (u, q) {
final query = q.toLowerCase();
return u
.where((u) => u.username.contains(query) || u.display.contains(query))
.where((u) => u.email.contains(query) || u.displayName.contains(query))
.toList(growable: false);
});
if (user == null || !context.mounted) return;
@@ -96,7 +97,7 @@ class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
account.addAction(ResourceShareAction(
resourceId: widget.resourceId,
resourceName: resourceName!,
username: user.username,
user: user,
permission: permission,
));
},
@@ -112,15 +113,19 @@ class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
value: e.value,
onChanged: (userPermission! & permissionShare) == 0
? null
: (value) {
: (value) async {
if (value == null || value == e.value) return;
if (value == permissions[e.key]) {
value = 0;
}
final user =
await (account.db.users.select()..where((u) => u.id.equals(e.key))).getSingleOrNull();
if (user == null) return;
account.addAction(ResourceShareAction(
resourceId: widget.resourceId,
resourceName: resourceName!,
username: e.key,
user: user,
permission: value,
));
},

View File

@@ -21,7 +21,7 @@ class LoginPage extends StatefulWidget {
class _LoginPageState extends State<LoginPage> {
final Client _client = Client();
String _username = '';
String _email = '';
String _password = '';
Uri? serverUrl = kIsWeb ? Uri() : null;
String? urlError;
@@ -35,7 +35,7 @@ class _LoginPageState extends State<LoginPage> {
super.initState();
}
void _performLogin(BuildContext context, String username, String password) async {
void _performLogin(BuildContext context, String email, String password) async {
final serverUrl = this.serverUrl;
if (!serverUrlValid) return;
final builder = serverUrl == null ? UriBuilder() : UriBuilder.fromUri(serverUrl);
@@ -49,7 +49,7 @@ class _LoginPageState extends State<LoginPage> {
);
builder.path = '${builder.path}/api/v1/auth/password';
final request = MultipartRequest('post', builder.build());
request.fields['username'] = username;
request.fields['email'] = email;
request.fields['password'] = password;
try {
@@ -124,12 +124,12 @@ class _LoginPageState extends State<LoginPage> {
),
TextField(
decoration: const InputDecoration(
label: Text('Username'),
label: Text('Email'),
),
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
setState(() {
_username = value.trim();
_email = value.trim();
});
},
),
@@ -145,15 +145,15 @@ class _LoginPageState extends State<LoginPage> {
});
},
onSubmitted: (value) {
_performLogin(context, _username, _password);
_performLogin(context, _email, _password);
},
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: ElevatedButton(
onPressed: serverUrlValid && _username.isNotEmpty
onPressed: serverUrlValid && _email.isNotEmpty
? () async {
_performLogin(context, _username, _password);
_performLogin(context, _email, _password);
}
: null,
child: const Text('Login'),

View File

@@ -13,8 +13,9 @@ const Permission permissionSetReadWrite = permissionRead | permissionWrite;
const Permission permissionSetReadWriteShare = permissionRead | permissionWrite | permissionShare;
extension PermissionMapper on String? {
Map<String, Permission> parsePermissionMap() =>
this == null ? {} : (jsonDecode(this!) as Map).cast<String, Permission>();
Map<int, Permission> parsePermissionMap() => this == null
? {}
: (jsonDecode(this!) as Map).cast<String, Permission>().map((k, v) => MapEntry(int.parse(k), v));
// Map<String, Permission> parseGrantsMap() => this == null
// ? {}