mirror of
https://github.com/rainloreley/shlink-manager.git
synced 2024-11-24 02:33:01 +01:00
a ton of refactoring and commenting
This commit is contained in:
parent
de8bce4b5c
commit
7b16683d10
|
@ -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);
|
||||
}
|
|
@ -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"];
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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"];
|
||||
}
|
|
@ -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"];
|
||||
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
|
@ -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"];
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
|
@ -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"];
|
||||
|
||||
}
|
|
@ -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"];
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
|
@ -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("");
|
||||
|
|
|
@ -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
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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()));
|
|
@ -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;
|
|
@ -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();
|
|
@ -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("")
|
||||
),
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
],
|
||||
),
|
|
@ -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))
|
||||
],
|
||||
),
|
||||
)
|
|
@ -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")
|
|
@ -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,),
|
|
@ -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),),],
|
||||
)
|
||||
],
|
||||
)
|
|
@ -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)
|
||||
),
|
||||
);
|
||||
}
|
|
@ -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)
|
|
@ -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))
|
||||
],
|
||||
)
|
||||
),
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user