a ton of refactoring and commenting

This commit is contained in:
Adrian Baumgart 2024-01-27 23:07:06 +01:00
parent de8bce4b5c
commit 7b16683d10
No known key found for this signature in database
33 changed files with 457 additions and 410 deletions

View File

@ -1,10 +0,0 @@
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats_Visits.dart';
class ShlinkStats {
ShlinkStats_Visits nonOrphanVisits;
ShlinkStats_Visits orphanVisits;
int shortUrlsCount;
int tagsCount;
ShlinkStats(this.nonOrphanVisits, this.orphanVisits, this.shortUrlsCount, this.tagsCount);
}

View File

@ -1,12 +0,0 @@
class ShlinkStats_Visits {
int total;
int nonBots;
int bots;
ShlinkStats_Visits(this.total, this.nonBots, this.bots);
ShlinkStats_Visits.fromJson(Map<String, dynamic> json)
: total = json["total"],
nonBots = json["nonBots"],
bots = json["bots"];
}

View File

@ -0,0 +1,15 @@
import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
/// Includes data about the statistics of a Shlink instance
class ShlinkStats {
/// Data about non-orphan visits
VisitsSummary nonOrphanVisits;
/// Data about orphan visits (without any valid slug assigned)
VisitsSummary orphanVisits;
/// Total count of all short URLs
int shortUrlsCount;
/// Total count all all tags
int tagsCount;
ShlinkStats(this.nonOrphanVisits, this.orphanVisits, this.shortUrlsCount, this.tagsCount);
}

View File

@ -0,0 +1,17 @@
/// Visitor data
class ShlinkStatsVisits {
/// Count of URL visits
int total;
/// Count of URL visits from humans
int nonBots;
/// Count of URL visits from bots/crawlers
int bots;
ShlinkStatsVisits(this.total, this.nonBots, this.bots);
/// Converts the JSON data from the API to an instance of [ShlinkStatsVisits]
ShlinkStatsVisits.fromJson(Map<String, dynamic> json)
: total = json["total"],
nonBots = json["nonBots"],
bots = json["bots"];
}

View File

@ -1,33 +0,0 @@
import 'package:shlink_app/API/Classes/ShortURL/ShortURL_DeviceLongUrls.dart';
import 'package:shlink_app/API/Classes/ShortURL/ShortURL_Meta.dart';
import 'package:shlink_app/API/Classes/ShortURL/ShortURL_VisitsSummary.dart';
class ShortURL {
String shortCode;
String shortUrl;
String longUrl;
ShortURL_DeviceLongUrls deviceLongUrls;
DateTime dateCreated;
ShortURL_VisitsSummary visitsSummary;
List<dynamic> tags;
ShortURL_Meta meta;
String? domain;
String? title;
bool crawlable;
ShortURL(this.shortCode, this.shortUrl, this.longUrl, this.deviceLongUrls, this.dateCreated, this.visitsSummary, this.tags, this.meta, this.domain, this.title, this.crawlable);
ShortURL.fromJson(Map<String, dynamic> json):
shortCode = json["shortCode"],
shortUrl = json["shortUrl"],
longUrl = json["longUrl"],
deviceLongUrls = ShortURL_DeviceLongUrls.fromJson(json["deviceLongUrls"]),
dateCreated = DateTime.parse(json["dateCreated"]),
visitsSummary = ShortURL_VisitsSummary.fromJson(json["visitsSummary"]),
tags = json["tags"],
meta = ShortURL_Meta.fromJson(json["meta"]),
domain = json["domain"],
title = json["title"],
crawlable = json["crawlable"];
}

View File

@ -1,18 +0,0 @@
class ShortURL_DeviceLongUrls {
final String? android;
final String? ios;
final String? desktop;
ShortURL_DeviceLongUrls(this.android, this.ios, this.desktop);
ShortURL_DeviceLongUrls.fromJson(Map<String, dynamic> json)
: android = json["android"],
ios = json["ios"],
desktop = json["desktop"];
Map<String, dynamic> toJson() => {
"android": android,
"ios": ios,
"desktop": desktop
};
}

View File

@ -1,12 +0,0 @@
class ShortURL_Meta {
DateTime? validSince;
DateTime? validUntil;
int? maxVisits;
ShortURL_Meta(this.validSince, this.validUntil, this.maxVisits);
ShortURL_Meta.fromJson(Map<String, dynamic> json):
validSince = json["validSince"] != null ? DateTime.parse(json["validSince"]) : null,
validUntil = json["validUntil"] != null ? DateTime.parse(json["validUntil"]) : null,
maxVisits = json["maxVisits"];
}

View File

@ -1,12 +0,0 @@
class ShortURL_VisitsSummary {
int total;
int nonBots;
int bots;
ShortURL_VisitsSummary(this.total, this.nonBots, this.bots);
ShortURL_VisitsSummary.fromJson(Map<String, dynamic> json):
total = json["total"] as int,
nonBots = json["nonBots"] as int,
bots = json["bots"] as int;
}

View File

@ -0,0 +1,24 @@
/// Data about device-specific long URLs for one short URL
class DeviceLongUrls {
/// Custom URL for Android devices
final String? android;
/// Custom URL for iOS devices
final String? ios;
/// Custom URL for desktop
final String? desktop;
DeviceLongUrls(this.android, this.ios, this.desktop);
/// Converts JSON data from the API to an instance of [DeviceLongUrls]
DeviceLongUrls.fromJson(Map<String, dynamic> json)
: android = json["android"],
ios = json["ios"],
desktop = json["desktop"];
/// Converts data from this class to an JSON object of type
Map<String, dynamic> toJson() => {
"android": android,
"ios": ios,
"desktop": desktop
};
}

View File

@ -0,0 +1,46 @@
import 'package:shlink_app/API/Classes/ShortURL/device_long_urls.dart';
import 'package:shlink_app/API/Classes/ShortURL/short_url_meta.dart';
import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
/// Data about a short URL
class ShortURL {
/// Slug of the short URL used in the URL
String shortCode;
/// Entire short URL
String shortUrl;
/// Long URL where the user gets redirected to
String longUrl;
/// Device-specific long URLs
DeviceLongUrls deviceLongUrls;
/// Creation date of the short URL
DateTime dateCreated;
/// Visitor data
VisitsSummary visitsSummary;
/// List of tags assigned to this short URL
List<dynamic> tags;
/// Metadata
ShortURLMeta meta;
/// Associated domain
String? domain;
/// Optional title
String? title;
/// Whether the short URL is crawlable by a web crawler
bool crawlable;
ShortURL(this.shortCode, this.shortUrl, this.longUrl, this.deviceLongUrls, this.dateCreated, this.visitsSummary, this.tags, this.meta, this.domain, this.title, this.crawlable);
/// Converts the JSON data from the API to an instance of [ShortURL]
ShortURL.fromJson(Map<String, dynamic> json):
shortCode = json["shortCode"],
shortUrl = json["shortUrl"],
longUrl = json["longUrl"],
deviceLongUrls = DeviceLongUrls.fromJson(json["deviceLongUrls"]),
dateCreated = DateTime.parse(json["dateCreated"]),
visitsSummary = VisitsSummary.fromJson(json["visitsSummary"]),
tags = json["tags"],
meta = ShortURLMeta.fromJson(json["meta"]),
domain = json["domain"],
title = json["title"],
crawlable = json["crawlable"];
}

View File

@ -0,0 +1,17 @@
/// Metadata for a short URL
class ShortURLMeta {
/// The date since when this short URL has been valid
DateTime? validSince;
/// The data when this short URL expires
DateTime? validUntil;
/// Amount of maximum visits allowed to this short URL
int? maxVisits;
ShortURLMeta(this.validSince, this.validUntil, this.maxVisits);
/// Converts JSON data from the API to an instance of [ShortURLMeta]
ShortURLMeta.fromJson(Map<String, dynamic> json):
validSince = json["validSince"] != null ? DateTime.parse(json["validSince"]) : null,
validUntil = json["validUntil"] != null ? DateTime.parse(json["validUntil"]) : null,
maxVisits = json["maxVisits"];
}

View File

@ -0,0 +1,17 @@
/// Visitor data
class VisitsSummary {
/// Count of total visits
int total;
/// Count of visits from humans
int nonBots;
/// Count of visits from bots/crawlers
int bots;
VisitsSummary(this.total, this.nonBots, this.bots);
/// Converts JSON data from the API to an instance of [VisitsSummary]
VisitsSummary.fromJson(Map<String, dynamic> json):
total = json["total"] as int,
nonBots = json["nonBots"] as int,
bots = json["bots"] as int;
}

View File

@ -1,22 +1,37 @@
import '../ShortURL/ShortURL_DeviceLongUrls.dart';
import '../ShortURL/device_long_urls.dart';
/// Data for a short URL which can be submitted to the server
class ShortURLSubmission {
/// Long URL to redirect to
String longUrl;
ShortURL_DeviceLongUrls? deviceLongUrls;
/// Device-specific long URLs
DeviceLongUrls? deviceLongUrls;
/// Date since when this short URL is valid in ISO8601 format
String? validSince;
/// Date until when this short URL is valid in ISO8601 format
String? validUntil;
/// Amount of maximum visits allowed to this short URLs
int? maxVisits;
/// List of tags assigned to this short URL
List<String> tags;
/// Title of the page
String? title;
/// Whether the short URL is crawlable by web crawlers
bool crawlable;
/// Whether to forward query parameters
bool forwardQuery;
/// Custom slug (if not provided a random one will be generated)
String? customSlug;
/// Whether to use an existing short URL if the slug matches
bool findIfExists;
/// Domain to use
String? domain;
/// Length of the slug if a custom one is not provided
int? shortCodeLength;
ShortURLSubmission({required this.longUrl, required this.deviceLongUrls, this.validSince, this.validUntil, this.maxVisits, required this.tags, this.title, required this.crawlable, required this.forwardQuery, this.customSlug, required this.findIfExists, this.domain, this.shortCodeLength});
/// Converts class data to a JSON object
Map<String, dynamic> toJson() {
return {
"longUrl": longUrl,

View File

@ -2,12 +2,13 @@ import 'dart:async';
import 'dart:convert';
import 'package:dartz/dartz.dart';
import 'package:http/http.dart' as http;
import '../ServerManager.dart';
import '../server_manager.dart';
FutureOr<Either<String, Failure>> API_connect(String? api_key, String? server_url, String apiVersion) async {
/// Tries to connect to the Shlink server
FutureOr<Either<String, Failure>> apiConnect(String? apiKey, String? serverUrl, String apiVersion) async {
try {
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls"), headers: {
"X-Api-Key": api_key ?? "",
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 200) {
return left("");

View File

@ -2,12 +2,13 @@ import 'dart:async';
import 'dart:convert';
import 'package:dartz/dartz.dart';
import 'package:http/http.dart' as http;
import '../ServerManager.dart';
import '../server_manager.dart';
FutureOr<Either<String, Failure>> API_deleteShortUrl(String shortCode, String? api_key, String? server_url, String apiVersion) async {
/// Deletes a short URL from the server
FutureOr<Either<String, Failure>> apiDeleteShortUrl(String shortCode, String? apiKey, String? serverUrl, String apiVersion) async {
try {
final response = await http.delete(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls/${shortCode}"), headers: {
"X-Api-Key": api_key ?? "",
final response = await http.delete(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls/$shortCode"), headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 204) {
// get returned short url

View File

@ -2,13 +2,14 @@ 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';
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
import '../server_manager.dart';
FutureOr<Either<List<ShortURL>, Failure>> API_getRecentShortUrls(String? api_key, String? server_url, String apiVersion) async {
/// Gets recently created short URLs from the server
FutureOr<Either<List<ShortURL>, Failure>> apiGetRecentShortUrls(String? apiKey, String? serverUrl, 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 ?? "",
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls?itemsPerPage=5&orderBy=dateCreated-DESC"), headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 200) {
var jsonResponse = jsonDecode(response.body);

View File

@ -2,12 +2,13 @@ import 'dart:async';
import 'dart:convert';
import 'package:dartz/dartz.dart';
import 'package:http/http.dart' as http;
import '../ServerManager.dart';
import '../server_manager.dart';
FutureOr<Either<ServerHealthResponse, Failure>> API_getServerHealth(String? api_key, String? server_url, String apiVersion) async {
/// Gets the status of the server and health information
FutureOr<Either<ServerHealthResponse, Failure>> apiGetServerHealth(String? apiKey, String? serverUrl, String apiVersion) async {
try {
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/health"), headers: {
"X-Api-Key": api_key ?? "",
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/health"), headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);

View File

@ -2,11 +2,12 @@ import 'dart:async';
import 'dart:convert';
import 'package:dartz/dartz.dart';
import 'package:http/http.dart' as http;
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats_Visits.dart';
import '../Classes/ShlinkStats/ShlinkStats.dart';
import '../ServerManager.dart';
import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
import '../Classes/ShlinkStats/shlink_stats.dart';
import '../server_manager.dart';
FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, String? server_url, String apiVersion) async {
/// Gets statistics about the Shlink server
FutureOr<Either<ShlinkStats, Failure>> apiGetShlinkStats(String? apiKey, String? serverUrl, String apiVersion) async {
var nonOrphanVisits;
var orphanVisits;
@ -14,7 +15,7 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
var tagsCount;
var failure;
var visitStatsResponse = await _getVisitStats(api_key, server_url, apiVersion);
var visitStatsResponse = await _getVisitStats(apiKey, serverUrl, apiVersion);
visitStatsResponse.fold((l) {
nonOrphanVisits = l.nonOrphanVisits;
orphanVisits = l.orphanVisits;
@ -23,7 +24,7 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
return right(r);
});
var shortUrlsCountResponse = await _getShortUrlsCount(api_key, server_url, apiVersion);
var shortUrlsCountResponse = await _getShortUrlsCount(apiKey, serverUrl, apiVersion);
shortUrlsCountResponse.fold((l) {
shortUrlsCount = l;
}, (r) {
@ -31,7 +32,7 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
return right(r);
});
var tagsCountResponse = await _getTagsCount(api_key, server_url, apiVersion);
var tagsCountResponse = await _getTagsCount(apiKey, serverUrl, apiVersion);
tagsCountResponse.fold((l) {
tagsCount = l;
}, (r) {
@ -40,7 +41,7 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
});
while(failure == null && (nonOrphanVisits == null || orphanVisits == null || shortUrlsCount == null || tagsCount == null)) {
await Future.delayed(Duration(milliseconds: 100));
await Future.delayed(const Duration(milliseconds: 100));
}
if (failure != null) {
@ -49,37 +50,23 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
return left(ShlinkStats(nonOrphanVisits, orphanVisits, shortUrlsCount, tagsCount));
}
/*Future<tuple.Tuple3<FutureOr<Either<_ShlinkVisitStats, Failure>>, FutureOr<Either<int, Failure>>, FutureOr<Either<int, Failure>>>> waiterFunction(String? api_key, String? server_url, String apiVersion) async {
late FutureOr<Either<_ShlinkVisitStats, Failure>> visits;
late FutureOr<Either<int, Failure>> shortUrlsCount;
late FutureOr<Either<int, Failure>> tagsCount;
await Future.wait([
_getVisitStats(api_key, server_url, apiVersion).then((value) => visits = value),
_getShortUrlsCount(api_key, server_url, apiVersion).then((value) => shortUrlsCount = value),
_getTagsCount(api_key, server_url, apiVersion).then((value) => tagsCount = value),
]);
return Future.value(tuple.Tuple3(visits, shortUrlsCount, tagsCount));
}*/
class _ShlinkVisitStats {
ShlinkStats_Visits nonOrphanVisits;
ShlinkStats_Visits orphanVisits;
VisitsSummary nonOrphanVisits;
VisitsSummary orphanVisits;
_ShlinkVisitStats(this.nonOrphanVisits, this.orphanVisits);
}
FutureOr<Either<_ShlinkVisitStats, Failure>> _getVisitStats(String? api_key, String? server_url, String apiVersion) async {
/// Gets visitor statistics about the entire server
FutureOr<Either<_ShlinkVisitStats, Failure>> _getVisitStats(String? apiKey, String? serverUrl, String apiVersion) async {
try {
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/visits"), headers: {
"X-Api-Key": api_key ?? "",
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/visits"), headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 200) {
var jsonResponse = jsonDecode(response.body);
var nonOrphanVisits = ShlinkStats_Visits.fromJson(jsonResponse["visits"]["nonOrphanVisits"]);
var orphanVisits = ShlinkStats_Visits.fromJson(jsonResponse["visits"]["orphanVisits"]);
var nonOrphanVisits = VisitsSummary.fromJson(jsonResponse["visits"]["nonOrphanVisits"]);
var orphanVisits = VisitsSummary.fromJson(jsonResponse["visits"]["orphanVisits"]);
return left(_ShlinkVisitStats(nonOrphanVisits, orphanVisits));
}
@ -98,11 +85,11 @@ FutureOr<Either<_ShlinkVisitStats, Failure>> _getVisitStats(String? api_key, Str
}
}
// get short urls count
FutureOr<Either<int, Failure>> _getShortUrlsCount(String? api_key, String? server_url, String apiVersion) async {
/// Gets amount of short URLs
FutureOr<Either<int, Failure>> _getShortUrlsCount(String? apiKey, String? serverUrl, String apiVersion) async {
try {
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls"), headers: {
"X-Api-Key": api_key ?? "",
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 200) {
var jsonResponse = jsonDecode(response.body);
@ -123,11 +110,11 @@ FutureOr<Either<int, Failure>> _getShortUrlsCount(String? api_key, String? serve
}
}
// get tags count
FutureOr<Either<int, Failure>> _getTagsCount(String? api_key, String? server_url, String apiVersion) async {
/// Gets amount of tags
FutureOr<Either<int, Failure>> _getTagsCount(String? apiKey, String? serverUrl, String apiVersion) async {
try {
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/tags"), headers: {
"X-Api-Key": api_key ?? "",
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/tags"), headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 200) {
var jsonResponse = jsonDecode(response.body);

View File

@ -2,39 +2,41 @@ 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';
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
import '../server_manager.dart';
FutureOr<Either<List<ShortURL>, Failure>> API_getShortUrls(String? api_key, String? server_url, String apiVersion) async {
var _currentPage = 1;
var _maxPages = 2;
List<ShortURL> _allUrls = [];
/// Gets all short URLs
FutureOr<Either<List<ShortURL>, Failure>> apiGetShortUrls(String? apiKey, String? serverUrl, String apiVersion) async {
var currentPage = 1;
var maxPages = 2;
List<ShortURL> allUrls = [];
Failure? error;
while (_currentPage <= _maxPages) {
final response = await _getShortUrlPage(_currentPage, api_key, server_url, apiVersion);
while (currentPage <= maxPages) {
final response = await _getShortUrlPage(currentPage, apiKey, serverUrl, apiVersion);
response.fold((l) {
_allUrls.addAll(l.urls);
_maxPages = l.totalPages;
_currentPage++;
allUrls.addAll(l.urls);
maxPages = l.totalPages;
currentPage++;
}, (r) {
_maxPages = 0;
maxPages = 0;
error = r;
});
}
if (error == null) {
return left(_allUrls);
return left(allUrls);
}
else {
return right(error!);
}
}
FutureOr<Either<ShortURLPageResponse, Failure>> _getShortUrlPage(int page, String? api_key, String? server_url, String apiVersion) async {
/// Gets all short URLs from a specific page
FutureOr<Either<ShortURLPageResponse, Failure>> _getShortUrlPage(int page, String? apiKey, String? serverUrl, String apiVersion) async {
try {
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls?page=${page}"), headers: {
"X-Api-Key": api_key ?? "",
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls?page=$page"), headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 200) {
var jsonResponse = jsonDecode(response.body);

View File

@ -2,13 +2,14 @@ import 'dart:async';
import 'dart:convert';
import 'package:dartz/dartz.dart';
import 'package:http/http.dart' as http;
import 'package:shlink_app/API/Classes/ShortURLSubmission/ShortURLSubmission.dart';
import '../ServerManager.dart';
import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.dart';
import '../server_manager.dart';
FutureOr<Either<String, Failure>> API_submitShortUrl(ShortURLSubmission shortUrl, String? api_key, String? server_url, String apiVersion) async {
/// Submits a short URL to a server for it to be added
FutureOr<Either<String, Failure>> apiSubmitShortUrl(ShortURLSubmission shortUrl, String? apiKey, String? serverUrl, String apiVersion) async {
try {
final response = await http.post(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls"), headers: {
"X-Api-Key": api_key ?? "",
final response = await http.post(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
"X-Api-Key": apiKey ?? "",
}, body: jsonEncode(shortUrl.toJson()));
if (response.statusCode == 200) {
// get returned short url
@ -18,7 +19,7 @@ FutureOr<Either<String, Failure>> API_submitShortUrl(ShortURLSubmission shortUrl
else {
try {
var jsonBody = jsonDecode(response.body);
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"], invalidElements: jsonBody["invalidElements"] ?? null));
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"], invalidElements: jsonBody["invalidElements"]));
}
catch(resErr) {
return right(RequestFailure(response.statusCode, resErr.toString()));

View File

@ -2,113 +2,125 @@ import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats.dart';
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
import 'package:shlink_app/API/Classes/ShortURLSubmission/ShortURLSubmission.dart';
import 'package:shlink_app/API/Classes/ShlinkStats/shlink_stats.dart';
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.dart';
import 'package:shlink_app/API/Methods/connect.dart';
import 'package:shlink_app/API/Methods/getRecentShortUrls.dart';
import 'package:shlink_app/API/Methods/getServerHealth.dart';
import 'package:shlink_app/API/Methods/getShlinkStats.dart';
import 'package:shlink_app/API/Methods/getShortUrls.dart';
import 'package:shlink_app/API/Methods/get_recent_short_urls.dart';
import 'package:shlink_app/API/Methods/get_server_health.dart';
import 'package:shlink_app/API/Methods/get_shlink_stats.dart';
import 'package:shlink_app/API/Methods/get_short_urls.dart';
import 'Methods/deleteShortUrl.dart';
import 'Methods/submitShortUrl.dart';
import 'Methods/delete_short_url.dart';
import 'Methods/submit_short_url.dart';
class ServerManager {
String? _server_url;
String? _api_key;
/// The URL of the Shlink server
String? serverUrl;
/// The API key to access the server
String? apiKey;
/// Current Shlink API Version used by the app
static String apiVersion = "3";
String getServerUrl() {
return _server_url ?? "";
return serverUrl ?? "";
}
String getApiVersion() {
return apiVersion;
}
/// Checks whether the user provided information about the server (url and apikey)
Future<bool> checkLogin() async {
await _loadCredentials();
return (_server_url != null);
return (serverUrl != null);
}
/// Logs out the user and removes data about the Shlink server
Future<void> logOut() async {
const storage = FlutterSecureStorage();
await storage.delete(key: "shlink_url");
await storage.delete(key: "shlink_apikey");
}
/// Loads the server credentials from [FlutterSecureStorage]
Future<void> _loadCredentials() async {
const storage = FlutterSecureStorage();
final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('first_run') ?? true) {
FlutterSecureStorage storage = FlutterSecureStorage();
FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.deleteAll();
prefs.setBool('first_run', false);
}
_server_url = await storage.read(key: "shlink_url");
_api_key = await storage.read(key: "shlink_apikey");
serverUrl = await storage.read(key: "shlink_url");
apiKey = await storage.read(key: "shlink_apikey");
}
/// Saves the provided server credentials to [FlutterSecureStorage]
void _saveCredentials(String url, String apiKey) async {
const storage = FlutterSecureStorage();
storage.write(key: "shlink_url", value: url);
storage.write(key: "shlink_apikey", value: apiKey);
}
void _removeCredentials() async {
const storage = FlutterSecureStorage();
storage.delete(key: "shlink_url");
storage.delete(key: "shlink_apikey");
}
/// Saves provided server credentials and tries to establish a connection
FutureOr<Either<String, Failure>> initAndConnect(String url, String apiKey) async {
// TODO: convert url to correct format
_server_url = url;
_api_key = apiKey;
serverUrl = url;
this.apiKey = apiKey;
_saveCredentials(url, apiKey);
final result = await connect();
result.fold((l) => null, (r) {
_removeCredentials();
logOut();
});
return result;
}
/// Establishes a connection to the server
FutureOr<Either<String, Failure>> connect() async {
_loadCredentials();
return API_connect(_api_key, _server_url, apiVersion);
return apiConnect(apiKey, serverUrl, apiVersion);
}
/// Gets all short URLs from the server
FutureOr<Either<List<ShortURL>, Failure>> getShortUrls() async {
return API_getShortUrls(_api_key, _server_url, apiVersion);
return apiGetShortUrls(apiKey, serverUrl, apiVersion);
}
/// Gets statistics about the Shlink instance
FutureOr<Either<ShlinkStats, Failure>> getShlinkStats() async {
return API_getShlinkStats(_api_key, _server_url, apiVersion);
return apiGetShlinkStats(apiKey, serverUrl, apiVersion);
}
/// Saves a new short URL to the server
FutureOr<Either<String, Failure>> submitShortUrl(ShortURLSubmission shortUrl) async {
return API_submitShortUrl(shortUrl, _api_key, _server_url, apiVersion);
return apiSubmitShortUrl(shortUrl, apiKey, serverUrl, apiVersion);
}
/// Deletes a short URL from the server, identified by its slug
FutureOr<Either<String, Failure>> deleteShortUrl(String shortCode) async {
return API_deleteShortUrl(shortCode, _api_key, _server_url, apiVersion);
return apiDeleteShortUrl(shortCode, apiKey, serverUrl, apiVersion);
}
/// Gets health data about the server
FutureOr<Either<ServerHealthResponse, Failure>> getServerHealth() async {
return API_getServerHealth(_api_key, _server_url, apiVersion);
return apiGetServerHealth(apiKey, serverUrl, apiVersion);
}
/// Gets recently created/used short URLs from the server
FutureOr<Either<List<ShortURL>, Failure>> getRecentShortUrls() async {
return API_getRecentShortUrls(_api_key, _server_url, apiVersion);
return apiGetRecentShortUrls(apiKey, serverUrl, apiVersion);
}
}
/// Server response data type about a page of short URLs from the server
class ShortURLPageResponse {
List<ShortURL> urls;
int totalPages;
@ -116,6 +128,7 @@ class ShortURLPageResponse {
ShortURLPageResponse(this.urls, this.totalPages);
}
/// Server response data type about the health status of the server
class ServerHealthResponse {
String status;
String version;
@ -123,8 +136,10 @@ class ServerHealthResponse {
ServerHealthResponse({required this.status, required this.version});
}
/// Failure class, used for the API
abstract class Failure {}
/// Used when a request to a server fails (due to networking issues or an unexpected response)
class RequestFailure extends Failure {
int statusCode;
String description;
@ -132,6 +147,7 @@ class RequestFailure extends Failure {
RequestFailure(this.statusCode, this.description);
}
/// Contains information about an error returned by the Shlink API
class ApiFailure extends Failure {
String type;
String detail;

View File

@ -1,4 +1,4 @@
library dev.abmgrt.shlink_app.globals;
import 'package:shlink_app/API/ServerManager.dart';
import 'package:shlink_app/API/server_manager.dart';
ServerManager serverManager = ServerManager();

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:shlink_app/LoginView.dart';
import 'package:shlink_app/NavigationBarView.dart';
import 'package:shlink_app/views/login_view.dart';
import 'package:shlink_app/views/navigationbar_view.dart';
import 'globals.dart' as globals;
import 'package:dynamic_color/dynamic_color.dart';
@ -11,7 +11,7 @@ void main() {
class MyApp extends StatelessWidget {
const MyApp({super.key});
static final _defaultLightColorScheme =
static const _defaultLightColorScheme =
ColorScheme.light();//.fromSwatch(primarySwatch: Colors.blue, backgroundColor: Colors.white);
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(
@ -25,14 +25,14 @@ class MyApp extends StatelessWidget {
title: 'Shlink',
debugShowCheckedModeBanner: false,
theme: ThemeData(
appBarTheme: AppBarTheme(
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xfffafafa),
),
colorScheme: lightColorScheme ?? _defaultLightColorScheme,
useMaterial3: true
),
darkTheme: ThemeData(
appBarTheme: AppBarTheme(
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xff0d0d0d),
foregroundColor: Colors.white,
elevation: 0,
@ -40,26 +40,8 @@ class MyApp extends StatelessWidget {
colorScheme: darkColorScheme?.copyWith(background: Colors.black) ?? _defaultDarkColorScheme,
useMaterial3: true,
),
/*theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
useMaterial3: true,
colorScheme: ColorScheme.fromSwatch().copyWith(
secondary: Colors.orange
)
),*/
/*darkTheme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
useMaterial3: true,
colorScheme: ColorScheme.dark(
background: Colors.black,
surface: Color(0xff0d0d0d),
secondaryContainer: Colors.grey[300]
)
),*/
themeMode: ThemeMode.system,
home: InitialPage()
home: const InitialPage()
);
});
}
@ -97,7 +79,7 @@ class _InitialPageState extends State<InitialPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
return const Scaffold(
body: Center(
child: Text("")
),

View File

@ -26,7 +26,7 @@ class LicenseUtil {
static List<License> getLicenses() {
return [
License(
const License(
name: r'cupertino_icons',
license: r'''The MIT License (MIT)
@ -52,7 +52,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''',
homepage: null,
repository: r'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons',
),
License(
const License(
name: r'dartz',
license: r'''The MIT License (MIT)
@ -80,7 +80,7 @@ SOFTWARE.
homepage: r'https://github.com/spebbe/dartz',
repository: null,
),
License(
const License(
name: r'dynamic_color',
license: r''' Apache License
Version 2.0, January 2004
@ -288,7 +288,7 @@ SOFTWARE.
homepage: null,
repository: r'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color',
),
License(
const License(
name: r'flutter',
license: r'''Copyright 2014 The Flutter Authors. All rights reserved.
@ -320,7 +320,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
homepage: r'https://flutter.dev/',
repository: r'https://github.com/flutter/flutter',
),
License(
const License(
name: r'flutter_launcher_icons',
license: r'''MIT License
@ -348,7 +348,7 @@ SOFTWARE.
homepage: r'https://github.com/fluttercommunity/flutter_launcher_icons',
repository: r'https://github.com/fluttercommunity/flutter_launcher_icons/',
),
License(
const License(
name: r'flutter_lints',
license: r'''Copyright 2013 The Flutter Authors. All rights reserved.
@ -380,7 +380,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
homepage: null,
repository: r'https://github.com/flutter/packages/tree/main/packages/flutter_lints',
),
License(
const License(
name: r'flutter_process_text',
license: r'''BSD 3-Clause License
@ -413,7 +413,7 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
homepage: null,
repository: r'https://github.com/DevsOnFlutter/flutter_process_text',
),
License(
const License(
name: r'flutter_secure_storage',
license: r'''BSD 3-Clause License
@ -448,7 +448,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
homepage: null,
repository: r'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage',
),
License(
const License(
name: r'flutter_test',
license: r'''Copyright 2014 The Flutter Authors. All rights reserved.
@ -480,7 +480,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
homepage: r'https://flutter.dev/',
repository: r'https://github.com/flutter/flutter',
),
License(
const License(
name: r'http',
license: r'''Copyright 2014, the Dart project authors.
@ -514,7 +514,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
homepage: null,
repository: r'https://github.com/dart-lang/http/tree/master/pkgs/http',
),
License(
const License(
name: r'intl',
license: r'''Copyright 2013, the Dart project authors.
@ -548,7 +548,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
homepage: null,
repository: r'https://github.com/dart-lang/i18n/tree/main/pkgs/intl',
),
License(
const License(
name: r'license_generator',
license: r'''MIT License
@ -576,7 +576,7 @@ SOFTWARE.
homepage: r'https://github.com/icapps/flutter-icapps-license',
repository: null,
),
License(
const License(
name: r'package_info_plus',
license: r'''Copyright 2017 The Chromium Authors. All rights reserved.
@ -610,7 +610,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
homepage: r'https://plus.fluttercommunity.dev/',
repository: r'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/',
),
License(
const License(
name: r'qr_flutter',
license: r'''BSD 3-Clause License
@ -646,7 +646,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
homepage: r'https://github.com/theyakka/qr.flutter',
repository: null,
),
License(
const License(
name: r'shared_preferences',
license: r'''Copyright 2013 The Flutter Authors. All rights reserved.
@ -678,7 +678,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
homepage: null,
repository: r'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences',
),
License(
const License(
name: r'tuple',
license: r'''Copyright (c) 2014, the tuple project authors.
All rights reserved.
@ -706,7 +706,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
homepage: null,
repository: r'https://github.com/google/tuple.dart',
),
License(
const License(
name: r'url_launcher',
license: r'''Copyright 2013 The Flutter Authors. All rights reserved.

View File

@ -1,12 +1,11 @@
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/ServerManager.dart';
import 'package:shlink_app/LoginView.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 'package:shlink_app/API/Classes/ShlinkStats/shlink_stats.dart';
import 'package:shlink_app/API/server_manager.dart';
import 'package:shlink_app/views/short_url_edit_view.dart';
import 'package:shlink_app/views/url_list_view.dart';
import '../API/Classes/ShortURL/short_url.dart';
import '../globals.dart' as globals;
class HomeView extends StatefulWidget {
const HomeView({Key? key}) : super(key: key);
@ -35,8 +34,8 @@ class _HomeViewState extends State<HomeView> {
}
Future<void> loadAllData() async {
var resultStats = await loadShlinkStats();
var resultShortUrls = await loadRecentShortUrls();
await loadShlinkStats();
await loadRecentShortUrls();
return;
}
@ -99,7 +98,7 @@ class _HomeViewState extends State<HomeView> {
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Shlink", style: TextStyle(fontWeight: FontWeight.bold)),
const Text("Shlink", style: TextStyle(fontWeight: FontWeight.bold)),
Text(globals.serverManager.getServerUrl(), style: TextStyle(fontSize: 16, color: Colors.grey[600]))
],
)
@ -119,12 +118,12 @@ class _HomeViewState extends State<HomeView> {
SliverToBoxAdapter(
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 50),
padding: const EdgeInsets.only(top: 50),
child: Column(
children: [
Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
const Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
Padding(
padding: EdgeInsets.only(top: 8),
padding: const EdgeInsets.only(top: 8),
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
)
],
@ -134,9 +133,9 @@ class _HomeViewState extends State<HomeView> {
)
else
SliverList(delegate: SliverChildBuilderDelegate(
(BuildContext _context, int index) {
(BuildContext context, int index) {
if (index == 0) {
return Padding(
return const Padding(
padding: EdgeInsets.only(top: 16, left: 12, right: 12),
child: Text("Recent Short URLs", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
);
@ -200,10 +199,10 @@ class _HomeViewState extends State<HomeView> {
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
loadRecentShortUrls();
},
child: Icon(Icons.add),
child: const Icon(Icons.add),
)
);
}
@ -211,11 +210,11 @@ class _HomeViewState extends State<HomeView> {
// stats card widget
class _ShlinkStatsCardWidget extends StatefulWidget {
const _ShlinkStatsCardWidget({this.text, this.icon, this.borderColor});
const _ShlinkStatsCardWidget({required this.text, required this.icon, this.borderColor});
final icon;
final borderColor;
final text;
final IconData icon;
final Color? borderColor;
final String text;
@override
State<_ShlinkStatsCardWidget> createState() => _ShlinkStatsCardWidgetState();
@ -226,9 +225,9 @@ class _ShlinkStatsCardWidgetState extends State<_ShlinkStatsCardWidget> {
Widget build(BuildContext context) {
var randomColor = ([...Colors.primaries]..shuffle()).first;
return Padding(
padding: EdgeInsets.all(4),
padding: const EdgeInsets.all(4),
child: Container(
padding: EdgeInsets.all(12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: widget.borderColor ?? randomColor),
borderRadius: BorderRadius.circular(8)
@ -238,8 +237,8 @@ class _ShlinkStatsCardWidgetState extends State<_ShlinkStatsCardWidget> {
children: [
Icon(widget.icon),
Padding(
padding: EdgeInsets.only(left: 4),
child: Text(widget.text, style: TextStyle(fontWeight: FontWeight.bold)),
padding: const EdgeInsets.only(left: 4),
child: Text(widget.text, style: const TextStyle(fontWeight: FontWeight.bold)),
)
],
),

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:shlink_app/API/ServerManager.dart';
import 'package:shlink_app/API/server_manager.dart';
import 'package:shlink_app/main.dart';
import 'globals.dart' as globals;
import '../globals.dart' as globals;
class LoginView extends StatefulWidget {
const LoginView({Key? key}) : super(key: key);
@ -11,8 +11,8 @@ class LoginView extends StatefulWidget {
}
class _LoginViewState extends State<LoginView> {
late TextEditingController _server_url_controller;
late TextEditingController _apikey_controller;
late TextEditingController _serverUrlController;
late TextEditingController _apiKeyController;
bool _isLoggingIn = false;
String _errorMessage = "";
@ -21,8 +21,8 @@ class _LoginViewState extends State<LoginView> {
void initState() {
// TODO: implement initState
super.initState();
_server_url_controller = TextEditingController();
_apikey_controller = TextEditingController();
_serverUrlController = TextEditingController();
_apiKeyController = TextEditingController();
}
void _connect() async {
@ -30,7 +30,7 @@ class _LoginViewState extends State<LoginView> {
_isLoggingIn = true;
_errorMessage = "";
});
final connectResult = await globals.serverManager.initAndConnect(_server_url_controller.text, _apikey_controller.text);
final connectResult = await globals.serverManager.initAndConnect(_serverUrlController.text, _apiKeyController.text);
connectResult.fold((l) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => const InitialPage())
@ -61,8 +61,8 @@ class _LoginViewState extends State<LoginView> {
extendBody: true,
body: CustomScrollView(
slivers: [
SliverAppBar.medium(
title: const Text("Add server", style: TextStyle(fontWeight: FontWeight.bold))
const SliverAppBar.medium(
title: Text("Add server", style: TextStyle(fontWeight: FontWeight.bold))
),
SliverFillRemaining(
child: Padding(
@ -75,10 +75,10 @@ class _LoginViewState extends State<LoginView> {
child: Text("Server URL", style: TextStyle(fontWeight: FontWeight.bold),)),
Row(
children: [
Icon(Icons.dns_outlined),
SizedBox(width: 8),
const Icon(Icons.dns_outlined),
const SizedBox(width: 8),
Expanded(child: TextField(
controller: _server_url_controller,
controller: _serverUrlController,
keyboardType: TextInputType.url,
decoration: const InputDecoration(
border: OutlineInputBorder(),
@ -93,10 +93,10 @@ class _LoginViewState extends State<LoginView> {
),
Row(
children: [
Icon(Icons.key),
SizedBox(width: 8),
const Icon(Icons.key),
const SizedBox(width: 8),
Expanded(child: TextField(
controller: _apikey_controller,
controller: _apiKeyController,
keyboardType: TextInputType.text,
obscureText: true,
decoration: const InputDecoration(
@ -130,7 +130,7 @@ class _LoginViewState extends State<LoginView> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(child: Text(_errorMessage, style: TextStyle(color: Colors.red), textAlign: TextAlign.center))
Flexible(child: Text(_errorMessage, style: const TextStyle(color: Colors.red), textAlign: TextAlign.center))
],
),
)

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:shlink_app/SettingsView.dart';
import 'package:shlink_app/HomeView.dart';
import 'package:shlink_app/URLListView.dart';
import 'package:shlink_app/views/settings_view.dart';
import 'package:shlink_app/views/home_view.dart';
import 'package:shlink_app/views/url_list_view.dart';
class NavigationBarView extends StatefulWidget {
const NavigationBarView({Key? key}) : super(key: key);
@ -12,7 +12,7 @@ class NavigationBarView extends StatefulWidget {
class _NavigationBarViewState extends State<NavigationBarView> {
final List<Widget> views = [HomeView(), URLListView(), SettingsView()];
final List<Widget> views = [const HomeView(), const URLListView(), const SettingsView()];
int _selectedView = 0;
@override
@ -20,7 +20,7 @@ class _NavigationBarViewState extends State<NavigationBarView> {
return Scaffold(
body: views.elementAt(_selectedView),
bottomNavigationBar: NavigationBar(
destinations: [
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: "Home"),
NavigationDestination(icon: Icon(Icons.link), label: "Short URLs"),
NavigationDestination(icon: Icon(Icons.settings), label: "Settings")

View File

@ -15,9 +15,9 @@ class _OpenSourceLicensesViewState extends State<OpenSourceLicensesView> {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar.medium(
const SliverAppBar.medium(
expandedHeight: 120,
title: const Text("Open Source Licenses", style: TextStyle(fontWeight: FontWeight.bold),)
title: Text("Open Source Licenses", style: TextStyle(fontWeight: FontWeight.bold),)
),
SliverList(
delegate: SliverChildBuilderDelegate(
@ -32,23 +32,23 @@ class _OpenSourceLicensesViewState extends State<OpenSourceLicensesView> {
}
},
child: Padding(
padding: EdgeInsets.all(12),
padding: const EdgeInsets.all(12),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
),
child: Padding(
padding: EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
padding: const EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("${currentLicense.name}", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
Text("Version: ${currentLicense.version ?? "N/A"}", style: TextStyle(color: Colors.grey)),
SizedBox(height: 8),
Divider(),
SizedBox(height: 8),
Text("${currentLicense.license}", textAlign: TextAlign.justify, style: TextStyle(color: Colors.grey)),
Text(currentLicense.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
Text("Version: ${currentLicense.version ?? "N/A"}", style: const TextStyle(color: Colors.grey)),
const SizedBox(height: 8),
const Divider(),
const SizedBox(height: 8),
Text(currentLicense.license, textAlign: TextAlign.justify, style: const TextStyle(color: Colors.grey)),
],
),
),
@ -59,7 +59,7 @@ class _OpenSourceLicensesViewState extends State<OpenSourceLicensesView> {
childCount: LicenseUtil.getLicenses().length
),
),
SliverToBoxAdapter(
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.only(top: 8, bottom: 20),
child: Text("Thank you to all maintainers of these repositories 💝", style: TextStyle(color: Colors.grey), textAlign: TextAlign.center,),

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shlink_app/API/ServerManager.dart';
import 'package:shlink_app/LoginView.dart';
import 'package:shlink_app/OpenSourceLicensesView.dart';
import 'package:shlink_app/API/server_manager.dart';
import 'package:shlink_app/views/login_view.dart';
import 'package:shlink_app/views/opensource_licenses_view.dart';
import 'package:url_launcher/url_launcher.dart';
import 'globals.dart' as globals;
import '../globals.dart' as globals;
class SettingsView extends StatefulWidget {
const SettingsView({super.key});
@ -21,9 +21,9 @@ enum ServerStatus {
class _SettingsViewState extends State<SettingsView> {
var _server_version = "---";
ServerStatus _server_status = ServerStatus.connecting;
var packageInfo = null;
var _serverVersion = "---";
ServerStatus _serverStatus = ServerStatus.connecting;
PackageInfo packageInfo = PackageInfo(appName: "", packageName: "", version: "", buildNumber: "");
@override
void initState() {
@ -34,19 +34,19 @@ class _SettingsViewState extends State<SettingsView> {
}
void getServerHealth() async {
var _packageInfo = await PackageInfo.fromPlatform();
var packageInfo = await PackageInfo.fromPlatform();
setState(() {
packageInfo = _packageInfo;
packageInfo = packageInfo;
});
final response = await globals.serverManager.getServerHealth();
response.fold((l) {
setState(() {
_server_version = l.version;
_server_status = ServerStatus.connected;
_serverVersion = l.version;
_serverStatus = ServerStatus.connected;
});
}, (r) {
setState(() {
_server_status = ServerStatus.disconnected;
_serverStatus = ServerStatus.disconnected;
});
var text = "";
@ -74,7 +74,7 @@ class _SettingsViewState extends State<SettingsView> {
PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
const PopupMenuItem(
value: 0,
child: Text("Log out...", style: TextStyle(color: Colors.red)),
)
@ -105,7 +105,7 @@ class _SettingsViewState extends State<SettingsView> {
child: Row(
children: [
Icon(Icons.dns_outlined, color: (() {
switch (_server_status) {
switch (_serverStatus) {
case ServerStatus.connected:
return Colors.green;
case ServerStatus.connecting:
@ -114,19 +114,19 @@ class _SettingsViewState extends State<SettingsView> {
return Colors.red;
}
}())),
SizedBox(width: 8),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Connected to", style: TextStyle(color: Colors.grey)),
Text(globals.serverManager.getServerUrl(), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
const Text("Connected to", style: TextStyle(color: Colors.grey)),
Text(globals.serverManager.getServerUrl(), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
Row(
children: [
Text("API Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
Text("${globals.serverManager.getApiVersion()}", style: TextStyle(color: Colors.grey)),
SizedBox(width: 16),
Text("Server Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
Text("${_server_version}", style: TextStyle(color: Colors.grey))
const Text("API Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
Text(globals.serverManager.getApiVersion(), style: const TextStyle(color: Colors.grey)),
const SizedBox(width: 16),
const Text("Server Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
Text(_serverVersion, style: const TextStyle(color: Colors.grey))
],
),
],
@ -135,9 +135,9 @@ class _SettingsViewState extends State<SettingsView> {
),
),
),
SizedBox(height: 8),
Divider(),
SizedBox(height: 8),
const SizedBox(height: 8),
const Divider(),
const SizedBox(height: 8),
GestureDetector(
onTap: () {
Navigator.of(context).push(
@ -149,7 +149,7 @@ class _SettingsViewState extends State<SettingsView> {
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
),
child: Padding(
child: const Padding(
padding: EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -167,7 +167,7 @@ class _SettingsViewState extends State<SettingsView> {
),
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
GestureDetector(
onTap: () async {
var url = Uri.parse("https://github.com/rainloreley/shlink-mobile-app");
@ -180,7 +180,7 @@ class _SettingsViewState extends State<SettingsView> {
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
),
child: Padding(
child: const Padding(
padding: EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -198,7 +198,7 @@ class _SettingsViewState extends State<SettingsView> {
),
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
GestureDetector(
onTap: () async {
var url = Uri.parse("https://abmgrt.dev/shlink-manager/privacy");
@ -211,7 +211,7 @@ class _SettingsViewState extends State<SettingsView> {
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
),
child: Padding(
child: const Padding(
padding: EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -229,12 +229,12 @@ class _SettingsViewState extends State<SettingsView> {
),
),
),
SizedBox(height: 16),
if (packageInfo != null)
const SizedBox(height: 16),
if (packageInfo.appName != "")
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text("${packageInfo.appName}, v${packageInfo.version} (${packageInfo.buildNumber})", style: TextStyle(color: Colors.grey),),],
Text("${packageInfo.appName}, v${packageInfo.version} (${packageInfo.buildNumber})", style: const TextStyle(color: Colors.grey),),],
)
],
)

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shlink_app/API/Classes/ShortURLSubmission/ShortURLSubmission.dart';
import 'package:shlink_app/API/ServerManager.dart';
import 'globals.dart' as globals;
import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.dart';
import 'package:shlink_app/API/server_manager.dart';
import '../globals.dart' as globals;
class ShortURLEditView extends StatefulWidget {
const ShortURLEditView({super.key});
@ -34,7 +34,7 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
void initState() {
_customSlugDiceAnimationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
duration: const Duration(milliseconds: 500),
);
super.initState();
}
@ -67,11 +67,11 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
if (copyToClipboard) {
await Clipboard.setData(ClipboardData(text: l));
final snackBar = SnackBar(content: Text("Copied to clipboard!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
final snackBar = SnackBar(content: const Text("Copied to clipboard!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
else {
final snackBar = SnackBar(content: Text("Short URL created!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
final snackBar = SnackBar(content: const Text("Short URL created!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
Navigator.pop(context);
@ -89,8 +89,8 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
}
else {
text = (r as ApiFailure).detail;
if ((r as ApiFailure).invalidElements != null) {
text = text + ": " + (r as ApiFailure).invalidElements.toString();
if ((r).invalidElements != null) {
text = "$text: ${(r).invalidElements}";
}
}
@ -106,12 +106,12 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar.medium(
const SliverAppBar.medium(
title: Text("New Short URL", style: TextStyle(fontWeight: FontWeight.bold)),
),
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.only(left: 16, right: 16, top: 16),
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@ -119,8 +119,8 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
controller: longUrlController,
decoration: InputDecoration(
errorText: longUrlError != "" ? longUrlError : null,
border: OutlineInputBorder(),
label: Row(
border: const OutlineInputBorder(),
label: const Row(
children: [
Icon(Icons.public),
SizedBox(width: 8),
@ -129,7 +129,7 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
)
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
Row(
children: [
Expanded(
@ -137,23 +137,25 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
controller: customSlugController,
style: TextStyle(color: randomSlug ? Colors.grey : Theme.of(context).brightness == Brightness.light ? Colors.black : Colors.white),
onChanged: (_) {
if (randomSlug) setState(() {
if (randomSlug) {
setState(() {
randomSlug = false;
});
}
},
decoration: InputDecoration(
border: OutlineInputBorder(),
border: const OutlineInputBorder(),
label: Row(
children: [
Icon(Icons.link),
SizedBox(width: 8),
const Icon(Icons.link),
const SizedBox(width: 8),
Text("${randomSlug ? "Random" : "Custom"} slug", style: TextStyle(fontStyle: randomSlug ? FontStyle.italic : FontStyle.normal),)
],
)
),
),
),
SizedBox(width: 8),
const SizedBox(width: 8),
RotationTransition(
turns: Tween(begin: 0.0, end: 3.0).animate(CurvedAnimation(parent: _customSlugDiceAnimationController, curve: Curves.easeInOutExpo)),
child: IconButton(
@ -175,13 +177,13 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
],
),
if (randomSlug)
SizedBox(height: 16),
const SizedBox(height: 16),
if (randomSlug)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Random slug length"),
const Text("Random slug length"),
SizedBox(
width: 100,
child: TextField(
@ -189,8 +191,8 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
keyboardType: TextInputType.number,
decoration: InputDecoration(
errorText: randomSlugLengthError != "" ? "" : null,
border: OutlineInputBorder(),
label: Row(
border: const OutlineInputBorder(),
label: const Row(
children: [
Icon(Icons.tag),
SizedBox(width: 8),
@ -201,10 +203,10 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
))
],
),
SizedBox(height: 16),
const SizedBox(height: 16),
TextField(
controller: titleController,
decoration: InputDecoration(
decoration: const InputDecoration(
border: OutlineInputBorder(),
label: Row(
children: [
@ -215,11 +217,11 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
)
),
),
SizedBox(height: 16),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Crawlable"),
const Text("Crawlable"),
Switch(
value: isCrawlable,
onChanged: (_) {
@ -230,11 +232,11 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
)
],
),
SizedBox(height: 16),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Forward query params"),
const Text("Forward query params"),
Switch(
value: forwardQuery,
onChanged: (_) {
@ -245,11 +247,11 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
)
],
),
SizedBox(height: 16),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Copy to clipboard"),
const Text("Copy to clipboard"),
Switch(
value: copyToClipboard,
onChanged: (_) {
@ -293,7 +295,7 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
}
}
},
child: isSaving ? Padding(padding: EdgeInsets.all(16), child: CircularProgressIndicator(strokeWidth: 3)) : Icon(Icons.save)
child: isSaving ? const Padding(padding: EdgeInsets.all(16), child: CircularProgressIndicator(strokeWidth: 3)) : const Icon(Icons.save)
),
);
}

View File

@ -1,9 +1,9 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
import 'package:intl/intl.dart';
import 'package:shlink_app/API/ServerManager.dart';
import 'globals.dart' as globals;
import 'package:shlink_app/API/server_manager.dart';
import '../globals.dart' as globals;
class URLDetailView extends StatefulWidget {
const URLDetailView({super.key, required this.shortURL});
@ -26,9 +26,9 @@ class _URLDetailViewState extends State<URLDetailView> {
child: ListBody(
children: [
const Text("You're about to delete"),
SizedBox(height: 4),
Text("${widget.shortURL.title ?? widget.shortURL.shortCode}", style: TextStyle(fontStyle: FontStyle.italic),),
SizedBox(height: 4),
const SizedBox(height: 4),
Text(widget.shortURL.title ?? widget.shortURL.shortCode, style: const TextStyle(fontStyle: FontStyle.italic),),
const SizedBox(height: 4),
const Text("It'll be gone forever! (a very long time)")
],
),
@ -43,7 +43,7 @@ class _URLDetailViewState extends State<URLDetailView> {
Navigator.pop(context);
Navigator.pop(context, "reload");
final snackBar = SnackBar(content: Text("Short URL deleted!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
final snackBar = SnackBar(content: const Text("Short URL deleted!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return true;
}, (r) {
@ -60,7 +60,7 @@ class _URLDetailViewState extends State<URLDetailView> {
return false;
});
},
child: Text("Delete", style: TextStyle(color: Colors.red)),
child: const Text("Delete", style: TextStyle(color: Colors.red)),
)
],
);
@ -74,11 +74,11 @@ class _URLDetailViewState extends State<URLDetailView> {
body: CustomScrollView(
slivers: [
SliverAppBar.medium(
title: Text(widget.shortURL.title ?? widget.shortURL.shortCode, style: TextStyle(fontWeight: FontWeight.bold)),
title: Text(widget.shortURL.title ?? widget.shortURL.shortCode, style: const TextStyle(fontWeight: FontWeight.bold)),
actions: [
IconButton(onPressed: () {
showDeletionConfirmation();
}, icon: Icon(Icons.delete, color: Colors.red,))
}, icon: const Icon(Icons.delete, color: Colors.red,))
],
),
SliverToBoxAdapter(
@ -88,9 +88,9 @@ class _URLDetailViewState extends State<URLDetailView> {
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),
padding: const EdgeInsets.only(right: 4, top: 4),
child: Container(
padding: EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: randomColor,
@ -109,11 +109,11 @@ class _URLDetailViewState extends State<URLDetailView> {
_ListCell(title: "Android", content: widget.shortURL.deviceLongUrls.android, sub: true),
_ListCell(title: "Desktop", content: widget.shortURL.deviceLongUrls.desktop, sub: true),
_ListCell(title: "Creation Date", content: widget.shortURL.dateCreated),
_ListCell(title: "Visits", content: ""),
const _ListCell(title: "Visits", content: ""),
_ListCell(title: "Total", content: widget.shortURL.visitsSummary.total, sub: true),
_ListCell(title: "Non-Bots", content: widget.shortURL.visitsSummary.nonBots, sub: true),
_ListCell(title: "Bots", content: widget.shortURL.visitsSummary.bots, sub: true),
_ListCell(title: "Meta", content: ""),
const _ListCell(title: "Meta", content: ""),
_ListCell(title: "Valid Since", content: widget.shortURL.meta.validSince, sub: true),
_ListCell(title: "Valid Until", content: widget.shortURL.meta.validUntil, sub: true),
_ListCell(title: "Max Visits", content: widget.shortURL.meta.maxVisits, sub: true),
@ -146,7 +146,7 @@ class _ListCellState extends State<_ListCell> {
child: Padding(
padding: EdgeInsets.only(top: 16, bottom: widget.last ? 30 : 0),
child: Container(
padding: EdgeInsets.only(top: 16, left: 8, right: 8),
padding: const EdgeInsets.only(top: 16, left: 8, right: 8),
decoration: BoxDecoration(
border: Border(top: BorderSide(width: 1, color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.grey[800]! : Colors.grey[300]!)),
),
@ -157,7 +157,7 @@ class _ListCellState extends State<_ListCell> {
children: [
if (widget.sub)
Padding(
padding: EdgeInsets.only(right: 4),
padding: const EdgeInsets.only(right: 4),
child: SizedBox(
width: 20,
height: 6,
@ -169,7 +169,7 @@ class _ListCellState extends State<_ListCell> {
),
),
),
Text(widget.title, style: TextStyle(fontWeight: FontWeight.bold),)],
Text(widget.title, style: const TextStyle(fontWeight: FontWeight.bold),)],
),
if (widget.content is bool)
Icon(widget.content ? Icons.check : Icons.close, color: widget.content ? Colors.green : Colors.red)

View File

@ -1,11 +1,11 @@
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
import 'package:shlink_app/API/ServerManager.dart';
import 'package:shlink_app/ShortURLEditView.dart';
import 'package:shlink_app/URLDetailView.dart';
import 'globals.dart' as globals;
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
import 'package:shlink_app/API/server_manager.dart';
import 'package:shlink_app/views/short_url_edit_view.dart';
import 'package:shlink_app/views/url_detail_view.dart';
import '../globals.dart' as globals;
import 'package:flutter/services.dart';
class URLListView extends StatefulWidget {
@ -59,10 +59,10 @@ class _URLListViewState extends State<URLListView> {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () async {
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => ShortURLEditView()));
await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
loadAllShortUrls();
},
child: Icon(Icons.add),
child: const Icon(Icons.add),
),
body: Stack(
children: [
@ -74,19 +74,19 @@ class _URLListViewState extends State<URLListView> {
},
child: CustomScrollView(
slivers: [
SliverAppBar.medium(
const SliverAppBar.medium(
title: Text("Short URLs", style: TextStyle(fontWeight: FontWeight.bold))
),
if (shortUrlsLoaded && shortUrls.length == 0)
if (shortUrlsLoaded && shortUrls.isEmpty)
SliverToBoxAdapter(
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 50),
padding: const EdgeInsets.only(top: 50),
child: Column(
children: [
Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
const Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
Padding(
padding: EdgeInsets.only(top: 8),
padding: const EdgeInsets.only(top: 8),
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
)
],
@ -96,7 +96,7 @@ class _URLListViewState extends State<URLListView> {
)
else
SliverList(delegate: SliverChildBuilderDelegate(
(BuildContext _context, int index) {
(BuildContext context, int index) {
final shortURL = shortUrls[index];
return ShortURLCell(shortURL: shortURL, reload: () {
loadAllShortUrls();
@ -181,7 +181,7 @@ class _ShortURLCellState extends State<ShortURLCell> {
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),
padding: const 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]!)),
),
@ -193,16 +193,16 @@ class _ShortURLCellState extends State<ShortURLCell> {
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]),),
Text(widget.shortURL.title ?? widget.shortURL.shortCode, textScaleFactor: 1.4, style: const 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),
padding: const EdgeInsets.only(right: 4, top: 4),
child: Container(
padding: EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: randomColor,
@ -218,12 +218,12 @@ class _ShortURLCellState extends State<ShortURLCell> {
),
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]);
final snackBar = SnackBar(content: const Text("Copied to clipboard!"), behavior: SnackBarBehavior.floating, backgroundColor: Colors.green[400]);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, icon: Icon(Icons.copy)),
}, icon: const Icon(Icons.copy)),
IconButton(onPressed: () {
widget.showQRCode(widget.shortURL.shortUrl);
}, icon: Icon(Icons.qr_code))
}, icon: const Icon(Icons.qr_code))
],
)
),

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 0.9.0+4
version: 0.9.1+5
environment:
sdk: ^2.19.0