mirror of
https://github.com/rainloreley/shlink-manager.git
synced 2024-11-21 17:33:03 +01:00
formatting
This commit is contained in:
parent
7b16683d10
commit
086ca47fc0
|
@ -9,7 +9,23 @@
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
|
rules:
|
||||||
|
# Style rules
|
||||||
|
- camel_case_types
|
||||||
|
- library_names
|
||||||
|
- avoid_catching_errors
|
||||||
|
- avoid_empty_else
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- avoid_redundant_argument_values
|
||||||
|
- leading_newlines_in_multiline_strings
|
||||||
|
# formatting
|
||||||
|
- lines_longer_than_80_chars
|
||||||
|
- curly_braces_in_flow_control_structures
|
||||||
|
# doc comments
|
||||||
|
- slash_for_doc_comments
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
# included above or to enable additional rules. A list of all available lints
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
@ -21,7 +37,6 @@ linter:
|
||||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,16 @@ import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
|
||||||
class ShlinkStats {
|
class ShlinkStats {
|
||||||
/// Data about non-orphan visits
|
/// Data about non-orphan visits
|
||||||
VisitsSummary nonOrphanVisits;
|
VisitsSummary nonOrphanVisits;
|
||||||
|
|
||||||
/// Data about orphan visits (without any valid slug assigned)
|
/// Data about orphan visits (without any valid slug assigned)
|
||||||
VisitsSummary orphanVisits;
|
VisitsSummary orphanVisits;
|
||||||
|
|
||||||
/// Total count of all short URLs
|
/// Total count of all short URLs
|
||||||
int shortUrlsCount;
|
int shortUrlsCount;
|
||||||
|
|
||||||
/// Total count all all tags
|
/// Total count all all tags
|
||||||
int tagsCount;
|
int tagsCount;
|
||||||
|
|
||||||
ShlinkStats(this.nonOrphanVisits, this.orphanVisits, this.shortUrlsCount, this.tagsCount);
|
ShlinkStats(this.nonOrphanVisits, this.orphanVisits, this.shortUrlsCount,
|
||||||
|
this.tagsCount);
|
||||||
}
|
}
|
|
@ -2,8 +2,10 @@
|
||||||
class ShlinkStatsVisits {
|
class ShlinkStatsVisits {
|
||||||
/// Count of URL visits
|
/// Count of URL visits
|
||||||
int total;
|
int total;
|
||||||
|
|
||||||
/// Count of URL visits from humans
|
/// Count of URL visits from humans
|
||||||
int nonBots;
|
int nonBots;
|
||||||
|
|
||||||
/// Count of URL visits from bots/crawlers
|
/// Count of URL visits from bots/crawlers
|
||||||
int bots;
|
int bots;
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
class DeviceLongUrls {
|
class DeviceLongUrls {
|
||||||
/// Custom URL for Android devices
|
/// Custom URL for Android devices
|
||||||
final String? android;
|
final String? android;
|
||||||
|
|
||||||
/// Custom URL for iOS devices
|
/// Custom URL for iOS devices
|
||||||
final String? ios;
|
final String? ios;
|
||||||
|
|
||||||
/// Custom URL for desktop
|
/// Custom URL for desktop
|
||||||
final String? desktop;
|
final String? desktop;
|
||||||
|
|
||||||
|
@ -11,14 +13,11 @@ class DeviceLongUrls {
|
||||||
|
|
||||||
/// Converts JSON data from the API to an instance of [DeviceLongUrls]
|
/// Converts JSON data from the API to an instance of [DeviceLongUrls]
|
||||||
DeviceLongUrls.fromJson(Map<String, dynamic> json)
|
DeviceLongUrls.fromJson(Map<String, dynamic> json)
|
||||||
: android = json["android"],
|
: android = json["android"],
|
||||||
ios = json["ios"],
|
ios = json["ios"],
|
||||||
desktop = json["desktop"];
|
desktop = json["desktop"];
|
||||||
|
|
||||||
/// Converts data from this class to an JSON object of type
|
/// Converts data from this class to an JSON object of type
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() =>
|
||||||
"android": android,
|
{"android": android, "ios": ios, "desktop": desktop};
|
||||||
"ios": ios,
|
|
||||||
"desktop": desktop
|
|
||||||
};
|
|
||||||
}
|
}
|
|
@ -6,41 +6,61 @@ import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
|
||||||
class ShortURL {
|
class ShortURL {
|
||||||
/// Slug of the short URL used in the URL
|
/// Slug of the short URL used in the URL
|
||||||
String shortCode;
|
String shortCode;
|
||||||
|
|
||||||
/// Entire short URL
|
/// Entire short URL
|
||||||
String shortUrl;
|
String shortUrl;
|
||||||
|
|
||||||
/// Long URL where the user gets redirected to
|
/// Long URL where the user gets redirected to
|
||||||
String longUrl;
|
String longUrl;
|
||||||
|
|
||||||
/// Device-specific long URLs
|
/// Device-specific long URLs
|
||||||
DeviceLongUrls deviceLongUrls;
|
DeviceLongUrls deviceLongUrls;
|
||||||
|
|
||||||
/// Creation date of the short URL
|
/// Creation date of the short URL
|
||||||
DateTime dateCreated;
|
DateTime dateCreated;
|
||||||
|
|
||||||
/// Visitor data
|
/// Visitor data
|
||||||
VisitsSummary visitsSummary;
|
VisitsSummary visitsSummary;
|
||||||
|
|
||||||
/// List of tags assigned to this short URL
|
/// List of tags assigned to this short URL
|
||||||
List<dynamic> tags;
|
List<dynamic> tags;
|
||||||
|
|
||||||
/// Metadata
|
/// Metadata
|
||||||
ShortURLMeta meta;
|
ShortURLMeta meta;
|
||||||
|
|
||||||
/// Associated domain
|
/// Associated domain
|
||||||
String? domain;
|
String? domain;
|
||||||
|
|
||||||
/// Optional title
|
/// Optional title
|
||||||
String? title;
|
String? title;
|
||||||
|
|
||||||
/// Whether the short URL is crawlable by a web crawler
|
/// Whether the short URL is crawlable by a web crawler
|
||||||
bool crawlable;
|
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(
|
||||||
|
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]
|
/// Converts the JSON data from the API to an instance of [ShortURL]
|
||||||
ShortURL.fromJson(Map<String, dynamic> json):
|
ShortURL.fromJson(Map<String, dynamic> json)
|
||||||
shortCode = json["shortCode"],
|
: shortCode = json["shortCode"],
|
||||||
shortUrl = json["shortUrl"],
|
shortUrl = json["shortUrl"],
|
||||||
longUrl = json["longUrl"],
|
longUrl = json["longUrl"],
|
||||||
deviceLongUrls = DeviceLongUrls.fromJson(json["deviceLongUrls"]),
|
deviceLongUrls = DeviceLongUrls.fromJson(json["deviceLongUrls"]),
|
||||||
dateCreated = DateTime.parse(json["dateCreated"]),
|
dateCreated = DateTime.parse(json["dateCreated"]),
|
||||||
visitsSummary = VisitsSummary.fromJson(json["visitsSummary"]),
|
visitsSummary = VisitsSummary.fromJson(json["visitsSummary"]),
|
||||||
tags = json["tags"],
|
tags = json["tags"],
|
||||||
meta = ShortURLMeta.fromJson(json["meta"]),
|
meta = ShortURLMeta.fromJson(json["meta"]),
|
||||||
domain = json["domain"],
|
domain = json["domain"],
|
||||||
title = json["title"],
|
title = json["title"],
|
||||||
crawlable = json["crawlable"];
|
crawlable = json["crawlable"];
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,16 +2,22 @@
|
||||||
class ShortURLMeta {
|
class ShortURLMeta {
|
||||||
/// The date since when this short URL has been valid
|
/// The date since when this short URL has been valid
|
||||||
DateTime? validSince;
|
DateTime? validSince;
|
||||||
|
|
||||||
/// The data when this short URL expires
|
/// The data when this short URL expires
|
||||||
DateTime? validUntil;
|
DateTime? validUntil;
|
||||||
|
|
||||||
/// Amount of maximum visits allowed to this short URL
|
/// Amount of maximum visits allowed to this short URL
|
||||||
int? maxVisits;
|
int? maxVisits;
|
||||||
|
|
||||||
ShortURLMeta(this.validSince, this.validUntil, this.maxVisits);
|
ShortURLMeta(this.validSince, this.validUntil, this.maxVisits);
|
||||||
|
|
||||||
/// Converts JSON data from the API to an instance of [ShortURLMeta]
|
/// Converts JSON data from the API to an instance of [ShortURLMeta]
|
||||||
ShortURLMeta.fromJson(Map<String, dynamic> json):
|
ShortURLMeta.fromJson(Map<String, dynamic> json)
|
||||||
validSince = json["validSince"] != null ? DateTime.parse(json["validSince"]) : null,
|
: validSince = json["validSince"] != null
|
||||||
validUntil = json["validUntil"] != null ? DateTime.parse(json["validUntil"]) : null,
|
? DateTime.parse(json["validSince"])
|
||||||
maxVisits = json["maxVisits"];
|
: null,
|
||||||
|
validUntil = json["validUntil"] != null
|
||||||
|
? DateTime.parse(json["validUntil"])
|
||||||
|
: null,
|
||||||
|
maxVisits = json["maxVisits"];
|
||||||
}
|
}
|
|
@ -2,16 +2,18 @@
|
||||||
class VisitsSummary {
|
class VisitsSummary {
|
||||||
/// Count of total visits
|
/// Count of total visits
|
||||||
int total;
|
int total;
|
||||||
|
|
||||||
/// Count of visits from humans
|
/// Count of visits from humans
|
||||||
int nonBots;
|
int nonBots;
|
||||||
|
|
||||||
/// Count of visits from bots/crawlers
|
/// Count of visits from bots/crawlers
|
||||||
int bots;
|
int bots;
|
||||||
|
|
||||||
VisitsSummary(this.total, this.nonBots, this.bots);
|
VisitsSummary(this.total, this.nonBots, this.bots);
|
||||||
|
|
||||||
/// Converts JSON data from the API to an instance of [VisitsSummary]
|
/// Converts JSON data from the API to an instance of [VisitsSummary]
|
||||||
VisitsSummary.fromJson(Map<String, dynamic> json):
|
VisitsSummary.fromJson(Map<String, dynamic> json)
|
||||||
total = json["total"] as int,
|
: total = json["total"] as int,
|
||||||
nonBots = json["nonBots"] as int,
|
nonBots = json["nonBots"] as int,
|
||||||
bots = json["bots"] as int;
|
bots = json["bots"] as int;
|
||||||
}
|
}
|
|
@ -4,32 +4,57 @@ import '../ShortURL/device_long_urls.dart';
|
||||||
class ShortURLSubmission {
|
class ShortURLSubmission {
|
||||||
/// Long URL to redirect to
|
/// Long URL to redirect to
|
||||||
String longUrl;
|
String longUrl;
|
||||||
|
|
||||||
/// Device-specific long URLs
|
/// Device-specific long URLs
|
||||||
DeviceLongUrls? deviceLongUrls;
|
DeviceLongUrls? deviceLongUrls;
|
||||||
|
|
||||||
/// Date since when this short URL is valid in ISO8601 format
|
/// Date since when this short URL is valid in ISO8601 format
|
||||||
String? validSince;
|
String? validSince;
|
||||||
|
|
||||||
/// Date until when this short URL is valid in ISO8601 format
|
/// Date until when this short URL is valid in ISO8601 format
|
||||||
String? validUntil;
|
String? validUntil;
|
||||||
|
|
||||||
/// Amount of maximum visits allowed to this short URLs
|
/// Amount of maximum visits allowed to this short URLs
|
||||||
int? maxVisits;
|
int? maxVisits;
|
||||||
|
|
||||||
/// List of tags assigned to this short URL
|
/// List of tags assigned to this short URL
|
||||||
List<String> tags;
|
List<String> tags;
|
||||||
|
|
||||||
/// Title of the page
|
/// Title of the page
|
||||||
String? title;
|
String? title;
|
||||||
|
|
||||||
/// Whether the short URL is crawlable by web crawlers
|
/// Whether the short URL is crawlable by web crawlers
|
||||||
bool crawlable;
|
bool crawlable;
|
||||||
|
|
||||||
/// Whether to forward query parameters
|
/// Whether to forward query parameters
|
||||||
bool forwardQuery;
|
bool forwardQuery;
|
||||||
|
|
||||||
/// Custom slug (if not provided a random one will be generated)
|
/// Custom slug (if not provided a random one will be generated)
|
||||||
String? customSlug;
|
String? customSlug;
|
||||||
|
|
||||||
/// Whether to use an existing short URL if the slug matches
|
/// Whether to use an existing short URL if the slug matches
|
||||||
bool findIfExists;
|
bool findIfExists;
|
||||||
|
|
||||||
/// Domain to use
|
/// Domain to use
|
||||||
String? domain;
|
String? domain;
|
||||||
|
|
||||||
/// Length of the slug if a custom one is not provided
|
/// Length of the slug if a custom one is not provided
|
||||||
int? shortCodeLength;
|
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});
|
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
|
/// Converts class data to a JSON object
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
|
|
@ -5,25 +5,28 @@ import 'package:http/http.dart' as http;
|
||||||
import '../server_manager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
/// Tries to connect to the Shlink server
|
/// Tries to connect to the Shlink server
|
||||||
FutureOr<Either<String, Failure>> apiConnect(String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<String, Failure>> apiConnect(
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
|
final response = await http
|
||||||
|
.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
|
||||||
"X-Api-Key": apiKey ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return left("");
|
return left("");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,26 +5,30 @@ import 'package:http/http.dart' as http;
|
||||||
import '../server_manager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
/// Deletes a short URL from the server
|
/// Deletes a short URL from the server
|
||||||
FutureOr<Either<String, Failure>> apiDeleteShortUrl(String shortCode, String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<String, Failure>> apiDeleteShortUrl(String shortCode,
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.delete(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls/$shortCode"), headers: {
|
final response = await http.delete(
|
||||||
"X-Api-Key": apiKey ?? "",
|
Uri.parse("$serverUrl/rest/v$apiVersion/short-urls/$shortCode"),
|
||||||
});
|
headers: {
|
||||||
|
"X-Api-Key": apiKey ?? "",
|
||||||
|
});
|
||||||
if (response.statusCode == 204) {
|
if (response.statusCode == 204) {
|
||||||
// get returned short url
|
// get returned short url
|
||||||
return left("");
|
return left("");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,29 +6,35 @@ import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
|
||||||
import '../server_manager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
/// Gets recently created short URLs from the server
|
/// Gets recently created short URLs from the server
|
||||||
FutureOr<Either<List<ShortURL>, Failure>> apiGetRecentShortUrls(String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<List<ShortURL>, Failure>> apiGetRecentShortUrls(
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls?itemsPerPage=5&orderBy=dateCreated-DESC"), headers: {
|
final response = await http.get(
|
||||||
"X-Api-Key": apiKey ?? "",
|
Uri.parse(
|
||||||
});
|
"$serverUrl/rest/v$apiVersion/short-urls?itemsPerPage=5&orderBy=dateCreated-DESC"),
|
||||||
|
headers: {
|
||||||
|
"X-Api-Key": apiKey ?? "",
|
||||||
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
||||||
List<ShortURL> shortURLs = (jsonResponse["shortUrls"]["data"] as List<dynamic>).map((e) {
|
List<ShortURL> shortURLs =
|
||||||
|
(jsonResponse["shortUrls"]["data"] as List<dynamic>).map((e) {
|
||||||
return ShortURL.fromJson(e);
|
return ShortURL.fromJson(e);
|
||||||
}).toList();
|
}).toList();
|
||||||
return left(shortURLs);
|
return left(shortURLs);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,26 +5,30 @@ import 'package:http/http.dart' as http;
|
||||||
import '../server_manager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
/// Gets the status of the server and health information
|
/// Gets the status of the server and health information
|
||||||
FutureOr<Either<ServerHealthResponse, Failure>> apiGetServerHealth(String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<ServerHealthResponse, Failure>> apiGetServerHealth(
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/health"), headers: {
|
final response = await http
|
||||||
|
.get(Uri.parse("$serverUrl/rest/v$apiVersion/health"), headers: {
|
||||||
"X-Api-Key": apiKey ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonData = jsonDecode(response.body);
|
var jsonData = jsonDecode(response.body);
|
||||||
return left(ServerHealthResponse(status: jsonData["status"], version: jsonData["version"]));
|
return left(ServerHealthResponse(
|
||||||
}
|
status: jsonData["status"], version: jsonData["version"]));
|
||||||
else {
|
} else {
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,8 +7,8 @@ import '../Classes/ShlinkStats/shlink_stats.dart';
|
||||||
import '../server_manager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
/// Gets statistics about the Shlink server
|
/// Gets statistics about the Shlink server
|
||||||
FutureOr<Either<ShlinkStats, Failure>> apiGetShlinkStats(String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<ShlinkStats, Failure>> apiGetShlinkStats(
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
var nonOrphanVisits;
|
var nonOrphanVisits;
|
||||||
var orphanVisits;
|
var orphanVisits;
|
||||||
var shortUrlsCount;
|
var shortUrlsCount;
|
||||||
|
@ -24,7 +24,8 @@ FutureOr<Either<ShlinkStats, Failure>> apiGetShlinkStats(String? apiKey, String?
|
||||||
return right(r);
|
return right(r);
|
||||||
});
|
});
|
||||||
|
|
||||||
var shortUrlsCountResponse = await _getShortUrlsCount(apiKey, serverUrl, apiVersion);
|
var shortUrlsCountResponse =
|
||||||
|
await _getShortUrlsCount(apiKey, serverUrl, apiVersion);
|
||||||
shortUrlsCountResponse.fold((l) {
|
shortUrlsCountResponse.fold((l) {
|
||||||
shortUrlsCount = l;
|
shortUrlsCount = l;
|
||||||
}, (r) {
|
}, (r) {
|
||||||
|
@ -40,14 +41,15 @@ FutureOr<Either<ShlinkStats, Failure>> apiGetShlinkStats(String? apiKey, String?
|
||||||
return right(r);
|
return right(r);
|
||||||
});
|
});
|
||||||
|
|
||||||
while(failure == null && (nonOrphanVisits == null || orphanVisits == null || shortUrlsCount == null || tagsCount == null)) {
|
while (failure == null && (orphanVisits == null)) {
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failure != null) {
|
if (failure != null) {
|
||||||
return right(failure);
|
return right(failure);
|
||||||
}
|
}
|
||||||
return left(ShlinkStats(nonOrphanVisits, orphanVisits, shortUrlsCount, tagsCount));
|
return left(
|
||||||
|
ShlinkStats(nonOrphanVisits, orphanVisits, shortUrlsCount, tagsCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ShlinkVisitStats {
|
class _ShlinkVisitStats {
|
||||||
|
@ -58,79 +60,89 @@ class _ShlinkVisitStats {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets visitor statistics about the entire server
|
/// Gets visitor statistics about the entire server
|
||||||
FutureOr<Either<_ShlinkVisitStats, Failure>> _getVisitStats(String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<_ShlinkVisitStats, Failure>> _getVisitStats(
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/visits"), headers: {
|
final response = await http
|
||||||
|
.get(Uri.parse("$serverUrl/rest/v$apiVersion/visits"), headers: {
|
||||||
"X-Api-Key": apiKey ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
||||||
var nonOrphanVisits = VisitsSummary.fromJson(jsonResponse["visits"]["nonOrphanVisits"]);
|
var nonOrphanVisits =
|
||||||
var orphanVisits = VisitsSummary.fromJson(jsonResponse["visits"]["orphanVisits"]);
|
VisitsSummary.fromJson(jsonResponse["visits"]["nonOrphanVisits"]);
|
||||||
|
var orphanVisits =
|
||||||
|
VisitsSummary.fromJson(jsonResponse["visits"]["orphanVisits"]);
|
||||||
return left(_ShlinkVisitStats(nonOrphanVisits, orphanVisits));
|
return left(_ShlinkVisitStats(nonOrphanVisits, orphanVisits));
|
||||||
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets amount of short URLs
|
/// Gets amount of short URLs
|
||||||
FutureOr<Either<int, Failure>> _getShortUrlsCount(String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<int, Failure>> _getShortUrlsCount(
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
|
final response = await http
|
||||||
|
.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
|
||||||
"X-Api-Key": apiKey ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
||||||
return left(jsonResponse["shortUrls"]["pagination"]["totalItems"]);
|
return left(jsonResponse["shortUrls"]["pagination"]["totalItems"]);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets amount of tags
|
/// Gets amount of tags
|
||||||
FutureOr<Either<int, Failure>> _getTagsCount(String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<int, Failure>> _getTagsCount(
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/tags"), headers: {
|
final response = await http
|
||||||
|
.get(Uri.parse("$serverUrl/rest/v$apiVersion/tags"), headers: {
|
||||||
"X-Api-Key": apiKey ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
||||||
return left(jsonResponse["tags"]["pagination"]["totalItems"]);
|
return left(jsonResponse["tags"]["pagination"]["totalItems"]);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,7 +6,8 @@ import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
|
||||||
import '../server_manager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
/// Gets all short URLs
|
/// Gets all short URLs
|
||||||
FutureOr<Either<List<ShortURL>, Failure>> apiGetShortUrls(String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<List<ShortURL>, Failure>> apiGetShortUrls(
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
var currentPage = 1;
|
var currentPage = 1;
|
||||||
var maxPages = 2;
|
var maxPages = 2;
|
||||||
List<ShortURL> allUrls = [];
|
List<ShortURL> allUrls = [];
|
||||||
|
@ -14,7 +15,8 @@ FutureOr<Either<List<ShortURL>, Failure>> apiGetShortUrls(String? apiKey, String
|
||||||
Failure? error;
|
Failure? error;
|
||||||
|
|
||||||
while (currentPage <= maxPages) {
|
while (currentPage <= maxPages) {
|
||||||
final response = await _getShortUrlPage(currentPage, apiKey, serverUrl, apiVersion);
|
final response =
|
||||||
|
await _getShortUrlPage(currentPage, apiKey, serverUrl, apiVersion);
|
||||||
response.fold((l) {
|
response.fold((l) {
|
||||||
allUrls.addAll(l.urls);
|
allUrls.addAll(l.urls);
|
||||||
maxPages = l.totalPages;
|
maxPages = l.totalPages;
|
||||||
|
@ -26,37 +28,42 @@ FutureOr<Either<List<ShortURL>, Failure>> apiGetShortUrls(String? apiKey, String
|
||||||
}
|
}
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
return left(allUrls);
|
return left(allUrls);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return right(error!);
|
return right(error!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets all short URLs from a specific page
|
/// Gets all short URLs from a specific page
|
||||||
FutureOr<Either<ShortURLPageResponse, Failure>> _getShortUrlPage(int page, String? apiKey, String? serverUrl, String apiVersion) async {
|
FutureOr<Either<ShortURLPageResponse, Failure>> _getShortUrlPage(
|
||||||
|
int page, String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls?page=$page"), headers: {
|
final response = await http.get(
|
||||||
"X-Api-Key": apiKey ?? "",
|
Uri.parse("$serverUrl/rest/v$apiVersion/short-urls?page=$page"),
|
||||||
});
|
headers: {
|
||||||
|
"X-Api-Key": apiKey ?? "",
|
||||||
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
||||||
var pagesCount = jsonResponse["shortUrls"]["pagination"]["pagesCount"] as int;
|
var pagesCount =
|
||||||
List<ShortURL> shortURLs = (jsonResponse["shortUrls"]["data"] as List<dynamic>).map((e) {
|
jsonResponse["shortUrls"]["pagination"]["pagesCount"] as int;
|
||||||
|
List<ShortURL> shortURLs =
|
||||||
|
(jsonResponse["shortUrls"]["data"] as List<dynamic>).map((e) {
|
||||||
return ShortURL.fromJson(e);
|
return ShortURL.fromJson(e);
|
||||||
}).toList();
|
}).toList();
|
||||||
return left(ShortURLPageResponse(shortURLs, pagesCount));
|
return left(ShortURLPageResponse(shortURLs, pagesCount));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,27 +6,33 @@ import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.d
|
||||||
import '../server_manager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
/// Submits a short URL to a server for it to be added
|
/// 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 {
|
FutureOr<Either<String, Failure>> apiSubmitShortUrl(ShortURLSubmission shortUrl,
|
||||||
|
String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.post(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
|
final response =
|
||||||
"X-Api-Key": apiKey ?? "",
|
await http.post(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"),
|
||||||
}, body: jsonEncode(shortUrl.toJson()));
|
headers: {
|
||||||
|
"X-Api-Key": apiKey ?? "",
|
||||||
|
},
|
||||||
|
body: jsonEncode(shortUrl.toJson()));
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// get returned short url
|
// get returned short url
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return left(jsonBody["shortUrl"]);
|
return left(jsonBody["shortUrl"]);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
try {
|
try {
|
||||||
var jsonBody = jsonDecode(response.body);
|
var jsonBody = jsonDecode(response.body);
|
||||||
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"], invalidElements: jsonBody["invalidElements"]));
|
return right(ApiFailure(
|
||||||
}
|
type: jsonBody["type"],
|
||||||
catch(resErr) {
|
detail: jsonBody["detail"],
|
||||||
|
title: jsonBody["title"],
|
||||||
|
status: jsonBody["status"],
|
||||||
|
invalidElements: jsonBody["invalidElements"]));
|
||||||
|
} catch (resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (reqErr) {
|
||||||
catch(reqErr) {
|
|
||||||
return right(RequestFailure(0, reqErr.toString()));
|
return right(RequestFailure(0, reqErr.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,6 @@ import 'Methods/delete_short_url.dart';
|
||||||
import 'Methods/submit_short_url.dart';
|
import 'Methods/submit_short_url.dart';
|
||||||
|
|
||||||
class ServerManager {
|
class ServerManager {
|
||||||
|
|
||||||
/// The URL of the Shlink server
|
/// The URL of the Shlink server
|
||||||
String? serverUrl;
|
String? serverUrl;
|
||||||
|
|
||||||
|
@ -69,9 +68,9 @@ class ServerManager {
|
||||||
storage.write(key: "shlink_apikey", value: apiKey);
|
storage.write(key: "shlink_apikey", value: apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Saves provided server credentials and tries to establish a connection
|
/// Saves provided server credentials and tries to establish a connection
|
||||||
FutureOr<Either<String, Failure>> initAndConnect(String url, String apiKey) async {
|
FutureOr<Either<String, Failure>> initAndConnect(
|
||||||
|
String url, String apiKey) async {
|
||||||
// TODO: convert url to correct format
|
// TODO: convert url to correct format
|
||||||
serverUrl = url;
|
serverUrl = url;
|
||||||
this.apiKey = apiKey;
|
this.apiKey = apiKey;
|
||||||
|
@ -100,7 +99,8 @@ class ServerManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves a new short URL to the server
|
/// Saves a new short URL to the server
|
||||||
FutureOr<Either<String, Failure>> submitShortUrl(ShortURLSubmission shortUrl) async {
|
FutureOr<Either<String, Failure>> submitShortUrl(
|
||||||
|
ShortURLSubmission shortUrl) async {
|
||||||
return apiSubmitShortUrl(shortUrl, apiKey, serverUrl, apiVersion);
|
return apiSubmitShortUrl(shortUrl, apiKey, serverUrl, apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +139,8 @@ class ServerHealthResponse {
|
||||||
/// Failure class, used for the API
|
/// Failure class, used for the API
|
||||||
abstract class Failure {}
|
abstract class Failure {}
|
||||||
|
|
||||||
/// Used when a request to a server fails (due to networking issues or an unexpected response)
|
/// Used when a request to a server fails
|
||||||
|
/// (due to networking issues or an unexpected response)
|
||||||
class RequestFailure extends Failure {
|
class RequestFailure extends Failure {
|
||||||
int statusCode;
|
int statusCode;
|
||||||
String description;
|
String description;
|
||||||
|
@ -155,5 +156,10 @@ class ApiFailure extends Failure {
|
||||||
int status;
|
int status;
|
||||||
List<dynamic>? invalidElements;
|
List<dynamic>? invalidElements;
|
||||||
|
|
||||||
ApiFailure({required this.type, required this.detail, required this.title, required this.status, this.invalidElements});
|
ApiFailure(
|
||||||
|
{required this.type,
|
||||||
|
required this.detail,
|
||||||
|
required this.title,
|
||||||
|
required this.status,
|
||||||
|
this.invalidElements});
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
library dev.abmgrt.shlink_app.globals;
|
library dev.abmgrt.shlink_app.globals;
|
||||||
|
|
||||||
import 'package:shlink_app/API/server_manager.dart';
|
import 'package:shlink_app/API/server_manager.dart';
|
||||||
|
|
||||||
ServerManager serverManager = ServerManager();
|
ServerManager serverManager = ServerManager();
|
|
@ -11,11 +11,11 @@ void main() {
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
static const _defaultLightColorScheme =
|
static const _defaultLightColorScheme = ColorScheme
|
||||||
ColorScheme.light();//.fromSwatch(primarySwatch: Colors.blue, backgroundColor: Colors.white);
|
.light(); //.fromSwatch(primarySwatch: Colors.blue, backgroundColor: Colors.white);
|
||||||
|
|
||||||
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(
|
static final _defaultDarkColorScheme =
|
||||||
primarySwatch: Colors.blue, brightness: Brightness.dark);
|
ColorScheme.fromSwatch(brightness: Brightness.dark);
|
||||||
|
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
@override
|
@override
|
||||||
|
@ -25,24 +25,22 @@ class MyApp extends StatelessWidget {
|
||||||
title: 'Shlink',
|
title: 'Shlink',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
appBarTheme: const AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color(0xfffafafa),
|
backgroundColor: Color(0xfffafafa),
|
||||||
),
|
),
|
||||||
colorScheme: lightColorScheme ?? _defaultLightColorScheme,
|
colorScheme: lightColorScheme ?? _defaultLightColorScheme,
|
||||||
useMaterial3: true
|
useMaterial3: true),
|
||||||
),
|
|
||||||
darkTheme: ThemeData(
|
darkTheme: ThemeData(
|
||||||
appBarTheme: const AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color(0xff0d0d0d),
|
backgroundColor: Color(0xff0d0d0d),
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
colorScheme: darkColorScheme?.copyWith(background: Colors.black) ?? _defaultDarkColorScheme,
|
colorScheme: darkColorScheme?.copyWith(background: Colors.black) ??
|
||||||
|
_defaultDarkColorScheme,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
themeMode: ThemeMode.system,
|
home: const InitialPage());
|
||||||
home: const InitialPage()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +53,6 @@ class InitialPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InitialPageState extends State<InitialPage> {
|
class _InitialPageState extends State<InitialPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -63,26 +60,20 @@ class _InitialPageState extends State<InitialPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkLogin() async {
|
void checkLogin() async {
|
||||||
|
|
||||||
bool result = await globals.serverManager.checkLogin();
|
bool result = await globals.serverManager.checkLogin();
|
||||||
if (result) {
|
if (result) {
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(builder: (context) => const NavigationBarView())
|
MaterialPageRoute(builder: (context) => const NavigationBarView()));
|
||||||
);
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(builder: (context) => const LoginView())
|
MaterialPageRoute(builder: (context) => const LoginView()));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Scaffold(
|
return const Scaffold(
|
||||||
body: Center(
|
body: Center(child: Text("")),
|
||||||
child: Text("")
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,8 @@ class LicenseUtil {
|
||||||
return [
|
return [
|
||||||
const License(
|
const License(
|
||||||
name: r'cupertino_icons',
|
name: r'cupertino_icons',
|
||||||
license: r'''The MIT License (MIT)
|
license: r'''
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Vladimir Kharlampidi
|
Copyright (c) 2016 Vladimir Kharlampidi
|
||||||
|
|
||||||
|
@ -49,12 +50,13 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''',
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''',
|
||||||
version: r'^1.0.5',
|
version: r'^1.0.5',
|
||||||
homepage: null,
|
repository:
|
||||||
repository: r'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons',
|
r'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'dartz',
|
name: r'dartz',
|
||||||
license: r'''The MIT License (MIT)
|
license: r'''
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016, 2017, 2018, 2019, 2020, 2021 Björn Sperber
|
Copyright (c) 2016, 2017, 2018, 2019, 2020, 2021 Björn Sperber
|
||||||
|
|
||||||
|
@ -78,11 +80,11 @@ SOFTWARE.
|
||||||
''',
|
''',
|
||||||
version: r'^0.10.1',
|
version: r'^0.10.1',
|
||||||
homepage: r'https://github.com/spebbe/dartz',
|
homepage: r'https://github.com/spebbe/dartz',
|
||||||
repository: null,
|
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'dynamic_color',
|
name: r'dynamic_color',
|
||||||
license: r''' Apache License
|
license: r'''
|
||||||
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
@ -285,12 +287,13 @@ SOFTWARE.
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
''',
|
''',
|
||||||
version: r'^1.6.6',
|
version: r'^1.6.6',
|
||||||
homepage: null,
|
repository:
|
||||||
repository: r'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color',
|
r'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'flutter',
|
name: r'flutter',
|
||||||
license: r'''Copyright 2014 The Flutter Authors. All rights reserved.
|
license: r'''
|
||||||
|
Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
are permitted provided that the following conditions are met:
|
are permitted provided that the following conditions are met:
|
||||||
|
@ -316,13 +319,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: null,
|
|
||||||
homepage: r'https://flutter.dev/',
|
homepage: r'https://flutter.dev/',
|
||||||
repository: r'https://github.com/flutter/flutter',
|
repository: r'https://github.com/flutter/flutter',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'flutter_launcher_icons',
|
name: r'flutter_launcher_icons',
|
||||||
license: r'''MIT License
|
license: r'''
|
||||||
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 Mark O'Sullivan
|
Copyright (c) 2019 Mark O'Sullivan
|
||||||
|
|
||||||
|
@ -346,11 +349,13 @@ SOFTWARE.
|
||||||
''',
|
''',
|
||||||
version: r'0.13.1',
|
version: r'0.13.1',
|
||||||
homepage: r'https://github.com/fluttercommunity/flutter_launcher_icons',
|
homepage: r'https://github.com/fluttercommunity/flutter_launcher_icons',
|
||||||
repository: r'https://github.com/fluttercommunity/flutter_launcher_icons/',
|
repository:
|
||||||
|
r'https://github.com/fluttercommunity/flutter_launcher_icons/',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'flutter_lints',
|
name: r'flutter_lints',
|
||||||
license: r'''Copyright 2013 The Flutter Authors. All rights reserved.
|
license: r'''
|
||||||
|
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
are permitted provided that the following conditions are met:
|
are permitted provided that the following conditions are met:
|
||||||
|
@ -377,12 +382,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: r'^2.0.2',
|
version: r'^2.0.2',
|
||||||
homepage: null,
|
repository:
|
||||||
repository: r'https://github.com/flutter/packages/tree/main/packages/flutter_lints',
|
r'https://github.com/flutter/packages/tree/main/packages/flutter_lints',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'flutter_process_text',
|
name: r'flutter_process_text',
|
||||||
license: r'''BSD 3-Clause License
|
license: r'''
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
(c) Copyright 2021 divshekhar (Divyanshu Shekhar)
|
(c) Copyright 2021 divshekhar (Divyanshu Shekhar)
|
||||||
|
|
||||||
|
@ -410,12 +416,12 @@ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI
|
||||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
version: r'^1.1.2',
|
version: r'^1.1.2',
|
||||||
homepage: null,
|
|
||||||
repository: r'https://github.com/DevsOnFlutter/flutter_process_text',
|
repository: r'https://github.com/DevsOnFlutter/flutter_process_text',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'flutter_secure_storage',
|
name: r'flutter_secure_storage',
|
||||||
license: r'''BSD 3-Clause License
|
license: r'''
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright 2017 German Saprykin
|
Copyright 2017 German Saprykin
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
@ -445,12 +451,13 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
version: r'^8.0.0',
|
version: r'^8.0.0',
|
||||||
homepage: null,
|
repository:
|
||||||
repository: r'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage',
|
r'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'flutter_test',
|
name: r'flutter_test',
|
||||||
license: r'''Copyright 2014 The Flutter Authors. All rights reserved.
|
license: r'''
|
||||||
|
Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
are permitted provided that the following conditions are met:
|
are permitted provided that the following conditions are met:
|
||||||
|
@ -476,13 +483,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: null,
|
|
||||||
homepage: r'https://flutter.dev/',
|
homepage: r'https://flutter.dev/',
|
||||||
repository: r'https://github.com/flutter/flutter',
|
repository: r'https://github.com/flutter/flutter',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'http',
|
name: r'http',
|
||||||
license: r'''Copyright 2014, the Dart project authors.
|
license: r'''
|
||||||
|
Copyright 2014, the Dart project authors.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
@ -511,12 +518,12 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: r'^0.13.6',
|
version: r'^0.13.6',
|
||||||
homepage: null,
|
|
||||||
repository: r'https://github.com/dart-lang/http/tree/master/pkgs/http',
|
repository: r'https://github.com/dart-lang/http/tree/master/pkgs/http',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'intl',
|
name: r'intl',
|
||||||
license: r'''Copyright 2013, the Dart project authors.
|
license: r'''
|
||||||
|
Copyright 2013, the Dart project authors.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
@ -545,12 +552,12 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: r'^0.18.1',
|
version: r'^0.18.1',
|
||||||
homepage: null,
|
|
||||||
repository: r'https://github.com/dart-lang/i18n/tree/main/pkgs/intl',
|
repository: r'https://github.com/dart-lang/i18n/tree/main/pkgs/intl',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'license_generator',
|
name: r'license_generator',
|
||||||
license: r'''MIT License
|
license: r'''
|
||||||
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 icapps
|
Copyright (c) 2022 icapps
|
||||||
|
|
||||||
|
@ -574,11 +581,11 @@ SOFTWARE.
|
||||||
''',
|
''',
|
||||||
version: r'^1.0.5',
|
version: r'^1.0.5',
|
||||||
homepage: r'https://github.com/icapps/flutter-icapps-license',
|
homepage: r'https://github.com/icapps/flutter-icapps-license',
|
||||||
repository: null,
|
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'package_info_plus',
|
name: r'package_info_plus',
|
||||||
license: r'''Copyright 2017 The Chromium Authors. All rights reserved.
|
license: r'''
|
||||||
|
Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
@ -608,11 +615,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: r'^4.0.2',
|
version: r'^4.0.2',
|
||||||
homepage: r'https://plus.fluttercommunity.dev/',
|
homepage: r'https://plus.fluttercommunity.dev/',
|
||||||
repository: r'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/',
|
repository:
|
||||||
|
r'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'qr_flutter',
|
name: r'qr_flutter',
|
||||||
license: r'''BSD 3-Clause License
|
license: r'''
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2020, Luke Freeman.
|
Copyright (c) 2020, Luke Freeman.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
@ -644,11 +653,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: r'^4.1.0',
|
version: r'^4.1.0',
|
||||||
homepage: r'https://github.com/theyakka/qr.flutter',
|
homepage: r'https://github.com/theyakka/qr.flutter',
|
||||||
repository: null,
|
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'shared_preferences',
|
name: r'shared_preferences',
|
||||||
license: r'''Copyright 2013 The Flutter Authors. All rights reserved.
|
license: r'''
|
||||||
|
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
are permitted provided that the following conditions are met:
|
are permitted provided that the following conditions are met:
|
||||||
|
@ -675,12 +684,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: r'^2.2.2',
|
version: r'^2.2.2',
|
||||||
homepage: null,
|
repository:
|
||||||
repository: r'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences',
|
r'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'tuple',
|
name: r'tuple',
|
||||||
license: r'''Copyright (c) 2014, the tuple project authors.
|
license: r'''
|
||||||
|
Copyright (c) 2014, the tuple project authors.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
@ -703,12 +713,12 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
version: r'^2.0.2',
|
version: r'^2.0.2',
|
||||||
homepage: null,
|
|
||||||
repository: r'https://github.com/google/tuple.dart',
|
repository: r'https://github.com/google/tuple.dart',
|
||||||
),
|
),
|
||||||
const License(
|
const License(
|
||||||
name: r'url_launcher',
|
name: r'url_launcher',
|
||||||
license: r'''Copyright 2013 The Flutter Authors. All rights reserved.
|
license: r'''
|
||||||
|
Copyright 2013 The Flutter Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
are permitted provided that the following conditions are met:
|
are permitted provided that the following conditions are met:
|
||||||
|
@ -735,8 +745,8 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
''',
|
''',
|
||||||
version: r'6.1.9',
|
version: r'6.1.9',
|
||||||
homepage: null,
|
repository:
|
||||||
repository: r'https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher',
|
r'https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,13 @@ import '../API/Classes/ShortURL/short_url.dart';
|
||||||
import '../globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
|
|
||||||
class HomeView extends StatefulWidget {
|
class HomeView extends StatefulWidget {
|
||||||
const HomeView({Key? key}) : super(key: key);
|
const HomeView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<HomeView> createState() => _HomeViewState();
|
State<HomeView> createState() => _HomeViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeViewState extends State<HomeView> {
|
class _HomeViewState extends State<HomeView> {
|
||||||
|
|
||||||
ShlinkStats? shlinkStats;
|
ShlinkStats? shlinkStats;
|
||||||
|
|
||||||
List<ShortURL> shortUrls = [];
|
List<ShortURL> shortUrls = [];
|
||||||
|
@ -27,9 +26,8 @@ class _HomeViewState extends State<HomeView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
// TODO: implement initState
|
// TODO: implement initState
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
.addPostFrameCallback((_) {
|
loadAllData();
|
||||||
loadAllData();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,12 +47,14 @@ class _HomeViewState extends State<HomeView> {
|
||||||
var text = "";
|
var text = "";
|
||||||
if (r is RequestFailure) {
|
if (r is RequestFailure) {
|
||||||
text = r.description;
|
text = r.description;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
text = (r as ApiFailure).detail;
|
text = (r as ApiFailure).detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
final snackBar = SnackBar(content: Text(text), backgroundColor: Colors.red[400], behavior: SnackBarBehavior.floating);
|
final snackBar = SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: Colors.red[400],
|
||||||
|
behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -70,12 +70,14 @@ class _HomeViewState extends State<HomeView> {
|
||||||
var text = "";
|
var text = "";
|
||||||
if (r is RequestFailure) {
|
if (r is RequestFailure) {
|
||||||
text = r.description;
|
text = r.description;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
text = (r as ApiFailure).detail;
|
text = (r as ApiFailure).detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
final snackBar = SnackBar(content: Text(text), backgroundColor: Colors.red[400], behavior: SnackBarBehavior.floating);
|
final snackBar = SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: Colors.red[400],
|
||||||
|
behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -83,134 +85,171 @@ class _HomeViewState extends State<HomeView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
ColorFiltered(
|
ColorFiltered(
|
||||||
colorFilter: ColorFilter.mode(Colors.black.withOpacity(_qrCodeShown ? 0.4 : 0), BlendMode.srcOver),
|
colorFilter: ColorFilter.mode(
|
||||||
child: RefreshIndicator(
|
Colors.black.withOpacity(_qrCodeShown ? 0.4 : 0),
|
||||||
onRefresh: () async {
|
BlendMode.srcOver),
|
||||||
return loadAllData();
|
child: RefreshIndicator(
|
||||||
},
|
onRefresh: () async {
|
||||||
child: CustomScrollView(
|
return loadAllData();
|
||||||
slivers: [
|
},
|
||||||
SliverAppBar.medium(
|
child: CustomScrollView(
|
||||||
expandedHeight: 160,
|
slivers: [
|
||||||
title: Column(
|
SliverAppBar.medium(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
expandedHeight: 160,
|
||||||
children: [
|
title: Column(
|
||||||
const Text("Shlink", style: TextStyle(fontWeight: FontWeight.bold)),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(globals.serverManager.getServerUrl(), style: TextStyle(fontSize: 16, color: Colors.grey[600]))
|
children: [
|
||||||
],
|
const Text("Shlink",
|
||||||
)
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
),
|
Text(globals.serverManager.getServerUrl(),
|
||||||
SliverToBoxAdapter(
|
style: TextStyle(
|
||||||
child: Wrap(
|
fontSize: 16, color: Colors.grey[600]))
|
||||||
alignment: WrapAlignment.spaceEvenly,
|
],
|
||||||
children: [
|
)),
|
||||||
_ShlinkStatsCardWidget(icon: Icons.link, text: "${shlinkStats?.shortUrlsCount.toString() ?? "0"} Short URLs", borderColor: Colors.blue),
|
|
||||||
_ShlinkStatsCardWidget(icon: Icons.remove_red_eye, text: "${shlinkStats?.nonOrphanVisits.total ?? "0"} Visits", borderColor: Colors.green),
|
|
||||||
_ShlinkStatsCardWidget(icon: Icons.warning, text: "${shlinkStats?.orphanVisits.total ?? "0"} Orphan Visits", borderColor: Colors.red),
|
|
||||||
_ShlinkStatsCardWidget(icon: Icons.sell, text: "${shlinkStats?.tagsCount.toString() ?? "0"} Tags", borderColor: Colors.purple),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (shortUrlsLoaded && shortUrls.isEmpty)
|
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Center(
|
child: Wrap(
|
||||||
child: Padding(
|
alignment: WrapAlignment.spaceEvenly,
|
||||||
padding: const EdgeInsets.only(top: 50),
|
children: [
|
||||||
child: Column(
|
_ShlinkStatsCardWidget(
|
||||||
children: [
|
icon: Icons.link,
|
||||||
const Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
|
text:
|
||||||
Padding(
|
"${shlinkStats?.shortUrlsCount.toString() ?? "0"} Short URLs",
|
||||||
padding: const EdgeInsets.only(top: 8),
|
borderColor: Colors.blue),
|
||||||
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
|
_ShlinkStatsCardWidget(
|
||||||
)
|
icon: Icons.remove_red_eye,
|
||||||
],
|
text:
|
||||||
)
|
"${shlinkStats?.nonOrphanVisits.total ?? "0"} Visits",
|
||||||
)
|
borderColor: Colors.green),
|
||||||
)
|
_ShlinkStatsCardWidget(
|
||||||
)
|
icon: Icons.warning,
|
||||||
else
|
text:
|
||||||
SliverList(delegate: SliverChildBuilderDelegate(
|
"${shlinkStats?.orphanVisits.total ?? "0"} Orphan Visits",
|
||||||
(BuildContext context, int index) {
|
borderColor: Colors.red),
|
||||||
if (index == 0) {
|
_ShlinkStatsCardWidget(
|
||||||
return const Padding(
|
icon: Icons.sell,
|
||||||
padding: EdgeInsets.only(top: 16, left: 12, right: 12),
|
text:
|
||||||
child: Text("Recent Short URLs", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
"${shlinkStats?.tagsCount.toString() ?? "0"} Tags",
|
||||||
);
|
borderColor: Colors.purple),
|
||||||
}
|
],
|
||||||
else {
|
),
|
||||||
final shortURL = shortUrls[index - 1];
|
),
|
||||||
return ShortURLCell(shortURL: shortURL, reload: () {
|
if (shortUrlsLoaded && shortUrls.isEmpty)
|
||||||
loadRecentShortUrls();
|
SliverToBoxAdapter(
|
||||||
}, showQRCode: (String url) {
|
child: Center(
|
||||||
setState(() {
|
child: Padding(
|
||||||
_qrUrl = url;
|
padding: const EdgeInsets.only(top: 50),
|
||||||
_qrCodeShown = true;
|
child: Column(
|
||||||
});
|
children: [
|
||||||
}, isLast: index == shortUrls.length);
|
const Text(
|
||||||
}
|
"No Short URLs",
|
||||||
},
|
style: TextStyle(
|
||||||
childCount: shortUrls.length + 1
|
fontSize: 24,
|
||||||
))
|
fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
],
|
Padding(
|
||||||
),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
),
|
child: Text(
|
||||||
),
|
'Create one by tapping the "+" button below',
|
||||||
if (_qrCodeShown)
|
style: TextStyle(
|
||||||
GestureDetector(
|
fontSize: 16,
|
||||||
onTap: () {
|
color: Colors.grey[600]),
|
||||||
setState(() {
|
),
|
||||||
_qrCodeShown = false;
|
)
|
||||||
});
|
],
|
||||||
},
|
))))
|
||||||
child: Container(
|
else
|
||||||
color: Colors.black.withOpacity(0),
|
SliverList(
|
||||||
),
|
delegate: SliverChildBuilderDelegate(
|
||||||
),
|
(BuildContext context, int index) {
|
||||||
if (_qrCodeShown)
|
if (index == 0) {
|
||||||
Center(
|
return const Padding(
|
||||||
child: SizedBox(
|
padding:
|
||||||
width: MediaQuery.of(context).size.width / 1.7,
|
EdgeInsets.only(top: 16, left: 12, right: 12),
|
||||||
height: MediaQuery.of(context).size.width / 1.7,
|
child: Text("Recent Short URLs",
|
||||||
child: Card(
|
style: TextStyle(
|
||||||
child: Padding(
|
fontSize: 20, fontWeight: FontWeight.bold)),
|
||||||
padding: const EdgeInsets.all(16),
|
);
|
||||||
child: QrImageView(
|
} else {
|
||||||
data: _qrUrl,
|
final shortURL = shortUrls[index - 1];
|
||||||
version: QrVersions.auto,
|
return ShortURLCell(
|
||||||
size: 200.0,
|
shortURL: shortURL,
|
||||||
eyeStyle: QrEyeStyle(
|
reload: () {
|
||||||
eyeShape: QrEyeShape.square,
|
loadRecentShortUrls();
|
||||||
color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.white : Colors.black,
|
},
|
||||||
),
|
showQRCode: (String url) {
|
||||||
dataModuleStyle: QrDataModuleStyle(
|
setState(() {
|
||||||
dataModuleShape: QrDataModuleShape.square,
|
_qrUrl = url;
|
||||||
color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.white : Colors.black,
|
_qrCodeShown = true;
|
||||||
),
|
});
|
||||||
)
|
},
|
||||||
)
|
isLast: index == shortUrls.length);
|
||||||
|
}
|
||||||
|
}, childCount: shortUrls.length + 1))
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
if (_qrCodeShown)
|
||||||
),
|
GestureDetector(
|
||||||
floatingActionButton: FloatingActionButton(
|
onTap: () {
|
||||||
onPressed: () async {
|
setState(() {
|
||||||
await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
|
_qrCodeShown = false;
|
||||||
loadRecentShortUrls();
|
});
|
||||||
},
|
},
|
||||||
child: const Icon(Icons.add),
|
child: Container(
|
||||||
)
|
color: Colors.black.withOpacity(0),
|
||||||
);
|
),
|
||||||
|
),
|
||||||
|
if (_qrCodeShown)
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width / 1.7,
|
||||||
|
height: MediaQuery.of(context).size.width / 1.7,
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: QrImageView(
|
||||||
|
data: _qrUrl,
|
||||||
|
size: 200.0,
|
||||||
|
eyeStyle: QrEyeStyle(
|
||||||
|
eyeShape: QrEyeShape.square,
|
||||||
|
color:
|
||||||
|
MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.dark
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
dataModuleStyle: QrDataModuleStyle(
|
||||||
|
dataModuleShape: QrDataModuleShape.square,
|
||||||
|
color:
|
||||||
|
MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.dark
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) => const ShortURLEditView()));
|
||||||
|
loadRecentShortUrls();
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stats card widget
|
// stats card widget
|
||||||
class _ShlinkStatsCardWidget extends StatefulWidget {
|
class _ShlinkStatsCardWidget extends StatefulWidget {
|
||||||
const _ShlinkStatsCardWidget({required this.text, required this.icon, this.borderColor});
|
const _ShlinkStatsCardWidget(
|
||||||
|
{required this.text, required this.icon, this.borderColor});
|
||||||
|
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Color? borderColor;
|
final Color? borderColor;
|
||||||
|
@ -230,20 +269,19 @@ class _ShlinkStatsCardWidgetState extends State<_ShlinkStatsCardWidget> {
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: widget.borderColor ?? randomColor),
|
border: Border.all(color: widget.borderColor ?? randomColor),
|
||||||
borderRadius: BorderRadius.circular(8)
|
borderRadius: BorderRadius.circular(8)),
|
||||||
),
|
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: [
|
children: [
|
||||||
Icon(widget.icon),
|
Icon(widget.icon),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 4),
|
padding: const EdgeInsets.only(left: 4),
|
||||||
child: Text(widget.text, style: const TextStyle(fontWeight: FontWeight.bold)),
|
child: Text(widget.text,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:shlink_app/main.dart';
|
||||||
import '../globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
|
|
||||||
class LoginView extends StatefulWidget {
|
class LoginView extends StatefulWidget {
|
||||||
const LoginView({Key? key}) : super(key: key);
|
const LoginView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LoginView> createState() => _LoginViewState();
|
State<LoginView> createState() => _LoginViewState();
|
||||||
|
@ -30,11 +30,11 @@ class _LoginViewState extends State<LoginView> {
|
||||||
_isLoggingIn = true;
|
_isLoggingIn = true;
|
||||||
_errorMessage = "";
|
_errorMessage = "";
|
||||||
});
|
});
|
||||||
final connectResult = await globals.serverManager.initAndConnect(_serverUrlController.text, _apiKeyController.text);
|
final connectResult = await globals.serverManager
|
||||||
|
.initAndConnect(_serverUrlController.text, _apiKeyController.text);
|
||||||
connectResult.fold((l) {
|
connectResult.fold((l) {
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(builder: (context) => const InitialPage())
|
MaterialPageRoute(builder: (context) => const InitialPage()));
|
||||||
);
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoggingIn = false;
|
_isLoggingIn = false;
|
||||||
});
|
});
|
||||||
|
@ -44,8 +44,7 @@ class _LoginViewState extends State<LoginView> {
|
||||||
_errorMessage = r.detail;
|
_errorMessage = r.detail;
|
||||||
_isLoggingIn = false;
|
_isLoggingIn = false;
|
||||||
});
|
});
|
||||||
}
|
} else if (r is RequestFailure) {
|
||||||
else if (r is RequestFailure) {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_errorMessage = r.description;
|
_errorMessage = r.description;
|
||||||
_isLoggingIn = false;
|
_isLoggingIn = false;
|
||||||
|
@ -54,55 +53,58 @@ class _LoginViewState extends State<LoginView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
const SliverAppBar.medium(
|
const SliverAppBar.medium(
|
||||||
title: Text("Add server", style: TextStyle(fontWeight: FontWeight.bold))
|
title: Text("Add server",
|
||||||
),
|
style: TextStyle(fontWeight: FontWeight.bold))),
|
||||||
SliverFillRemaining(
|
SliverFillRemaining(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Padding(padding: EdgeInsets.only(bottom: 8),
|
const Padding(
|
||||||
child: Text("Server URL", style: TextStyle(fontWeight: FontWeight.bold),)),
|
padding: EdgeInsets.only(bottom: 8),
|
||||||
|
child: Text(
|
||||||
|
"Server URL",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
)),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.dns_outlined),
|
const Icon(Icons.dns_outlined),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: TextField(
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
controller: _serverUrlController,
|
controller: _serverUrlController,
|
||||||
keyboardType: TextInputType.url,
|
keyboardType: TextInputType.url,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
labelText: "https://shlink.example.com"
|
labelText: "https://shlink.example.com"),
|
||||||
),
|
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.only(top: 8, bottom: 8),
|
padding: EdgeInsets.only(top: 8, bottom: 8),
|
||||||
child: Text("API Key", style: TextStyle(fontWeight: FontWeight.bold)),
|
child: Text("API Key",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.key),
|
const Icon(Icons.key),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: TextField(
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
controller: _apiKeyController,
|
controller: _apiKeyController,
|
||||||
keyboardType: TextInputType.text,
|
keyboardType: TextInputType.text,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(), labelText: "..."),
|
||||||
labelText: "..."
|
|
||||||
),
|
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -112,15 +114,16 @@ class _LoginViewState extends State<LoginView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.tonal(
|
FilledButton.tonal(
|
||||||
onPressed: () => {
|
onPressed: () => {_connect()},
|
||||||
_connect()
|
child: _isLoggingIn
|
||||||
},
|
? Container(
|
||||||
child: _isLoggingIn ? Container(
|
width: 34,
|
||||||
width: 34,
|
height: 34,
|
||||||
height: 34,
|
padding: const EdgeInsets.all(4),
|
||||||
padding: const EdgeInsets.all(4),
|
child: const CircularProgressIndicator(),
|
||||||
child: const CircularProgressIndicator(),
|
)
|
||||||
) : const Text("Connect", style: TextStyle(fontSize: 20)),
|
: const Text("Connect",
|
||||||
|
style: TextStyle(fontSize: 20)),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -130,17 +133,17 @@ class _LoginViewState extends State<LoginView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Flexible(child: Text(_errorMessage, style: const TextStyle(color: Colors.red), textAlign: TextAlign.center))
|
Flexible(
|
||||||
|
child: Text(_errorMessage,
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
textAlign: TextAlign.center))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
],
|
||||||
],
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,18 @@ import 'package:shlink_app/views/home_view.dart';
|
||||||
import 'package:shlink_app/views/url_list_view.dart';
|
import 'package:shlink_app/views/url_list_view.dart';
|
||||||
|
|
||||||
class NavigationBarView extends StatefulWidget {
|
class NavigationBarView extends StatefulWidget {
|
||||||
const NavigationBarView({Key? key}) : super(key: key);
|
const NavigationBarView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<NavigationBarView> createState() => _NavigationBarViewState();
|
State<NavigationBarView> createState() => _NavigationBarViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NavigationBarViewState extends State<NavigationBarView> {
|
class _NavigationBarViewState extends State<NavigationBarView> {
|
||||||
|
final List<Widget> views = [
|
||||||
final List<Widget> views = [const HomeView(), const URLListView(), const SettingsView()];
|
const HomeView(),
|
||||||
|
const URLListView(),
|
||||||
|
const SettingsView()
|
||||||
|
];
|
||||||
int _selectedView = 0;
|
int _selectedView = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -16,55 +16,68 @@ class _OpenSourceLicensesViewState extends State<OpenSourceLicensesView> {
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
const SliverAppBar.medium(
|
const SliverAppBar.medium(
|
||||||
expandedHeight: 120,
|
expandedHeight: 120,
|
||||||
title: Text("Open Source Licenses", style: TextStyle(fontWeight: FontWeight.bold),)
|
title: Text(
|
||||||
),
|
"Open Source Licenses",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
)),
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate:
|
||||||
(BuildContext context, int index) {
|
SliverChildBuilderDelegate((BuildContext context, int index) {
|
||||||
final currentLicense = LicenseUtil.getLicenses()[index];
|
final currentLicense = LicenseUtil.getLicenses()[index];
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (currentLicense.repository != null) {
|
if (currentLicense.repository != null) {
|
||||||
if (await canLaunchUrl(Uri.parse(currentLicense.repository ?? ""))) {
|
if (await canLaunchUrl(
|
||||||
launchUrl(Uri.parse(currentLicense.repository ?? ""), mode: LaunchMode.externalApplication);
|
Uri.parse(currentLicense.repository ?? ""))) {
|
||||||
}
|
launchUrl(Uri.parse(currentLicense.repository ?? ""),
|
||||||
}
|
mode: LaunchMode.externalApplication);
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
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(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.only(
|
||||||
child: Container(
|
left: 12, right: 12, top: 20, bottom: 20),
|
||||||
decoration: BoxDecoration(
|
child: Column(
|
||||||
borderRadius: BorderRadius.circular(8),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
|
children: [
|
||||||
),
|
Text(currentLicense.name,
|
||||||
child: Padding(
|
style: const TextStyle(
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
|
fontWeight: FontWeight.bold, fontSize: 18)),
|
||||||
child: Column(
|
Text("Version: ${currentLicense.version ?? "N/A"}",
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
style: const TextStyle(color: Colors.grey)),
|
||||||
children: [
|
const SizedBox(height: 8),
|
||||||
Text(currentLicense.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
|
const Divider(),
|
||||||
Text("Version: ${currentLicense.version ?? "N/A"}", style: const TextStyle(color: Colors.grey)),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 8),
|
Text(currentLicense.license,
|
||||||
const Divider(),
|
textAlign: TextAlign.justify,
|
||||||
const SizedBox(height: 8),
|
style: const TextStyle(color: Colors.grey)),
|
||||||
Text(currentLicense.license, textAlign: TextAlign.justify, style: const TextStyle(color: Colors.grey)),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
childCount: LicenseUtil.getLicenses().length
|
);
|
||||||
),
|
}, childCount: LicenseUtil.getLicenses().length),
|
||||||
),
|
),
|
||||||
const SliverToBoxAdapter(
|
const SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(top: 8, bottom: 20),
|
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,),
|
child: Text(
|
||||||
)
|
"Thank you to all maintainers of these repositories 💝",
|
||||||
)
|
style: TextStyle(color: Colors.grey),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,24 +13,19 @@ class SettingsView extends StatefulWidget {
|
||||||
State<SettingsView> createState() => _SettingsViewState();
|
State<SettingsView> createState() => _SettingsViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServerStatus {
|
enum ServerStatus { connected, connecting, disconnected }
|
||||||
connected,
|
|
||||||
connecting,
|
|
||||||
disconnected
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SettingsViewState extends State<SettingsView> {
|
class _SettingsViewState extends State<SettingsView> {
|
||||||
|
|
||||||
var _serverVersion = "---";
|
var _serverVersion = "---";
|
||||||
ServerStatus _serverStatus = ServerStatus.connecting;
|
ServerStatus _serverStatus = ServerStatus.connecting;
|
||||||
PackageInfo packageInfo = PackageInfo(appName: "", packageName: "", version: "", buildNumber: "");
|
PackageInfo packageInfo =
|
||||||
|
PackageInfo(appName: "", packageName: "", version: "", buildNumber: "");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
// TODO: implement initState
|
// TODO: implement initState
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback((_) => getServerHealth());
|
||||||
.addPostFrameCallback((_) => getServerHealth());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void getServerHealth() async {
|
void getServerHealth() async {
|
||||||
|
@ -52,12 +47,14 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
var text = "";
|
var text = "";
|
||||||
if (r is RequestFailure) {
|
if (r is RequestFailure) {
|
||||||
text = r.description;
|
text = r.description;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
text = (r as ApiFailure).detail;
|
text = (r as ApiFailure).detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
final snackBar = SnackBar(content: Text(text), backgroundColor: Colors.red[400], behavior: SnackBarBehavior.floating);
|
final snackBar = SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: Colors.red[400],
|
||||||
|
behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -65,68 +62,89 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar.medium(
|
SliverAppBar.medium(
|
||||||
expandedHeight: 120,
|
expandedHeight: 120,
|
||||||
title: const Text("Settings", style: TextStyle(fontWeight: FontWeight.bold),),
|
title: const Text(
|
||||||
actions: [
|
"Settings",
|
||||||
PopupMenuButton(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
itemBuilder: (context) {
|
|
||||||
return [
|
|
||||||
const PopupMenuItem(
|
|
||||||
value: 0,
|
|
||||||
child: Text("Log out...", style: TextStyle(color: Colors.red)),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
},
|
|
||||||
onSelected: (value) {
|
|
||||||
if (value == 0) {
|
|
||||||
globals.serverManager.logOut().then((value) => Navigator.of(context).pushReplacement(
|
|
||||||
MaterialPageRoute(builder: (context) => const LoginView())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
actions: [
|
||||||
child: Padding(
|
PopupMenuButton(
|
||||||
|
itemBuilder: (context) {
|
||||||
|
return [
|
||||||
|
const PopupMenuItem(
|
||||||
|
value: 0,
|
||||||
|
child:
|
||||||
|
Text("Log out...", style: TextStyle(color: Colors.red)),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 0) {
|
||||||
|
globals.serverManager.logOut().then((value) =>
|
||||||
|
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
||||||
|
builder: (context) => const LoginView())));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
),
|
? Colors.grey[100]
|
||||||
|
: Colors.grey[900],
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.dns_outlined, color: (() {
|
Icon(Icons.dns_outlined,
|
||||||
switch (_serverStatus) {
|
color: (() {
|
||||||
case ServerStatus.connected:
|
switch (_serverStatus) {
|
||||||
return Colors.green;
|
case ServerStatus.connected:
|
||||||
case ServerStatus.connecting:
|
return Colors.green;
|
||||||
return Colors.orange;
|
case ServerStatus.connecting:
|
||||||
case ServerStatus.disconnected:
|
return Colors.orange;
|
||||||
return Colors.red;
|
case ServerStatus.disconnected:
|
||||||
}
|
return Colors.red;
|
||||||
}())),
|
}
|
||||||
|
}())),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text("Connected to", style: TextStyle(color: Colors.grey)),
|
const Text("Connected to",
|
||||||
Text(globals.serverManager.getServerUrl(), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
style: TextStyle(color: Colors.grey)),
|
||||||
|
Text(globals.serverManager.getServerUrl(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16)),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Text("API Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
|
const Text("API Version: ",
|
||||||
Text(globals.serverManager.getApiVersion(), style: const TextStyle(color: Colors.grey)),
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.w600)),
|
||||||
|
Text(globals.serverManager.getApiVersion(),
|
||||||
|
style:
|
||||||
|
const TextStyle(color: Colors.grey)),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
const Text("Server Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
|
const Text("Server Version: ",
|
||||||
Text(_serverVersion, style: const TextStyle(color: Colors.grey))
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.w600)),
|
||||||
|
Text(_serverVersion,
|
||||||
|
style:
|
||||||
|
const TextStyle(color: Colors.grey))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -140,17 +158,20 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
MaterialPageRoute(builder: (context) => const OpenSourceLicensesView())
|
builder: (context) =>
|
||||||
);
|
const OpenSourceLicensesView()));
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? Colors.grey[100]
|
||||||
|
: Colors.grey[900],
|
||||||
),
|
),
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
padding: EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
|
padding: EdgeInsets.only(
|
||||||
|
left: 12, right: 12, top: 20, bottom: 20),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
@ -158,50 +179,21 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.policy_outlined),
|
Icon(Icons.policy_outlined),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text("Open Source Licenses", style: TextStyle(fontWeight: FontWeight.w500)),
|
Text("Open Source Licenses",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Icon(Icons.chevron_right)
|
Icon(Icons.chevron_right)
|
||||||
]
|
]),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var url = Uri.parse("https://github.com/rainloreley/shlink-mobile-app");
|
var url = Uri.parse(
|
||||||
if (await canLaunchUrl(url)) {
|
"https://github.com/rainloreley/shlink-mobile-app");
|
||||||
launchUrl(url, mode: LaunchMode.externalApplication);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
|
|
||||||
),
|
|
||||||
child: const Padding(
|
|
||||||
padding: EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.code),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text("GitHub", style: TextStyle(fontWeight: FontWeight.w500)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Icon(Icons.chevron_right)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
var url = Uri.parse("https://abmgrt.dev/shlink-manager/privacy");
|
|
||||||
if (await canLaunchUrl(url)) {
|
if (await canLaunchUrl(url)) {
|
||||||
launchUrl(url, mode: LaunchMode.externalApplication);
|
launchUrl(url, mode: LaunchMode.externalApplication);
|
||||||
}
|
}
|
||||||
|
@ -209,10 +201,49 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(context).brightness == Brightness.light ? Colors.grey[100] : Colors.grey[900],
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? Colors.grey[100]
|
||||||
|
: Colors.grey[900],
|
||||||
),
|
),
|
||||||
child: const Padding(
|
child: const Padding(
|
||||||
padding: EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
|
padding: EdgeInsets.only(
|
||||||
|
left: 12, right: 12, top: 20, bottom: 20),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.code),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text("GitHub",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Icon(Icons.chevron_right)
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
var url = Uri.parse(
|
||||||
|
"https://abmgrt.dev/shlink-manager/privacy");
|
||||||
|
if (await canLaunchUrl(url)) {
|
||||||
|
launchUrl(url, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? Colors.grey[100]
|
||||||
|
: Colors.grey[900],
|
||||||
|
),
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 12, right: 12, top: 20, bottom: 20),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
@ -220,12 +251,13 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.lock),
|
Icon(Icons.lock),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text("Privacy Policy", style: TextStyle(fontWeight: FontWeight.w500)),
|
Text("Privacy Policy",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Icon(Icons.chevron_right)
|
Icon(Icons.chevron_right)
|
||||||
]
|
]),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -234,14 +266,16 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text("${packageInfo.appName}, v${packageInfo.version} (${packageInfo.buildNumber})", style: const TextStyle(color: Colors.grey),),],
|
Text(
|
||||||
|
"${packageInfo.appName}, v${packageInfo.version} (${packageInfo.buildNumber})",
|
||||||
|
style: const TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)),
|
||||||
),
|
)
|
||||||
)
|
],
|
||||||
],
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ class ShortURLEditView extends StatefulWidget {
|
||||||
State<ShortURLEditView> createState() => _ShortURLEditViewState();
|
State<ShortURLEditView> createState() => _ShortURLEditViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerProviderStateMixin {
|
class _ShortURLEditViewState extends State<ShortURLEditView>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
final longUrlController = TextEditingController();
|
final longUrlController = TextEditingController();
|
||||||
final customSlugController = TextEditingController();
|
final customSlugController = TextEditingController();
|
||||||
final titleController = TextEditingController();
|
final titleController = TextEditingController();
|
||||||
|
@ -51,13 +51,17 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
void _submitShortUrl() async {
|
void _submitShortUrl() async {
|
||||||
var newSubmission = ShortURLSubmission(
|
var newSubmission = ShortURLSubmission(
|
||||||
longUrl: longUrlController.text,
|
longUrl: longUrlController.text,
|
||||||
deviceLongUrls: null, tags: [],
|
deviceLongUrls: null,
|
||||||
|
tags: [],
|
||||||
crawlable: isCrawlable,
|
crawlable: isCrawlable,
|
||||||
forwardQuery: forwardQuery,
|
forwardQuery: forwardQuery,
|
||||||
findIfExists: true,
|
findIfExists: true,
|
||||||
title: titleController.text != "" ? titleController.text : null,
|
title: titleController.text != "" ? titleController.text : null,
|
||||||
customSlug: customSlugController.text != "" && !randomSlug ? customSlugController.text : null,
|
customSlug: customSlugController.text != "" && !randomSlug
|
||||||
shortCodeLength: randomSlug ? int.parse(randomSlugLengthController.text) : null);
|
? customSlugController.text
|
||||||
|
: null,
|
||||||
|
shortCodeLength:
|
||||||
|
randomSlug ? int.parse(randomSlugLengthController.text) : null);
|
||||||
var response = await globals.serverManager.submitShortUrl(newSubmission);
|
var response = await globals.serverManager.submitShortUrl(newSubmission);
|
||||||
|
|
||||||
response.fold((l) async {
|
response.fold((l) async {
|
||||||
|
@ -67,11 +71,16 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
|
|
||||||
if (copyToClipboard) {
|
if (copyToClipboard) {
|
||||||
await Clipboard.setData(ClipboardData(text: l));
|
await Clipboard.setData(ClipboardData(text: l));
|
||||||
final snackBar = SnackBar(content: const 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);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
} else {
|
||||||
else {
|
final snackBar = SnackBar(
|
||||||
final snackBar = SnackBar(content: const Text("Short URL created!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
|
content: const Text("Short URL created!"),
|
||||||
|
backgroundColor: Colors.green[400],
|
||||||
|
behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
@ -86,38 +95,39 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
|
|
||||||
if (r is RequestFailure) {
|
if (r is RequestFailure) {
|
||||||
text = r.description;
|
text = r.description;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
text = (r as ApiFailure).detail;
|
text = (r as ApiFailure).detail;
|
||||||
if ((r).invalidElements != null) {
|
if ((r).invalidElements != null) {
|
||||||
text = "$text: ${(r).invalidElements}";
|
text = "$text: ${(r).invalidElements}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final snackBar = SnackBar(content: Text(text), backgroundColor: Colors.red[400], behavior: SnackBarBehavior.floating);
|
final snackBar = SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: Colors.red[400],
|
||||||
|
behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
const SliverAppBar.medium(
|
const SliverAppBar.medium(
|
||||||
title: Text("New Short URL", style: TextStyle(fontWeight: FontWeight.bold)),
|
title: Text("New Short URL",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
children: [
|
TextField(
|
||||||
TextField(
|
controller: longUrlController,
|
||||||
controller: longUrlController,
|
decoration: InputDecoration(
|
||||||
decoration: InputDecoration(
|
|
||||||
errorText: longUrlError != "" ? longUrlError : null,
|
errorText: longUrlError != "" ? longUrlError : null,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
label: const Row(
|
label: const Row(
|
||||||
|
@ -126,87 +136,99 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text("Long URL")
|
Text("Long URL")
|
||||||
],
|
],
|
||||||
)
|
)),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
Expanded(
|
||||||
Expanded(
|
child: TextField(
|
||||||
child: TextField(
|
controller: customSlugController,
|
||||||
controller: customSlugController,
|
style: TextStyle(
|
||||||
style: TextStyle(color: randomSlug ? Colors.grey : Theme.of(context).brightness == Brightness.light ? Colors.black : Colors.white),
|
color: randomSlug
|
||||||
onChanged: (_) {
|
? Colors.grey
|
||||||
if (randomSlug) {
|
: Theme.of(context).brightness ==
|
||||||
setState(() {
|
Brightness.light
|
||||||
|
? Colors.black
|
||||||
|
: Colors.white),
|
||||||
|
onChanged: (_) {
|
||||||
|
if (randomSlug) {
|
||||||
|
setState(() {
|
||||||
randomSlug = false;
|
randomSlug = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
label: Row(
|
label: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.link),
|
const Icon(Icons.link),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text("${randomSlug ? "Random" : "Custom"} slug", style: TextStyle(fontStyle: randomSlug ? FontStyle.italic : FontStyle.normal),)
|
Text(
|
||||||
|
"${randomSlug ? "Random" : "Custom"} slug",
|
||||||
|
style: TextStyle(
|
||||||
|
fontStyle: randomSlug
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
),
|
||||||
RotationTransition(
|
const SizedBox(width: 8),
|
||||||
turns: Tween(begin: 0.0, end: 3.0).animate(CurvedAnimation(parent: _customSlugDiceAnimationController, curve: Curves.easeInOutExpo)),
|
RotationTransition(
|
||||||
child: IconButton(
|
turns: Tween(begin: 0.0, end: 3.0).animate(
|
||||||
onPressed: () {
|
CurvedAnimation(
|
||||||
if (randomSlug) {
|
parent: _customSlugDiceAnimationController,
|
||||||
_customSlugDiceAnimationController.reverse(from: 1);
|
curve: Curves.easeInOutExpo)),
|
||||||
}
|
child: IconButton(
|
||||||
else {
|
onPressed: () {
|
||||||
_customSlugDiceAnimationController.forward(from: 0);
|
if (randomSlug) {
|
||||||
}
|
_customSlugDiceAnimationController.reverse(
|
||||||
setState(() {
|
from: 1);
|
||||||
randomSlug = !randomSlug;
|
} else {
|
||||||
});
|
_customSlugDiceAnimationController.forward(
|
||||||
|
from: 0);
|
||||||
},
|
}
|
||||||
icon: Icon(randomSlug ? Icons.casino : Icons.casino_outlined, color: randomSlug ? Colors.green : Colors.grey)
|
setState(() {
|
||||||
),
|
randomSlug = !randomSlug;
|
||||||
)
|
});
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
randomSlug ? Icons.casino : Icons.casino_outlined,
|
||||||
|
color: randomSlug ? Colors.green : Colors.grey)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (randomSlug) const SizedBox(height: 16),
|
||||||
|
if (randomSlug)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text("Random slug length"),
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: TextField(
|
||||||
|
controller: randomSlugLengthController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
errorText:
|
||||||
|
randomSlugLengthError != "" ? "" : null,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
label: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.tag),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text("Length")
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (randomSlug)
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
TextField(
|
||||||
|
controller: titleController,
|
||||||
if (randomSlug)
|
decoration: const InputDecoration(
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const Text("Random slug length"),
|
|
||||||
SizedBox(
|
|
||||||
width: 100,
|
|
||||||
child: TextField(
|
|
||||||
controller: randomSlugLengthController,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
errorText: randomSlugLengthError != "" ? "" : null,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
label: const Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.tag),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
Text("Length")
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextField(
|
|
||||||
controller: titleController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
label: Row(
|
label: Row(
|
||||||
children: [
|
children: [
|
||||||
|
@ -214,89 +236,91 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text("Title")
|
Text("Title")
|
||||||
],
|
],
|
||||||
)
|
)),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
children: [
|
const Text("Crawlable"),
|
||||||
const Text("Crawlable"),
|
Switch(
|
||||||
Switch(
|
value: isCrawlable,
|
||||||
value: isCrawlable,
|
onChanged: (_) {
|
||||||
onChanged: (_) {
|
setState(() {
|
||||||
setState(() {
|
isCrawlable = !isCrawlable;
|
||||||
isCrawlable = !isCrawlable;
|
});
|
||||||
});
|
},
|
||||||
},
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
children: [
|
const Text("Forward query params"),
|
||||||
const Text("Forward query params"),
|
Switch(
|
||||||
Switch(
|
value: forwardQuery,
|
||||||
value: forwardQuery,
|
onChanged: (_) {
|
||||||
onChanged: (_) {
|
setState(() {
|
||||||
setState(() {
|
forwardQuery = !forwardQuery;
|
||||||
forwardQuery = !forwardQuery;
|
});
|
||||||
});
|
},
|
||||||
},
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
const SizedBox(height: 16),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
children: [
|
||||||
children: [
|
const Text("Copy to clipboard"),
|
||||||
const Text("Copy to clipboard"),
|
Switch(
|
||||||
Switch(
|
value: copyToClipboard,
|
||||||
value: copyToClipboard,
|
onChanged: (_) {
|
||||||
onChanged: (_) {
|
setState(() {
|
||||||
setState(() {
|
copyToClipboard = !copyToClipboard;
|
||||||
copyToClipboard = !copyToClipboard;
|
});
|
||||||
});
|
},
|
||||||
},
|
)
|
||||||
)
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (!isSaving) {
|
if (!isSaving) {
|
||||||
setState(() {
|
|
||||||
isSaving = true;
|
|
||||||
longUrlError = "";
|
|
||||||
randomSlugLengthError = "";
|
|
||||||
});
|
|
||||||
if (longUrlController.text == "") {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
longUrlError = "URL cannot be empty";
|
isSaving = true;
|
||||||
isSaving = false;
|
longUrlError = "";
|
||||||
|
randomSlugLengthError = "";
|
||||||
});
|
});
|
||||||
return;
|
if (longUrlController.text == "") {
|
||||||
|
setState(() {
|
||||||
|
longUrlError = "URL cannot be empty";
|
||||||
|
isSaving = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else if (int.tryParse(randomSlugLengthController.text) ==
|
||||||
|
null ||
|
||||||
|
int.tryParse(randomSlugLengthController.text)! < 1 ||
|
||||||
|
int.tryParse(randomSlugLengthController.text)! > 50) {
|
||||||
|
setState(() {
|
||||||
|
randomSlugLengthError = "invalid number";
|
||||||
|
isSaving = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_submitShortUrl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (int.tryParse(randomSlugLengthController.text) == null || int.tryParse(randomSlugLengthController.text)! < 1 || int.tryParse(randomSlugLengthController.text)! > 50) {
|
},
|
||||||
setState(() {
|
child: isSaving
|
||||||
randomSlugLengthError = "invalid number";
|
? const Padding(
|
||||||
isSaving = false;
|
padding: EdgeInsets.all(16),
|
||||||
});
|
child: CircularProgressIndicator(strokeWidth: 3))
|
||||||
return;
|
: const Icon(Icons.save)),
|
||||||
}
|
|
||||||
else {
|
|
||||||
_submitShortUrl();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: isSaving ? const Padding(padding: EdgeInsets.all(16), child: CircularProgressIndicator(strokeWidth: 3)) : const Icon(Icons.save)
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ class URLDetailView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _URLDetailViewState extends State<URLDetailView> {
|
class _URLDetailViewState extends State<URLDetailView> {
|
||||||
|
|
||||||
Future showDeletionConfirmation() {
|
Future showDeletionConfirmation() {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -27,58 +26,75 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
children: [
|
children: [
|
||||||
const Text("You're about to delete"),
|
const Text("You're about to delete"),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(widget.shortURL.title ?? widget.shortURL.shortCode, style: const TextStyle(fontStyle: FontStyle.italic),),
|
Text(
|
||||||
|
widget.shortURL.title ?? widget.shortURL.shortCode,
|
||||||
|
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
const Text("It'll be gone forever! (a very long time)")
|
const Text("It'll be gone forever! (a very long time)")
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: () => { Navigator.of(context).pop() }, child: const Text("Cancel")),
|
TextButton(
|
||||||
|
onPressed: () => {Navigator.of(context).pop()},
|
||||||
|
child: const Text("Cancel")),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var response = await globals.serverManager.deleteShortUrl(widget.shortURL.shortCode);
|
var response = await globals.serverManager
|
||||||
|
.deleteShortUrl(widget.shortURL.shortCode);
|
||||||
|
|
||||||
response.fold((l) {
|
response.fold((l) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Navigator.pop(context, "reload");
|
Navigator.pop(context, "reload");
|
||||||
|
|
||||||
final snackBar = SnackBar(content: const 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);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
return true;
|
return true;
|
||||||
}, (r) {
|
}, (r) {
|
||||||
var text = "";
|
var text = "";
|
||||||
if (r is RequestFailure) {
|
if (r is RequestFailure) {
|
||||||
text = r.description;
|
text = r.description;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
text = (r as ApiFailure).detail;
|
text = (r as ApiFailure).detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
final snackBar = SnackBar(content: Text(text), backgroundColor: Colors.red[400], behavior: SnackBarBehavior.floating);
|
final snackBar = SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: Colors.red[400],
|
||||||
|
behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: const Text("Delete", style: TextStyle(color: Colors.red)),
|
child:
|
||||||
|
const Text("Delete", style: TextStyle(color: Colors.red)),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar.medium(
|
SliverAppBar.medium(
|
||||||
title: Text(widget.shortURL.title ?? widget.shortURL.shortCode, style: const TextStyle(fontWeight: FontWeight.bold)),
|
title: Text(widget.shortURL.title ?? widget.shortURL.shortCode,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(onPressed: () {
|
IconButton(
|
||||||
showDeletionConfirmation();
|
onPressed: () {
|
||||||
}, icon: const Icon(Icons.delete, color: Colors.red,))
|
showDeletionConfirmation();
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete,
|
||||||
|
color: Colors.red,
|
||||||
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
|
@ -86,41 +102,78 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
|
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
children: widget.shortURL.tags.map((tag) {
|
children: widget.shortURL.tags.map((tag) {
|
||||||
var randomColor = ([...Colors.primaries]..shuffle()).first.harmonizeWith(Theme.of(context).colorScheme.primary);
|
var randomColor = ([...Colors.primaries]..shuffle())
|
||||||
return Padding(
|
.first
|
||||||
padding: const EdgeInsets.only(right: 4, top: 4),
|
.harmonizeWith(Theme.of(context).colorScheme.primary);
|
||||||
child: Container(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
|
padding: const EdgeInsets.only(right: 4, top: 4),
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
borderRadius: BorderRadius.circular(4),
|
padding: const EdgeInsets.only(
|
||||||
color: randomColor,
|
top: 4, bottom: 4, left: 12, right: 12),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Text(tag, style: TextStyle(color: randomColor.computeLuminance() < 0.5 ? Colors.white : Colors.black),),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
color: randomColor,
|
||||||
);
|
),
|
||||||
}).toList()
|
child: Text(
|
||||||
),
|
tag,
|
||||||
|
style: TextStyle(
|
||||||
|
color: randomColor.computeLuminance() < 0.5
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_ListCell(title: "Short Code", content: widget.shortURL.shortCode),
|
_ListCell(title: "Short Code", content: widget.shortURL.shortCode),
|
||||||
_ListCell(title: "Short URL", content: widget.shortURL.shortUrl),
|
_ListCell(title: "Short URL", content: widget.shortURL.shortUrl),
|
||||||
_ListCell(title: "Long URL", content: widget.shortURL.longUrl),
|
_ListCell(title: "Long URL", content: widget.shortURL.longUrl),
|
||||||
_ListCell(title: "iOS", content: widget.shortURL.deviceLongUrls.ios, sub: true),
|
_ListCell(
|
||||||
_ListCell(title: "Android", content: widget.shortURL.deviceLongUrls.android, sub: true),
|
title: "iOS",
|
||||||
_ListCell(title: "Desktop", content: widget.shortURL.deviceLongUrls.desktop, sub: true),
|
content: widget.shortURL.deviceLongUrls.ios,
|
||||||
_ListCell(title: "Creation Date", content: widget.shortURL.dateCreated),
|
sub: true),
|
||||||
|
_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),
|
||||||
const _ListCell(title: "Visits", content: ""),
|
const _ListCell(title: "Visits", content: ""),
|
||||||
_ListCell(title: "Total", content: widget.shortURL.visitsSummary.total, sub: true),
|
_ListCell(
|
||||||
_ListCell(title: "Non-Bots", content: widget.shortURL.visitsSummary.nonBots, sub: true),
|
title: "Total",
|
||||||
_ListCell(title: "Bots", content: widget.shortURL.visitsSummary.bots, sub: true),
|
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),
|
||||||
const _ListCell(title: "Meta", content: ""),
|
const _ListCell(title: "Meta", content: ""),
|
||||||
_ListCell(title: "Valid Since", content: widget.shortURL.meta.validSince, sub: true),
|
_ListCell(
|
||||||
_ListCell(title: "Valid Until", content: widget.shortURL.meta.validUntil, sub: true),
|
title: "Valid Since",
|
||||||
_ListCell(title: "Max Visits", content: widget.shortURL.meta.maxVisits, sub: true),
|
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),
|
||||||
_ListCell(title: "Domain", content: widget.shortURL.domain),
|
_ListCell(title: "Domain", content: widget.shortURL.domain),
|
||||||
_ListCell(title: "Crawlable", content: widget.shortURL.crawlable, last: true)
|
_ListCell(
|
||||||
|
title: "Crawlable",
|
||||||
|
content: widget.shortURL.crawlable,
|
||||||
|
last: true)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -128,7 +181,11 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ListCell extends StatefulWidget {
|
class _ListCell extends StatefulWidget {
|
||||||
const _ListCell({required this.title, required this.content, this.sub = false, this.last = false});
|
const _ListCell(
|
||||||
|
{required this.title,
|
||||||
|
required this.content,
|
||||||
|
this.sub = false,
|
||||||
|
this.last = false});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final dynamic content;
|
final dynamic content;
|
||||||
|
@ -143,51 +200,66 @@ class _ListCellState extends State<_ListCell> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(top: 16, bottom: widget.last ? 30 : 0),
|
padding: EdgeInsets.only(top: 16, bottom: widget.last ? 30 : 0),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.only(top: 16, left: 8, right: 8),
|
padding: const EdgeInsets.only(top: 16, left: 8, right: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(top: BorderSide(width: 1, color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.grey[800]! : Colors.grey[300]!)),
|
border: Border(
|
||||||
),
|
top: BorderSide(
|
||||||
child: Row(
|
color: MediaQuery.of(context).platformBrightness ==
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
Brightness.dark
|
||||||
children: [
|
? Colors.grey[800]!
|
||||||
Row(
|
: Colors.grey[300]!)),
|
||||||
children: [
|
),
|
||||||
if (widget.sub)
|
child: Row(
|
||||||
Padding(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
padding: const EdgeInsets.only(right: 4),
|
children: [
|
||||||
child: SizedBox(
|
Row(
|
||||||
width: 20,
|
children: [
|
||||||
height: 6,
|
if (widget.sub)
|
||||||
child: Container(
|
Padding(
|
||||||
decoration: BoxDecoration(
|
padding: const EdgeInsets.only(right: 4),
|
||||||
borderRadius: BorderRadius.circular(8),
|
child: SizedBox(
|
||||||
color: Theme.of(context).brightness == Brightness.dark ? Colors.grey[700] : Colors.grey[300],
|
width: 20,
|
||||||
),
|
height: 6,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? Colors.grey[700]
|
||||||
|
: Colors.grey[300],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(widget.title, style: const TextStyle(fontWeight: FontWeight.bold),)],
|
),
|
||||||
),
|
Text(
|
||||||
if (widget.content is bool)
|
widget.title,
|
||||||
Icon(widget.content ? Icons.check : Icons.close, color: widget.content ? Colors.green : Colors.red)
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
else if (widget.content is int)
|
)
|
||||||
Text(widget.content.toString())
|
],
|
||||||
else if (widget.content is String)
|
),
|
||||||
Expanded(
|
if (widget.content is bool)
|
||||||
child: Text(widget.content, textAlign: TextAlign.end, overflow: TextOverflow.ellipsis, maxLines: 1,),
|
Icon(widget.content ? Icons.check : Icons.close,
|
||||||
)
|
color: widget.content ? Colors.green : Colors.red)
|
||||||
else if (widget.content is DateTime)
|
else if (widget.content is int)
|
||||||
Text(DateFormat('yyyy-MM-dd - HH:mm').format(widget.content))
|
Text(widget.content.toString())
|
||||||
else
|
else if (widget.content is String)
|
||||||
const Text("N/A")
|
Expanded(
|
||||||
],
|
child: Text(
|
||||||
),
|
widget.content,
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (widget.content is DateTime)
|
||||||
|
Text(DateFormat('yyyy-MM-dd - HH:mm').format(widget.content))
|
||||||
|
else
|
||||||
|
const Text("N/A")
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,13 @@ import '../globals.dart' as globals;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class URLListView extends StatefulWidget {
|
class URLListView extends StatefulWidget {
|
||||||
const URLListView({Key? key}) : super(key: key);
|
const URLListView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<URLListView> createState() => _URLListViewState();
|
State<URLListView> createState() => _URLListViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _URLListViewState extends State<URLListView> {
|
class _URLListViewState extends State<URLListView> {
|
||||||
|
|
||||||
List<ShortURL> shortUrls = [];
|
List<ShortURL> shortUrls = [];
|
||||||
bool _qrCodeShown = false;
|
bool _qrCodeShown = false;
|
||||||
String _qrUrl = "";
|
String _qrUrl = "";
|
||||||
|
@ -27,8 +26,7 @@ class _URLListViewState extends State<URLListView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
// TODO: implement initState
|
// TODO: implement initState
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback((_) => loadAllShortUrls());
|
||||||
.addPostFrameCallback((_) => loadAllShortUrls());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadAllShortUrls() async {
|
Future<void> loadAllShortUrls() async {
|
||||||
|
@ -43,12 +41,14 @@ class _URLListViewState extends State<URLListView> {
|
||||||
var text = "";
|
var text = "";
|
||||||
if (r is RequestFailure) {
|
if (r is RequestFailure) {
|
||||||
text = r.description;
|
text = r.description;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
text = (r as ApiFailure).detail;
|
text = (r as ApiFailure).detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
final snackBar = SnackBar(content: Text(text), backgroundColor: Colors.red[400], behavior: SnackBarBehavior.floating);
|
final snackBar = SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: Colors.red[400],
|
||||||
|
behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -57,106 +57,128 @@ class _URLListViewState extends State<URLListView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
|
await Navigator.of(context).push(MaterialPageRoute(
|
||||||
loadAllShortUrls();
|
builder: (context) => const ShortURLEditView()));
|
||||||
},
|
loadAllShortUrls();
|
||||||
child: const Icon(Icons.add),
|
},
|
||||||
),
|
child: const Icon(Icons.add),
|
||||||
body: Stack(
|
),
|
||||||
children: [
|
body: Stack(
|
||||||
ColorFiltered(
|
children: [
|
||||||
colorFilter: ColorFilter.mode(Colors.black.withOpacity(_qrCodeShown ? 0.4 : 0), BlendMode.srcOver),
|
ColorFiltered(
|
||||||
child: RefreshIndicator(
|
colorFilter: ColorFilter.mode(
|
||||||
onRefresh: () async {
|
Colors.black.withOpacity(_qrCodeShown ? 0.4 : 0),
|
||||||
return loadAllShortUrls();
|
BlendMode.srcOver),
|
||||||
},
|
child: RefreshIndicator(
|
||||||
child: CustomScrollView(
|
onRefresh: () async {
|
||||||
slivers: [
|
return loadAllShortUrls();
|
||||||
const SliverAppBar.medium(
|
},
|
||||||
title: Text("Short URLs", style: TextStyle(fontWeight: FontWeight.bold))
|
child: CustomScrollView(
|
||||||
),
|
slivers: [
|
||||||
if (shortUrlsLoaded && shortUrls.isEmpty)
|
const SliverAppBar.medium(
|
||||||
SliverToBoxAdapter(
|
title: Text("Short URLs",
|
||||||
child: Center(
|
style: TextStyle(fontWeight: FontWeight.bold))),
|
||||||
child: Padding(
|
if (shortUrlsLoaded && shortUrls.isEmpty)
|
||||||
padding: const EdgeInsets.only(top: 50),
|
SliverToBoxAdapter(
|
||||||
child: Column(
|
child: Center(
|
||||||
children: [
|
child: Padding(
|
||||||
const Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
|
padding: const EdgeInsets.only(top: 50),
|
||||||
Padding(
|
child: Column(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
children: [
|
||||||
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
|
const Text(
|
||||||
)
|
"No Short URLs",
|
||||||
],
|
style: TextStyle(
|
||||||
)
|
fontSize: 24,
|
||||||
)
|
fontWeight: FontWeight.bold),
|
||||||
)
|
),
|
||||||
)
|
Padding(
|
||||||
else
|
padding: const EdgeInsets.only(top: 8),
|
||||||
SliverList(delegate: SliverChildBuilderDelegate(
|
child: Text(
|
||||||
(BuildContext context, int index) {
|
'Create one by tapping the "+" button below',
|
||||||
final shortURL = shortUrls[index];
|
style: TextStyle(
|
||||||
return ShortURLCell(shortURL: shortURL, reload: () {
|
fontSize: 16,
|
||||||
loadAllShortUrls();
|
color: Colors.grey[600]),
|
||||||
}, showQRCode: (String url) {
|
),
|
||||||
setState(() {
|
)
|
||||||
_qrUrl = url;
|
],
|
||||||
_qrCodeShown = true;
|
))))
|
||||||
});
|
else
|
||||||
}, isLast: index == shortUrls.length - 1);
|
SliverList(
|
||||||
},
|
delegate: SliverChildBuilderDelegate(
|
||||||
childCount: shortUrls.length
|
(BuildContext context, int index) {
|
||||||
))
|
final shortURL = shortUrls[index];
|
||||||
],
|
return ShortURLCell(
|
||||||
),
|
shortURL: shortURL,
|
||||||
),
|
reload: () {
|
||||||
),
|
loadAllShortUrls();
|
||||||
if (_qrCodeShown)
|
},
|
||||||
GestureDetector(
|
showQRCode: (String url) {
|
||||||
onTap: () {
|
setState(() {
|
||||||
setState(() {
|
_qrUrl = url;
|
||||||
_qrCodeShown = false;
|
_qrCodeShown = true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Container(
|
isLast: index == shortUrls.length - 1);
|
||||||
color: Colors.black.withOpacity(0),
|
}, childCount: shortUrls.length))
|
||||||
),
|
],
|
||||||
),
|
|
||||||
if (_qrCodeShown)
|
|
||||||
Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width / 1.7,
|
|
||||||
height: MediaQuery.of(context).size.width / 1.7,
|
|
||||||
child: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: QrImageView(
|
|
||||||
data: _qrUrl,
|
|
||||||
version: QrVersions.auto,
|
|
||||||
size: 200.0,
|
|
||||||
eyeStyle: QrEyeStyle(
|
|
||||||
eyeShape: QrEyeShape.square,
|
|
||||||
color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
dataModuleStyle: QrDataModuleStyle(
|
|
||||||
dataModuleShape: QrDataModuleShape.square,
|
|
||||||
color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
if (_qrCodeShown)
|
||||||
)
|
GestureDetector(
|
||||||
);
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_qrCodeShown = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_qrCodeShown)
|
||||||
|
Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width / 1.7,
|
||||||
|
height: MediaQuery.of(context).size.width / 1.7,
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: QrImageView(
|
||||||
|
data: _qrUrl,
|
||||||
|
size: 200.0,
|
||||||
|
eyeStyle: QrEyeStyle(
|
||||||
|
eyeShape: QrEyeShape.square,
|
||||||
|
color:
|
||||||
|
MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.dark
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
dataModuleStyle: QrDataModuleStyle(
|
||||||
|
dataModuleShape: QrDataModuleShape.square,
|
||||||
|
color:
|
||||||
|
MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.dark
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShortURLCell extends StatefulWidget {
|
class ShortURLCell extends StatefulWidget {
|
||||||
const ShortURLCell({super.key, required this.shortURL, required this.reload, required this.showQRCode, required this.isLast});
|
const ShortURLCell(
|
||||||
|
{super.key,
|
||||||
|
required this.shortURL,
|
||||||
|
required this.reload,
|
||||||
|
required this.showQRCode,
|
||||||
|
required this.isLast});
|
||||||
|
|
||||||
final ShortURL shortURL;
|
final ShortURL shortURL;
|
||||||
final Function() reload;
|
final Function() reload;
|
||||||
|
@ -172,63 +194,93 @@ class _ShortURLCellState extends State<ShortURLCell> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => URLDetailView(shortURL: widget.shortURL)));
|
final result = await Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) => URLDetailView(shortURL: widget.shortURL)));
|
||||||
|
|
||||||
if (result == "reload") {
|
if (result == "reload") {
|
||||||
widget.reload();
|
widget.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(left: 8, right: 8, bottom: widget.isLast ? 90 : 0),
|
padding: EdgeInsets.only(
|
||||||
|
left: 8, right: 8, bottom: widget.isLast ? 90 : 0),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 16, top: 16),
|
padding:
|
||||||
decoration: BoxDecoration(
|
const EdgeInsets.only(left: 8, right: 8, bottom: 16, top: 16),
|
||||||
border: Border(bottom: BorderSide(color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.grey[800]! : Colors.grey[300]!)),
|
decoration: BoxDecoration(
|
||||||
),
|
border: Border(
|
||||||
child: Row(
|
bottom: BorderSide(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
color: MediaQuery.of(context).platformBrightness ==
|
||||||
children: [
|
Brightness.dark
|
||||||
Expanded(
|
? Colors.grey[800]!
|
||||||
child: Column(
|
: Colors.grey[300]!)),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Text(widget.shortURL.title ?? widget.shortURL.shortCode, textScaleFactor: 1.4, style: const TextStyle(fontWeight: FontWeight.bold),),
|
children: [
|
||||||
Text(widget.shortURL.longUrl,maxLines: 1, overflow: TextOverflow.ellipsis, textScaleFactor: 0.9, style: TextStyle(color: Colors.grey[600]),),
|
Expanded(
|
||||||
// List tags in a row
|
child: Column(
|
||||||
Wrap(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: widget.shortURL.tags.map((tag) {
|
children: [
|
||||||
var randomColor = ([...Colors.primaries]..shuffle()).first.harmonizeWith(Theme.of(context).colorScheme.primary);
|
Text(
|
||||||
return Padding(
|
widget.shortURL.title ?? widget.shortURL.shortCode,
|
||||||
padding: const EdgeInsets.only(right: 4, top: 4),
|
textScaleFactor: 1.4,
|
||||||
child: Container(
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
|
),
|
||||||
decoration: BoxDecoration(
|
Text(
|
||||||
borderRadius: BorderRadius.circular(4),
|
widget.shortURL.longUrl,
|
||||||
color: randomColor,
|
maxLines: 1,
|
||||||
),
|
overflow: TextOverflow.ellipsis,
|
||||||
child: Text(tag, style: TextStyle(color: randomColor.computeLuminance() < 0.5 ? Colors.white : Colors.black),),
|
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: const EdgeInsets.only(right: 4, top: 4),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4, bottom: 4, left: 12, right: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
color: randomColor,
|
||||||
),
|
),
|
||||||
);
|
child: Text(
|
||||||
}).toList()
|
tag,
|
||||||
|
style: TextStyle(
|
||||||
)
|
color: randomColor.computeLuminance() < 0.5
|
||||||
],
|
? Colors.white
|
||||||
|
: Colors.black),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList())
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
IconButton(
|
||||||
IconButton(onPressed: () async {
|
onPressed: () async {
|
||||||
await Clipboard.setData(ClipboardData(text: widget.shortURL.shortUrl));
|
await Clipboard.setData(
|
||||||
final snackBar = SnackBar(content: const Text("Copied to clipboard!"), behavior: SnackBarBehavior.floating, backgroundColor: Colors.green[400]);
|
ClipboardData(text: widget.shortURL.shortUrl));
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
final snackBar = SnackBar(
|
||||||
}, icon: const Icon(Icons.copy)),
|
content: const Text("Copied to clipboard!"),
|
||||||
IconButton(onPressed: () {
|
behavior: SnackBarBehavior.floating,
|
||||||
widget.showQRCode(widget.shortURL.shortUrl);
|
backgroundColor: Colors.green[400]);
|
||||||
}, icon: const Icon(Icons.qr_code))
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
],
|
},
|
||||||
)
|
icon: const Icon(Icons.copy)),
|
||||||
),
|
IconButton(
|
||||||
)
|
onPressed: () {
|
||||||
);
|
widget.showQRCode(widget.shortURL.shortUrl);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.qr_code))
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -155,10 +155,10 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
|
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "3.0.1"
|
||||||
flutter_process_text:
|
flutter_process_text:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -285,10 +285,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
|
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -52,7 +52,7 @@ dev_dependencies:
|
||||||
url: https://github.com/OutdatedGuy/flutter_launcher_icons.git
|
url: https://github.com/OutdatedGuy/flutter_launcher_icons.git
|
||||||
ref: feat/monochrome-icons-support
|
ref: feat/monochrome-icons-support
|
||||||
|
|
||||||
flutter_lints: ^2.0.2
|
flutter_lints: ^3.0.1
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
Loading…
Reference in New Issue
Block a user