mirror of
https://github.com/rainloreley/shlink-manager.git
synced 2024-11-21 17:33:03 +01:00
modified home view
This commit is contained in:
parent
c35ca22e07
commit
34e72205ac
|
@ -0,0 +1,33 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
|
||||||
|
import '../ServerManager.dart';
|
||||||
|
|
||||||
|
FutureOr<Either<List<ShortURL>, Failure>> API_getRecentShortUrls(String? api_key, String? server_url, String apiVersion) async {
|
||||||
|
try {
|
||||||
|
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls?itemsPerPage=5&orderBy=dateCreated-DESC"), headers: {
|
||||||
|
"X-Api-Key": api_key ?? "",
|
||||||
|
});
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
var jsonResponse = jsonDecode(response.body);
|
||||||
|
List<ShortURL> shortURLs = (jsonResponse["shortUrls"]["data"] as List<dynamic>).map((e) {
|
||||||
|
return ShortURL.fromJson(e);
|
||||||
|
}).toList();
|
||||||
|
return left(shortURLs);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
var jsonBody = jsonDecode(response.body);
|
||||||
|
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
||||||
|
}
|
||||||
|
catch(resErr) {
|
||||||
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(reqErr) {
|
||||||
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats.dart';
|
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats.dart';
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/ServerManager.dart';
|
||||||
import 'package:shlink_app/LoginView.dart';
|
import 'package:shlink_app/LoginView.dart';
|
||||||
import 'package:shlink_app/ShortURLEditView.dart';
|
import 'package:shlink_app/ShortURLEditView.dart';
|
||||||
|
import 'package:shlink_app/URLListView.dart';
|
||||||
|
import 'API/Classes/ShortURL/ShortURL.dart';
|
||||||
import 'globals.dart' as globals;
|
import 'globals.dart' as globals;
|
||||||
|
|
||||||
class HomeView extends StatefulWidget {
|
class HomeView extends StatefulWidget {
|
||||||
|
@ -16,15 +19,28 @@ class _HomeViewState extends State<HomeView> {
|
||||||
|
|
||||||
ShlinkStats? shlinkStats;
|
ShlinkStats? shlinkStats;
|
||||||
|
|
||||||
|
List<ShortURL> shortUrls = [];
|
||||||
|
bool shortUrlsLoaded = false;
|
||||||
|
bool _qrCodeShown = false;
|
||||||
|
String _qrUrl = "";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
// TODO: implement initState
|
// TODO: implement initState
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance
|
||||||
.addPostFrameCallback((_) => loadShlinkStats());
|
.addPostFrameCallback((_) {
|
||||||
|
loadAllData();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadShlinkStats() async {
|
Future<void> loadAllData() async {
|
||||||
|
var resultStats = await loadShlinkStats();
|
||||||
|
var resultShortUrls = await loadRecentShortUrls();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadShlinkStats() async {
|
||||||
final response = await globals.serverManager.getShlinkStats();
|
final response = await globals.serverManager.getShlinkStats();
|
||||||
response.fold((l) {
|
response.fold((l) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -44,56 +60,148 @@ class _HomeViewState extends State<HomeView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> loadRecentShortUrls() async {
|
||||||
|
final response = await globals.serverManager.getRecentShortUrls();
|
||||||
|
response.fold((l) {
|
||||||
|
setState(() {
|
||||||
|
shortUrls = l;
|
||||||
|
shortUrlsLoaded = true;
|
||||||
|
});
|
||||||
|
}, (r) {
|
||||||
|
var text = "";
|
||||||
|
if (r is RequestFailure) {
|
||||||
|
text = r.description;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text = (r as ApiFailure).detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
final snackBar = SnackBar(content: Text(text), backgroundColor: Colors.red[400], behavior: SnackBarBehavior.floating);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: CustomScrollView(
|
body: Stack(
|
||||||
slivers: [
|
children: [
|
||||||
SliverAppBar.medium(
|
ColorFiltered(
|
||||||
expandedHeight: 160,
|
colorFilter: ColorFilter.mode(Colors.black.withOpacity(_qrCodeShown ? 0.4 : 0), BlendMode.srcOver),
|
||||||
title: Column(
|
child: RefreshIndicator(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
onRefresh: () async {
|
||||||
children: [
|
return loadAllData();
|
||||||
Text("Shlink", style: TextStyle(fontWeight: FontWeight.bold)),
|
},
|
||||||
Text(globals.serverManager.getServerUrl(), style: TextStyle(fontSize: 16, color: Colors.grey[600]))
|
child: CustomScrollView(
|
||||||
],
|
slivers: [
|
||||||
),
|
SliverAppBar.medium(
|
||||||
actions: [
|
expandedHeight: 160,
|
||||||
PopupMenuButton(
|
title: Column(
|
||||||
itemBuilder: (context) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
return [
|
children: [
|
||||||
PopupMenuItem(
|
Text("Shlink", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
value: 0,
|
Text(globals.serverManager.getServerUrl(), style: TextStyle(fontSize: 16, color: Colors.grey[600]))
|
||||||
child: Text("Log out...", style: TextStyle(color: Colors.red)),
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_ShlinkStatsCardWidget(icon: Icons.link, text: "${shlinkStats?.shortUrlsCount.toString() ?? "0"} Short URLs", borderColor: Colors.blue),
|
||||||
|
_ShlinkStatsCardWidget(icon: Icons.remove_red_eye, text: "${shlinkStats?.nonOrphanVisits.total ?? "0"} Visits", borderColor: Colors.green),
|
||||||
|
_ShlinkStatsCardWidget(icon: Icons.warning, text: "${shlinkStats?.orphanVisits.total ?? "0"} Orphan Visits", borderColor: Colors.red),
|
||||||
|
_ShlinkStatsCardWidget(icon: Icons.sell, text: "${shlinkStats?.tagsCount.toString() ?? "0"} Tags", borderColor: Colors.purple),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (shortUrlsLoaded && shortUrls.isEmpty)
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 50),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 8),
|
||||||
|
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
];
|
else
|
||||||
},
|
SliverList(delegate: SliverChildBuilderDelegate(
|
||||||
onSelected: (value) {
|
(BuildContext _context, int index) {
|
||||||
if (value == 0) {
|
if (index == 0) {
|
||||||
globals.serverManager.logOut().then((value) => Navigator.of(context).pushReplacement(
|
return Padding(
|
||||||
MaterialPageRoute(builder: (context) => const LoginView())
|
padding: EdgeInsets.only(top: 16, left: 12, right: 12),
|
||||||
));
|
child: Text("Recent Short URLs", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||||
}
|
);
|
||||||
},
|
}
|
||||||
)
|
else {
|
||||||
],
|
final shortURL = shortUrls[index - 1];
|
||||||
),
|
return ShortURLCell(shortURL: shortURL, reload: () {
|
||||||
SliverToBoxAdapter(
|
loadRecentShortUrls();
|
||||||
child: Wrap(
|
}, showQRCode: (String url) {
|
||||||
alignment: WrapAlignment.spaceEvenly,
|
setState(() {
|
||||||
children: [
|
_qrUrl = url;
|
||||||
_ShlinkStatsCardWidget(icon: Icons.link, text: "${shlinkStats?.shortUrlsCount.toString() ?? "0"} Short URLs", borderColor: Colors.blue),
|
_qrCodeShown = true;
|
||||||
_ShlinkStatsCardWidget(icon: Icons.remove_red_eye, text: "${shlinkStats?.nonOrphanVisits.total ?? "0"} Visits", borderColor: Colors.green),
|
});
|
||||||
_ShlinkStatsCardWidget(icon: Icons.warning, text: "${shlinkStats?.orphanVisits.total ?? "0"} Orphan Visits", borderColor: Colors.red),
|
}, isLast: index == shortUrls.length);
|
||||||
_ShlinkStatsCardWidget(icon: Icons.sell, text: "${shlinkStats?.tagsCount.toString() ?? "0"} Tags", borderColor: Colors.purple),
|
}
|
||||||
],
|
},
|
||||||
|
childCount: shortUrls.length + 1
|
||||||
|
))
|
||||||
|
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
if (_qrCodeShown)
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_qrCodeShown = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_qrCodeShown)
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width / 1.7,
|
||||||
|
height: MediaQuery.of(context).size.width / 1.7,
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: QrImageView(
|
||||||
|
data: _qrUrl,
|
||||||
|
version: QrVersions.auto,
|
||||||
|
size: 200.0,
|
||||||
|
eyeStyle: QrEyeStyle(
|
||||||
|
eyeShape: QrEyeShape.square,
|
||||||
|
color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.white : Colors.black,
|
||||||
|
),
|
||||||
|
dataModuleStyle: QrDataModuleStyle(
|
||||||
|
dataModuleShape: QrDataModuleShape.square,
|
||||||
|
color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.white : Colors.black,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (context) => ShortURLEditView()));
|
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
|
||||||
|
loadRecentShortUrls();
|
||||||
},
|
},
|
||||||
child: Icon(Icons.add),
|
child: Icon(Icons.add),
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
|
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/ServerManager.dart';
|
||||||
|
import 'package:shlink_app/ShortURLEditView.dart';
|
||||||
import 'package:shlink_app/URLDetailView.dart';
|
import 'package:shlink_app/URLDetailView.dart';
|
||||||
import 'globals.dart' as globals;
|
import 'globals.dart' as globals;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -19,6 +20,8 @@ class _URLListViewState extends State<URLListView> {
|
||||||
List<ShortURL> shortUrls = [];
|
List<ShortURL> shortUrls = [];
|
||||||
bool _qrCodeShown = false;
|
bool _qrCodeShown = false;
|
||||||
String _qrUrl = "";
|
String _qrUrl = "";
|
||||||
|
|
||||||
|
bool shortUrlsLoaded = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -33,6 +36,7 @@ class _URLListViewState extends State<URLListView> {
|
||||||
response.fold((l) {
|
response.fold((l) {
|
||||||
setState(() {
|
setState(() {
|
||||||
shortUrls = l;
|
shortUrls = l;
|
||||||
|
shortUrlsLoaded = true;
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}, (r) {
|
}, (r) {
|
||||||
|
@ -53,92 +57,58 @@ class _URLListViewState extends State<URLListView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => ShortURLEditView()));
|
||||||
|
loadAllShortUrls();
|
||||||
|
},
|
||||||
|
child: Icon(Icons.add),
|
||||||
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
ColorFiltered(
|
ColorFiltered(
|
||||||
colorFilter: ColorFilter.mode(Colors.black.withOpacity(_qrCodeShown ? 0.4 : 0), BlendMode.srcOver),
|
colorFilter: ColorFilter.mode(Colors.black.withOpacity(_qrCodeShown ? 0.4 : 0), BlendMode.srcOver),
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
//loadAllShortUrls();
|
|
||||||
return loadAllShortUrls();
|
return loadAllShortUrls();
|
||||||
//Future.value(true);
|
|
||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar.medium(
|
SliverAppBar.medium(
|
||||||
title: Text("Short URLs", style: TextStyle(fontWeight: FontWeight.bold))
|
title: Text("Short URLs", style: TextStyle(fontWeight: FontWeight.bold))
|
||||||
),
|
),
|
||||||
SliverList(delegate: SliverChildBuilderDelegate(
|
if (shortUrlsLoaded && shortUrls.length == 0)
|
||||||
(BuildContext _context, int index) {
|
SliverToBoxAdapter(
|
||||||
final shortURL = shortUrls[index];
|
child: Center(
|
||||||
return GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => URLDetailView(shortURL: shortURL)));
|
|
||||||
|
|
||||||
if (result == "reload") {
|
|
||||||
loadAllShortUrls();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(8),
|
padding: EdgeInsets.only(top: 50),
|
||||||
child: Container(
|
child: Column(
|
||||||
padding: EdgeInsets.only(top: 8, bottom: 8),
|
children: [
|
||||||
decoration: BoxDecoration(
|
Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
|
||||||
border: Border(bottom: BorderSide(color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.grey[800]! : Colors.grey[300]!)),
|
Padding(
|
||||||
),
|
padding: EdgeInsets.only(top: 8),
|
||||||
child: Padding(
|
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
|
||||||
padding: EdgeInsets.all(8),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text("${shortURL.title ?? shortURL.shortCode}", textScaleFactor: 1.4, style: TextStyle(fontWeight: FontWeight.bold),),
|
|
||||||
Text("${shortURL.longUrl}",maxLines: 1, overflow: TextOverflow.ellipsis, textScaleFactor: 0.9, style: TextStyle(color: Colors.grey[600]),),
|
|
||||||
// List tags in a row
|
|
||||||
Wrap(
|
|
||||||
children: shortURL.tags.map((tag) {
|
|
||||||
var randomColor = ([...Colors.primaries]..shuffle()).first.harmonizeWith(Theme.of(context).colorScheme.primary);
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(right: 4, top: 4),
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
color: randomColor,
|
|
||||||
),
|
|
||||||
child: Text(tag, style: TextStyle(color: randomColor.computeLuminance() < 0.5 ? Colors.white : Colors.black),),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList()
|
|
||||||
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(onPressed: () async {
|
|
||||||
await Clipboard.setData(ClipboardData(text: shortURL.shortUrl));
|
|
||||||
final snackBar = SnackBar(content: Text("Copied to clipboard!"), behavior: SnackBarBehavior.floating, backgroundColor: Colors.green[400]);
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
|
||||||
}, icon: Icon(Icons.copy)),
|
|
||||||
IconButton(onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_qrUrl = shortURL.shortUrl;
|
|
||||||
_qrCodeShown = true;
|
|
||||||
});
|
|
||||||
}, icon: Icon(Icons.qr_code))
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
),
|
],
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
},
|
)
|
||||||
childCount: shortUrls.length
|
else
|
||||||
))
|
SliverList(delegate: SliverChildBuilderDelegate(
|
||||||
|
(BuildContext _context, int index) {
|
||||||
|
final shortURL = shortUrls[index];
|
||||||
|
return ShortURLCell(shortURL: shortURL, reload: () {
|
||||||
|
loadAllShortUrls();
|
||||||
|
}, showQRCode: (String url) {
|
||||||
|
setState(() {
|
||||||
|
_qrUrl = url;
|
||||||
|
_qrCodeShown = true;
|
||||||
|
});
|
||||||
|
}, isLast: index == shortUrls.length - 1);
|
||||||
|
},
|
||||||
|
childCount: shortUrls.length
|
||||||
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -184,3 +154,81 @@ class _URLListViewState extends State<URLListView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ShortURLCell extends StatefulWidget {
|
||||||
|
const ShortURLCell({super.key, required this.shortURL, required this.reload, required this.showQRCode, required this.isLast});
|
||||||
|
|
||||||
|
final ShortURL shortURL;
|
||||||
|
final Function() reload;
|
||||||
|
final Function(String url) showQRCode;
|
||||||
|
final bool isLast;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShortURLCell> createState() => _ShortURLCellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShortURLCellState extends State<ShortURLCell> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => URLDetailView(shortURL: widget.shortURL)));
|
||||||
|
|
||||||
|
if (result == "reload") {
|
||||||
|
widget.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: 8, right: 8, bottom: widget.isLast ? 90 : 0),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(left: 8, right: 8, bottom: 16, top: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(bottom: BorderSide(color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.grey[800]! : Colors.grey[300]!)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text("${widget.shortURL.title ?? widget.shortURL.shortCode}", textScaleFactor: 1.4, style: TextStyle(fontWeight: FontWeight.bold),),
|
||||||
|
Text("${widget.shortURL.longUrl}",maxLines: 1, overflow: TextOverflow.ellipsis, textScaleFactor: 0.9, style: TextStyle(color: Colors.grey[600]),),
|
||||||
|
// List tags in a row
|
||||||
|
Wrap(
|
||||||
|
children: widget.shortURL.tags.map((tag) {
|
||||||
|
var randomColor = ([...Colors.primaries]..shuffle()).first.harmonizeWith(Theme.of(context).colorScheme.primary);
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(right: 4, top: 4),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
color: randomColor,
|
||||||
|
),
|
||||||
|
child: Text(tag, style: TextStyle(color: randomColor.computeLuminance() < 0.5 ? Colors.white : Colors.black),),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList()
|
||||||
|
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(onPressed: () async {
|
||||||
|
await Clipboard.setData(ClipboardData(text: widget.shortURL.shortUrl));
|
||||||
|
final snackBar = SnackBar(content: Text("Copied to clipboard!"), behavior: SnackBarBehavior.floating, backgroundColor: Colors.green[400]);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
}, icon: Icon(Icons.copy)),
|
||||||
|
IconButton(onPressed: () {
|
||||||
|
widget.showQRCode(widget.shortURL.shortUrl);
|
||||||
|
}, icon: Icon(Icons.qr_code))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user