formatting

This commit is contained in:
Adrian Baumgart 2024-07-25 17:37:17 +02:00
parent 0eea6ee9a2
commit ba058e2af3
Signed by: rainloreley
SSH Key Fingerprint: SHA256:DrGrohIPualL1UkyUym0K4C+uC5njuzPFBdXgtVZntM
11 changed files with 216 additions and 192 deletions

View File

@ -14,11 +14,9 @@ enum ConditionDeviceType {
}
throw ArgumentError("Invalid type $api");
}
}
extension ConditionTypeExtension on ConditionDeviceType {
String get api {
switch (this) {
case ConditionDeviceType.IOS:
@ -29,6 +27,7 @@ extension ConditionTypeExtension on ConditionDeviceType {
return "desktop";
}
}
String get humanReadable {
switch (this) {
case ConditionDeviceType.IOS:
@ -39,4 +38,4 @@ extension ConditionTypeExtension on ConditionDeviceType {
return "Desktop";
}
}
}
}

View File

@ -9,10 +9,11 @@ class RedirectRule {
RedirectRule(this.longUrl, this.priority, this.conditions);
RedirectRule.fromJson(Map<String, dynamic> json)
: longUrl = json["longUrl"],
priority = json["priority"],
conditions = (json["conditions"] as List<dynamic>).map((e)
=> RedirectRuleCondition.fromJson(e)).toList();
: longUrl = json["longUrl"],
priority = json["priority"],
conditions = (json["conditions"] as List<dynamic>)
.map((e) => RedirectRuleCondition.fromJson(e))
.toList();
Map<String, dynamic> toJson() {
return {
@ -20,4 +21,4 @@ class RedirectRule {
"conditions": conditions.map((e) => e.toJson()).toList()
};
}
}
}

View File

@ -5,19 +5,15 @@ class RedirectRuleCondition {
String matchValue;
String? matchKey;
RedirectRuleCondition(String type, this.matchValue, this.matchKey) :
type = RedirectRuleConditionType.fromApi(type);
RedirectRuleCondition(String type, this.matchValue, this.matchKey)
: type = RedirectRuleConditionType.fromApi(type);
RedirectRuleCondition.fromJson(Map<String, dynamic> json)
: type = RedirectRuleConditionType.fromApi(json["type"]),
matchValue = json["matchValue"],
matchKey = json["matchKey"];
: type = RedirectRuleConditionType.fromApi(json["type"]),
matchValue = json["matchValue"],
matchKey = json["matchKey"];
Map<String, dynamic> toJson() {
return {
"type": type.api,
"matchValue": matchValue,
"matchKey": matchKey
};
return {"type": type.api, "matchValue": matchValue, "matchKey": matchKey};
}
}
}

View File

@ -14,11 +14,9 @@ enum RedirectRuleConditionType {
}
throw ArgumentError("Invalid type $api");
}
}
extension ConditionTypeExtension on RedirectRuleConditionType {
String get api {
switch (this) {
case RedirectRuleConditionType.DEVICE:
@ -29,6 +27,7 @@ extension ConditionTypeExtension on RedirectRuleConditionType {
return "query-param";
}
}
String get humanReadable {
switch (this) {
case RedirectRuleConditionType.DEVICE:
@ -39,4 +38,4 @@ extension ConditionTypeExtension on RedirectRuleConditionType {
return "Query parameter";
}
}
}
}

View File

@ -12,8 +12,9 @@ FutureOr<Either<List<RedirectRule>, Failure>> apiGetRedirectRules(
String? serverUrl,
String apiVersion) async {
try {
final response =
await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"),
final response = await http.get(
Uri.parse(
"$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"),
headers: {
"X-Api-Key": apiKey ?? "",
});
@ -22,9 +23,10 @@ FutureOr<Either<List<RedirectRule>, Failure>> apiGetRedirectRules(
var jsonBody = jsonDecode(response.body) as Map<String, dynamic>;
// convert json array to object array
List<RedirectRule> redirectRules = (jsonBody["redirectRules"]
as List<dynamic>).map((e)
=> RedirectRule.fromJson(e)).toList();
List<RedirectRule> redirectRules =
(jsonBody["redirectRules"] as List<dynamic>)
.map((e) => RedirectRule.fromJson(e))
.toList();
return left(redirectRules);
} else {
@ -43,4 +45,4 @@ FutureOr<Either<List<RedirectRule>, Failure>> apiGetRedirectRules(
} catch (reqErr) {
return right(RequestFailure(0, reqErr.toString()));
}
}
}

View File

@ -14,13 +14,16 @@ FutureOr<Either<bool, Failure>> apiSetRedirectRules(
String apiVersion) async {
try {
Map<String, dynamic> body = {};
List<Map<String, dynamic>> redirectRulesJson = redirectRules.map((e) => e.toJson()).toList();
List<Map<String, dynamic>> redirectRulesJson =
redirectRules.map((e) => e.toJson()).toList();
body["redirectRules"] = redirectRulesJson;
final response =
await http.post(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"),
final response = await http.post(
Uri.parse(
"$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"),
headers: {
"X-Api-Key": apiKey ?? "",
}, body: jsonEncode(body));
},
body: jsonEncode(body));
if (response.statusCode == 200) {
return left(true);
} else {
@ -39,4 +42,4 @@ FutureOr<Either<bool, Failure>> apiSetRedirectRules(
} catch (reqErr) {
return right(RequestFailure(0, reqErr.toString()));
}
}
}

View File

@ -127,6 +127,7 @@ class ServerManager {
FutureOr<Either<List<ShortURL>, Failure>> getRecentShortUrls() async {
return apiGetRecentShortUrls(apiKey, serverUrl, apiVersion);
}
/// Gets redirect rules for a given short URL (code)
FutureOr<Either<List<RedirectRule>, Failure>> getRedirectRules(
String shortCode) async {
@ -136,7 +137,8 @@ class ServerManager {
/// Sets redirect rules for a given short URL (code)
FutureOr<Either<bool, Failure>> setRedirectRules(
String shortCode, List<RedirectRule> redirectRules) async {
return apiSetRedirectRules(shortCode, redirectRules, apiKey, serverUrl, apiVersion);
return apiSetRedirectRules(
shortCode, redirectRules, apiKey, serverUrl, apiVersion);
}
}

View File

@ -11,13 +11,13 @@ void main() {
class MyApp extends StatelessWidget {
const MyApp({super.key});
static final ColorScheme _defaultLightColorScheme = ColorScheme
.fromSeed(seedColor: Colors.blue);
static final ColorScheme _defaultLightColorScheme =
ColorScheme.fromSeed(seedColor: Colors.blue);
static final _defaultDarkColorScheme = ColorScheme.fromSeed(
brightness: Brightness.dark,
seedColor: Colors.blue,
background: Colors.black);
brightness: Brightness.dark,
seedColor: Colors.blue,
background: Colors.black);
// This widget is the root of your application.
@override

View File

@ -50,7 +50,8 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''',
version: r'^1.0.5',
homepage: null,
repository: r'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons',
repository:
r'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons',
),
const License(
name: r'dartz',
@ -286,7 +287,8 @@ SOFTWARE.
''',
version: r'^1.6.6',
homepage: null,
repository: r'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color',
repository:
r'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color',
),
const License(
name: r'flutter',
@ -346,7 +348,8 @@ SOFTWARE.
''',
version: r'^0.13.1',
homepage: r'https://github.com/fluttercommunity/flutter_launcher_icons',
repository: r'https://github.com/fluttercommunity/flutter_launcher_icons/',
repository:
r'https://github.com/fluttercommunity/flutter_launcher_icons/',
),
const License(
name: r'flutter_lints',
@ -378,36 +381,37 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''',
version: r'^3.0.1',
homepage: null,
repository: r'https://github.com/flutter/packages/tree/main/packages/flutter_lints',
repository:
r'https://github.com/flutter/packages/tree/main/packages/flutter_lints',
),
const License(
name: r'flutter_process_text',
license: r'''BSD 3-Clause License
(c) Copyright 2021 divshekhar (Divyanshu Shekhar)
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
license: r'''BSD 3-Clause License
(c) Copyright 2021 divshekhar (Divyanshu Shekhar)
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
version: r'^1.1.2',
homepage: null,
@ -446,7 +450,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
version: r'^9.0.0',
homepage: null,
repository: r'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage',
repository:
r'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage',
),
const License(
name: r'flutter_sharing_intent',
@ -652,7 +657,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
See the License for the specific language governing permissions and
limitations under the License.''',
version: r'^1.1.1',
homepage: r'https://github.com/bhagat-techind/flutter_sharing_intent.git',
homepage:
r'https://github.com/bhagat-techind/flutter_sharing_intent.git',
repository: null,
),
const License(
@ -815,7 +821,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''',
version: r'^4.0.2',
homepage: r'https://plus.fluttercommunity.dev/',
repository: r'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/package_info_plus',
repository:
r'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/package_info_plus',
),
const License(
name: r'qr_flutter',
@ -883,7 +890,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''',
version: r'^2.2.2',
homepage: null,
repository: r'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences',
repository:
r'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences',
),
const License(
name: r'tuple',
@ -943,7 +951,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''',
version: r'^6.2.4',
homepage: null,
repository: r'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher',
repository:
r'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher',
),
];
}

View File

@ -13,7 +13,8 @@ class RedirectRulesDetailView extends StatefulWidget {
final ShortURL shortURL;
@override
State<RedirectRulesDetailView> createState() => _RedirectRulesDetailViewState();
State<RedirectRulesDetailView> createState() =>
_RedirectRulesDetailViewState();
}
class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
@ -30,7 +31,8 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
}
Future<void> loadRedirectRules() async {
final response = await globals.serverManager.getRedirectRules(widget.shortURL.shortCode);
final response =
await globals.serverManager.getRedirectRules(widget.shortURL.shortCode);
response.fold((l) {
setState(() {
redirectRules = l;
@ -56,7 +58,8 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
}
void _saveRedirectRules() async {
final response = await globals.serverManager.setRedirectRules(widget.shortURL.shortCode, redirectRules);
final response = await globals.serverManager
.setRedirectRules(widget.shortURL.shortCode, redirectRules);
response.fold((l) {
Navigator.pop(context);
}, (r) {
@ -97,7 +100,7 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
spacing: 16,
children: [
FloatingActionButton(
onPressed: () {
onPressed: () {
if (!isSaving & redirectRulesLoaded) {
setState(() {
isSaving = true;
@ -106,10 +109,10 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
}
},
child: isSaving
? const Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(strokeWidth: 3))
: const Icon(Icons.save))
? const Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(strokeWidth: 3))
: const Icon(Icons.save))
],
),
body: CustomScrollView(
@ -131,16 +134,14 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
const Text(
"No Redirect Rules",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold),
fontSize: 24, fontWeight: FontWeight.bold),
),
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'Adding redirect rules will be supported soon!',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600]),
fontSize: 16, color: Colors.grey[600]),
),
)
],
@ -149,30 +150,34 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _ListCell(
redirectRule: redirectRules[index],
moveUp: index == 0 ? null : () {
setState(() {
redirectRules[index].priority -= 1;
redirectRules[index - 1].priority += 1;
});
_sortListByPriority();
},
moveDown: index == (redirectRules.length - 1) ? null : () {
setState(() {
redirectRules[index].priority += 1;
redirectRules[index + 1].priority -= 1;
});
_sortListByPriority();
},
delete: () {
setState(() {
redirectRules.removeAt(index);
});
_fixPriorities();
},
);
}, childCount: redirectRules.length))
return _ListCell(
redirectRule: redirectRules[index],
moveUp: index == 0
? null
: () {
setState(() {
redirectRules[index].priority -= 1;
redirectRules[index - 1].priority += 1;
});
_sortListByPriority();
},
moveDown: index == (redirectRules.length - 1)
? null
: () {
setState(() {
redirectRules[index].priority += 1;
redirectRules[index + 1].priority -= 1;
});
_sortListByPriority();
},
delete: () {
setState(() {
redirectRules.removeAt(index);
});
_fixPriorities();
},
);
}, childCount: redirectRules.length))
],
),
);
@ -180,11 +185,12 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
}
class _ListCell extends StatefulWidget {
const _ListCell({super.key,
required this.redirectRule,
required this.moveUp,
required this.moveDown,
required this.delete});
const _ListCell(
{super.key,
required this.redirectRule,
required this.moveUp,
required this.moveDown,
required this.delete});
final VoidCallback? moveUp;
final VoidCallback? moveDown;
@ -196,7 +202,6 @@ class _ListCell extends StatefulWidget {
}
class _ListCellState extends State<_ListCell> {
String _conditionToTagString(RedirectRuleCondition condition) {
switch (condition.type) {
case RedirectRuleConditionType.DEVICE:
@ -210,76 +215,84 @@ class _ListCellState extends State<_ListCell> {
@override
Widget build(BuildContext context) {
return Padding(padding: EdgeInsets.only(
left: 8, right: 8
), child: Container(
padding: EdgeInsets.only(left: 8, right: 8, top: 16, bottom: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[800]!
: Colors.grey[300]!)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text("Long URL ", style: TextStyle(fontWeight: FontWeight.bold)),
Text(widget.redirectRule.longUrl)
],
),
Text("Conditions:", style: TextStyle(fontWeight: FontWeight.bold)),
Row(
children: [
Expanded(
child: Wrap(
children: widget.redirectRule.conditions.map((condition) {
return Padding(
padding: const EdgeInsets.only(right: 4, top: 4),
child: Container(
padding:
const EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: MediaQuery.of(context).platformBrightness == Brightness.dark ?
Colors.grey[900] : Colors.grey[300],
),
child: Text(
_conditionToTagString(condition)
),
),
);
}).toList(),
return Padding(
padding: EdgeInsets.only(left: 8, right: 8),
child: Container(
padding: EdgeInsets.only(left: 8, right: 8, top: 16, bottom: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[800]!
: Colors.grey[300]!)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text("Long URL ",
style: TextStyle(fontWeight: FontWeight.bold)),
Text(widget.redirectRule.longUrl)
],
),
)
],
),
Wrap(
children: [
IconButton(
disabledColor: MediaQuery.of(context).platformBrightness
== Brightness.dark ? Colors.grey[700] : Colors.grey[400],
onPressed: widget.moveUp,
icon: Icon(Icons.arrow_upward),
),
IconButton(
disabledColor: MediaQuery.of(context).platformBrightness
== Brightness.dark ? Colors.grey[700] : Colors.grey[400],
onPressed: widget.moveDown,
icon: Icon(Icons.arrow_downward),
),
IconButton(
onPressed: widget.delete,
icon: Icon(Icons.delete, color: Colors.red),
)
],
)
],
)
));
Text("Conditions:",
style: TextStyle(fontWeight: FontWeight.bold)),
Row(
children: [
Expanded(
child: Wrap(
children:
widget.redirectRule.conditions.map((condition) {
return Padding(
padding: const EdgeInsets.only(right: 4, top: 4),
child: Container(
padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 12, right: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color:
MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[900]
: Colors.grey[300],
),
child: Text(_conditionToTagString(condition)),
),
);
}).toList(),
),
)
],
),
Wrap(
children: [
IconButton(
disabledColor:
MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[700]
: Colors.grey[400],
onPressed: widget.moveUp,
icon: Icon(Icons.arrow_upward),
),
IconButton(
disabledColor:
MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[700]
: Colors.grey[400],
onPressed: widget.moveDown,
icon: Icon(Icons.arrow_downward),
),
IconButton(
onPressed: widget.delete,
icon: Icon(Icons.delete, color: Colors.red),
)
],
)
],
)));
}
}

View File

@ -130,7 +130,9 @@ class _URLDetailViewState extends State<URLDetailView> {
title: "Short URL", content: shortURL.shortUrl, isUrl: true),
_ListCell(title: "Long URL", content: shortURL.longUrl, isUrl: true),
_ListCell(title: "Creation Date", content: shortURL.dateCreated),
_ListCell(title: "Redirect Rules", content: null,
_ListCell(
title: "Redirect Rules",
content: null,
clickableDetailView: RedirectRulesDetailView(shortURL: shortURL)),
const _ListCell(title: "Visits", content: ""),
_ListCell(
@ -189,10 +191,8 @@ class _ListCellState extends State<_ListCell> {
child: GestureDetector(
onTap: () async {
if (widget.clickableDetailView != null) {
Navigator.of(context)
.push(MaterialPageRoute(
builder: (context) =>
widget.clickableDetailView!));
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => widget.clickableDetailView!));
} else if (widget.content is String) {
Uri? parsedUrl = Uri.tryParse(widget.content);
if (widget.isUrl &&
@ -258,7 +258,7 @@ class _ListCellState extends State<_ListCell> {
Text(DateFormat('yyyy-MM-dd - HH:mm')
.format(widget.content))
else if (widget.clickableDetailView != null)
const Icon(Icons.chevron_right)
const Icon(Icons.chevron_right)
else
const Text("N/A")
],