Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
9064bf67
Unverified
Commit
9064bf67
authored
Oct 19, 2022
by
Zachary Anderson
Committed by
GitHub
Oct 19, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "[text_input] introduce TextInputControl (#76072)" (#113724)
This reverts commit
2fdfa29e
.
parent
38ef9410
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
83 additions
and
902 deletions
+83
-902
AUTHORS
AUTHORS
+0
-1
text_input_control.0.dart
...les/api/lib/services/text_input/text_input_control.0.dart
+0
-167
text_input_control.0_test.dart
...i/test/services/text_input/text_input_control.0_test.dart
+0
-40
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+83
-421
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+0
-8
autofill_test.dart
packages/flutter/test/services/autofill_test.dart
+0
-5
delta_text_input_test.dart
packages/flutter/test/services/delta_text_input_test.dart
+0
-5
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+0
-255
No files found.
AUTHORS
View file @
9064bf67
...
@@ -77,7 +77,6 @@ Hidenori Matsubayashi <Hidenori.Matsubayashi@sony.com>
...
@@ -77,7 +77,6 @@ Hidenori Matsubayashi <Hidenori.Matsubayashi@sony.com>
Perqin Xie <perqinxie@gmail.com>
Perqin Xie <perqinxie@gmail.com>
Seongyun Kim <helloworld@cau.ac.kr>
Seongyun Kim <helloworld@cau.ac.kr>
Ludwik Trammer <ludwik@gmail.com>
Ludwik Trammer <ludwik@gmail.com>
J-P Nurmi <jpnurmi@gmail.com>
Marian Triebe <m.triebe@live.de>
Marian Triebe <m.triebe@live.de>
Alexis Rouillard <contact@arouillard.fr>
Alexis Rouillard <contact@arouillard.fr>
Mirko Mucaria <skogsfrae@gmail.com>
Mirko Mucaria <skogsfrae@gmail.com>
...
...
examples/api/lib/services/text_input/text_input_control.0.dart
deleted
100644 → 0
View file @
38ef9410
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flutter code sample for TextInputControl
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
void
main
(
)
=>
runApp
(
const
MyApp
());
class
MyApp
extends
StatelessWidget
{
const
MyApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
const
MaterialApp
(
home:
MyStatefulWidget
(),
);
}
}
class
MyStatefulWidget
extends
StatefulWidget
{
const
MyStatefulWidget
({
super
.
key
});
@override
MyStatefulWidgetState
createState
()
=>
MyStatefulWidgetState
();
}
class
MyStatefulWidgetState
extends
State
<
MyStatefulWidget
>
{
final
TextEditingController
_controller
=
TextEditingController
();
final
FocusNode
_focusNode
=
FocusNode
();
@override
void
dispose
()
{
super
.
dispose
();
_controller
.
dispose
();
_focusNode
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
body:
Center
(
child:
TextField
(
autofocus:
true
,
controller:
_controller
,
focusNode:
_focusNode
,
decoration:
InputDecoration
(
suffix:
IconButton
(
icon:
const
Icon
(
Icons
.
clear
),
tooltip:
'Clear and unfocus'
,
onPressed:
()
{
_controller
.
clear
();
_focusNode
.
unfocus
();
},
),
),
),
),
bottomSheet:
const
MyVirtualKeyboard
(),
);
}
}
class
MyVirtualKeyboard
extends
StatefulWidget
{
const
MyVirtualKeyboard
({
super
.
key
});
@override
MyVirtualKeyboardState
createState
()
=>
MyVirtualKeyboardState
();
}
class
MyVirtualKeyboardState
extends
State
<
MyVirtualKeyboard
>
{
final
MyTextInputControl
_inputControl
=
MyTextInputControl
();
@override
void
initState
()
{
super
.
initState
();
_inputControl
.
register
();
}
@override
void
dispose
()
{
super
.
dispose
();
_inputControl
.
unregister
();
}
void
_handleKeyPress
(
String
key
)
{
_inputControl
.
processUserInput
(
key
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
ValueListenableBuilder
<
bool
>(
valueListenable:
_inputControl
.
visible
,
builder:
(
_
,
bool
visible
,
__
)
{
return
Visibility
(
visible:
visible
,
child:
FocusScope
(
canRequestFocus:
false
,
child:
TextFieldTapRegion
(
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
<
Widget
>[
for
(
final
String
key
in
<
String
>[
'A'
,
'B'
,
'C'
])
ElevatedButton
(
child:
Text
(
key
),
onPressed:
()
=>
_handleKeyPress
(
key
),
),
],
),
),
),
);
},
);
}
}
class
MyTextInputControl
with
TextInputControl
{
TextEditingValue
_editingState
=
TextEditingValue
.
empty
;
final
ValueNotifier
<
bool
>
_visible
=
ValueNotifier
<
bool
>(
false
);
/// The input control's visibility state for updating the visual presentation.
ValueListenable
<
bool
>
get
visible
=>
_visible
;
/// Register the input control.
void
register
()
=>
TextInput
.
setInputControl
(
this
);
/// Restore the original platform input control.
void
unregister
()
=>
TextInput
.
restorePlatformInputControl
();
@override
void
show
()
=>
_visible
.
value
=
true
;
@override
void
hide
()
=>
_visible
.
value
=
false
;
@override
void
setEditingState
(
TextEditingValue
value
)
=>
_editingState
=
value
;
/// Process user input.
///
/// Updates the internal editing state by inserting the input text,
/// and by replacing the current selection if any.
void
processUserInput
(
String
input
)
{
_editingState
=
_editingState
.
copyWith
(
text:
_insertText
(
input
),
selection:
_replaceSelection
(
input
),
);
// Request the attached client to update accordingly.
TextInput
.
updateEditingValue
(
_editingState
);
}
String
_insertText
(
String
input
)
{
final
String
text
=
_editingState
.
text
;
final
TextSelection
selection
=
_editingState
.
selection
;
return
text
.
replaceRange
(
selection
.
start
,
selection
.
end
,
input
);
}
TextSelection
_replaceSelection
(
String
input
)
{
final
TextSelection
selection
=
_editingState
.
selection
;
return
TextSelection
.
collapsed
(
offset:
selection
.
start
+
input
.
length
);
}
}
examples/api/test/services/text_input/text_input_control.0_test.dart
deleted
100644 → 0
View file @
38ef9410
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_api_samples/services/text_input/text_input_control.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Enter text using the VKB'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
MyApp
());
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
descendant
(
of:
find
.
byType
(
example
.
MyVirtualKeyboard
),
matching:
find
.
widgetWithText
(
ElevatedButton
,
'A'
),
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
widgetWithText
(
TextField
,
'A'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
descendant
(
of:
find
.
byType
(
example
.
MyVirtualKeyboard
),
matching:
find
.
widgetWithText
(
ElevatedButton
,
'B'
),
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
widgetWithText
(
TextField
,
'AB'
),
findsOneWidget
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
descendant
(
of:
find
.
byType
(
example
.
MyVirtualKeyboard
),
matching:
find
.
widgetWithText
(
ElevatedButton
,
'C'
),
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
widgetWithText
(
TextField
,
'ACB'
),
findsOneWidget
);
});
}
packages/flutter/lib/src/services/text_input.dart
View file @
9064bf67
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/widgets/editable_text.dart
View file @
9064bf67
...
@@ -2675,14 +2675,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -2675,14 +2675,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
@override
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{
if
(
_hasFocus
&&
_hasInputConnection
)
{
oldControl
?.
hide
();
newControl
?.
show
();
}
}
@override
@override
void
connectionClosed
()
{
void
connectionClosed
()
{
if
(
_hasInputConnection
)
{
if
(
_hasInputConnection
)
{
...
...
packages/flutter/test/services/autofill_test.dart
View file @
9064bf67
...
@@ -139,11 +139,6 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
...
@@ -139,11 +139,6 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
latestMethodCall
=
'showAutocorrectionPromptRect'
;
latestMethodCall
=
'showAutocorrectionPromptRect'
;
}
}
@override
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{
latestMethodCall
=
'didChangeInputControl'
;
}
@override
@override
void
autofill
(
TextEditingValue
newEditingValue
)
=>
updateEditingValue
(
newEditingValue
);
void
autofill
(
TextEditingValue
newEditingValue
)
=>
updateEditingValue
(
newEditingValue
);
...
...
packages/flutter/test/services/delta_text_input_test.dart
View file @
9064bf67
...
@@ -292,9 +292,4 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
...
@@ -292,9 +292,4 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
}
}
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
(
enableDeltaModel:
true
);
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
(
enableDeltaModel:
true
);
@override
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{
latestMethodCall
=
'didChangeInputControl'
;
}
}
}
packages/flutter/test/services/text_input_test.dart
View file @
9064bf67
...
@@ -780,178 +780,6 @@ void main() {
...
@@ -780,178 +780,6 @@ void main() {
isTrue
,
isTrue
,
);
);
});
});
group
(
'TextInputControl'
,
()
{
late
FakeTextChannel
fakeTextChannel
;
setUp
(()
{
fakeTextChannel
=
FakeTextChannel
((
MethodCall
call
)
async
{});
TextInput
.
setChannel
(
fakeTextChannel
);
});
tearDown
(()
{
TextInput
.
restorePlatformInputControl
();
TextInputConnection
.
debugResetId
();
TextInput
.
setChannel
(
SystemChannels
.
textInput
);
});
test
(
'gets attached and detached'
,
()
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
const
TextInputConfiguration
());
final
List
<
String
>
expectedMethodCalls
=
<
String
>[
'attach'
];
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
connection
.
close
();
expectedMethodCalls
.
add
(
'detach'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
});
test
(
'receives text input state changes'
,
()
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
const
TextInputConfiguration
());
control
.
methodCalls
.
clear
();
final
List
<
String
>
expectedMethodCalls
=
<
String
>[];
connection
.
updateConfig
(
const
TextInputConfiguration
());
expectedMethodCalls
.
add
(
'updateConfig'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
connection
.
setEditingState
(
TextEditingValue
.
empty
);
expectedMethodCalls
.
add
(
'setEditingState'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
connection
.
close
();
expectedMethodCalls
.
add
(
'detach'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
});
test
(
'does not interfere with platform text input'
,
()
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
TextInput
.
attach
(
client
,
const
TextInputConfiguration
());
fakeTextChannel
.
outgoingCalls
.
clear
();
fakeTextChannel
.
incoming
!(
MethodCall
(
'TextInputClient.updateEditingState'
,
<
dynamic
>[
1
,
TextEditingValue
.
empty
.
toJSON
()]));
expect
(
client
.
latestMethodCall
,
'updateEditingValue'
);
expect
(
control
.
methodCalls
,
<
String
>[
'attach'
,
'setEditingState'
]);
expect
(
fakeTextChannel
.
outgoingCalls
,
isEmpty
);
});
test
(
'both input controls receive requests'
,
()
async
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
const
TextInputConfiguration
textConfig
=
TextInputConfiguration
();
const
TextInputConfiguration
numberConfig
=
TextInputConfiguration
(
inputType:
TextInputType
.
number
);
const
TextInputConfiguration
noneConfig
=
TextInputConfiguration
(
inputType:
TextInputType
.
none
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
textConfig
);
final
List
<
String
>
expectedMethodCalls
=
<
String
>[
'attach'
];
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
control
.
inputType
,
TextInputType
.
text
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
// When there's a custom text input control installed, the platform text
// input control receives TextInputType.none
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
noneConfig
.
toJson
()]),
]);
connection
.
show
();
expectedMethodCalls
.
add
(
'show'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
2
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.show'
);
connection
.
updateConfig
(
numberConfig
);
expectedMethodCalls
.
add
(
'updateConfig'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
control
.
inputType
,
TextInputType
.
number
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
3
);
fakeTextChannel
.
validateOutgoingMethodCalls
(<
MethodCall
>[
// When there's a custom text input control installed, the platform text
// input control receives TextInputType.none
MethodCall
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
noneConfig
.
toJson
()]),
const
MethodCall
(
'TextInput.show'
),
MethodCall
(
'TextInput.updateConfig'
,
noneConfig
.
toJson
()),
]);
connection
.
setComposingRect
(
Rect
.
zero
);
expectedMethodCalls
.
add
(
'setComposingRect'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
4
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setMarkedTextRect'
);
connection
.
setCaretRect
(
Rect
.
zero
);
expectedMethodCalls
.
add
(
'setCaretRect'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
5
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setCaretRect'
);
connection
.
setEditableSizeAndTransform
(
Size
.
zero
,
Matrix4
.
identity
());
expectedMethodCalls
.
add
(
'setEditableSizeAndTransform'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
6
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setEditableSizeAndTransform'
);
connection
.
setSelectionRects
(
const
<
SelectionRect
>[
SelectionRect
(
position:
0
,
bounds:
Rect
.
zero
)]);
expectedMethodCalls
.
add
(
'setSelectionRects'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
7
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setSelectionRects'
);
connection
.
setStyle
(
fontFamily:
null
,
fontSize:
null
,
fontWeight:
null
,
textDirection:
TextDirection
.
ltr
,
textAlign:
TextAlign
.
left
,
);
expectedMethodCalls
.
add
(
'setStyle'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
8
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setStyle'
);
connection
.
close
();
expectedMethodCalls
.
add
(
'detach'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
9
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.clearClient'
);
expectedMethodCalls
.
add
(
'hide'
);
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
();
await
binding
.
runAsync
(()
async
{});
await
expectLater
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
10
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.hide'
);
});
test
(
'notifies changes to the attached client'
,
()
async
{
final
FakeTextInputControl
control
=
FakeTextInputControl
();
TextInput
.
setInputControl
(
control
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
const
TextInputConfiguration
());
TextInput
.
setInputControl
(
null
);
expect
(
client
.
latestMethodCall
,
'didChangeInputControl'
);
connection
.
show
();
expect
(
client
.
latestMethodCall
,
'didChangeInputControl'
);
});
});
}
}
class
FakeTextInputClient
with
TextInputClient
{
class
FakeTextInputClient
with
TextInputClient
{
...
@@ -1005,11 +833,6 @@ class FakeTextInputClient with TextInputClient {
...
@@ -1005,11 +833,6 @@ class FakeTextInputClient with TextInputClient {
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
();
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
();
@override
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{
latestMethodCall
=
'didChangeInputControl'
;
}
@override
@override
void
insertTextPlaceholder
(
Size
size
)
{
void
insertTextPlaceholder
(
Size
size
)
{
latestMethodCall
=
'insertTextPlaceholder'
;
latestMethodCall
=
'insertTextPlaceholder'
;
...
@@ -1026,81 +849,3 @@ class FakeTextInputClient with TextInputClient {
...
@@ -1026,81 +849,3 @@ class FakeTextInputClient with TextInputClient {
performedSelectors
.
add
(
selectorName
);
performedSelectors
.
add
(
selectorName
);
}
}
}
}
class
FakeTextInputControl
with
TextInputControl
{
final
List
<
String
>
methodCalls
=
<
String
>[];
late
TextInputType
inputType
;
@override
void
attach
(
TextInputClient
client
,
TextInputConfiguration
configuration
)
{
methodCalls
.
add
(
'attach'
);
inputType
=
configuration
.
inputType
;
}
@override
void
detach
(
TextInputClient
client
)
{
methodCalls
.
add
(
'detach'
);
}
@override
void
setEditingState
(
TextEditingValue
value
)
{
methodCalls
.
add
(
'setEditingState'
);
}
@override
void
updateConfig
(
TextInputConfiguration
configuration
)
{
methodCalls
.
add
(
'updateConfig'
);
inputType
=
configuration
.
inputType
;
}
@override
void
show
()
{
methodCalls
.
add
(
'show'
);
}
@override
void
hide
()
{
methodCalls
.
add
(
'hide'
);
}
@override
void
setComposingRect
(
Rect
rect
)
{
methodCalls
.
add
(
'setComposingRect'
);
}
@override
void
setCaretRect
(
Rect
rect
)
{
methodCalls
.
add
(
'setCaretRect'
);
}
@override
void
setEditableSizeAndTransform
(
Size
editableBoxSize
,
Matrix4
transform
)
{
methodCalls
.
add
(
'setEditableSizeAndTransform'
);
}
@override
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
methodCalls
.
add
(
'setSelectionRects'
);
}
@override
void
setStyle
({
required
String
?
fontFamily
,
required
double
?
fontSize
,
required
FontWeight
?
fontWeight
,
required
TextDirection
textDirection
,
required
TextAlign
textAlign
,
})
{
methodCalls
.
add
(
'setStyle'
);
}
@override
void
finishAutofillContext
({
bool
shouldSave
=
true
})
{
methodCalls
.
add
(
'finishAutofillContext'
);
}
@override
void
requestAutofill
()
{
methodCalls
.
add
(
'requestAutofill'
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment