Init repo

This commit is contained in:
PartyDonut 2024-09-15 14:12:28 +02:00
commit 764b6034e3
566 changed files with 212335 additions and 0 deletions

View file

@ -0,0 +1,145 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:fladder/models/account_model.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/login/widgets/login_icon.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/screens/shared/passcode_input.dart';
import 'package:fladder/util/auth_service.dart';
final lockScreenActiveProvider = StateProvider<bool>((ref) => false);
class LockScreen extends ConsumerStatefulWidget {
final bool selfLock;
const LockScreen({this.selfLock = false, super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _LockScreenState();
}
class _LockScreenState extends ConsumerState<LockScreen> with WidgetsBindingObserver {
bool poppingLockScreen = false;
@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.resumed:
hackyFixForBlackNavbar();
default:
break;
}
}
void hackyFixForBlackNavbar() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent,
));
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
Future.microtask(() {
ref.read(lockScreenActiveProvider.notifier).update((state) => true);
final user = ref.read(userProvider);
if (user != null && !widget.selfLock) {
tapLoggedInAccount(user);
}
});
hackyFixForBlackNavbar();
}
void handleLogin(AccountModel user) {
ref.read(lockScreenActiveProvider.notifier).update((state) => false);
poppingLockScreen = true;
context.pop();
}
void tapLoggedInAccount(AccountModel user) async {
switch (user.authMethod) {
case Authentication.autoLogin:
handleLogin(user);
break;
case Authentication.biometrics:
final authenticated = await AuthService.authenticateUser(context, user);
if (authenticated && context.mounted) {
handleLogin(user);
}
break;
case Authentication.passcode:
if (context.mounted) {
showPassCodeDialog(context, (newPin) {
if (newPin == user.localPin) {
handleLogin(user);
} else {
fladderSnackbar(context, title: context.localized.incorrectPinTryAgain);
}
});
}
break;
case Authentication.none:
handleLogin(user);
break;
}
}
@override
Widget build(BuildContext context) {
final user = ref.watch(userProvider);
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
if (!poppingLockScreen) {
SystemNavigator.pop();
}
},
child: Scaffold(
body: Center(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
children: [
const Icon(
IconsaxOutline.lock_1,
size: 38,
),
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: 400,
maxWidth: 400,
),
child: Padding(
padding: const EdgeInsets.all(64.0),
child: LoginIcon(
user: user!,
onPressed: () => tapLoggedInAccount(user),
),
),
),
ElevatedButton.icon(
onPressed: () {
ref.read(lockScreenActiveProvider.notifier).update((state) => false);
context.routeGo(LoginRoute());
},
icon: const Icon(Icons.login_rounded),
label: Text(context.localized.login),
),
],
),
),
),
);
}
}

View file

@ -0,0 +1,99 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/models/account_model.dart';
import 'package:fladder/providers/auth_provider.dart';
import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
class LoginEditUser extends ConsumerWidget {
final AccountModel user;
final ValueChanged<String>? onTapServer;
const LoginEditUser({required this.user, this.onTapServer, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return AlertDialog.adaptive(
title: Center(child: Text(user.name)),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Divider(),
if (user.credentials.serverName.isNotEmpty)
Row(
children: [
const Icon(Icons.dns_rounded),
const SizedBox(width: 8),
Text(user.credentials.serverName),
],
),
if (user.credentials.server.isNotEmpty)
Row(
children: [
const Icon(Icons.http_rounded),
const SizedBox(width: 8),
Text(user.credentials.server),
if (onTapServer != null) ...{
const SizedBox(width: 8),
IconButton.filledTonal(
onPressed: () {
onTapServer?.call(user.credentials.server);
},
icon: const Icon(
Icons.send_rounded,
),
)
}
],
),
Row(
children: [
Icon(user.authMethod.icon),
const SizedBox(width: 8),
Text(user.authMethod.name(context)),
],
),
Row(
children: [
Icon(IconsaxBold.clock),
const SizedBox(width: 8),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(DateFormat.yMMMEd().format(user.lastUsed)),
Text(DateFormat.Hms().format(user.lastUsed)),
],
),
],
),
const Divider(),
Tooltip(
message: "Removes the user and forces a logout",
waitDuration: const Duration(milliseconds: 500),
child: SizedBox(
height: 50,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
onPressed: () async {
await ref.read(sharedUtilityProvider).removeAccount(user);
ref.read(authProvider.notifier).getSavedAccounts();
if (context.mounted) {
Navigator.of(context).pop();
}
},
icon: const Icon(Icons.remove_rounded),
label: const Text("Remove user"),
),
),
),
].addPadding(const EdgeInsets.symmetric(vertical: 8)),
),
);
}
}

View file

@ -0,0 +1,393 @@
import 'dart:async';
import 'dart:developer';
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/screens/login/lock_screen.dart';
import 'package:fladder/screens/login/widgets/discover_servers_widget.dart';
import 'package:fladder/screens/shared/fladder_logo.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:fladder/models/account_model.dart';
import 'package:fladder/providers/auth_provider.dart';
import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/home_routes.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/login/login_edit_user.dart';
import 'package:fladder/screens/login/login_user_grid.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/screens/shared/outlined_text_field.dart';
import 'package:fladder/screens/shared/passcode_input.dart';
import 'package:fladder/util/auth_service.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:fladder/widgets/navigation_scaffold/components/fladder_appbar.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginScreen> {
List<AccountModel> users = const [];
bool loading = false;
String? invalidUrl = "";
bool startCheckingForErrors = false;
bool addingNewUser = false;
bool editingUsers = false;
late final TextEditingController serverTextController = TextEditingController(text: "");
final usernameController = TextEditingController();
final passwordController = TextEditingController();
final FocusNode focusNode = FocusNode();
void startAddingNewUser() {
setState(() {
addingNewUser = true;
editingUsers = false;
});
}
@override
void initState() {
super.initState();
Future.microtask(() {
ref.read(userProvider.notifier).clear();
final currentAccounts = ref.read(authProvider.notifier).getSavedAccounts();
addingNewUser = currentAccounts.isEmpty;
ref.read(lockScreenActiveProvider.notifier).update((state) => true);
});
}
@override
Widget build(BuildContext context) {
final loggedInUsers = ref.watch(authProvider.select((value) => value.accounts));
final authLoading = ref.watch(authProvider.select((value) => value.loading));
return Scaffold(
appBar: const FladderAppbar(),
floatingActionButton: !addingNewUser
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (!AdaptiveLayout.of(context).isDesktop)
FloatingActionButton(
key: Key("edit_button"),
child: Icon(IconsaxOutline.edit_2),
onPressed: () => setState(() => editingUsers = !editingUsers),
),
FloatingActionButton(
key: Key("new_button"),
child: Icon(IconsaxOutline.add_square),
onPressed: startAddingNewUser,
),
].addInBetween(const SizedBox(width: 16)),
)
: null,
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 900),
child: ListView(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 32),
children: [
Center(
child: FladderLogo(),
),
AnimatedFadeSize(
child: addingNewUser
? addUserFields(loggedInUsers, authLoading)
: Column(
key: UniqueKey(),
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
LoginUserGrid(
users: loggedInUsers,
editMode: editingUsers,
onPressed: (user) async => tapLoggedInAccount(user),
onLongPress: (user) => openUserEditDialogue(context, user),
),
],
),
),
].addPadding(const EdgeInsets.symmetric(vertical: 16)),
),
),
),
);
}
void _parseUrl(String url) {
setState(() {
ref.read(authProvider.notifier).setServer("");
users = [];
if (url.isEmpty) {
invalidUrl = "";
return;
}
if (!Uri.parse(url).isAbsolute) {
invalidUrl = context.localized.invalidUrl;
return;
}
if (!url.startsWith('https://') && !url.startsWith('http://')) {
invalidUrl = context.localized.invalidUrlDesc;
return;
}
invalidUrl = null;
if (invalidUrl == null) {
ref.read(authProvider.notifier).setServer(url.rtrim('/'));
}
});
}
void openUserEditDialogue(BuildContext context, AccountModel user) {
showDialog(
context: context,
builder: (context) => LoginEditUser(
user: user,
onTapServer: (value) {
setState(() {
_parseUrl(value);
serverTextController.text = value;
startAddingNewUser();
});
context.pop();
},
),
);
}
void tapLoggedInAccount(AccountModel user) async {
switch (user.authMethod) {
case Authentication.autoLogin:
handleLogin(user);
break;
case Authentication.biometrics:
final authenticated = await AuthService.authenticateUser(context, user);
if (authenticated) {
handleLogin(user);
}
break;
case Authentication.passcode:
if (context.mounted) {
showPassCodeDialog(context, (newPin) {
if (newPin == user.localPin) {
handleLogin(user);
} else {
fladderSnackbar(context, title: context.localized.incorrectPinTryAgain);
}
});
}
break;
case Authentication.none:
handleLogin(user);
break;
}
}
Future<void> handleLogin(AccountModel user) async {
await ref.read(authProvider.notifier).switchUser();
await ref.read(sharedUtilityProvider).updateAccountInfo(user.copyWith(
lastUsed: DateTime.now(),
));
ref.read(userProvider.notifier).updateUser(user.copyWith(lastUsed: DateTime.now()));
loggedInGoToHome();
}
void loggedInGoToHome() {
ref.read(lockScreenActiveProvider.notifier).update((state) => false);
if (context.mounted) {
context.routeGo(DashboardRoute());
}
}
Future<Null> Function()? get enterCredentialsTryLogin => emptyFields()
? null
: () async {
log('try login');
serverTextController.text = serverTextController.text.rtrim('/');
ref.read(authProvider.notifier).setServer(serverTextController.text.rtrim('/'));
final response = await ref.read(authProvider.notifier).authenticateByName(
usernameController.text,
passwordController.text,
);
if (response?.isSuccessful == false) {
fladderSnackbar(context,
title:
"(${response?.base.statusCode}) ${response?.base.reasonPhrase ?? context.localized.somethingWentWrongPasswordCheck}");
} else if (response?.body != null) {
loggedInGoToHome();
}
};
bool emptyFields() {
return usernameController.text.isEmpty || passwordController.text.isEmpty;
}
void retrieveListOfUsers() async {
serverTextController.text = serverTextController.text.rtrim('/');
ref.read(authProvider.notifier).setServer(serverTextController.text);
setState(() => loading = true);
final response = await ref.read(authProvider.notifier).getPublicUsers();
if ((response == null || response.isSuccessful == false) && context.mounted) {
fladderSnackbar(context, title: response?.base.reasonPhrase ?? context.localized.unableToConnectHost);
setState(() => startCheckingForErrors = true);
}
if (response?.body?.isEmpty == true) {
await Future.delayed(const Duration(seconds: 1));
}
setState(() {
users = response?.body ?? [];
loading = false;
});
}
Widget addUserFields(List<AccountModel> accounts, bool authLoading) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (accounts.isNotEmpty)
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton.filledTonal(
onPressed: () {
setState(() {
addingNewUser = false;
loading = false;
startCheckingForErrors = false;
serverTextController.text = "";
usernameController.text = "";
passwordController.text = "";
invalidUrl = "";
});
ref.read(authProvider.notifier).setServer("");
},
icon: const Icon(
IconsaxOutline.arrow_left_2,
),
),
),
Flexible(
child: OutlinedTextField(
controller: serverTextController,
onChanged: _parseUrl,
onSubmitted: (value) => retrieveListOfUsers(),
autoFillHints: const [AutofillHints.url],
keyboardType: TextInputType.url,
textInputAction: TextInputAction.go,
label: context.localized.server,
errorText: (invalidUrl == null || serverTextController.text.isEmpty || !startCheckingForErrors)
? null
: invalidUrl,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Tooltip(
message: context.localized.retrievePublicListOfUsers,
waitDuration: const Duration(seconds: 1),
child: IconButton.filled(
onPressed: () => retrieveListOfUsers(),
icon: const Icon(
IconsaxOutline.refresh,
),
),
),
),
],
),
AnimatedFadeSize(
child: invalidUrl == null
? Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (loading || users.isNotEmpty)
AnimatedFadeSize(
duration: const Duration(milliseconds: 250),
child: loading
? CircularProgressIndicator(key: UniqueKey(), strokeCap: StrokeCap.round)
: LoginUserGrid(
users: users,
onPressed: (value) {
usernameController.text = value.name;
passwordController.text = "";
focusNode.requestFocus();
},
),
),
AutofillGroup(
child: Column(
children: [
OutlinedTextField(
controller: usernameController,
autoFillHints: const [AutofillHints.username],
textInputAction: TextInputAction.next,
onChanged: (value) => setState(() {}),
label: context.localized.userName,
),
OutlinedTextField(
controller: passwordController,
autoFillHints: const [AutofillHints.password],
keyboardType: TextInputType.visiblePassword,
focusNode: focusNode,
textInputAction: TextInputAction.send,
onSubmitted: (value) => enterCredentialsTryLogin?.call(),
onChanged: (value) => setState(() {}),
label: context.localized.password,
),
FilledButton(
onPressed: enterCredentialsTryLogin,
child: authLoading
? SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
color: Theme.of(context).colorScheme.inversePrimary,
strokeCap: StrokeCap.round),
)
: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(context.localized.login),
const SizedBox(width: 8),
Icon(IconsaxBold.send_1),
],
),
),
].addPadding(const EdgeInsets.symmetric(vertical: 4)),
),
),
].addPadding(const EdgeInsets.symmetric(vertical: 10)),
)
: DiscoverServersWidget(
serverCredentials: accounts.map((e) => e.credentials).toList(),
onPressed: (server) {
serverTextController.text = server.address;
_parseUrl(server.address);
retrieveListOfUsers();
},
),
)
].addPadding(const EdgeInsets.symmetric(vertical: 8)),
);
}
}

View file

@ -0,0 +1,149 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/models/account_model.dart';
import 'package:fladder/providers/auth_provider.dart';
import 'package:fladder/screens/shared/user_icon.dart';
import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:reorderable_grid/reorderable_grid.dart';
class LoginUserGrid extends ConsumerWidget {
final List<AccountModel> users;
final bool editMode;
final ValueChanged<AccountModel>? onPressed;
final ValueChanged<AccountModel>? onLongPress;
const LoginUserGrid({this.users = const [], this.onPressed, this.editMode = false, this.onLongPress, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final mainAxisExtent = 175.0;
final maxCount = (MediaQuery.of(context).size.width ~/ mainAxisExtent).clamp(1, 3);
return ReorderableGridView.builder(
onReorder: (oldIndex, newIndex) => ref.read(authProvider.notifier).reOrderUsers(oldIndex, newIndex),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
autoScroll: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: users.length == 1 ? 1 : maxCount,
mainAxisSpacing: 24,
crossAxisSpacing: 24,
mainAxisExtent: mainAxisExtent,
),
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return _CardHolder(
key: Key(user.id),
content: Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisSize: MainAxisSize.max,
children: [
Flexible(
child: UserIcon(
labelStyle: Theme.of(context).textTheme.headlineMedium,
user: user,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Icon(
user.authMethod.icon,
size: 18,
),
const SizedBox(width: 4),
Flexible(
child: Text(
user.name,
maxLines: 2,
softWrap: true,
)),
],
),
if (user.credentials.serverName.isNotEmpty)
Opacity(
opacity: 0.75,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
const Icon(
IconsaxBold.driver_2,
size: 14,
),
const SizedBox(width: 4),
Flexible(
child: Text(
user.credentials.serverName,
maxLines: 2,
softWrap: true,
),
),
],
),
)
].addInBetween(SizedBox(width: 4, height: 4)),
),
if (editMode)
Align(
alignment: Alignment.topRight,
child: Card(
color: Theme.of(context).colorScheme.errorContainer,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: const Icon(
IconsaxBold.edit_2,
size: 14,
),
),
),
)
],
),
onTap: () => editMode ? onLongPress?.call(user) : onPressed?.call(user),
onLongPress: () => onLongPress?.call(user),
);
},
);
}
}
class _CardHolder extends StatelessWidget {
final Widget content;
final Function() onTap;
final Function() onLongPress;
const _CardHolder({
required this.content,
required this.onTap,
required this.onLongPress,
super.key,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 1,
shadowColor: Colors.transparent,
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.zero,
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 150, maxWidth: 150),
child: FlatButton(
onTap: onTap,
onLongPress: AdaptiveLayout.of(context).isDesktop ? onLongPress : null,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: content,
),
),
),
);
}
}

View file

@ -0,0 +1,161 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/models/credentials_model.dart';
import 'package:fladder/providers/discovery_provider.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/theme_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class DiscoverServersWidget extends ConsumerWidget {
final List<CredentialsModel> serverCredentials;
final Function(DiscoveryInfo server) onPressed;
const DiscoverServersWidget({
required this.serverCredentials,
required this.onPressed,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final existingServers = serverCredentials
.map(
(credentials) => DiscoveryInfo(
id: credentials.serverId,
name: credentials.serverName,
address: credentials.server,
endPointAddress: null),
)
.toSet()
.toList();
final discoverdServersStream = ref.watch(serverDiscoveryProvider);
return ListView(
padding: const EdgeInsets.all(6),
shrinkWrap: true,
children: [
if (existingServers.isNotEmpty) ...[
Row(
children: [
Text(
context.localized.saved,
style: context.textTheme.bodyLarge,
),
const Spacer(),
Opacity(opacity: 0.65, child: Icon(IconsaxOutline.bookmark, size: 16)),
],
),
const SizedBox(height: 4),
...existingServers
.map(
(server) => _ServerInfoCard(
server: server,
onPressed: onPressed,
),
)
.toList()
.addInBetween(const SizedBox(height: 4)),
const Divider(),
],
Row(
children: [
Text(
context.localized.discovered,
style: context.textTheme.bodyLarge,
),
const Spacer(),
Opacity(opacity: 0.65, child: Icon(IconsaxBold.airdrop, size: 16)),
],
),
const SizedBox(height: 4),
discoverdServersStream.when(
data: (data) {
final servers = data.where((discoverdServer) => !existingServers.contains(discoverdServer));
return servers.isNotEmpty
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...servers.map(
(serverInfo) => _ServerInfoCard(
server: serverInfo,
onPressed: onPressed,
),
)
].toList().addInBetween(const SizedBox(height: 4)),
)
: Center(
child: Opacity(
opacity: 0.65,
child: Text(
context.localized.noServersFound,
style: context.textTheme.bodyLarge,
),
));
},
error: (error, stackTrace) => Text(context.localized.error),
loading: () => Center(
child: SizedBox.square(
dimension: 24.0,
child: CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round),
),
),
),
const SizedBox(height: 32),
],
);
}
}
class _ServerInfoCard extends StatelessWidget {
final Function(DiscoveryInfo server) onPressed;
final DiscoveryInfo server;
const _ServerInfoCard({
required this.server,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: () => onPressed(server),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Card(
color: Theme.of(context).colorScheme.primaryContainer,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
IconsaxBold.driver,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
server.name,
style: context.textTheme.bodyLarge,
),
Opacity(
opacity: 0.6,
child: Text(
server.address,
style: context.textTheme.bodyMedium,
),
),
],
),
),
Icon(IconsaxOutline.edit_2, size: 16)
].addInBetween(const SizedBox(width: 12)),
),
),
),
);
}
}

View file

@ -0,0 +1,97 @@
import 'package:fladder/models/account_model.dart';
import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/screens/shared/user_icon.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class LoginIcon extends ConsumerWidget {
final AccountModel user;
final Function()? onPressed;
final Function()? onLongPress;
final Function()? onNewPressed;
const LoginIcon({
required this.user,
this.onPressed,
this.onLongPress,
this.onNewPressed,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return AspectRatio(
aspectRatio: 1.0,
child: Card(
elevation: 1,
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.zero,
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
flex: 4,
child: UserIcon(
labelStyle: Theme.of(context).textTheme.displayMedium,
size: const Size(125, 125),
user: user,
),
),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (onNewPressed != null)
Icon(
user.authMethod.icon,
size: 26,
),
const SizedBox(width: 4),
Flexible(
child: Text(
user.name,
maxLines: 2,
style: Theme.of(context).textTheme.titleLarge,
softWrap: true,
),
),
],
),
),
if (user.credentials.serverName.isNotEmpty)
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.dns_rounded,
size: 14,
),
const SizedBox(width: 4),
Flexible(
child: Text(
user.credentials.serverName,
maxLines: 2,
softWrap: true,
),
),
],
),
)
].addInBetween(SizedBox(width: 8, height: 8)),
),
),
FlatButton(
onTap: onPressed,
onLongPress: onLongPress,
)
],
),
),
);
}
}