Created project base structure, state management, router and login

This commit is contained in:
Juan Gilsanz Polo
2024-02-21 23:53:22 +01:00
commit 460e13b2e9
168 changed files with 6966 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:my_linkding/helpers/wrapped_class.dart';
class ConnectModel {
final bool isConnecting;
final int method;
final TextEditingController ipDomainController;
final String? ipDomainError;
final TextEditingController portController;
final String? portError;
final TextEditingController pathController;
final String? pathError;
final TextEditingController tokenController;
final String? tokenError;
ConnectModel({
required this.isConnecting,
required this.method,
required this.ipDomainController,
required this.ipDomainError,
required this.portController,
required this.portError,
required this.pathController,
required this.pathError,
required this.tokenController,
required this.tokenError,
});
ConnectModel copyWidth({
bool? isConnecting,
int? method,
TextEditingController? ipDomainController,
Wrapped<String?>? ipDomainError,
TextEditingController? portController,
Wrapped<String?>? portError,
TextEditingController? pathController,
Wrapped<String?>? pathError,
TextEditingController? tokenController,
Wrapped<String?>? tokenError,
}) =>
ConnectModel(
isConnecting: isConnecting ?? this.isConnecting,
method: method ?? this.method,
ipDomainController: ipDomainController ?? this.ipDomainController,
ipDomainError: ipDomainError != null ? ipDomainError.value : this.ipDomainError,
portController: portController ?? this.portController,
portError: portError != null ? portError.value : this.portError,
pathController: pathController ?? this.pathController,
pathError: pathError != null ? pathError.value : this.pathError,
tokenController: tokenController ?? this.tokenController,
tokenError: tokenError != null ? tokenError.value : this.tokenError,
);
}

View File

@@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:my_linkding/providers/shared_preferences_provider.dart';
import 'package:uuid/uuid.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:my_linkding/screens/connect/model/connect_model.dart';
import 'package:my_linkding/providers/server_instances_provider.dart';
import 'package:my_linkding/constants/enums.dart';
import 'package:my_linkding/utils/process_modal.dart';
import 'package:my_linkding/providers/router_provider.dart';
import 'package:my_linkding/router/paths.dart';
import 'package:my_linkding/utils/snackbar.dart';
import 'package:my_linkding/models/server_instance.dart';
import 'package:my_linkding/providers/api_client_provider.dart';
import 'package:my_linkding/services/api_client.dart';
import 'package:my_linkding/constants/regexp.dart';
import 'package:my_linkding/helpers/wrapped_class.dart';
part 'connect_provider.g.dart';
@riverpod
class Connect extends _$Connect {
@override
ConnectModel build() {
return ConnectModel(
isConnecting: false,
method: 0,
ipDomainController: TextEditingController(),
ipDomainError: null,
portController: TextEditingController(),
portError: null,
pathController: TextEditingController(),
pathError: null,
tokenController: TextEditingController(),
tokenError: null,
);
}
void setConnectionMethod(int method) {
state = state.copyWidth(method: method);
}
void validateIpDomain(String value) {
if (Regexps.ipAddress.hasMatch(value) || Regexps.domain.hasMatch(value)) {
state = state.copyWidth(ipDomainError: const Wrapped.value(null));
} else {
state = state.copyWidth(ipDomainError: const Wrapped.value("Error"));
}
}
void validatePort(value) {
if (value != null && value != '') {
if (int.tryParse(value) != null && int.parse(value) <= 65535) {
state = state.copyWidth(portError: const Wrapped.value(null));
} else {
state = state.copyWidth(portError: const Wrapped.value("Invalid port"));
}
}
}
void validateToken(String value) {
if (value != "") {
state = state.copyWidth(tokenError: const Wrapped.value(null));
} else {
state = state.copyWidth(tokenError: const Wrapped.value("Error"));
}
}
bool validValues() {
return state.tokenController.text != "" &&
state.tokenError == null &&
state.ipDomainController.text != "" &&
state.ipDomainError == null;
}
}
@riverpod
FutureOr<bool> connectToServer(ConnectToServerRef ref) async {
const uuid = Uuid();
final serverInstance = ServerInstance(
id: uuid.v4(),
method: ConnectionMethod.values[ref.watch(connectProvider).method].name,
ipDomain: ref.watch(connectProvider).ipDomainController.text,
token: ref.watch(connectProvider).tokenController.text,
);
final apiClient = ApiClient(serverInstance: serverInstance);
final processModal = ProcessModal();
processModal.open("Connecting...");
final result = await apiClient.checkConnectionInstance();
processModal.close();
if (result == true) {
ref.read(apiClientProviderProvider.notifier).setApiClient(apiClient);
ref.read(serverInstancesProvider.notifier).saveNewInstance(serverInstance);
// ref.watch(routerProvider).replace(RoutesPaths.links);
} else {
showSnacbkar(label: "Invalida auth", color: Colors.red);
}
return false;
}

View File

@@ -0,0 +1,40 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'connect_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$connectToServerHash() => r'af66fee9a4bf26aba08b27ff5a7fe4643215117a';
/// See also [connectToServer].
@ProviderFor(connectToServer)
final connectToServerProvider = AutoDisposeFutureProvider<bool>.internal(
connectToServer,
name: r'connectToServerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$connectToServerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef ConnectToServerRef = AutoDisposeFutureProviderRef<bool>;
String _$connectHash() => r'bbdae015118381c538b7a3c15ca8345d30a28995';
/// See also [Connect].
@ProviderFor(Connect)
final connectProvider =
AutoDisposeNotifierProvider<Connect, ConnectModel>.internal(
Connect.new,
name: r'connectProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$connectHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$Connect = AutoDisposeNotifier<ConnectModel>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:my_linkding/constants/enums.dart';
import 'package:my_linkding/screens/connect/provider/connect_provider.dart';
import 'package:segmented_button_slide/segmented_button_slide.dart';
class Connect extends ConsumerWidget {
const Connect({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(connectProvider);
final validValies = ref.read(connectProvider.notifier).validValues();
final connectionMethod = ConnectionMethod.values[provider.method];
return Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ListView(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
margin: const EdgeInsets.only(top: 24, left: 24, right: 24),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.05),
borderRadius: BorderRadius.circular(30),
border: Border.all(color: Theme.of(context).colorScheme.primary),
),
child: Text(
"${connectionMethod.name}://${provider.ipDomainController.text}${provider.pathController.text != '' ? ':${provider.portController.text}' : ""}${provider.pathController.text}",
textAlign: TextAlign.center,
style: TextStyle(color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w500),
),
),
const SizedBox(height: 30),
SegmentedButtonSlide(
entries: const [
SegmentedButtonSlideEntry(label: "HTTP"),
SegmentedButtonSlideEntry(label: "HTTPS"),
],
selectedEntry: provider.method,
onChange: ref.read(connectProvider.notifier).setConnectionMethod,
colors: SegmentedButtonSlideColors(
barColor: Theme.of(context).colorScheme.primary.withOpacity(0.2),
backgroundSelectedColor: Theme.of(context).colorScheme.primary,
foregroundSelectedColor: Theme.of(context).colorScheme.onPrimary,
foregroundUnselectedColor: Theme.of(context).colorScheme.onSurface,
hoverColor: Theme.of(context).colorScheme.onSurfaceVariant,
),
textOverflow: TextOverflow.ellipsis,
fontSize: 14,
height: 40,
margin: const EdgeInsets.symmetric(
horizontal: 24,
),
),
const SizedBox(height: 30),
TextFormField(
controller: provider.ipDomainController,
onChanged: ref.read(connectProvider.notifier).validateIpDomain,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.ipDomainError,
labelText: "IP address or domain",
),
),
const SizedBox(height: 30),
TextFormField(
controller: provider.portController,
onChanged: ref.read(connectProvider.notifier).validatePort,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.link_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.portError,
labelText: "Port",
),
),
const SizedBox(height: 30),
TextFormField(
controller: provider.tokenController,
onChanged: ref.read(connectProvider.notifier).validateToken,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.key_rounded),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
errorText: provider.tokenError,
labelText: "Token",
),
obscureText: true,
),
const SizedBox(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FilledButton.icon(
onPressed: validValies ? () => ref.read(connectToServerProvider) : null,
icon: const Icon(Icons.login_rounded),
label: Text("Connect"),
),
],
),
],
),
),
),
);
}
}