2021-11-02 02:29:58 +00:00
import ' dart:convert ' ;
import ' package:cwtch/cwtch/cwtch.dart ' ;
import ' package:cwtch/cwtch_icons_icons.dart ' ;
import ' package:cwtch/models/servers.dart ' ;
import ' package:cwtch/widgets/cwtchlabel.dart ' ;
import ' package:cwtch/widgets/passwordfield.dart ' ;
import ' package:cwtch/widgets/textfield.dart ' ;
import ' package:package_info_plus/package_info_plus.dart ' ;
import ' package:flutter/material.dart ' ;
import ' package:cwtch/settings.dart ' ;
import ' package:provider/provider.dart ' ;
import ' package:flutter_gen/gen_l10n/app_localizations.dart ' ;
import ' ../errorHandler.dart ' ;
import ' ../main.dart ' ;
import ' ../config.dart ' ;
2021-11-02 20:36:59 +00:00
/// Pane to add or edit a server
2021-11-02 02:29:58 +00:00
class AddEditServerView extends StatefulWidget {
const AddEditServerView ( ) ;
@ override
_AddEditServerViewState createState ( ) = > _AddEditServerViewState ( ) ;
}
class _AddEditServerViewState extends State < AddEditServerView > {
final _formKey = GlobalKey < FormState > ( ) ;
final ctrlrDesc = TextEditingController ( text: " " ) ;
final ctrlrOldPass = TextEditingController ( text: " " ) ;
final ctrlrPass = TextEditingController ( text: " " ) ;
final ctrlrPass2 = TextEditingController ( text: " " ) ;
final ctrlrOnion = TextEditingController ( text: " " ) ;
late bool usePassword ;
2021-12-10 04:22:55 +00:00
2021-11-02 02:29:58 +00:00
@ override
void initState ( ) {
super . initState ( ) ;
var serverInfoState = Provider . of < ServerInfoState > ( context , listen: false ) ;
ctrlrOnion . text = serverInfoState . onion ;
usePassword = serverInfoState . isEncrypted ;
if ( serverInfoState . description . isNotEmpty ) {
ctrlrDesc . text = serverInfoState . description ;
}
}
@ override
void dispose ( ) {
super . dispose ( ) ;
}
@ override
Widget build ( BuildContext context ) {
return Scaffold (
appBar: AppBar (
2021-11-02 22:10:36 +00:00
title: ctrlrOnion . text . isEmpty ? Text ( AppLocalizations . of ( context ) ! . addServerTitle ) : Text ( AppLocalizations . of ( context ) ! . editServerTitle ) ,
2021-11-02 02:29:58 +00:00
) ,
body: _buildSettingsList ( ) ,
) ;
}
void _handleSwitchPassword ( bool ? value ) {
setState ( ( ) {
usePassword = value ! ;
} ) ;
}
Widget _buildSettingsList ( ) {
return Consumer2 < ServerInfoState , Settings > ( builder: ( context , serverInfoState , settings , child ) {
return LayoutBuilder ( builder: ( BuildContext context , BoxConstraints viewportConstraints ) {
return Scrollbar (
isAlwaysShown: true ,
child: SingleChildScrollView (
clipBehavior: Clip . antiAlias ,
child: ConstrainedBox (
constraints: BoxConstraints (
minHeight: viewportConstraints . maxHeight ,
) ,
child: Form (
key: _formKey ,
child: Container (
2021-11-26 02:13:36 +00:00
margin: EdgeInsets . fromLTRB ( 30 , 5 , 30 , 10 ) ,
padding: EdgeInsets . fromLTRB ( 20 , 5 , 20 , 10 ) ,
2021-11-25 23:59:54 +00:00
child: Column ( mainAxisAlignment: MainAxisAlignment . start , crossAxisAlignment: CrossAxisAlignment . stretch , children: [
// Onion
Visibility (
visible: serverInfoState . onion . isNotEmpty ,
2021-12-15 22:29:27 +00:00
child: Column (
mainAxisAlignment: MainAxisAlignment . start ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [ CwtchLabel ( label: AppLocalizations . of ( context ) ! . serverAddress ) , SelectableText ( serverInfoState . onion ) ] ) ) ,
2021-11-25 23:59:54 +00:00
// Description
Column ( mainAxisAlignment: MainAxisAlignment . start , crossAxisAlignment: CrossAxisAlignment . start , children: [
SizedBox (
height: 20 ,
) ,
CwtchLabel ( label: AppLocalizations . of ( context ) ! . serverDescriptionLabel ) ,
Text ( AppLocalizations . of ( context ) ! . serverDescriptionDescription ) ,
SizedBox (
height: 20 ,
) ,
CwtchTextField (
controller: ctrlrDesc ,
2021-12-14 19:29:13 +00:00
hintText: AppLocalizations . of ( context ) ! . fieldDescriptionLabel ,
2021-11-25 23:59:54 +00:00
autofocus: false ,
)
] ) ,
SizedBox (
height: 20 ,
) ,
// Enabled
Visibility (
visible: serverInfoState . onion . isNotEmpty ,
child: SwitchListTile (
2021-12-09 05:40:40 +00:00
title: Text ( AppLocalizations . of ( context ) ! . serverEnabled , style: TextStyle ( color: settings . current ( ) . mainTextColor ) ) ,
2021-11-25 23:59:54 +00:00
subtitle: Text ( AppLocalizations . of ( context ) ! . serverEnabledDescription ) ,
value: serverInfoState . running ,
onChanged: ( bool value ) {
serverInfoState . setRunning ( value ) ;
if ( value ) {
Provider . of < FlwtchState > ( context , listen: false ) . cwtch . LaunchServer ( serverInfoState . onion ) ;
} else {
Provider . of < FlwtchState > ( context , listen: false ) . cwtch . StopServer ( serverInfoState . onion ) ;
}
} ,
2021-12-11 05:07:47 +00:00
activeTrackColor: settings . theme . defaultButtonColor ,
2021-12-09 05:40:40 +00:00
inactiveTrackColor: settings . theme . defaultButtonDisabledColor ,
secondary: Icon ( CwtchIcons . negative_heart_24px , color: settings . current ( ) . mainTextColor ) ,
2021-11-25 23:59:54 +00:00
) ) ,
// Auto start
SwitchListTile (
2021-12-09 05:40:40 +00:00
title: Text ( AppLocalizations . of ( context ) ! . serverAutostartLabel , style: TextStyle ( color: settings . current ( ) . mainTextColor ) ) ,
2021-11-25 23:59:54 +00:00
subtitle: Text ( AppLocalizations . of ( context ) ! . serverAutostartDescription ) ,
value: serverInfoState . autoStart ,
onChanged: ( bool value ) {
serverInfoState . setAutostart ( value ) ;
if ( ! serverInfoState . onion . isEmpty ) {
Provider . of < FlwtchState > ( context , listen: false ) . cwtch . SetServerAttribute ( serverInfoState . onion , " autostart " , value ? " true " : " false " ) ;
}
} ,
2021-12-11 05:07:47 +00:00
activeTrackColor: settings . theme . defaultButtonColor ,
2021-12-09 05:40:40 +00:00
inactiveTrackColor: settings . theme . defaultButtonDisabledColor ,
secondary: Icon ( CwtchIcons . favorite_24dp , color: settings . current ( ) . mainTextColor ) ,
2021-11-25 23:59:54 +00:00
) ,
2021-11-26 02:13:36 +00:00
// metrics
Visibility (
2021-12-20 19:33:34 +00:00
visible: serverInfoState . onion . isNotEmpty & & serverInfoState . running ,
2021-11-26 02:13:36 +00:00
child: Column ( crossAxisAlignment: CrossAxisAlignment . start , children: [
SizedBox (
height: 20 ,
) ,
Text ( AppLocalizations . of ( context ) ! . serverMetricsLabel , style: Provider . of < FlwtchState > ( context ) . biggerFont ) ,
2021-12-15 22:29:27 +00:00
Row ( mainAxisAlignment: MainAxisAlignment . spaceBetween , children: [
Row ( crossAxisAlignment: CrossAxisAlignment . start , children: [
Text ( AppLocalizations . of ( context ) ! . serverTotalMessagesLabel ) ,
] ) ,
Text ( serverInfoState . totalMessages . toString ( ) )
] ) ,
Row ( mainAxisAlignment: MainAxisAlignment . spaceBetween , children: [
Row ( crossAxisAlignment: CrossAxisAlignment . start , children: [
Text ( AppLocalizations . of ( context ) ! . serverConnectionsLabel ) ,
] ) ,
Text ( serverInfoState . connections . toString ( ) )
] ) ,
2021-11-26 02:13:36 +00:00
] ) ) ,
2021-11-25 23:59:54 +00:00
// ***** Password *****
// use password toggle
Visibility (
visible: serverInfoState . onion . isEmpty ,
child: Column ( mainAxisAlignment: MainAxisAlignment . center , children: < Widget > [
SizedBox (
height: 20 ,
) ,
Checkbox (
value: usePassword ,
2021-12-09 05:40:40 +00:00
fillColor: MaterialStateProperty . all ( settings . current ( ) . defaultButtonColor ) ,
activeColor: settings . current ( ) . defaultButtonActiveColor ,
2021-11-25 23:59:54 +00:00
onChanged: _handleSwitchPassword ,
) ,
Text (
AppLocalizations . of ( context ) ! . radioUsePassword ,
2021-12-09 05:40:40 +00:00
style: TextStyle ( color: settings . current ( ) . mainTextColor ) ,
2021-11-25 23:59:54 +00:00
) ,
SizedBox (
height: 20 ,
) ,
Padding (
padding: EdgeInsets . symmetric ( horizontal: 24 ) ,
child: Text (
usePassword ? AppLocalizations . of ( context ) ! . encryptedServerDescription : AppLocalizations . of ( context ) ! . plainServerDescription ,
textAlign: TextAlign . center ,
) ) ,
SizedBox (
height: 20 ,
) ,
] ) ) ,
// current password
Visibility (
visible: serverInfoState . onion . isNotEmpty & & serverInfoState . isEncrypted ,
child: Column ( mainAxisAlignment: MainAxisAlignment . start , crossAxisAlignment: CrossAxisAlignment . start , children: < Widget > [
CwtchLabel ( label: AppLocalizations . of ( context ) ! . currentPasswordLabel ) ,
SizedBox (
height: 20 ,
) ,
CwtchPasswordField (
controller: ctrlrOldPass ,
autoFillHints: [ AutofillHints . newPassword ] ,
validator: ( value ) {
// Password field can be empty when just updating the profile, not on creation
if ( serverInfoState . isEncrypted & & serverInfoState . onion . isEmpty & & value . isEmpty & & usePassword ) {
return AppLocalizations . of ( context ) ! . passwordErrorEmpty ;
}
if ( Provider . of < ErrorHandler > ( context ) . deletedServerError = = true ) {
return AppLocalizations . of ( context ) ! . enterCurrentPasswordForDeleteServer ;
}
return null ;
} ,
) ,
SizedBox (
height: 20 ,
) ,
] ) ) ,
// new passwords 1 & 2
Visibility (
// Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check
visible: serverInfoState . onion . isEmpty & & usePassword ,
child: Column ( mainAxisAlignment: MainAxisAlignment . start , crossAxisAlignment: CrossAxisAlignment . start , children: [
CwtchLabel ( label: AppLocalizations . of ( context ) ! . newPassword ) ,
SizedBox (
height: 20 ,
) ,
CwtchPasswordField (
controller: ctrlrPass ,
validator: ( value ) {
// Password field can be empty when just updating the profile, not on creation
if ( serverInfoState . onion . isEmpty & & value . isEmpty & & usePassword ) {
return AppLocalizations . of ( context ) ! . passwordErrorEmpty ;
}
if ( value ! = ctrlrPass2 . value . text ) {
return AppLocalizations . of ( context ) ! . passwordErrorMatch ;
}
return null ;
} ,
) ,
SizedBox (
height: 20 ,
) ,
CwtchLabel ( label: AppLocalizations . of ( context ) ! . password2Label ) ,
SizedBox (
height: 20 ,
) ,
CwtchPasswordField (
controller: ctrlrPass2 ,
validator: ( value ) {
// Password field can be empty when just updating the profile, not on creation
if ( serverInfoState . onion . isEmpty & & value . isEmpty & & usePassword ) {
return AppLocalizations . of ( context ) ! . passwordErrorEmpty ;
}
if ( value ! = ctrlrPass . value . text ) {
return AppLocalizations . of ( context ) ! . passwordErrorMatch ;
}
return null ;
} ) ,
] ) ,
) ,
SizedBox (
height: 20 ,
) ,
Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Expanded (
child: ElevatedButton (
onPressed: serverInfoState . onion . isEmpty ? _createPressed : _savePressed ,
child: Text (
serverInfoState . onion . isEmpty ? AppLocalizations . of ( context ) ! . addServerTitle : AppLocalizations . of ( context ) ! . saveServerButton ,
textAlign: TextAlign . center ,
) ,
) ,
) ,
] ,
) ,
Visibility (
visible: serverInfoState . onion . isNotEmpty ,
child: Column ( mainAxisAlignment: MainAxisAlignment . start , crossAxisAlignment: CrossAxisAlignment . end , children: [
SizedBox (
height: 20 ,
) ,
Tooltip (
message: AppLocalizations . of ( context ) ! . enterCurrentPasswordForDeleteServer ,
child: ElevatedButton . icon (
onPressed: ( ) {
showAlertDialog ( context ) ;
} ,
icon: Icon ( Icons . delete_forever ) ,
label: Text ( AppLocalizations . of ( context ) ! . deleteBtn ) ,
) )
] ) )
// ***** END Password *****
] ) ) ) ) ) ) ;
2021-11-02 02:29:58 +00:00
} ) ;
} ) ;
}
void _createPressed ( ) {
// This will run all the validations in the form including
// checking that display name is not empty, and an actual check that the passwords
// match (and are provided if the user has requested an encrypted profile).
if ( _formKey . currentState ! . validate ( ) ) {
if ( usePassword ) {
2021-11-25 23:59:54 +00:00
Provider . of < FlwtchState > ( context , listen: false ) . cwtch . CreateServer ( ctrlrPass . value . text , ctrlrDesc . value . text , Provider . of < ServerInfoState > ( context , listen: false ) . autoStart ) ;
2021-11-02 02:29:58 +00:00
} else {
2021-11-25 23:59:54 +00:00
Provider . of < FlwtchState > ( context , listen: false ) . cwtch . CreateServer ( DefaultPassword , ctrlrDesc . value . text , Provider . of < ServerInfoState > ( context , listen: false ) . autoStart ) ;
2021-11-02 02:29:58 +00:00
}
Navigator . of ( context ) . pop ( ) ;
}
}
void _savePressed ( ) {
var server = Provider . of < ServerInfoState > ( context , listen: false ) ;
2021-11-25 23:59:54 +00:00
Provider . of < FlwtchState > ( context , listen: false ) . cwtch . SetServerAttribute ( server . onion , " description " , ctrlrDesc . text ) ;
2021-11-02 02:29:58 +00:00
server . setDescription ( ctrlrDesc . text ) ;
if ( _formKey . currentState ! . validate ( ) ) {
2021-11-02 21:48:52 +00:00
// TODO support change password
2021-11-02 02:29:58 +00:00
}
Navigator . of ( context ) . pop ( ) ;
}
2021-11-04 01:56:53 +00:00
showAlertDialog ( BuildContext context ) {
// set up the buttons
Widget cancelButton = ElevatedButton (
child: Text ( AppLocalizations . of ( context ) ! . cancel ) ,
onPressed: ( ) {
Navigator . of ( context ) . pop ( ) ; // dismiss dialog
} ,
) ;
Widget continueButton = ElevatedButton (
child: Text ( AppLocalizations . of ( context ) ! . deleteServerConfirmBtn ) ,
onPressed: ( ) {
2021-11-25 23:59:54 +00:00
var onion = Provider . of < ServerInfoState > ( context , listen: false ) . onion ;
Provider . of < FlwtchState > ( context , listen: false ) . cwtch . DeleteServer ( onion , Provider . of < ServerInfoState > ( context , listen: false ) . isEncrypted ? ctrlrOldPass . value . text : DefaultPassword ) ;
2021-11-04 01:56:53 +00:00
Future . delayed (
const Duration ( milliseconds: 500 ) ,
2021-11-25 23:59:54 +00:00
( ) {
2021-11-04 01:56:53 +00:00
if ( globalErrorHandler . deletedServerSuccess ) {
final snackBar = SnackBar ( content: Text ( AppLocalizations . of ( context ) ! . deleteServerSuccess + " : " + onion ) ) ;
ScaffoldMessenger . of ( context ) . showSnackBar ( snackBar ) ;
Navigator . of ( context ) . popUntil ( ( route ) = > route . settings . name = = " servers " ) ; // dismiss dialog
} else {
Navigator . of ( context ) . pop ( ) ;
}
} ,
) ;
} ) ;
// set up the AlertDialog
AlertDialog alert = AlertDialog (
title: Text ( AppLocalizations . of ( context ) ! . deleteServerConfirmBtn ) ,
actions: [
cancelButton ,
continueButton ,
] ,
) ;
// show the dialog
showDialog (
context: context ,
builder: ( BuildContext context ) {
return alert ;
} ,
) ;
}
2021-11-25 23:59:54 +00:00
}