WIP
This commit is contained in:
parent
56f16d2934
commit
1dc611004a
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "flutter-freaccesss-hub",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"--no-enable-impeller"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "flutter-freaccesss-hub (profile mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter-freaccesss-hub (release mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -101,6 +101,7 @@ class _CardItemTemplateComponentWidgetState
|
|||
}
|
||||
|
||||
Widget _generateImage() {
|
||||
print('img: ${widget.imagePath ?? ''}');
|
||||
// CachedNetworkImage.evictFromCache(widget.imagePath ?? '');
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
|
|
|
@ -5,11 +5,14 @@ import 'dart:convert';
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hub/features/documents/index.dart' as doc;
|
||||
import 'package:hub/features/notification/index.dart';
|
||||
import 'package:hub/features/storage/index.dart';
|
||||
import 'package:hub/shared/extensions/index.dart';
|
||||
|
||||
import 'package:hub/shared/utils/log_util.dart';
|
||||
import 'package:hub/shared/utils/validator_util.dart';
|
||||
import 'package:hub/shared/widgets/widgets.dart';
|
||||
|
||||
import '/flutter_flow/flutter_flow_util.dart';
|
||||
import 'api_manager.dart';
|
||||
|
@ -24,7 +27,7 @@ abstract class Api {
|
|||
GetLicense getLicense = GetLicense();
|
||||
}
|
||||
|
||||
abstract class Endpoint {}
|
||||
abstract interface class Endpoint {}
|
||||
|
||||
class FreAccessWSGlobal extends Api {
|
||||
static String getBaseUrl() => 'https://freaccess.com.br/freaccess';
|
||||
|
@ -70,6 +73,7 @@ class FreAccessWSGlobal extends Api {
|
|||
static GetResidentsByProperty getResidentsByProperty =
|
||||
GetResidentsByProperty();
|
||||
static GetOpenedVisits getOpenedVisits = GetOpenedVisits();
|
||||
@override
|
||||
GetLicense getLicense = GetLicense();
|
||||
static GetProvSchedules getProvSchedules = GetProvSchedules();
|
||||
static GetCategories getCategories = GetCategories();
|
||||
|
@ -77,6 +81,7 @@ class FreAccessWSGlobal extends Api {
|
|||
}
|
||||
|
||||
class GetCategories extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -113,7 +118,11 @@ class GetCategories extends Endpoint {
|
|||
}
|
||||
|
||||
class GetDocuments extends Endpoint {
|
||||
Future<ApiCallResponse> call(final String page) async {
|
||||
@override
|
||||
Future<ApiCallResponse> call(
|
||||
final dynamic page,
|
||||
final Query query,
|
||||
) async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
(await StorageHelper().get(ProfileStorageKey.devUUID.key)) ?? '';
|
||||
|
@ -124,6 +133,8 @@ class GetDocuments extends Endpoint {
|
|||
const String atividade = 'listaDocumentos';
|
||||
const String pageSize = '10';
|
||||
// final bool isFiltered = filter != '' && filter != '.*';
|
||||
// final isCategory = !category;
|
||||
// final isDescription = !desc.isNullOrEmpty;
|
||||
return await ApiManager.instance.makeApiCall(
|
||||
callName: 'listaDocumentos',
|
||||
apiUrl: '$baseUrl/processRequest.php',
|
||||
|
@ -131,12 +142,15 @@ class GetDocuments extends Endpoint {
|
|||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
params: {
|
||||
// if (isFiltered) 'filter': filter,
|
||||
|
||||
'devUUID': devUUID,
|
||||
'userUUID': userUUID,
|
||||
'cliID': cliID,
|
||||
'atividade': atividade,
|
||||
'page': page,
|
||||
'page': page.toString(),
|
||||
'pageSize': pageSize,
|
||||
if (query is doc.Category) 'categoryId': query.id,
|
||||
if (query is doc.Document) 'searh': query.description,
|
||||
},
|
||||
bodyType: BodyType.X_WWW_FORM_URL_ENCODED,
|
||||
returnBody: true,
|
||||
|
@ -150,6 +164,7 @@ class GetDocuments extends Endpoint {
|
|||
}
|
||||
|
||||
class GetProvSchedules extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call(final String page, final String status) async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -188,6 +203,7 @@ class GetProvSchedules extends Endpoint {
|
|||
}
|
||||
|
||||
class GetOpenedVisits extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call(final String page) async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -223,6 +239,7 @@ class GetOpenedVisits extends Endpoint {
|
|||
}
|
||||
|
||||
class GetResidentsByProperty extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call(final String page) async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -257,6 +274,7 @@ class GetResidentsByProperty extends Endpoint {
|
|||
}
|
||||
|
||||
class GetVehiclesByProperty extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call(final String page) async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -293,6 +311,7 @@ class GetVehiclesByProperty extends Endpoint {
|
|||
}
|
||||
|
||||
class GetLicense extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -327,6 +346,7 @@ class GetLicense extends Endpoint {
|
|||
}
|
||||
|
||||
class UnregisterDevice extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -357,6 +377,7 @@ class UnregisterDevice extends Endpoint {
|
|||
}
|
||||
|
||||
class DeletePet extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({final int? petID = 0}) async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -393,6 +414,7 @@ class DeletePet extends Endpoint {
|
|||
}
|
||||
|
||||
class UpdatePet extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final int? petID,
|
||||
final String? image,
|
||||
|
@ -452,6 +474,7 @@ class UpdatePet extends Endpoint {
|
|||
}
|
||||
|
||||
class GetPets extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final int? page,
|
||||
final int? pageSize,
|
||||
|
@ -492,6 +515,7 @@ class GetPets extends Endpoint {
|
|||
}
|
||||
|
||||
class GetPetPhoto extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({final int? petId}) async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
|
||||
|
@ -528,6 +552,7 @@ class GetPetPhoto extends Endpoint {
|
|||
}
|
||||
|
||||
class RegisterPet extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? image,
|
||||
final String? name,
|
||||
|
@ -585,6 +610,7 @@ class RegisterPet extends Endpoint {
|
|||
}
|
||||
|
||||
class BuscaEnconcomendas extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? page,
|
||||
final String? pageSize,
|
||||
|
@ -629,6 +655,7 @@ class BuscaEnconcomendas extends Endpoint {
|
|||
}
|
||||
|
||||
class CancelaVisita extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final int? idDestino,
|
||||
final int? idVisita,
|
||||
|
@ -674,6 +701,7 @@ class CancelaVisita extends Endpoint {
|
|||
}
|
||||
|
||||
class DeleteAccount extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String devUUID =
|
||||
(await StorageHelper().get(ProfileStorageKey.devUUID.key)) ?? '';
|
||||
|
@ -704,6 +732,7 @@ class DeleteAccount extends Endpoint {
|
|||
}
|
||||
|
||||
class ChangePanic extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? newSenhaPanico,
|
||||
}) async {
|
||||
|
@ -743,6 +772,7 @@ class ChangePanic extends Endpoint {
|
|||
}
|
||||
|
||||
class ChangePass extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? newSenha,
|
||||
}) async {
|
||||
|
@ -782,6 +812,7 @@ class ChangePass extends Endpoint {
|
|||
}
|
||||
|
||||
class RespondeVinculo extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? tarefa,
|
||||
}) async {
|
||||
|
@ -819,6 +850,7 @@ class RespondeVinculo extends Endpoint {
|
|||
}
|
||||
|
||||
class ChangeNotifica extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? notifica,
|
||||
}) async {
|
||||
|
@ -858,6 +890,7 @@ class ChangeNotifica extends Endpoint {
|
|||
}
|
||||
|
||||
class UpdateIDE extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
|
||||
|
@ -897,6 +930,7 @@ class UpdateIDE extends Endpoint {
|
|||
}
|
||||
|
||||
class UpdToken extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
|
||||
|
@ -930,6 +964,7 @@ class UpdToken extends Endpoint {
|
|||
}
|
||||
|
||||
class LoginCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final String devUUID =
|
||||
|
@ -980,6 +1015,7 @@ class LoginCall extends Endpoint {
|
|||
}
|
||||
|
||||
class RegisterCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
required final String name,
|
||||
required final String email,
|
||||
|
@ -1019,6 +1055,7 @@ class RegisterCall extends Endpoint {
|
|||
}
|
||||
|
||||
class ChangePasswordCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
required final String email,
|
||||
required final String token,
|
||||
|
@ -1059,6 +1096,7 @@ class ChangePasswordCall extends Endpoint {
|
|||
}
|
||||
|
||||
class ForgotPasswordCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? email,
|
||||
}) async {
|
||||
|
@ -1086,6 +1124,7 @@ class ForgotPasswordCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetLocalsCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
|
||||
|
@ -1125,6 +1164,7 @@ class GetLocalsCall extends Endpoint {
|
|||
}
|
||||
|
||||
class PostScheduleVisitorCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
required final String documento,
|
||||
required final String nome,
|
||||
|
@ -1179,6 +1219,7 @@ class PostScheduleVisitorCall extends Endpoint {
|
|||
}
|
||||
|
||||
class PostScheduleVisitCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? devDesc,
|
||||
final String? idVisitante,
|
||||
|
@ -1241,6 +1282,7 @@ class PostScheduleVisitCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetScheduleVisitCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final int? pageSize,
|
||||
final int? pageNumber,
|
||||
|
@ -1522,6 +1564,7 @@ class GetScheduleVisitCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetDadosCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
|
||||
|
@ -1768,6 +1811,7 @@ class GetDadosCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetVisitorByDocCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? documento,
|
||||
}) async {
|
||||
|
@ -1824,6 +1868,7 @@ class GetVisitorByDocCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetFotoVisitanteCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? documento,
|
||||
final String? tipo,
|
||||
|
@ -1864,6 +1909,7 @@ class GetFotoVisitanteCall extends Endpoint {
|
|||
}
|
||||
|
||||
class PostProvVisitSchedulingCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? data,
|
||||
final String? motivo,
|
||||
|
@ -1918,6 +1964,7 @@ class PostProvVisitSchedulingCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetVisitsCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final int? pageSize,
|
||||
final int? pageNumber,
|
||||
|
@ -2185,6 +2232,7 @@ class GetVisitsCall extends Endpoint {
|
|||
}
|
||||
|
||||
class DeleteVisitCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? idVisita,
|
||||
}) async {
|
||||
|
@ -2233,6 +2281,7 @@ class DeleteVisitCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetPessoasLocalCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call() async {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
|
||||
|
@ -2298,6 +2347,7 @@ class GetPessoasLocalCall extends Endpoint {
|
|||
}
|
||||
|
||||
class RespondeSolicitacaoCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? referencia,
|
||||
final String? tarefa,
|
||||
|
@ -2352,6 +2402,7 @@ class RespondeSolicitacaoCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetAccessCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? pageSize,
|
||||
final String? pageNumber,
|
||||
|
@ -2606,6 +2657,7 @@ class GetAccessCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetLiberationsCall extends Endpoint {
|
||||
@override
|
||||
Stream<ApiCallResponse> call() {
|
||||
final String baseUrl = FreAccessWSGlobal.getBaseUrl();
|
||||
final StreamController<ApiCallResponse> controller = StreamController();
|
||||
|
@ -2799,6 +2851,7 @@ class GetLiberationsCall extends Endpoint {
|
|||
}
|
||||
|
||||
class GetMessagesCall extends Endpoint {
|
||||
@override
|
||||
Future<ApiCallResponse> call({
|
||||
final String? pageSize,
|
||||
final String? pageNumber,
|
||||
|
|
|
@ -1,31 +1,60 @@
|
|||
part of 'index.dart';
|
||||
|
||||
interface class Category extends Entity {
|
||||
abstract interface class DocumentEntity extends Entity {}
|
||||
|
||||
interface class Category extends DocumentEntity {
|
||||
final int id;
|
||||
final Color color;
|
||||
final String title;
|
||||
|
||||
Category({
|
||||
required this.id,
|
||||
required this.color,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
factory Category.fromDesc(String desc) {
|
||||
return Category(
|
||||
id: 0,
|
||||
color: Colors.transparent,
|
||||
title: desc,
|
||||
);
|
||||
}
|
||||
|
||||
static Color isSelected() => Colors.black;
|
||||
}
|
||||
|
||||
interface class Document extends Entity {
|
||||
final String title;
|
||||
interface class Document extends DocumentEntity {
|
||||
final int id;
|
||||
final String description;
|
||||
final String type;
|
||||
final Category category;
|
||||
final String to;
|
||||
final String from;
|
||||
final String createdAt;
|
||||
final String updatedAt;
|
||||
final String person;
|
||||
final String property;
|
||||
String createdAt;
|
||||
String updatedAt;
|
||||
|
||||
Document({
|
||||
required this.id,
|
||||
required this.description,
|
||||
required this.type,
|
||||
required this.category,
|
||||
required this.person,
|
||||
required this.property,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.category,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
factory Document.fromDesc(String desc) => Document(
|
||||
id: 0,
|
||||
description: desc,
|
||||
type: '',
|
||||
category: Category.fromDesc(''),
|
||||
person: '',
|
||||
property: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
);
|
||||
}
|
||||
|
||||
class DocumentItem extends StatelessWidget {
|
||||
|
@ -77,6 +106,16 @@ class DocumentItem extends StatelessWidget {
|
|||
fontWeight: FontWeight.normal,
|
||||
fontStyle: FontStyle.italic,
|
||||
);
|
||||
final Map<String, dynamic> extra = <String, dynamic>{
|
||||
'document': document,
|
||||
kTransitionInfoKey: const TransitionInfo(
|
||||
hasTransition: true,
|
||||
transitionType: PageTransitionType.rightToLeft,
|
||||
alignment: Alignment.bottomCenter,
|
||||
),
|
||||
};
|
||||
Future<Object?> onTap() =>
|
||||
context.push('/documentViewerScreen', extra: extra);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
@ -87,9 +126,9 @@ class DocumentItem extends StatelessWidget {
|
|||
: MediaQuery.of(context).size.height * 2;
|
||||
|
||||
return InkWell(
|
||||
onTap: () => print('Click'),
|
||||
onTap: onTap,
|
||||
enableFeedback: true,
|
||||
overlayColor: MaterialStateProperty.all<Color>(primaryColor),
|
||||
overlayColor: WidgetStateProperty.all<Color>(primaryColor),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: SizedBox(
|
||||
height: boxHeight,
|
||||
|
@ -105,15 +144,16 @@ class DocumentItem extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Tooltip(
|
||||
message: document.title,
|
||||
message: document.description,
|
||||
child: AutoText(
|
||||
document.title,
|
||||
document.description,
|
||||
style: textStyleMajor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
AutoText(
|
||||
document.updatedAt,
|
||||
ValidatorUtil.toLocalDateTime(
|
||||
'yyyy-MM-dd', document.updatedAt),
|
||||
style: textStyleMinor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
|
@ -1,102 +1,27 @@
|
|||
part of 'index.dart';
|
||||
|
||||
class DocumentManagerScreen extends StatelessScreen {
|
||||
DocumentManagerScreen({
|
||||
super.key,
|
||||
required this.documents,
|
||||
required this.categories,
|
||||
});
|
||||
final DocumentPageModel model;
|
||||
|
||||
List<Document> documents;
|
||||
final List<Category> categories;
|
||||
const DocumentManagerScreen({
|
||||
super.key,
|
||||
required this.model,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final GlobalKey<LocalSearchViewState> _listViewKey =
|
||||
GlobalKey<LocalSearchViewState>();
|
||||
|
||||
bool filter(document, query) {
|
||||
final lowerQuery = query.toLowerCase();
|
||||
return document.title.toLowerCase().contains(lowerQuery) ||
|
||||
document.description.toLowerCase().contains(lowerQuery) ||
|
||||
document.category.title.toLowerCase().contains(lowerQuery) ||
|
||||
document.to.toLowerCase().contains(lowerQuery) ||
|
||||
document.from.toLowerCase().contains(lowerQuery) ||
|
||||
document.createdAt.toLowerCase().contains(lowerQuery) ||
|
||||
document.updatedAt.toLowerCase().contains(lowerQuery);
|
||||
}
|
||||
|
||||
DocumentItem itemBuilder(dynamic item) {
|
||||
log('item: $item');
|
||||
|
||||
final doc = Document(
|
||||
createdAt: '00/00/00',
|
||||
updatedAt: '00/00/00',
|
||||
category: Category(color: Colors.black, title: item['category']),
|
||||
to: item['person'],
|
||||
from: '',
|
||||
title: item['description'],
|
||||
);
|
||||
final docItem = DocumentItem(document: doc);
|
||||
return docItem;
|
||||
}
|
||||
|
||||
void filterByCategory(Category query) {
|
||||
print('Test');
|
||||
final state = _listViewKey.currentState;
|
||||
|
||||
if (state != null) {
|
||||
state.safeSetState(() {
|
||||
state.filteredItems = documents
|
||||
.where((documents) => filter(documents, query.title))
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void unfilter(Category) {
|
||||
final state = _listViewKey.currentState;
|
||||
if (state != null) {
|
||||
state.safeSetState(() {
|
||||
state.filteredItems = documents;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final header = Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(15, 0, 50, 0),
|
||||
child: Text('Últimos Documentos'),
|
||||
),
|
||||
CategoryCarousel(
|
||||
categories: categories,
|
||||
onSelect: filterByCategory,
|
||||
onUnselect: unfilter,
|
||||
),
|
||||
],
|
||||
);
|
||||
List<Document> filterByString(String query) {
|
||||
return documents.where((documents) => filter(documents, query)).toList();
|
||||
}
|
||||
|
||||
final SizedBox space = SizedBox(height: 30);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: RemoteSearchListView<dynamic>(
|
||||
key: _listViewKey,
|
||||
header: header,
|
||||
onSearch: filterByString,
|
||||
list: documents,
|
||||
itemBuilder: itemBuilder,
|
||||
filter: filter,
|
||||
title: '',
|
||||
// fetchItems: (String ) { },,
|
||||
child: RemoteSearchView<Document>(
|
||||
key: model.searchKey,
|
||||
pagingController: model._pagingController,
|
||||
headerBuilder: model.listHeaderBuilder,
|
||||
bodyBuilder: model.listBodyBuilder,
|
||||
dataProvider: model.generateDocuments,
|
||||
onFetchError: model.onFetchError,
|
||||
),
|
||||
),
|
||||
] //
|
||||
|
|
|
@ -1 +1,156 @@
|
|||
part of 'index.dart';
|
||||
|
||||
class DocumentPageModel extends FlutterFlowModel<DocumentPage> {
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
@override
|
||||
void initState(BuildContext context) {}
|
||||
|
||||
final SearchKey searchKey = SearchKey();
|
||||
final DocumentKey docKey = DocumentKey();
|
||||
|
||||
final PagingController<int, Document> _pagingController =
|
||||
PagingController<int, Document>(firstPageKey: 1);
|
||||
int count = 0;
|
||||
final dynamic page = 1;
|
||||
|
||||
Query query = Document.fromDesc('');
|
||||
|
||||
late Document currentDocument;
|
||||
bool isCategorySelected = false;
|
||||
late Category currentCategory;
|
||||
|
||||
List<Document?> documents = [];
|
||||
List<Category?> categories = [];
|
||||
|
||||
/// [listBodyBuilder]
|
||||
Widget listBodyBuilder<T>(BuildContext context, Document item, int index) {
|
||||
return DocumentItem(document: item);
|
||||
}
|
||||
|
||||
/// [listHeaderBuilder]
|
||||
Widget listHeaderBuilder(BuildContext context) => Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(15, 0, 50, 0),
|
||||
child: Text(
|
||||
'Últimos Documentos',
|
||||
style: TextStyle(
|
||||
color: FlutterFlowTheme.of(context).primaryText,
|
||||
fontSize: LimitedFontSizeUtil.getHeaderFontSize(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
CategoryCarousel<Category>(
|
||||
categories: categories,
|
||||
filter: filterByCategory,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
/// [generateDocuments]
|
||||
Future<(bool, List<Document?>)> generateDocuments(
|
||||
pageKey, Query query) async {
|
||||
final List<Document?> error = [null];
|
||||
print('Query: ${query is Document}');
|
||||
final GetDocuments getDocuments = FreAccessWSGlobal.getDocuments;
|
||||
final ApiCallResponse newItems = await getDocuments.call(pageKey, query);
|
||||
|
||||
if (newItems.jsonBody == null) return (false, error);
|
||||
if (newItems.jsonBody['error'] == true) return (false, error);
|
||||
|
||||
final List<dynamic> list = newItems.jsonBody['value']['list'];
|
||||
|
||||
late final List<Document> docs = [];
|
||||
|
||||
for (var item in list) {
|
||||
log('-> generateDocuments: $item');
|
||||
final String description = item['description'];
|
||||
final String type = item['type'];
|
||||
final String category = item['category']['description'];
|
||||
final String color = item['category']['color'];
|
||||
final String person = item['person'] ?? '';
|
||||
final String property = item['property'] ?? '';
|
||||
final String createdAt = item['createdAt'];
|
||||
final String updatedAt = item['updatedAt'];
|
||||
final int categoryId = item['category']['id'];
|
||||
final int documentId = item['id'];
|
||||
|
||||
final doc = Document(
|
||||
id: documentId,
|
||||
description: description,
|
||||
type: type,
|
||||
category: Category(
|
||||
id: categoryId,
|
||||
color: color.toColor(),
|
||||
title: category,
|
||||
),
|
||||
person: person,
|
||||
property: property,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
);
|
||||
|
||||
docs.add(doc);
|
||||
}
|
||||
|
||||
return (true, docs);
|
||||
// listViewKey.currentState!.count = newItems.jsonBody['value']['count'] ?? 0;
|
||||
}
|
||||
|
||||
/// [generateCategories]
|
||||
Future<List<Category?>> generateCategories(List<Document?> documents) async {
|
||||
final List<Category?> error = [null];
|
||||
if (documents == []) return error;
|
||||
|
||||
final GetCategories getCategories = FreAccessWSGlobal.getCategories;
|
||||
final ApiCallResponse newItems = await getCategories.call();
|
||||
|
||||
if (newItems.jsonBody['error'] == true) return error;
|
||||
if (newItems.jsonBody == null) return error;
|
||||
final list = newItems.jsonBody['value'] as List<dynamic>;
|
||||
late final List<Category> cats = [];
|
||||
for (var item in list) {
|
||||
final String color = item['color'];
|
||||
final String title = item['description'];
|
||||
final int id = item['id'];
|
||||
|
||||
final cat = Category(
|
||||
id: id,
|
||||
color: color.toColor(),
|
||||
title: title,
|
||||
);
|
||||
cats.add(cat);
|
||||
}
|
||||
log('cats: $cats');
|
||||
return cats;
|
||||
}
|
||||
|
||||
void filterByCategory(Category query) {
|
||||
final state = searchKey.currentState;
|
||||
|
||||
if (state != null) {
|
||||
log('filterByCategories: ');
|
||||
|
||||
state.safeSetState(() {
|
||||
if (isCategorySelected) {
|
||||
state.filter(null);
|
||||
isCategorySelected = false;
|
||||
} else {
|
||||
state.filter(query);
|
||||
isCategorySelected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void onFetchError(Object e, StackTrace s) {
|
||||
DialogUtil.errorDefault(docKey.currentContext!);
|
||||
LogUtil.requestAPIFailed(
|
||||
"proccessRequest.php", "", "Consulta de Veículo", e, s);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +1,54 @@
|
|||
part of 'index.dart';
|
||||
|
||||
List<Document> generateDocuments(int count) {
|
||||
String str() => randomString(8, 8, true, true, true);
|
||||
Color color() => randomColor();
|
||||
typedef DocumentKey = GlobalKey<FREDocumentPageState>;
|
||||
|
||||
return List<Document>.generate(
|
||||
count,
|
||||
(index) => Document(
|
||||
title: 'Lorem Ipsum et Cetera $index',
|
||||
category: Category(color: color(), title: str()),
|
||||
to: str(),
|
||||
from: str(),
|
||||
createdAt: '00/00/0000',
|
||||
updatedAt: '00/00/0000',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Category> generateCategories(List<Document> documents) {
|
||||
final Map<String, Category> categoryMap = {};
|
||||
|
||||
for (var document in documents) {
|
||||
final category = document.category;
|
||||
if (!categoryMap.containsKey(category.title)) {
|
||||
categoryMap[category.title] = category;
|
||||
}
|
||||
}
|
||||
|
||||
return categoryMap.values.toList();
|
||||
}
|
||||
|
||||
class FREDocumentPage extends StatefulPage {
|
||||
const FREDocumentPage({super.key});
|
||||
class DocumentPage extends StatefulPage {
|
||||
const DocumentPage({super.key});
|
||||
|
||||
@override
|
||||
State<FREDocumentPage> createState() => _FREDocumentPageState();
|
||||
State<DocumentPage> createState() => FREDocumentPageState();
|
||||
}
|
||||
|
||||
class _FREDocumentPageState extends PageState<FREDocumentPage> {
|
||||
class FREDocumentPageState<T extends DocumentPage>
|
||||
extends PageState<DocumentPage> {
|
||||
DocumentPageModel model = DocumentPageModel();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String title = FFLocalizations.of(context)
|
||||
.getVariableText(enText: 'Documents', ptText: 'Documentos');
|
||||
final String title = FFLocalizations.of(context).getVariableText(
|
||||
enText: 'Documents',
|
||||
ptText: 'Documentos',
|
||||
);
|
||||
final theme = FlutterFlowTheme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.primaryBackground,
|
||||
appBar: buildAppBar(title, context),
|
||||
body: buildBody(context),
|
||||
);
|
||||
}
|
||||
|
||||
late List<Document> documents;
|
||||
late List<Category> categories;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
documents = generateDocuments(20);
|
||||
categories = generateCategories(documents);
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
return DocumentManagerScreen(
|
||||
documents: documents,
|
||||
categories: categories,
|
||||
return FutureBuilder<void>(
|
||||
future: initAsync(),
|
||||
builder: (context, snapshot) {
|
||||
return DocumentManagerScreen(model: model);
|
||||
},
|
||||
);
|
||||
// return DocumentViewScreen(document: documents.first);
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
final documents = await model.generateDocuments(model.page, model.query);
|
||||
final categories = await model.generateCategories(model.documents);
|
||||
model.documents = documents.$2;
|
||||
model.categories = categories;
|
||||
log('-> generateDocuments: $documents');
|
||||
log('-> generateCategories: $categories');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,33 +13,45 @@ class DocumentViewScreen extends StatefulScreen {
|
|||
}
|
||||
|
||||
class _DocumentViewScreenState extends State<DocumentViewScreen> {
|
||||
final PDFViewerState _viewerKey = PDFViewerState();
|
||||
final PDFViewerKey _viewerKey = PDFViewerKey();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Uri url = Uri.parse(
|
||||
'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf');
|
||||
|
||||
void onPressed() async {
|
||||
final response = await http.get(url);
|
||||
if (response.statusCode == 200) {
|
||||
final XFile xfile = XFile.fromData(response.bodyBytes,
|
||||
name:
|
||||
'${widget.document.description}_${widget.document.category.title}.pdf',
|
||||
mimeType: 'application/pdf');
|
||||
await Share.shareXFiles([xfile], text: 'Confira este PDF!');
|
||||
} else {
|
||||
print('Erro ao baixar o arquivo: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: FREViewerPDF(
|
||||
key: _viewerKey,
|
||||
url:
|
||||
'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf',
|
||||
search: _viewerKey,
|
||||
src: url.toString(),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.share, color: Colors.black),
|
||||
icon: Icon(
|
||||
Icons.share,
|
||||
color: Colors.black,
|
||||
),
|
||||
color: Colors.black,
|
||||
onPressed: () {
|
||||
_viewerKey.currentState?.openBookmarkView();
|
||||
// Share.share(FFLocalizations.of(context).getVariableText(
|
||||
// ptText: '',
|
||||
// enText: '',
|
||||
// ));
|
||||
},
|
||||
onPressed: onPressed,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:hub/features/backend/index.dart';
|
||||
import 'package:hub/flutter_flow/index.dart';
|
||||
import 'package:hub/shared/extensions/index.dart';
|
||||
import 'package:hub/shared/mixins/pegeable_mixin.dart';
|
||||
import 'package:hub/shared/utils/index.dart';
|
||||
import 'package:hub/shared/widgets/widgets.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
part 'document_manager_screen.dart';
|
||||
part 'document_page_widget.dart';
|
||||
|
|
|
@ -158,7 +158,7 @@ class LightModeTheme extends FlutterFlowTheme {
|
|||
|
||||
late Color primary = const Color(0xFF1AAB5F);
|
||||
late Color secondary = const Color(0xFFB59E9E);
|
||||
late Color tertiary = const Color(0xFF984BB6);
|
||||
late Color tertiary = const Color(0xFF000000);
|
||||
late Color alternate = const Color(0xFFF2F2F2);
|
||||
late Color alternate2 = const Color(0xFF232323);
|
||||
late Color primaryText = const Color(0xFF000000);
|
||||
|
@ -192,7 +192,7 @@ class DarkModeTheme extends FlutterFlowTheme {
|
|||
|
||||
late Color primary = const Color(0xFF1AAB5F);
|
||||
late Color secondary = const Color(0xFF18AA99);
|
||||
late Color tertiary = const Color(0xFF984BB6);
|
||||
late Color tertiary = const Color(0xFF000000);
|
||||
late Color alternate = const Color(0xFF232323);
|
||||
late Color alternate2 = const Color(0xFF171717);
|
||||
late Color primaryText = const Color(0xFFFFFFFF);
|
||||
|
|
|
@ -303,7 +303,19 @@ GoRouter createRouter(AppStateNotifier appStateNotifier) {
|
|||
name: 'documentPage',
|
||||
path: '/documentPage',
|
||||
builder: (context, params) {
|
||||
return FREDocumentPage();
|
||||
return DocumentPage();
|
||||
},
|
||||
),
|
||||
FFRoute(
|
||||
name: 'documentViewerScreen',
|
||||
path: '/documentViewerScreen',
|
||||
builder: (context, params) {
|
||||
final Document document =
|
||||
params.getParam('document', ParamType.Function);
|
||||
return DocumentViewScreen(
|
||||
key: UniqueKey(),
|
||||
document: document,
|
||||
);
|
||||
},
|
||||
),
|
||||
// FFRoute(name: 'settingsPage', path: '/settingsPage', builder: (context, params) => params.isEmpty ? const NavBarPage(initialPage: 'settingsPage') : const SettingsPageWidget())
|
||||
|
|
|
@ -74,38 +74,6 @@ class _AppState extends State<App> {
|
|||
late GoRouter _router;
|
||||
bool displaySplashImage = true;
|
||||
|
||||
final ThemeData _darkTheme = ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbVisibility: WidgetStateProperty.all(false),
|
||||
interactive: false,
|
||||
thumbColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.dragged)) {
|
||||
return const Color(0xff1aab5f);
|
||||
}
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return const Color(0xff1aab5f);
|
||||
}
|
||||
return const Color(0xff1aab5f);
|
||||
}),
|
||||
),
|
||||
);
|
||||
final ThemeData _theme = ThemeData(
|
||||
brightness: Brightness.light,
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbVisibility: WidgetStateProperty.all(false),
|
||||
interactive: false,
|
||||
thumbColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.dragged)) {
|
||||
return const Color(0xff1aab5f);
|
||||
}
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return const Color(0xff1aab5f);
|
||||
}
|
||||
return const Color(0xff1aab5f);
|
||||
}),
|
||||
),
|
||||
);
|
||||
final Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates =
|
||||
const [
|
||||
FFLocalizationsDelegate(),
|
||||
|
@ -201,6 +169,39 @@ class _AppState extends State<App> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final themeSchema = FlutterFlowTheme.of(context);
|
||||
|
||||
final ThemeData darkTheme = ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbVisibility: WidgetStateProperty.all(false),
|
||||
interactive: false,
|
||||
thumbColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.dragged)) return themeSchema.primary;
|
||||
if (states.contains(WidgetState.hovered)) return themeSchema.primary;
|
||||
return themeSchema.primary;
|
||||
}),
|
||||
),
|
||||
);
|
||||
final ThemeData lightTheme = ThemeData(
|
||||
brightness: Brightness.light,
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: themeSchema.primaryText, // Cor do cursor
|
||||
selectionColor: themeSchema.accent2, // Cor da seleção de texto
|
||||
selectionHandleColor:
|
||||
themeSchema.primaryText, // Cor do manipulador de seleção
|
||||
),
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbVisibility: WidgetStateProperty.all(false),
|
||||
interactive: false,
|
||||
thumbColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.dragged)) return themeSchema.primary;
|
||||
if (states.contains(WidgetState.hovered)) return themeSchema.primary;
|
||||
return themeSchema.primary;
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return MaterialApp.router(
|
||||
scrollBehavior: CustomScrollBehavior(),
|
||||
key: navigatorKey,
|
||||
|
@ -209,8 +210,8 @@ class _AppState extends State<App> {
|
|||
localizationsDelegates: localizationsDelegates,
|
||||
locale: _locale,
|
||||
supportedLocales: supportedLocales,
|
||||
theme: _theme,
|
||||
darkTheme: _darkTheme,
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: _themeMode,
|
||||
routerConfig: _router,
|
||||
);
|
||||
|
|
|
@ -163,8 +163,10 @@ class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
|
|||
|
||||
// updateImage!();
|
||||
(() async {
|
||||
Response response = await get(Uri.parse(
|
||||
'https://freaccess.com.br/freaccess/getImage.php?devUUID=$devUUID&userUUID=$userUUID&cliID=$cliUUID&atividade=consultaFotoPet&petId=$petId'));
|
||||
final String url =
|
||||
'https://freaccess.com.br/freaccess/getImage.php?devUUID=$devUUID&userUUID=$userUUID&cliID=$cliUUID&atividade=consultaFotoPet&petId=$petId';
|
||||
print('img: $url');
|
||||
Response response = await get(Uri.parse(url));
|
||||
String base64 = base64Encode(response.bodyBytes);
|
||||
uploadedTempFile = await ImageUtils.convertToUploadFile(base64);
|
||||
updateImage?.call();
|
||||
|
@ -280,6 +282,7 @@ class PetsPageModel extends FlutterFlowModel<PetsPageWidget> {
|
|||
img = "base64;jpeg,$img";
|
||||
final url =
|
||||
'https://freaccess.com.br/freaccess/getImage.php?devUUID=$devUUID&userUUID=$userUUID&cliID=$cliUUID&atividade=consultaFotoPet&petId=$petId';
|
||||
print('img: $url');
|
||||
final response = await FreAccessWSGlobal.updatePet.call(
|
||||
petID: petId,
|
||||
image: img,
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:ui';
|
||||
|
||||
extension StringNullableExtensions on String? {
|
||||
bool toBoolean() {
|
||||
if (this == null) return false;
|
||||
return this!.toLowerCase() == 'true';
|
||||
}
|
||||
|
||||
bool isNullOrEmpty() {
|
||||
bool get isNullOrEmpty {
|
||||
if (this == null) return true;
|
||||
if (this == '') return true;
|
||||
return false;
|
||||
|
@ -18,3 +20,16 @@ extension StringExtensions on String {
|
|||
}
|
||||
|
||||
extension StringExtension on String? {}
|
||||
|
||||
extension HexColor on String {
|
||||
Color toColor() {
|
||||
final hexCode = replaceAll('#', '');
|
||||
final buffer = StringBuffer();
|
||||
if (hexCode.length == 6) {
|
||||
buffer
|
||||
.write('ff'); // Adiciona opacidade total caso não esteja especificada
|
||||
}
|
||||
buffer.write(hexCode);
|
||||
return Color(int.parse(buffer.toString(), radix: 16));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export 'status_mixin.dart';
|
||||
export 'switcher_mixin.dart';
|
||||
export 'pegeable_mixin.dart';
|
||||
export 'status_mixin.dart';
|
||||
export 'switcher_mixin.dart';
|
||||
|
|
|
@ -7,30 +7,33 @@ extension PagedListViewExtension<PageKeyType, ItemType>
|
|||
on PagedSliverList<PageKeyType, ItemType> {}
|
||||
|
||||
mixin Pageable<T extends StatefulWidget> on State<T> {
|
||||
Expanded buildPaginatedListView<X, Y>(
|
||||
Expanded buildPaginatedListView<PageKeyType, ItemType>(
|
||||
String noDataFound,
|
||||
PagingController<X, Y> pg,
|
||||
Widget Function(BuildContext, Y, int) itemBuilder) {
|
||||
PagingController<PageKeyType, ItemType> pg,
|
||||
Widget Function(BuildContext) headerBuilder,
|
||||
Widget Function(BuildContext, ItemType, int) bodyBuilder) {
|
||||
final theme = FlutterFlowTheme.of(context);
|
||||
|
||||
return Expanded(
|
||||
child: RefreshIndicator(
|
||||
backgroundColor: theme.primaryBackground,
|
||||
color: theme.primary,
|
||||
onRefresh: () async => pg.refresh(),
|
||||
child: PagedListView<X, Y>(
|
||||
child: PagedListView<PageKeyType, ItemType>(
|
||||
pagingController: pg,
|
||||
builderDelegate: PagedChildBuilderDelegate<Y>(
|
||||
builderDelegate: PagedChildBuilderDelegate<ItemType>(
|
||||
animateTransitions: true,
|
||||
itemBuilder: (context, item, index) =>
|
||||
itemBuilder(context, item, index),
|
||||
// noMoreItemsIndicatorBuilder: ,
|
||||
itemBuilder: (context, item, int index) {
|
||||
return Column(children: [
|
||||
if (index == 0) headerBuilder(context),
|
||||
bodyBuilder(context, item, index),
|
||||
]);
|
||||
},
|
||||
newPageProgressIndicatorBuilder: (context) =>
|
||||
buildLoadingIndicator(context),
|
||||
firstPageProgressIndicatorBuilder: (context) =>
|
||||
buildLoadingIndicator(context),
|
||||
noItemsFoundIndicatorBuilder: (context) =>
|
||||
buildNoDataFound(context, noDataFound),
|
||||
buildNoDataFound(context, noDataFound, headerBuilder),
|
||||
firstPageErrorIndicatorBuilder: (context) => const Placeholder(),
|
||||
newPageErrorIndicatorBuilder: (context) => const Placeholder(),
|
||||
),
|
||||
|
@ -89,23 +92,33 @@ mixin Pageable<T extends StatefulWidget> on State<T> {
|
|||
showSnackbar(context, message, true);
|
||||
}
|
||||
|
||||
Widget buildNoDataFound(BuildContext context, String title) {
|
||||
Widget buildNoDataFound(
|
||||
BuildContext context,
|
||||
String title,
|
||||
Widget Function(BuildContext) headerBuilder,
|
||||
) {
|
||||
final headerFontSize = LimitedFontSizeUtil.getHeaderFontSize(context);
|
||||
// final bodyFontSize = LimitedFontSizeUtil.getBodyFontSize(context);
|
||||
return Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: headerFontSize,
|
||||
return Column(
|
||||
children: [
|
||||
headerBuilder(context),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontFamily: 'Nunito',
|
||||
fontSize: headerFontSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLoadingIndicator(BuildContext context) {
|
||||
print('Loading');
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
||||
child: Center(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ValidatorUtil {
|
||||
|
@ -34,10 +36,16 @@ class ValidatorUtil {
|
|||
}
|
||||
|
||||
static String toLocalDateTime(String format, String value) {
|
||||
DateFormat dateFormat = DateFormat(format);
|
||||
DateTime dateTime = dateFormat.parse(value);
|
||||
|
||||
return DateFormat('dd/MM/yyyy HH:mm:ss').format(dateTime);
|
||||
try {
|
||||
if (value.isEmpty) return '';
|
||||
DateFormat dateFormat = DateFormat(format);
|
||||
DateTime? dateTime = dateFormat.tryParse(value);
|
||||
if (dateTime == null) return '';
|
||||
return dateFormat.format(dateTime);
|
||||
} catch (e, s) {
|
||||
log(e.toString(), stackTrace: s);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
static String formatDateTimePicker(String dateTime) {
|
||||
|
|
|
@ -1,46 +1,28 @@
|
|||
part of '../widgets.dart';
|
||||
|
||||
class CategoryCarousel extends StatelessWidget {
|
||||
final List<Category> categories;
|
||||
final void Function(Category) onSelect;
|
||||
final void Function(Category) onUnselect;
|
||||
class CategoryCarousel<T> extends StatelessWidget {
|
||||
final List<T?> categories;
|
||||
final void Function(T) filter;
|
||||
|
||||
const CategoryCarousel({
|
||||
super.key,
|
||||
required this.categories,
|
||||
required this.onSelect,
|
||||
required this.onUnselect,
|
||||
required this.filter,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundTheme = FlutterFlowTheme.of(context).primaryBackground;
|
||||
|
||||
bool isSelected = false;
|
||||
Category? current = null;
|
||||
|
||||
return Container(
|
||||
return SizedBox(
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundTheme,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: CarouselView(
|
||||
itemExtent: 100,
|
||||
onTap: (index) {
|
||||
if (isSelected && current == categories[index]) {
|
||||
onUnselect(categories[index]);
|
||||
isSelected = false;
|
||||
current = null;
|
||||
} else {
|
||||
onSelect(categories[index]);
|
||||
isSelected = true;
|
||||
current = categories[index];
|
||||
}
|
||||
},
|
||||
onTap: (index) => filter(categories[index]!),
|
||||
children: categories.map((category) {
|
||||
return GestureDetector(
|
||||
onTap: () {},
|
||||
category as Category?;
|
||||
return ColoredBox(
|
||||
color: backgroundTheme,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
|
@ -48,7 +30,7 @@ class CategoryCarousel extends StatelessWidget {
|
|||
Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: category.color,
|
||||
color: category!.color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
part of '../widgets.dart';
|
||||
|
||||
typedef SearchKey = GlobalKey<RemoteSearchViewState>;
|
||||
|
||||
typedef Query<X extends DocumentEntity> = X?;
|
||||
|
||||
/// -----------------------------------------------
|
||||
/// [SearchView]
|
||||
/// -----------------------------------------------
|
||||
|
||||
class SearchView<T> extends StatefulComponent {
|
||||
const SearchView({super.key});
|
||||
|
@ -17,6 +22,10 @@ class _SearchViewState<T> extends State<SearchView> {
|
|||
}
|
||||
}
|
||||
|
||||
/// -----------------------------------------------
|
||||
/// [LocalSearchView]
|
||||
/// -----------------------------------------------
|
||||
|
||||
class LocalSearchView<T> extends SearchView<T> {
|
||||
final List<T> list;
|
||||
final Widget Function(T) itemBuilder;
|
||||
|
@ -77,109 +86,104 @@ class LocalSearchViewState<T> extends State<LocalSearchView<T>> {
|
|||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
child: TextField(
|
||||
onChanged: filter,
|
||||
controller: editingController,
|
||||
cursorColor: Colors.black,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search),
|
||||
focusColor: Colors.black,
|
||||
hoverColor: Colors.black,
|
||||
fillColor: Colors.black,
|
||||
iconColor: Colors.black,
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide:
|
||||
BorderSide(color: Colors.black), // Set border color here
|
||||
padding: const EdgeInsets.all(30.0),
|
||||
child: TextFormField(
|
||||
controller: editingController,
|
||||
onChanged: filter,
|
||||
cursorColor: Colors.black,
|
||||
cursorWidth: 2.0,
|
||||
cursorRadius: Radius.circular(2.0),
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(
|
||||
color: Colors.black), // Set focused border color here
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.search,
|
||||
autocorrect: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.search, color: Colors.black),
|
||||
labelText: 'Pesquisar',
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
hintText: 'Digite sua pesquisa',
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.black),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.blue),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.red),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: Colors.red, width: 2.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteSearchListView<T> extends SearchView<T> {
|
||||
final List<T> list;
|
||||
final String title;
|
||||
final Widget Function(T) itemBuilder;
|
||||
// final Future<List<T>> Function(String) fetchItems;
|
||||
final bool Function(T, String) filter;
|
||||
final Widget header;
|
||||
final List<T> Function(String)? onSearch;
|
||||
/// -----------------------------------------------
|
||||
/// [RemoteSearchView]
|
||||
/// -----------------------------------------------
|
||||
|
||||
RemoteSearchListView({
|
||||
class RemoteSearchView<T> extends SearchView<T> {
|
||||
final Widget Function(BuildContext, T, int) bodyBuilder;
|
||||
Widget Function(BuildContext) headerBuilder;
|
||||
final PagingController<int, T> pagingController;
|
||||
final Future<(bool, List<T?>)> Function(int pageKey, Query query)
|
||||
dataProvider;
|
||||
|
||||
final void Function(Object, StackTrace) onFetchError;
|
||||
|
||||
RemoteSearchView({
|
||||
Key? key,
|
||||
// required this.fetchItems,
|
||||
required this.title,
|
||||
required this.list,
|
||||
required this.itemBuilder,
|
||||
required this.filter,
|
||||
List<T> Function(String)? onSearch,
|
||||
Widget? header,
|
||||
}) : header = header ?? const SizedBox.shrink(),
|
||||
onSearch = onSearch ??
|
||||
((String query) =>
|
||||
list.where((documents) => filter(documents, query)).toList()),
|
||||
super(key: key);
|
||||
required this.bodyBuilder,
|
||||
required this.headerBuilder,
|
||||
required this.pagingController,
|
||||
required this.dataProvider,
|
||||
required this.onFetchError,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_RemoteSearchViewState<T> createState() => _RemoteSearchViewState<T>();
|
||||
RemoteSearchViewState<T> createState() => RemoteSearchViewState<T>();
|
||||
}
|
||||
|
||||
class _RemoteSearchViewState<T> extends State<RemoteSearchListView<T>>
|
||||
class RemoteSearchViewState<T> extends State<RemoteSearchView<T>>
|
||||
with Pageable {
|
||||
TextEditingController editingController = TextEditingController();
|
||||
late List<T> filteredItems;
|
||||
bool isLoading = false;
|
||||
final apiCall = FreAccessWSGlobal.getDocuments;
|
||||
int count = 0;
|
||||
final PagingController<int, dynamic> _pagingController =
|
||||
PagingController<int, dynamic>(firstPageKey: 1);
|
||||
Query query = Document.fromDesc('');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
filteredItems = widget.list;
|
||||
_pagingController.addPageRequestListener(
|
||||
(dynamic pageKey) => fetchPage(
|
||||
dataProvider: () async {
|
||||
final newItems = await apiCall.call(pageKey.toString());
|
||||
if (newItems.jsonBody == null) return (false, null);
|
||||
final List<dynamic> docs =
|
||||
(newItems.jsonBody['value']['list'] as List<dynamic>?) ?? [];
|
||||
_pagingController.nextPageKey = pageKey + 1;
|
||||
|
||||
safeSetState(() {
|
||||
count = newItems.jsonBody['value']['count'] ?? 0;
|
||||
});
|
||||
return (docs.isNotEmpty, docs);
|
||||
},
|
||||
onDataUnavailable: () {
|
||||
setState(() {});
|
||||
showNoMoreDataSnackBar(context);
|
||||
},
|
||||
onDataAvailable: (vehicles) {
|
||||
setState(() {});
|
||||
_pagingController.appendLastPage(vehicles);
|
||||
},
|
||||
onFetchError: (e, s) {
|
||||
DialogUtil.errorDefault(context);
|
||||
LogUtil.requestAPIFailed(
|
||||
"proccessRequest.php", "", "Consulta de Veículo", e, s);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
widget.pagingController.addPageRequestListener(
|
||||
(page) => fetchPage(
|
||||
dataProvider: () async => await widget.dataProvider(page, query),
|
||||
onDataUnavailable: () => showNoMoreDataSnackBar(context),
|
||||
onDataAvailable: (data) =>
|
||||
widget.pagingController.appendLastPage(data),
|
||||
onFetchError: (e, s) => widget.onFetchError),
|
||||
);
|
||||
_pagingController.addStatusListener(_showError);
|
||||
widget.pagingController.addStatusListener(_showError);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
@ -200,57 +204,122 @@ class _RemoteSearchViewState<T> extends State<RemoteSearchListView<T>>
|
|||
content: Text(message),
|
||||
action: SnackBarAction(
|
||||
label: retry,
|
||||
onPressed: () => _pagingController.retryLastFailedRequest(),
|
||||
onPressed: () => widget.pagingController.retryLastFailedRequest(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void filterSearchResults(String query) async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
// final results = await widget.fetchItems(query);
|
||||
// setState(() {
|
||||
// filteredItems = results;
|
||||
// isLoading = false;
|
||||
// });
|
||||
void filter(Query data) async {
|
||||
if (data is Category) {
|
||||
safeSetState(() => query = Category(
|
||||
id: data.id,
|
||||
color: data.color,
|
||||
title: data.title,
|
||||
));
|
||||
widget.pagingController.refresh();
|
||||
} else if (data is Document) {
|
||||
log('filter: ${data.description}');
|
||||
|
||||
safeSetState(() => query = data);
|
||||
widget.pagingController.refresh();
|
||||
} else {
|
||||
safeSetState(() {
|
||||
query = Document.fromDesc('');
|
||||
});
|
||||
widget.pagingController.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final noDataFound = FFLocalizations.of(context).getVariableText(
|
||||
ptText: "Nenhum veículo encontrado!",
|
||||
enText: "No vehicle found",
|
||||
ptText: "Nenhum item encontrado!",
|
||||
enText: "No item found",
|
||||
);
|
||||
final theme = FlutterFlowTheme.of(context);
|
||||
final locale = FFLocalizations.of(context);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
buildPaginatedListView<int, T>(
|
||||
noDataFound,
|
||||
widget.pagingController,
|
||||
widget.headerBuilder,
|
||||
widget.bodyBuilder,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
onChanged: (value) => filterSearchResults(value),
|
||||
child: TextFormField(
|
||||
controller: editingController,
|
||||
onChanged: (value) => EasyDebounce.debounce(
|
||||
'_model.keyTextFieldTextController',
|
||||
const Duration(milliseconds: 500),
|
||||
() => filter(Document.fromDesc(value)),
|
||||
),
|
||||
cursorColor: theme.primaryText,
|
||||
showCursor: false,
|
||||
cursorWidth: 2.0,
|
||||
cursorRadius: Radius.circular(100),
|
||||
style: TextStyle(
|
||||
color: theme.primaryText,
|
||||
fontSize: 16.0,
|
||||
decorationColor: Colors.amber,
|
||||
),
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.search,
|
||||
autocorrect: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
labelText: "Search",
|
||||
hintText: "Search",
|
||||
prefixIcon: Icon(Icons.search),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25.0)),
|
||||
prefixIcon: Icon(Icons.search, color: theme.primary),
|
||||
labelText: locale.getVariableText(
|
||||
ptText: 'Pesquisar',
|
||||
enText: 'Search',
|
||||
),
|
||||
labelStyle: TextStyle(
|
||||
color: theme.primaryText,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
hintText: locale.getVariableText(
|
||||
ptText: 'Digite sua pesquisa',
|
||||
enText: 'Enter your search',
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
color: theme.accent2,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.transparent,
|
||||
helperStyle: TextStyle(
|
||||
color: theme.primaryText,
|
||||
decorationColor: theme.primaryText,
|
||||
),
|
||||
focusColor: theme.primaryText,
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: theme.primaryText),
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: theme.primaryText),
|
||||
),
|
||||
errorBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: theme.primaryText),
|
||||
),
|
||||
focusedErrorBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
borderSide: BorderSide(color: theme.primaryText, width: 2.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
widget.header,
|
||||
buildPaginatedListView<int, dynamic>(
|
||||
noDataFound,
|
||||
_pagingController,
|
||||
(BuildContext context, dynamic item, int index) =>
|
||||
widget.itemBuilder(item),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
part of '../widgets.dart';
|
||||
|
||||
typedef PDFViewerState = GlobalKey<SfPdfViewerState>;
|
||||
typedef PDFViewerKey = GlobalKey<SfPdfViewerState>;
|
||||
|
||||
abstract class Viewer extends StatelessComponent {
|
||||
const Viewer({super.key, required this.src});
|
||||
abstract interface class Viewer extends StatelessComponent {
|
||||
const Viewer({
|
||||
super.key,
|
||||
required this.src,
|
||||
});
|
||||
final String src;
|
||||
|
||||
@override
|
||||
|
@ -14,13 +17,24 @@ abstract class Viewer extends StatelessComponent {
|
|||
Widget buildViewer(BuildContext context);
|
||||
}
|
||||
|
||||
class FREViewerPDF extends Viewer {
|
||||
const FREViewerPDF({required Key key, required this.url})
|
||||
: super(key: key as PDFViewerState, src: url);
|
||||
final String url;
|
||||
class FREViewerPDF extends StatelessComponent {
|
||||
final String src;
|
||||
final PDFViewerKey search;
|
||||
|
||||
const FREViewerPDF({
|
||||
required this.search,
|
||||
required this.src,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return buildViewer(context);
|
||||
}
|
||||
|
||||
Widget buildViewer(BuildContext context) {
|
||||
return SfPdfViewer.network(src, key: key as PDFViewerState);
|
||||
return SfPdfViewer.network(
|
||||
src,
|
||||
key: search,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hub/features/backend/api_requests/api_calls.dart';
|
||||
import 'package:hub/features/documents/index.dart';
|
||||
import 'package:hub/flutter_flow/index.dart';
|
||||
import 'package:hub/shared/mixins/pegeable_mixin.dart';
|
||||
import 'package:hub/shared/utils/index.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ dependencies:
|
|||
flutter_animate: ^4.5.2
|
||||
# flutter_cache_manager: ^3.4.1
|
||||
# flutter_plugin_android_lifecycle: ^2.0.23
|
||||
share_plus: ^10.0.0
|
||||
share_plus: ^10.1.4
|
||||
# connectivity_plus: ^6.0.5
|
||||
flutter_secure_storage: ^10.0.0-beta.2
|
||||
flutter_secure_storage_linux: ^2.0.0
|
||||
|
|
Loading…
Reference in New Issue