2023-07-09 23:00:00 +02:00
|
|
|
import 'package:flutter/material.dart';
|
2024-01-27 23:07:06 +01:00
|
|
|
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
|
2023-07-09 23:00:00 +02:00
|
|
|
import 'package:intl/intl.dart';
|
2024-07-26 20:30:13 +02:00
|
|
|
import 'package:shlink_app/util/build_api_error_snackbar.dart';
|
2024-07-25 17:31:54 +02:00
|
|
|
import 'package:shlink_app/views/redirect_rules_detail_view.dart';
|
2024-01-28 22:55:28 +01:00
|
|
|
import 'package:shlink_app/views/short_url_edit_view.dart';
|
2024-01-28 01:05:35 +01:00
|
|
|
import 'package:shlink_app/widgets/url_tags_list_widget.dart';
|
2024-03-31 21:08:58 +02:00
|
|
|
import 'package:url_launcher/url_launcher.dart';
|
2024-01-27 23:07:06 +01:00
|
|
|
import '../globals.dart' as globals;
|
2023-07-09 23:00:00 +02:00
|
|
|
|
|
|
|
class URLDetailView extends StatefulWidget {
|
|
|
|
const URLDetailView({super.key, required this.shortURL});
|
|
|
|
|
|
|
|
final ShortURL shortURL;
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<URLDetailView> createState() => _URLDetailViewState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _URLDetailViewState extends State<URLDetailView> {
|
2024-01-28 22:55:28 +01:00
|
|
|
ShortURL shortURL = ShortURL.empty();
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
setState(() {
|
|
|
|
shortURL = widget.shortURL;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-07-09 23:00:00 +02:00
|
|
|
Future showDeletionConfirmation() {
|
|
|
|
return showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (BuildContext context) {
|
|
|
|
return AlertDialog(
|
|
|
|
title: const Text("Delete Short URL"),
|
|
|
|
content: SingleChildScrollView(
|
|
|
|
child: ListBody(
|
|
|
|
children: [
|
|
|
|
const Text("You're about to delete"),
|
2024-01-27 23:07:06 +01:00
|
|
|
const SizedBox(height: 4),
|
2024-01-28 00:32:09 +01:00
|
|
|
Text(
|
2024-01-28 22:55:28 +01:00
|
|
|
shortURL.title ?? shortURL.shortCode,
|
2024-01-28 00:32:09 +01:00
|
|
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
|
|
|
),
|
2024-01-27 23:07:06 +01:00
|
|
|
const SizedBox(height: 4),
|
2023-07-09 23:00:00 +02:00
|
|
|
const Text("It'll be gone forever! (a very long time)")
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
actions: [
|
2024-01-28 00:32:09 +01:00
|
|
|
TextButton(
|
|
|
|
onPressed: () => {Navigator.of(context).pop()},
|
|
|
|
child: const Text("Cancel")),
|
2023-07-09 23:00:00 +02:00
|
|
|
TextButton(
|
|
|
|
onPressed: () async {
|
2024-01-28 00:32:09 +01:00
|
|
|
var response = await globals.serverManager
|
2024-01-28 22:55:28 +01:00
|
|
|
.deleteShortUrl(shortURL.shortCode);
|
2023-07-09 23:00:00 +02:00
|
|
|
|
|
|
|
response.fold((l) {
|
|
|
|
Navigator.pop(context);
|
2024-01-28 22:55:28 +01:00
|
|
|
Navigator.pop(context);
|
2023-07-09 23:00:00 +02:00
|
|
|
|
2024-01-28 00:32:09 +01:00
|
|
|
final snackBar = SnackBar(
|
|
|
|
content: const Text("Short URL deleted!"),
|
|
|
|
backgroundColor: Colors.green[400],
|
|
|
|
behavior: SnackBarBehavior.floating);
|
2023-07-09 23:00:00 +02:00
|
|
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
|
|
return true;
|
|
|
|
}, (r) {
|
2024-07-26 20:30:13 +02:00
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
buildApiErrorSnackbar(r, context)
|
|
|
|
);
|
2023-07-09 23:00:00 +02:00
|
|
|
return false;
|
|
|
|
});
|
|
|
|
},
|
2024-01-28 00:32:09 +01:00
|
|
|
child:
|
|
|
|
const Text("Delete", style: TextStyle(color: Colors.red)),
|
2023-07-09 23:00:00 +02:00
|
|
|
)
|
|
|
|
],
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Scaffold(
|
|
|
|
body: CustomScrollView(
|
|
|
|
slivers: [
|
|
|
|
SliverAppBar.medium(
|
2024-01-28 22:55:28 +01:00
|
|
|
title: Text(shortURL.title ?? shortURL.shortCode,
|
2024-01-28 00:32:09 +01:00
|
|
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
2023-07-09 23:00:00 +02:00
|
|
|
actions: [
|
2024-01-28 22:55:28 +01:00
|
|
|
IconButton(
|
2024-03-31 21:58:31 +02:00
|
|
|
onPressed: () async {
|
|
|
|
ShortURL updatedUrl = await Navigator.of(context).push(
|
|
|
|
MaterialPageRoute(
|
|
|
|
builder: (context) =>
|
|
|
|
ShortURLEditView(shortUrl: shortURL)));
|
|
|
|
setState(() {
|
|
|
|
shortURL = updatedUrl;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
icon: const Icon(Icons.edit)),
|
2024-01-28 00:32:09 +01:00
|
|
|
IconButton(
|
|
|
|
onPressed: () {
|
|
|
|
showDeletionConfirmation();
|
|
|
|
},
|
|
|
|
icon: const Icon(
|
|
|
|
Icons.delete,
|
|
|
|
color: Colors.red,
|
|
|
|
))
|
2023-07-09 23:00:00 +02:00
|
|
|
],
|
|
|
|
),
|
|
|
|
SliverToBoxAdapter(
|
|
|
|
child: Padding(
|
2024-03-31 21:58:31 +02:00
|
|
|
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
|
|
|
|
child: UrlTagsListWidget(tags: shortURL.tags)),
|
2023-07-09 23:00:00 +02:00
|
|
|
),
|
2024-01-28 22:55:28 +01:00
|
|
|
_ListCell(title: "Short Code", content: shortURL.shortCode),
|
2024-01-28 00:32:09 +01:00
|
|
|
_ListCell(
|
2024-03-31 21:58:31 +02:00
|
|
|
title: "Short URL", content: shortURL.shortUrl, isUrl: true),
|
|
|
|
_ListCell(title: "Long URL", content: shortURL.longUrl, isUrl: true),
|
|
|
|
_ListCell(title: "Creation Date", content: shortURL.dateCreated),
|
2024-07-25 17:37:17 +02:00
|
|
|
_ListCell(
|
|
|
|
title: "Redirect Rules",
|
|
|
|
content: null,
|
2024-07-25 17:31:54 +02:00
|
|
|
clickableDetailView: RedirectRulesDetailView(shortURL: shortURL)),
|
2024-01-27 23:07:06 +01:00
|
|
|
const _ListCell(title: "Visits", content: ""),
|
2024-01-28 00:32:09 +01:00
|
|
|
_ListCell(
|
2024-03-31 21:58:31 +02:00
|
|
|
title: "Total", content: shortURL.visitsSummary.total, sub: true),
|
2024-01-28 00:32:09 +01:00
|
|
|
_ListCell(
|
|
|
|
title: "Non-Bots",
|
2024-01-28 22:55:28 +01:00
|
|
|
content: shortURL.visitsSummary.nonBots,
|
2024-01-28 00:32:09 +01:00
|
|
|
sub: true),
|
|
|
|
_ListCell(
|
2024-03-31 21:58:31 +02:00
|
|
|
title: "Bots", content: shortURL.visitsSummary.bots, sub: true),
|
2024-01-27 23:07:06 +01:00
|
|
|
const _ListCell(title: "Meta", content: ""),
|
2024-01-28 00:32:09 +01:00
|
|
|
_ListCell(
|
|
|
|
title: "Valid Since",
|
2024-01-28 22:55:28 +01:00
|
|
|
content: shortURL.meta.validSince,
|
2024-01-28 00:32:09 +01:00
|
|
|
sub: true),
|
|
|
|
_ListCell(
|
|
|
|
title: "Valid Until",
|
2024-01-28 22:55:28 +01:00
|
|
|
content: shortURL.meta.validUntil,
|
2024-01-28 00:32:09 +01:00
|
|
|
sub: true),
|
|
|
|
_ListCell(
|
2024-03-31 21:58:31 +02:00
|
|
|
title: "Max Visits", content: shortURL.meta.maxVisits, sub: true),
|
2024-01-28 22:55:28 +01:00
|
|
|
_ListCell(title: "Domain", content: shortURL.domain),
|
2024-03-31 21:58:31 +02:00
|
|
|
_ListCell(title: "Crawlable", content: shortURL.crawlable, last: true)
|
2023-07-09 23:00:00 +02:00
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ListCell extends StatefulWidget {
|
2024-01-28 00:32:09 +01:00
|
|
|
const _ListCell(
|
|
|
|
{required this.title,
|
|
|
|
required this.content,
|
|
|
|
this.sub = false,
|
2024-03-31 21:08:58 +02:00
|
|
|
this.last = false,
|
2024-07-25 17:31:54 +02:00
|
|
|
this.isUrl = false,
|
2024-07-25 19:23:21 +02:00
|
|
|
this.clickableDetailView});
|
2023-07-09 23:00:00 +02:00
|
|
|
|
|
|
|
final String title;
|
|
|
|
final dynamic content;
|
|
|
|
final bool sub;
|
|
|
|
final bool last;
|
2024-03-31 21:08:58 +02:00
|
|
|
final bool isUrl;
|
2024-07-25 17:31:54 +02:00
|
|
|
final Widget? clickableDetailView;
|
2023-07-09 23:00:00 +02:00
|
|
|
|
|
|
|
@override
|
|
|
|
State<_ListCell> createState() => _ListCellState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ListCellState extends State<_ListCell> {
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return SliverToBoxAdapter(
|
2024-01-28 00:32:09 +01:00
|
|
|
child: Padding(
|
2024-03-31 21:58:31 +02:00
|
|
|
padding: EdgeInsets.only(top: 16, bottom: widget.last ? 30 : 0),
|
|
|
|
child: GestureDetector(
|
|
|
|
onTap: () async {
|
2024-07-25 17:31:54 +02:00
|
|
|
if (widget.clickableDetailView != null) {
|
2024-07-25 17:37:17 +02:00
|
|
|
Navigator.of(context).push(MaterialPageRoute(
|
|
|
|
builder: (context) => widget.clickableDetailView!));
|
2024-07-25 17:31:54 +02:00
|
|
|
} else if (widget.content is String) {
|
|
|
|
Uri? parsedUrl = Uri.tryParse(widget.content);
|
|
|
|
if (widget.isUrl &&
|
|
|
|
parsedUrl != null &&
|
|
|
|
await canLaunchUrl(parsedUrl)) {
|
|
|
|
launchUrl(parsedUrl);
|
|
|
|
}
|
2024-03-31 21:58:31 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
child: Container(
|
|
|
|
padding: const EdgeInsets.only(top: 16, left: 8, right: 8),
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
border: Border(
|
|
|
|
top: BorderSide(
|
2024-07-26 19:32:25 +02:00
|
|
|
color: Theme.of(context).dividerColor)),
|
2024-03-31 21:58:31 +02:00
|
|
|
),
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: [
|
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
if (widget.sub)
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.only(right: 4),
|
|
|
|
child: SizedBox(
|
|
|
|
width: 20,
|
|
|
|
height: 6,
|
|
|
|
child: Container(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
borderRadius: BorderRadius.circular(8),
|
2024-07-26 19:32:25 +02:00
|
|
|
color: Theme.of(context).colorScheme.outline,
|
2024-03-31 21:58:31 +02:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
2024-03-31 21:08:58 +02:00
|
|
|
),
|
2024-03-31 21:58:31 +02:00
|
|
|
Text(
|
|
|
|
widget.title,
|
|
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
|
|
)
|
|
|
|
],
|
2023-07-09 23:00:00 +02:00
|
|
|
),
|
2024-03-31 21:58:31 +02:00
|
|
|
if (widget.content is bool)
|
|
|
|
Icon(widget.content ? Icons.check : Icons.close,
|
|
|
|
color: widget.content ? Colors.green : Colors.red)
|
|
|
|
else if (widget.content is int)
|
|
|
|
Text(widget.content.toString())
|
|
|
|
else if (widget.content is String)
|
|
|
|
Expanded(
|
|
|
|
child: Text(
|
|
|
|
widget.content,
|
|
|
|
textAlign: TextAlign.end,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
maxLines: 1,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
else if (widget.content is DateTime)
|
|
|
|
Text(DateFormat('yyyy-MM-dd - HH:mm')
|
|
|
|
.format(widget.content))
|
2024-07-25 17:31:54 +02:00
|
|
|
else if (widget.clickableDetailView != null)
|
2024-07-25 17:37:17 +02:00
|
|
|
const Icon(Icons.chevron_right)
|
2024-03-31 21:58:31 +02:00
|
|
|
else
|
|
|
|
const Text("N/A")
|
|
|
|
],
|
|
|
|
),
|
2024-03-31 21:08:58 +02:00
|
|
|
),
|
2024-03-31 21:58:31 +02:00
|
|
|
)));
|
2023-07-09 23:00:00 +02:00
|
|
|
}
|
|
|
|
}
|