Subiendo actividades de listado

parent b42693cf
const apiApp = 'https://basic2.visorus.com.mx';
import 'package:flutter/material.dart';
import 'package:practica1_flutter/src/config/routes.dart';
import '../pages/category/category_widget.dart';
import '../pages/category_form.dart';
import '../pages/home_page.dart';
import '../pages/login_page.dart';
import '../pages/articles_page.dart';
......@@ -8,6 +10,8 @@ Map<String, WidgetBuilder> getApplicationRoutes() {
return <String, WidgetBuilder>{
'login': (BuildContext context) => const LoginPage(),
'homeSeller': (BuildContext context) => const HomeSellerPage(),
'articulos': (BuildContext context) => const ArticlesPage(),
//'articulos': (BuildContext context) => const ArticlesPage(),
'category_form': (BuildContext context) => CategoryForm(),
};
}
import 'dart:convert';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:practica1_flutter/src/models/article_model.dart';
import '../http_api/articles_api.dart';
class ArticleController {
final Connectivity _connectivity = Connectivity();
final ArticleApi _articleApi = ArticleApi();
Future<Map<String, dynamic>> getArticles(int categoryId) async {
Map<String, dynamic> mapResp = {
'ok': false,
'message': 'No hay artículos',
'data': null
};
ConnectivityResult connectivityResult = await _connectivity.checkConnectivity();
if (connectivityResult != ConnectivityResult.none) {
if (connectivityResult == ConnectivityResult.wifi || connectivityResult == ConnectivityResult.mobile) {
Map<String, dynamic> respGet = await _articleApi.getArticles();
if (respGet['statusCode'] == 200) {
try {
var decodeResp = json.decode(respGet['body']);
List<ArticleModel> listArticles = ArticleModel.fromJsonArray(decodeResp['data']);
mapResp['ok'] = true;
mapResp['message'] = "${listArticles.length} artículos encontrados";
mapResp['data'] = listArticles;
} catch (e) {
mapResp['message'] = "Error en el procesamiento de datos: $e";
}
} else {
mapResp['message'] = "${respGet['body']}";
}
}
}
return mapResp;
}
}
import 'dart:async';
import 'dart:convert';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:practica1_flutter/src/http_api/category_api.dart';
import 'package:practica1_flutter/src/models/category_model.dart';
//en este manejaremos la información que vamos a presentar visualmente al usuario.
class CategoryController {
final Connectivity _connectivity = Connectivity();
Future<Map<String, dynamic>> getCategories() async {
Map<String, dynamic> mapResp = {
'ok': false,
'message': 'No hay categorías',
'data': null
};
ConnectivityResult connectivityResult = await _connectivity.checkConnectivity();
if (connectivityResult != ConnectivityResult.none) {
if (connectivityResult == ConnectivityResult.wifi || connectivityResult == ConnectivityResult.mobile) {
CategoryService categoryApi = CategoryService();
Map<String, dynamic> respGet = await categoryApi.getCategories();
if (respGet['statusCode'] == 200) {
try {
var decodeResp = json.decode(respGet['body']);
List<CategoryModel> listCategories = CategoryModel.fromJsonArray(decodeResp['data']);
mapResp['ok'] = true;
mapResp['message'] = "${listCategories.length} categorías encontradas";
mapResp['data'] = listCategories;
} catch (e) {
mapResp['message'] = "Error en operador $e";
return mapResp;
}
} else {
mapResp['message'] = "${respGet['body']}";
}
}
}
return mapResp;
}
}
import 'package:connectivity_plus/connectivity_plus.dart';
// verificar el valor de connectivityResult.
// nos ermite verificar el tipo de conexión a internet que está disponible en el
// dispositivo y luego muestra un mensaje en la consola en función del tipo de red detectado.
Future<void> checkConnectivity() async {
final ConnectivityResult connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
print("Mobile network available.");
} else if (connectivityResult == ConnectivityResult.wifi) {
print("Wi-fi available.");
} else if (connectivityResult == ConnectivityResult.ethernet) {
print("Ethernet connection available.");
} else if (connectivityResult == ConnectivityResult.vpn) {
print("VPN connection active.");
} else if (connectivityResult == ConnectivityResult.bluetooth) {
print("Bluetooth connection available.");
} else if (connectivityResult == ConnectivityResult.other) {
print("Connected to other type of network.");
} else if (connectivityResult == ConnectivityResult.none) {
// No hay tipos de red disponibles.
print("No available network types.");
}
}
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../../environments/api_app.dart';
class ArticleApi {
Future<Map<String, dynamic>> getArticles() async{
String url= '${apiApp}/articulo?offset=0&max=100';
if (kDebugMode) {
print('Url -> $url');
}
try{
final resp = await http.get(Uri.parse(url));
return {"statusCode": resp.statusCode, "body": resp.body};
}catch(e){
return {"statusCode": 501, "body": '$e'};
}
}
}
\ No newline at end of file
import 'dart:async';
import 'dart:convert';
import 'package:flutter/cupertino.dart';
class Categorias extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
\ No newline at end of file
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:practica1_flutter/environments/api_app.dart';
class CategoryService {
Future<Map<String, dynamic>> getCategories() async {
final ConnectivityResult connectivityResult = await (Connectivity().checkConnectivity());
//El Connectivity().checkConnectivity() verifica el estado actual de la conexión a Internet
if (connectivityResult == ConnectivityResult.none) {
return {"statusCode": 0, "body": "No internet connection"};
} // y Si no hay conexión a Internet se retorna
// statusCode 0 y un mensaje de sin conexion a internet
String url = '${apiApp}/categoria?offset=0&max=100';
if (kDebugMode) {
print('Url -> $url');
}
try {
final resp = await http.get(Uri.parse(url));
return {"statusCode": resp.statusCode, "body": resp.body};
} catch (e) {
return {"statusCode": 501, "body": '$e'};
}
}
}
import 'dart:convert';
class ArticleModel {
final int id;
final String clave;
final String nombre;
final int categoriaId;
final List<Price> precios;
final bool activo;
ArticleModel({
required this.id,
required this.clave,
required this.nombre,
required this.categoriaId,
required this.precios,
required this.activo,
});
factory ArticleModel.fromJson(Map<String, dynamic> json) {
var list = json['precios'] as List;
List<Price> preciosList = list.map((i) => Price.fromJson(i)).toList();
return ArticleModel(
id: json['id'],
clave: json['clave'],
nombre: json['nombre'],
categoriaId: json['categoria']['id'],
precios: preciosList,
activo: json['activo'],
);
}
static List<ArticleModel> fromJsonArray(List<dynamic> jsonArray) {
return jsonArray.map((json) => ArticleModel.fromJson(json)).toList();
}
}
class Price {
final int id;
final double precio;
Price({
required this.id,
required this.precio,
});
factory Price.fromJson(Map<String, dynamic> json) {
return Price(
id: json['id'],
precio: json['precio'].toDouble(),
);
}
}
......@@ -40,6 +40,7 @@ class CategoryModel {
}
// Este metodo sirve para para convertir una instancia de CategoryModel a JSON
// enviar un metodo a post
Map<String, dynamic> toJson() {
return {
"id": id,
......
import 'package:flutter/material.dart';
import 'package:practica1_flutter/src/pages/home_page.dart';
class ArticlesPage extends StatelessWidget {
const ArticlesPage({Key? key}) : super(key: key);
import 'package:practica1_flutter/src/controllers/articles_controller.dart';
import 'package:practica1_flutter/src/models/article_model.dart';
class ArticlePage extends StatefulWidget {
final int categoryId;
// creamos un constructor de ArticlePage que recibe categoryId como parámetro.
const ArticlePage({Key? key, required this.categoryId}) : super(key: key);
@override
_ArticlePageState createState() => _ArticlePageState();
}
class _ArticlePageState extends State<ArticlePage> {
final ArticleController _articleController = ArticleController();
late Future<Map<String, dynamic>> _articlesFuture;
@override
void initState() {
super.initState();
// Inicializa _articlesFuture llamando al método getArticles del controlador de
// artículos con el categoryId.
_articlesFuture = _articleController.getArticles(widget.categoryId);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Estoy en Articulos'),
backgroundColor: Colors.red,
actions:[
Center(
child: const Icon(Icons.shop),
)
],
title: const Text('Artículos'),
backgroundColor: Colors.indigoAccent,
foregroundColor: Colors.white,
),
body: Center(
child: Text('Bienvenido a los articulos'),
body: FutureBuilder<Map<String, dynamic>>(
future: _articlesFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data!['ok']) {
if (snapshot.data!['data'] != null && (snapshot.data!['data'] as List).isNotEmpty) {
List<ArticleModel> articles = snapshot.data!['data'] as List<ArticleModel>;
//Lista de articulos
return ListView.builder(
// Definimos el número de elementos en la lista.
itemCount: articles.length,
itemBuilder: (context, index) {
final article = articles[index];
return Card(
child: ListTile(
title: Text(article.nombre),
subtitle: Text('id :${article.categoriaId}'),
//subtitle: Text('Clave: ${article.clave}'),
trailing: Text('\$${article.precios.isNotEmpty ? article.precios.first.precio.toStringAsFixed(2) : 'N/A'}'),
),
);
},
);
} else {
return Center(child: Text('No se encontraron artículos.'));
}
} else {
return Center(child: Text('Error: ${snapshot.data!['message']}'));
}
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
......
import 'package:flutter/material.dart';
import 'package:practica1_flutter/src/controllers/category_controller.dart';
import 'package:practica1_flutter/src/models/category_model.dart';
import 'package:practica1_flutter/src/pages/articles_page.dart';
class CategoryWidget extends StatefulWidget {
@override
_CategoryWidgetState createState() => _CategoryWidgetState();
}
class _CategoryWidgetState extends State<CategoryWidget> {
final CategoryController _categoryCtrl = CategoryController();
@override
Widget build(BuildContext context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: FutureBuilder<Map<String, dynamic>>(
future: _categoryCtrl.getCategories(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data!['ok']) {
if (snapshot.data!['data'] != null && (snapshot.data!['data'] as List).isNotEmpty) {
List<CategoryModel> categories = snapshot.data!['data'];
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
final category = categories[index];
return Card(
child: ListTile(
title: Text(category.name ?? 'No Name'),
leading: const Icon(Icons.touch_app),
onTap: () {
// Navegar al listado de artículos y pasa el ID de la categoría
Navigator.push(
//El push lo que hace es empujar la ruta dada por medio de la clase
// MaterialPageRoute, en esta clase se envía el contexto y el widget
// de la nueva pantalla que va a mostrar.
context,
MaterialPageRoute(
builder: (context) => ArticlePage(
categoryId: category.id,
),
),
);
},
),
);
},
itemCount: categories.length,
);
} else {
return Center(child: Text("No se encontraron categorías"));
}
} else {
return Center(child: Text("Error: ${snapshot.data!['message']}"));
}
} else if (snapshot.hasError) {
return Center(child: Text("Error: ${snapshot.error.toString()}"));
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
),
);
}
}
import 'package:flutter/material.dart';
class CategoryForm extends StatefulWidget {
@override
_NewCategoryFormState createState() => _NewCategoryFormState();
}
class _NewCategoryFormState extends State<CategoryForm> {
final _formKey = GlobalKey<FormState>();
final TextEditingController _claveController = TextEditingController();
final TextEditingController _fechaCreadoController = TextEditingController();
final TextEditingController _nombreController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agregar Categoría'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _claveController,
decoration: InputDecoration(labelText: 'Clave'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingrese la clave';
}
return null;
},
),
TextFormField(
controller: _fechaCreadoController,
decoration: InputDecoration(labelText: 'Fecha Creado (milisegundos)'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingrese la fecha en milisegundos';
}
return null;
},
keyboardType: TextInputType.number,
),
TextFormField(
controller: _nombreController,
decoration: InputDecoration(labelText: 'Nombre'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingrese el nombre';
}
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Guardar los valores y mostrar mensaje en consola
print({
"clave": _claveController.text,
"fechaCreado": int.parse(_fechaCreadoController.text),
"nombre": _nombreController.text,
});
}
},
child: Text('GUARDAR'),
),
],
),
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:practica1_flutter/src/pages/articles_page.dart';
import 'package:practica1_flutter/src/pages/category/category_widget.dart'; // Asegúrate de importar la página de categorías
import 'package:practica1_flutter/src/pages/category_form.dart'; // Asegúrate de importar la página del formulario
class HomeSellerPage extends StatelessWidget {
const HomeSellerPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return DefaultTabController(// Agrega un DefaultTabController para manejar el estado de las pestañas.
length: 2, // Define el número de pestañas.
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Estoy en Home',
theme: ThemeData(
primaryColor: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text("ShopMaster"),
bottom: const TabBar( // Agrega el TabBar en la parte inferior de la AppBar.
tabs: [
Tab(icon: Icon(Icons.article_outlined), text: "Articulos"),
Tab(icon: Icon(Icons.shop_2), text: "Ventas"),
],
),
),
drawer: MenuLateral(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
/*ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, 'articulos');
},
child: const Text('Acceder a articulos -->'),
),*/
const SizedBox(height: 200),
FloatingActionButton(
onPressed: () {
},
child: Icon(Icons.add),
tooltip: 'Agregar Categoría',//Mensaje
),
],
),
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text("ShopMaster"),
foregroundColor: Colors.white,
backgroundColor: Colors.indigoAccent,
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.article_outlined), text: "Artículos"),
Tab(icon: Icon(Icons.category), text: "Categorías"),
],
),
),
drawer: MenuLateral(),
body: TabBarView(
children: [
CategoryWidget(), // Página de categorías
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, 'category_form');
},
child: Icon(Icons.add),
tooltip: 'Agregar Categoría', // Mensaje para el botón
),
),
);
}
}
//Clase del meuno lateral
// Clase del menú lateral
class MenuLateral extends StatelessWidget {
@override
Widget build(BuildContext context) {
......@@ -60,26 +47,23 @@ class MenuLateral extends StatelessWidget {
child: ListView(
children: [
const UserAccountsDrawerHeader(
accountName: Text("Mi cuenta"),
accountEmail: Text("Prueba@gmail.com"),
accountName: Text("Efren David"),
accountEmail: Text("24mdavid25@gmail.com"),
),
Ink(
color: Colors.indigo, // Cambiar color de cada menú.
color: Colors.indigo,
child: ListTile(
title: const Text("Menu 1", style: TextStyle(color: Colors.white)),
title: const Text("Mi cuenta", style: TextStyle(color: Colors.white)),
),
),
const ListTile(
title: Text("Menu 2"),
title: Text("Categorías"),
),
const ListTile(
title: Text("Menu 2"),
title: Text("Configuración"),
),
],
),
);
}
}
//Crea un nuevo archivo dart en lib/src/shared_widgets/{{widgetMensajes}}
// en este widget tendremos Alerts dialogs para mostrar los resultados
// de las consultas API:
import 'package:flutter/material.dart';
Widget alertDanger(String message) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(8.0),
color: const Color.fromRGBO(192, 57, 43, 0.5),
child: Row(
children: <Widget>[
const Icon(
Icons.error,
color: Colors.white,
),
const SizedBox(
width: 10.0,
),
Expanded(
child: Text(
message
),
)
],
));
}
Widget alertSuccess(String message) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(8.0),
color: Colors.lightGreen,
child: Row(
children: <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.white,
),
const SizedBox(
width: 10.0,
),
Expanded(
child: Text(message),
)
],
));
}
Widget alertWait(String message) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(8.0),
color: Colors.lightBlueAccent,
child: Row(
children: <Widget>[
const Icon(
Icons.info,
color: Colors.white,
),
const SizedBox(
width: 10.0,
),
Expanded(
child: Text(message),
)
],
));
}
\ No newline at end of file
......@@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation
import connectivity_plus_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
}
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
args:
dependency: transitive
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
async:
dependency: transitive
description:
......@@ -41,6 +49,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
connectivity_plus:
dependency: "direct dev"
description:
name: connectivity_plus
sha256: "3f8fe4e504c2d33696dac671a54909743bc6a902a9bb0902306f7a2aed7e528e"
url: "https://pub.dev"
source: hosted
version: "2.3.9"
connectivity_plus_linux:
dependency: transitive
description:
name: connectivity_plus_linux
sha256: "3caf859d001f10407b8e48134c761483e4495ae38094ffcca97193f6c271f5e2"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
connectivity_plus_macos:
dependency: transitive
description:
name: connectivity_plus_macos
sha256: "488d2de1e47e1224ad486e501b20b088686ba1f4ee9c4420ecbc3b9824f0b920"
url: "https://pub.dev"
source: hosted
version: "1.2.6"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
url: "https://pub.dev"
source: hosted
version: "1.2.4"
connectivity_plus_web:
dependency: transitive
description:
name: connectivity_plus_web
sha256: "81332be1b4baf8898fed17bb4fdef27abb7c6fd990bf98c54fd978478adf2f1a"
url: "https://pub.dev"
source: hosted
version: "1.2.5"
connectivity_plus_windows:
dependency: transitive
description:
name: connectivity_plus_windows
sha256: "535b0404b4d5605c4dd8453d67e5d6d2ea0dd36e3b477f50f31af51b0aeab9dd"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
cupertino_icons:
dependency: "direct main"
description:
......@@ -49,6 +105,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
fake_async:
dependency: transitive
description:
......@@ -57,6 +121,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
flutter:
dependency: "direct main"
description: flutter
......@@ -75,6 +147,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: "direct main"
description:
......@@ -91,6 +168,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
leak_tracker:
dependency: transitive
description:
......@@ -147,6 +232,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.12.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
path:
dependency: transitive
description:
......@@ -155,6 +248,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
sky_engine:
dependency: transitive
description: flutter
......@@ -240,6 +349,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sdks:
dart: ">=3.4.4 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
......@@ -33,7 +33,7 @@ dev_dependencies:
sdk: flutter
flutter_lints: ^4.0.0
connectivity_plus: ^2.3.0
flutter:
# The following line ensures that the Material Icons font is
......
......@@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
}
......@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment