mirror of
https://github.com/rainloreley/shlink-manager.git
synced 2024-11-09 13:55:12 +01:00
a ton of refactoring and commenting
This commit is contained in:
parent
de8bce4b5c
commit
7b16683d10
|
@ -1,10 +0,0 @@
|
||||||
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats_Visits.dart';
|
|
||||||
|
|
||||||
class ShlinkStats {
|
|
||||||
ShlinkStats_Visits nonOrphanVisits;
|
|
||||||
ShlinkStats_Visits orphanVisits;
|
|
||||||
int shortUrlsCount;
|
|
||||||
int tagsCount;
|
|
||||||
|
|
||||||
ShlinkStats(this.nonOrphanVisits, this.orphanVisits, this.shortUrlsCount, this.tagsCount);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
class ShlinkStats_Visits {
|
|
||||||
int total;
|
|
||||||
int nonBots;
|
|
||||||
int bots;
|
|
||||||
|
|
||||||
ShlinkStats_Visits(this.total, this.nonBots, this.bots);
|
|
||||||
|
|
||||||
ShlinkStats_Visits.fromJson(Map<String, dynamic> json)
|
|
||||||
: total = json["total"],
|
|
||||||
nonBots = json["nonBots"],
|
|
||||||
bots = json["bots"];
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
|
||||||
|
|
||||||
|
/// Includes data about the statistics of a Shlink instance
|
||||||
|
class ShlinkStats {
|
||||||
|
/// Data about non-orphan visits
|
||||||
|
VisitsSummary nonOrphanVisits;
|
||||||
|
/// Data about orphan visits (without any valid slug assigned)
|
||||||
|
VisitsSummary orphanVisits;
|
||||||
|
/// Total count of all short URLs
|
||||||
|
int shortUrlsCount;
|
||||||
|
/// Total count all all tags
|
||||||
|
int tagsCount;
|
||||||
|
|
||||||
|
ShlinkStats(this.nonOrphanVisits, this.orphanVisits, this.shortUrlsCount, this.tagsCount);
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
/// Visitor data
|
||||||
|
class ShlinkStatsVisits {
|
||||||
|
/// Count of URL visits
|
||||||
|
int total;
|
||||||
|
/// Count of URL visits from humans
|
||||||
|
int nonBots;
|
||||||
|
/// Count of URL visits from bots/crawlers
|
||||||
|
int bots;
|
||||||
|
|
||||||
|
ShlinkStatsVisits(this.total, this.nonBots, this.bots);
|
||||||
|
|
||||||
|
/// Converts the JSON data from the API to an instance of [ShlinkStatsVisits]
|
||||||
|
ShlinkStatsVisits.fromJson(Map<String, dynamic> json)
|
||||||
|
: total = json["total"],
|
||||||
|
nonBots = json["nonBots"],
|
||||||
|
bots = json["bots"];
|
||||||
|
}
|
|
@ -1,33 +0,0 @@
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL_DeviceLongUrls.dart';
|
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL_Meta.dart';
|
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL_VisitsSummary.dart';
|
|
||||||
|
|
||||||
class ShortURL {
|
|
||||||
String shortCode;
|
|
||||||
String shortUrl;
|
|
||||||
String longUrl;
|
|
||||||
ShortURL_DeviceLongUrls deviceLongUrls;
|
|
||||||
DateTime dateCreated;
|
|
||||||
ShortURL_VisitsSummary visitsSummary;
|
|
||||||
List<dynamic> tags;
|
|
||||||
ShortURL_Meta meta;
|
|
||||||
String? domain;
|
|
||||||
String? title;
|
|
||||||
bool crawlable;
|
|
||||||
|
|
||||||
ShortURL(this.shortCode, this.shortUrl, this.longUrl, this.deviceLongUrls, this.dateCreated, this.visitsSummary, this.tags, this.meta, this.domain, this.title, this.crawlable);
|
|
||||||
|
|
||||||
ShortURL.fromJson(Map<String, dynamic> json):
|
|
||||||
shortCode = json["shortCode"],
|
|
||||||
shortUrl = json["shortUrl"],
|
|
||||||
longUrl = json["longUrl"],
|
|
||||||
deviceLongUrls = ShortURL_DeviceLongUrls.fromJson(json["deviceLongUrls"]),
|
|
||||||
dateCreated = DateTime.parse(json["dateCreated"]),
|
|
||||||
visitsSummary = ShortURL_VisitsSummary.fromJson(json["visitsSummary"]),
|
|
||||||
tags = json["tags"],
|
|
||||||
meta = ShortURL_Meta.fromJson(json["meta"]),
|
|
||||||
domain = json["domain"],
|
|
||||||
title = json["title"],
|
|
||||||
crawlable = json["crawlable"];
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
class ShortURL_DeviceLongUrls {
|
|
||||||
final String? android;
|
|
||||||
final String? ios;
|
|
||||||
final String? desktop;
|
|
||||||
|
|
||||||
ShortURL_DeviceLongUrls(this.android, this.ios, this.desktop);
|
|
||||||
|
|
||||||
ShortURL_DeviceLongUrls.fromJson(Map<String, dynamic> json)
|
|
||||||
: android = json["android"],
|
|
||||||
ios = json["ios"],
|
|
||||||
desktop = json["desktop"];
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
"android": android,
|
|
||||||
"ios": ios,
|
|
||||||
"desktop": desktop
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
class ShortURL_Meta {
|
|
||||||
DateTime? validSince;
|
|
||||||
DateTime? validUntil;
|
|
||||||
int? maxVisits;
|
|
||||||
|
|
||||||
ShortURL_Meta(this.validSince, this.validUntil, this.maxVisits);
|
|
||||||
|
|
||||||
ShortURL_Meta.fromJson(Map<String, dynamic> json):
|
|
||||||
validSince = json["validSince"] != null ? DateTime.parse(json["validSince"]) : null,
|
|
||||||
validUntil = json["validUntil"] != null ? DateTime.parse(json["validUntil"]) : null,
|
|
||||||
maxVisits = json["maxVisits"];
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
class ShortURL_VisitsSummary {
|
|
||||||
int total;
|
|
||||||
int nonBots;
|
|
||||||
int bots;
|
|
||||||
|
|
||||||
ShortURL_VisitsSummary(this.total, this.nonBots, this.bots);
|
|
||||||
|
|
||||||
ShortURL_VisitsSummary.fromJson(Map<String, dynamic> json):
|
|
||||||
total = json["total"] as int,
|
|
||||||
nonBots = json["nonBots"] as int,
|
|
||||||
bots = json["bots"] as int;
|
|
||||||
}
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/// Data about device-specific long URLs for one short URL
|
||||||
|
class DeviceLongUrls {
|
||||||
|
/// Custom URL for Android devices
|
||||||
|
final String? android;
|
||||||
|
/// Custom URL for iOS devices
|
||||||
|
final String? ios;
|
||||||
|
/// Custom URL for desktop
|
||||||
|
final String? desktop;
|
||||||
|
|
||||||
|
DeviceLongUrls(this.android, this.ios, this.desktop);
|
||||||
|
|
||||||
|
/// Converts JSON data from the API to an instance of [DeviceLongUrls]
|
||||||
|
DeviceLongUrls.fromJson(Map<String, dynamic> json)
|
||||||
|
: android = json["android"],
|
||||||
|
ios = json["ios"],
|
||||||
|
desktop = json["desktop"];
|
||||||
|
|
||||||
|
/// Converts data from this class to an JSON object of type
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"android": android,
|
||||||
|
"ios": ios,
|
||||||
|
"desktop": desktop
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:shlink_app/API/Classes/ShortURL/device_long_urls.dart';
|
||||||
|
import 'package:shlink_app/API/Classes/ShortURL/short_url_meta.dart';
|
||||||
|
import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
|
||||||
|
|
||||||
|
/// Data about a short URL
|
||||||
|
class ShortURL {
|
||||||
|
/// Slug of the short URL used in the URL
|
||||||
|
String shortCode;
|
||||||
|
/// Entire short URL
|
||||||
|
String shortUrl;
|
||||||
|
/// Long URL where the user gets redirected to
|
||||||
|
String longUrl;
|
||||||
|
/// Device-specific long URLs
|
||||||
|
DeviceLongUrls deviceLongUrls;
|
||||||
|
/// Creation date of the short URL
|
||||||
|
DateTime dateCreated;
|
||||||
|
/// Visitor data
|
||||||
|
VisitsSummary visitsSummary;
|
||||||
|
/// List of tags assigned to this short URL
|
||||||
|
List<dynamic> tags;
|
||||||
|
/// Metadata
|
||||||
|
ShortURLMeta meta;
|
||||||
|
/// Associated domain
|
||||||
|
String? domain;
|
||||||
|
/// Optional title
|
||||||
|
String? title;
|
||||||
|
/// Whether the short URL is crawlable by a web crawler
|
||||||
|
bool crawlable;
|
||||||
|
|
||||||
|
ShortURL(this.shortCode, this.shortUrl, this.longUrl, this.deviceLongUrls, this.dateCreated, this.visitsSummary, this.tags, this.meta, this.domain, this.title, this.crawlable);
|
||||||
|
|
||||||
|
/// Converts the JSON data from the API to an instance of [ShortURL]
|
||||||
|
ShortURL.fromJson(Map<String, dynamic> json):
|
||||||
|
shortCode = json["shortCode"],
|
||||||
|
shortUrl = json["shortUrl"],
|
||||||
|
longUrl = json["longUrl"],
|
||||||
|
deviceLongUrls = DeviceLongUrls.fromJson(json["deviceLongUrls"]),
|
||||||
|
dateCreated = DateTime.parse(json["dateCreated"]),
|
||||||
|
visitsSummary = VisitsSummary.fromJson(json["visitsSummary"]),
|
||||||
|
tags = json["tags"],
|
||||||
|
meta = ShortURLMeta.fromJson(json["meta"]),
|
||||||
|
domain = json["domain"],
|
||||||
|
title = json["title"],
|
||||||
|
crawlable = json["crawlable"];
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
/// Metadata for a short URL
|
||||||
|
class ShortURLMeta {
|
||||||
|
/// The date since when this short URL has been valid
|
||||||
|
DateTime? validSince;
|
||||||
|
/// The data when this short URL expires
|
||||||
|
DateTime? validUntil;
|
||||||
|
/// Amount of maximum visits allowed to this short URL
|
||||||
|
int? maxVisits;
|
||||||
|
|
||||||
|
ShortURLMeta(this.validSince, this.validUntil, this.maxVisits);
|
||||||
|
|
||||||
|
/// Converts JSON data from the API to an instance of [ShortURLMeta]
|
||||||
|
ShortURLMeta.fromJson(Map<String, dynamic> json):
|
||||||
|
validSince = json["validSince"] != null ? DateTime.parse(json["validSince"]) : null,
|
||||||
|
validUntil = json["validUntil"] != null ? DateTime.parse(json["validUntil"]) : null,
|
||||||
|
maxVisits = json["maxVisits"];
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
/// Visitor data
|
||||||
|
class VisitsSummary {
|
||||||
|
/// Count of total visits
|
||||||
|
int total;
|
||||||
|
/// Count of visits from humans
|
||||||
|
int nonBots;
|
||||||
|
/// Count of visits from bots/crawlers
|
||||||
|
int bots;
|
||||||
|
|
||||||
|
VisitsSummary(this.total, this.nonBots, this.bots);
|
||||||
|
|
||||||
|
/// Converts JSON data from the API to an instance of [VisitsSummary]
|
||||||
|
VisitsSummary.fromJson(Map<String, dynamic> json):
|
||||||
|
total = json["total"] as int,
|
||||||
|
nonBots = json["nonBots"] as int,
|
||||||
|
bots = json["bots"] as int;
|
||||||
|
}
|
|
@ -1,22 +1,37 @@
|
||||||
import '../ShortURL/ShortURL_DeviceLongUrls.dart';
|
import '../ShortURL/device_long_urls.dart';
|
||||||
|
|
||||||
|
/// Data for a short URL which can be submitted to the server
|
||||||
class ShortURLSubmission {
|
class ShortURLSubmission {
|
||||||
|
/// Long URL to redirect to
|
||||||
String longUrl;
|
String longUrl;
|
||||||
ShortURL_DeviceLongUrls? deviceLongUrls;
|
/// Device-specific long URLs
|
||||||
|
DeviceLongUrls? deviceLongUrls;
|
||||||
|
/// 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
|
||||||
String? validUntil;
|
String? validUntil;
|
||||||
|
/// Amount of maximum visits allowed to this short URLs
|
||||||
int? maxVisits;
|
int? maxVisits;
|
||||||
|
/// List of tags assigned to this short URL
|
||||||
List<String> tags;
|
List<String> tags;
|
||||||
|
/// Title of the page
|
||||||
String? title;
|
String? title;
|
||||||
|
/// Whether the short URL is crawlable by web crawlers
|
||||||
bool crawlable;
|
bool crawlable;
|
||||||
|
/// Whether to forward query parameters
|
||||||
bool forwardQuery;
|
bool forwardQuery;
|
||||||
|
/// 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
|
||||||
bool findIfExists;
|
bool findIfExists;
|
||||||
|
/// Domain to use
|
||||||
String? domain;
|
String? domain;
|
||||||
|
/// 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
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
"longUrl": longUrl,
|
"longUrl": longUrl,
|
|
@ -2,12 +2,13 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import '../ServerManager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
FutureOr<Either<String, Failure>> API_connect(String? api_key, String? server_url, String apiVersion) async {
|
/// Tries to connect to the Shlink server
|
||||||
|
FutureOr<Either<String, Failure>> apiConnect(String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls"), headers: {
|
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return left("");
|
return left("");
|
||||||
|
|
|
@ -2,12 +2,13 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import '../ServerManager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
FutureOr<Either<String, Failure>> API_deleteShortUrl(String shortCode, String? api_key, String? server_url, String apiVersion) async {
|
/// Deletes a short URL from the server
|
||||||
|
FutureOr<Either<String, Failure>> apiDeleteShortUrl(String shortCode, String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.delete(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls/${shortCode}"), headers: {
|
final response = await http.delete(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls/$shortCode"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 204) {
|
if (response.statusCode == 204) {
|
||||||
// get returned short url
|
// get returned short url
|
|
@ -2,13 +2,14 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
|
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
|
||||||
import '../ServerManager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
FutureOr<Either<List<ShortURL>, Failure>> API_getRecentShortUrls(String? api_key, String? server_url, String apiVersion) async {
|
/// Gets recently created short URLs from the server
|
||||||
|
FutureOr<Either<List<ShortURL>, Failure>> apiGetRecentShortUrls(String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls?itemsPerPage=5&orderBy=dateCreated-DESC"), headers: {
|
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls?itemsPerPage=5&orderBy=dateCreated-DESC"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
|
@ -2,12 +2,13 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import '../ServerManager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
FutureOr<Either<ServerHealthResponse, Failure>> API_getServerHealth(String? api_key, String? server_url, String apiVersion) async {
|
/// Gets the status of the server and health information
|
||||||
|
FutureOr<Either<ServerHealthResponse, Failure>> apiGetServerHealth(String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/health"), headers: {
|
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/health"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonData = jsonDecode(response.body);
|
var jsonData = jsonDecode(response.body);
|
|
@ -2,11 +2,12 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats_Visits.dart';
|
import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
|
||||||
import '../Classes/ShlinkStats/ShlinkStats.dart';
|
import '../Classes/ShlinkStats/shlink_stats.dart';
|
||||||
import '../ServerManager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, String? server_url, String apiVersion) async {
|
/// Gets statistics about the Shlink server
|
||||||
|
FutureOr<Either<ShlinkStats, Failure>> apiGetShlinkStats(String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
|
|
||||||
var nonOrphanVisits;
|
var nonOrphanVisits;
|
||||||
var orphanVisits;
|
var orphanVisits;
|
||||||
|
@ -14,7 +15,7 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
|
||||||
var tagsCount;
|
var tagsCount;
|
||||||
var failure;
|
var failure;
|
||||||
|
|
||||||
var visitStatsResponse = await _getVisitStats(api_key, server_url, apiVersion);
|
var visitStatsResponse = await _getVisitStats(apiKey, serverUrl, apiVersion);
|
||||||
visitStatsResponse.fold((l) {
|
visitStatsResponse.fold((l) {
|
||||||
nonOrphanVisits = l.nonOrphanVisits;
|
nonOrphanVisits = l.nonOrphanVisits;
|
||||||
orphanVisits = l.orphanVisits;
|
orphanVisits = l.orphanVisits;
|
||||||
|
@ -23,7 +24,7 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
|
||||||
return right(r);
|
return right(r);
|
||||||
});
|
});
|
||||||
|
|
||||||
var shortUrlsCountResponse = await _getShortUrlsCount(api_key, server_url, apiVersion);
|
var shortUrlsCountResponse = await _getShortUrlsCount(apiKey, serverUrl, apiVersion);
|
||||||
shortUrlsCountResponse.fold((l) {
|
shortUrlsCountResponse.fold((l) {
|
||||||
shortUrlsCount = l;
|
shortUrlsCount = l;
|
||||||
}, (r) {
|
}, (r) {
|
||||||
|
@ -31,7 +32,7 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
|
||||||
return right(r);
|
return right(r);
|
||||||
});
|
});
|
||||||
|
|
||||||
var tagsCountResponse = await _getTagsCount(api_key, server_url, apiVersion);
|
var tagsCountResponse = await _getTagsCount(apiKey, serverUrl, apiVersion);
|
||||||
tagsCountResponse.fold((l) {
|
tagsCountResponse.fold((l) {
|
||||||
tagsCount = l;
|
tagsCount = l;
|
||||||
}, (r) {
|
}, (r) {
|
||||||
|
@ -40,7 +41,7 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
|
||||||
});
|
});
|
||||||
|
|
||||||
while(failure == null && (nonOrphanVisits == null || orphanVisits == null || shortUrlsCount == null || tagsCount == null)) {
|
while(failure == null && (nonOrphanVisits == null || orphanVisits == null || shortUrlsCount == null || tagsCount == null)) {
|
||||||
await Future.delayed(Duration(milliseconds: 100));
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failure != null) {
|
if (failure != null) {
|
||||||
|
@ -49,37 +50,23 @@ FutureOr<Either<ShlinkStats, Failure>> API_getShlinkStats(String? api_key, Strin
|
||||||
return left(ShlinkStats(nonOrphanVisits, orphanVisits, shortUrlsCount, tagsCount));
|
return left(ShlinkStats(nonOrphanVisits, orphanVisits, shortUrlsCount, tagsCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Future<tuple.Tuple3<FutureOr<Either<_ShlinkVisitStats, Failure>>, FutureOr<Either<int, Failure>>, FutureOr<Either<int, Failure>>>> waiterFunction(String? api_key, String? server_url, String apiVersion) async {
|
|
||||||
late FutureOr<Either<_ShlinkVisitStats, Failure>> visits;
|
|
||||||
late FutureOr<Either<int, Failure>> shortUrlsCount;
|
|
||||||
late FutureOr<Either<int, Failure>> tagsCount;
|
|
||||||
|
|
||||||
await Future.wait([
|
|
||||||
_getVisitStats(api_key, server_url, apiVersion).then((value) => visits = value),
|
|
||||||
_getShortUrlsCount(api_key, server_url, apiVersion).then((value) => shortUrlsCount = value),
|
|
||||||
_getTagsCount(api_key, server_url, apiVersion).then((value) => tagsCount = value),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return Future.value(tuple.Tuple3(visits, shortUrlsCount, tagsCount));
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
class _ShlinkVisitStats {
|
class _ShlinkVisitStats {
|
||||||
ShlinkStats_Visits nonOrphanVisits;
|
VisitsSummary nonOrphanVisits;
|
||||||
ShlinkStats_Visits orphanVisits;
|
VisitsSummary orphanVisits;
|
||||||
|
|
||||||
_ShlinkVisitStats(this.nonOrphanVisits, this.orphanVisits);
|
_ShlinkVisitStats(this.nonOrphanVisits, this.orphanVisits);
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<Either<_ShlinkVisitStats, Failure>> _getVisitStats(String? api_key, String? server_url, String apiVersion) async {
|
/// Gets visitor statistics about the entire server
|
||||||
|
FutureOr<Either<_ShlinkVisitStats, Failure>> _getVisitStats(String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/visits"), headers: {
|
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/visits"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
||||||
var nonOrphanVisits = ShlinkStats_Visits.fromJson(jsonResponse["visits"]["nonOrphanVisits"]);
|
var nonOrphanVisits = VisitsSummary.fromJson(jsonResponse["visits"]["nonOrphanVisits"]);
|
||||||
var orphanVisits = ShlinkStats_Visits.fromJson(jsonResponse["visits"]["orphanVisits"]);
|
var orphanVisits = VisitsSummary.fromJson(jsonResponse["visits"]["orphanVisits"]);
|
||||||
return left(_ShlinkVisitStats(nonOrphanVisits, orphanVisits));
|
return left(_ShlinkVisitStats(nonOrphanVisits, orphanVisits));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -98,11 +85,11 @@ FutureOr<Either<_ShlinkVisitStats, Failure>> _getVisitStats(String? api_key, Str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get short urls count
|
/// Gets amount of short URLs
|
||||||
FutureOr<Either<int, Failure>> _getShortUrlsCount(String? api_key, String? server_url, String apiVersion) async {
|
FutureOr<Either<int, Failure>> _getShortUrlsCount(String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls"), headers: {
|
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
||||||
|
@ -123,11 +110,11 @@ FutureOr<Either<int, Failure>> _getShortUrlsCount(String? api_key, String? serve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get tags count
|
/// Gets amount of tags
|
||||||
FutureOr<Either<int, Failure>> _getTagsCount(String? api_key, String? server_url, String apiVersion) async {
|
FutureOr<Either<int, Failure>> _getTagsCount(String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/tags"), headers: {
|
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/tags"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
|
@ -2,39 +2,41 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
|
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
|
||||||
import '../ServerManager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
FutureOr<Either<List<ShortURL>, Failure>> API_getShortUrls(String? api_key, String? server_url, String apiVersion) async {
|
/// Gets all short URLs
|
||||||
var _currentPage = 1;
|
FutureOr<Either<List<ShortURL>, Failure>> apiGetShortUrls(String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
var _maxPages = 2;
|
var currentPage = 1;
|
||||||
List<ShortURL> _allUrls = [];
|
var maxPages = 2;
|
||||||
|
List<ShortURL> allUrls = [];
|
||||||
|
|
||||||
Failure? error;
|
Failure? error;
|
||||||
|
|
||||||
while (_currentPage <= _maxPages) {
|
while (currentPage <= maxPages) {
|
||||||
final response = await _getShortUrlPage(_currentPage, api_key, server_url, 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;
|
||||||
_currentPage++;
|
currentPage++;
|
||||||
}, (r) {
|
}, (r) {
|
||||||
_maxPages = 0;
|
maxPages = 0;
|
||||||
error = r;
|
error = r;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
return left(_allUrls);
|
return left(allUrls);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return right(error!);
|
return right(error!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<Either<ShortURLPageResponse, Failure>> _getShortUrlPage(int page, String? api_key, String? server_url, String apiVersion) async {
|
/// Gets all short URLs from a specific page
|
||||||
|
FutureOr<Either<ShortURLPageResponse, Failure>> _getShortUrlPage(int page, String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls?page=${page}"), headers: {
|
final response = await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls?page=$page"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var jsonResponse = jsonDecode(response.body);
|
var jsonResponse = jsonDecode(response.body);
|
|
@ -2,13 +2,14 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:shlink_app/API/Classes/ShortURLSubmission/ShortURLSubmission.dart';
|
import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.dart';
|
||||||
import '../ServerManager.dart';
|
import '../server_manager.dart';
|
||||||
|
|
||||||
FutureOr<Either<String, Failure>> API_submitShortUrl(ShortURLSubmission shortUrl, String? api_key, String? server_url, String apiVersion) async {
|
/// Submits a short URL to a server for it to be added
|
||||||
|
FutureOr<Either<String, Failure>> apiSubmitShortUrl(ShortURLSubmission shortUrl, String? apiKey, String? serverUrl, String apiVersion) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.post(Uri.parse("${server_url}/rest/v${apiVersion}/short-urls"), headers: {
|
final response = await http.post(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls"), headers: {
|
||||||
"X-Api-Key": api_key ?? "",
|
"X-Api-Key": apiKey ?? "",
|
||||||
}, body: jsonEncode(shortUrl.toJson()));
|
}, body: jsonEncode(shortUrl.toJson()));
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// get returned short url
|
// get returned short url
|
||||||
|
@ -18,7 +19,7 @@ FutureOr<Either<String, Failure>> API_submitShortUrl(ShortURLSubmission 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"] ?? null));
|
return right(ApiFailure(type: jsonBody["type"], detail: jsonBody["detail"], title: jsonBody["title"], status: jsonBody["status"], invalidElements: jsonBody["invalidElements"]));
|
||||||
}
|
}
|
||||||
catch(resErr) {
|
catch(resErr) {
|
||||||
return right(RequestFailure(response.statusCode, resErr.toString()));
|
return right(RequestFailure(response.statusCode, resErr.toString()));
|
|
@ -2,113 +2,125 @@ import 'dart:async';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats.dart';
|
import 'package:shlink_app/API/Classes/ShlinkStats/shlink_stats.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
|
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShortURLSubmission/ShortURLSubmission.dart';
|
import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.dart';
|
||||||
import 'package:shlink_app/API/Methods/connect.dart';
|
import 'package:shlink_app/API/Methods/connect.dart';
|
||||||
import 'package:shlink_app/API/Methods/getRecentShortUrls.dart';
|
import 'package:shlink_app/API/Methods/get_recent_short_urls.dart';
|
||||||
import 'package:shlink_app/API/Methods/getServerHealth.dart';
|
import 'package:shlink_app/API/Methods/get_server_health.dart';
|
||||||
import 'package:shlink_app/API/Methods/getShlinkStats.dart';
|
import 'package:shlink_app/API/Methods/get_shlink_stats.dart';
|
||||||
import 'package:shlink_app/API/Methods/getShortUrls.dart';
|
import 'package:shlink_app/API/Methods/get_short_urls.dart';
|
||||||
|
|
||||||
import 'Methods/deleteShortUrl.dart';
|
import 'Methods/delete_short_url.dart';
|
||||||
import 'Methods/submitShortUrl.dart';
|
import 'Methods/submit_short_url.dart';
|
||||||
|
|
||||||
class ServerManager {
|
class ServerManager {
|
||||||
|
|
||||||
String? _server_url;
|
/// The URL of the Shlink server
|
||||||
String? _api_key;
|
String? serverUrl;
|
||||||
|
|
||||||
|
/// The API key to access the server
|
||||||
|
String? apiKey;
|
||||||
|
|
||||||
|
/// Current Shlink API Version used by the app
|
||||||
static String apiVersion = "3";
|
static String apiVersion = "3";
|
||||||
|
|
||||||
String getServerUrl() {
|
String getServerUrl() {
|
||||||
return _server_url ?? "";
|
return serverUrl ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
String getApiVersion() {
|
String getApiVersion() {
|
||||||
return apiVersion;
|
return apiVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the user provided information about the server (url and apikey)
|
||||||
Future<bool> checkLogin() async {
|
Future<bool> checkLogin() async {
|
||||||
await _loadCredentials();
|
await _loadCredentials();
|
||||||
return (_server_url != null);
|
return (serverUrl != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Logs out the user and removes data about the Shlink server
|
||||||
Future<void> logOut() async {
|
Future<void> logOut() async {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
await storage.delete(key: "shlink_url");
|
await storage.delete(key: "shlink_url");
|
||||||
await storage.delete(key: "shlink_apikey");
|
await storage.delete(key: "shlink_apikey");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads the server credentials from [FlutterSecureStorage]
|
||||||
Future<void> _loadCredentials() async {
|
Future<void> _loadCredentials() async {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
if (prefs.getBool('first_run') ?? true) {
|
if (prefs.getBool('first_run') ?? true) {
|
||||||
FlutterSecureStorage storage = FlutterSecureStorage();
|
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
await storage.deleteAll();
|
await storage.deleteAll();
|
||||||
|
|
||||||
prefs.setBool('first_run', false);
|
prefs.setBool('first_run', false);
|
||||||
}
|
}
|
||||||
_server_url = await storage.read(key: "shlink_url");
|
serverUrl = await storage.read(key: "shlink_url");
|
||||||
_api_key = await storage.read(key: "shlink_apikey");
|
apiKey = await storage.read(key: "shlink_apikey");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves the provided server credentials to [FlutterSecureStorage]
|
||||||
void _saveCredentials(String url, String apiKey) async {
|
void _saveCredentials(String url, String apiKey) async {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
storage.write(key: "shlink_url", value: url);
|
storage.write(key: "shlink_url", value: url);
|
||||||
storage.write(key: "shlink_apikey", value: apiKey);
|
storage.write(key: "shlink_apikey", value: apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeCredentials() async {
|
|
||||||
const storage = FlutterSecureStorage();
|
|
||||||
storage.delete(key: "shlink_url");
|
|
||||||
storage.delete(key: "shlink_apikey");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Saves provided server credentials and tries to establish a connection
|
||||||
FutureOr<Either<String, Failure>> initAndConnect(String url, String apiKey) async {
|
FutureOr<Either<String, Failure>> initAndConnect(String url, String apiKey) async {
|
||||||
// TODO: convert url to correct format
|
// TODO: convert url to correct format
|
||||||
_server_url = url;
|
serverUrl = url;
|
||||||
_api_key = apiKey;
|
this.apiKey = apiKey;
|
||||||
_saveCredentials(url, apiKey);
|
_saveCredentials(url, apiKey);
|
||||||
final result = await connect();
|
final result = await connect();
|
||||||
result.fold((l) => null, (r) {
|
result.fold((l) => null, (r) {
|
||||||
_removeCredentials();
|
logOut();
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Establishes a connection to the server
|
||||||
FutureOr<Either<String, Failure>> connect() async {
|
FutureOr<Either<String, Failure>> connect() async {
|
||||||
_loadCredentials();
|
_loadCredentials();
|
||||||
return API_connect(_api_key, _server_url, apiVersion);
|
return apiConnect(apiKey, serverUrl, apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets all short URLs from the server
|
||||||
FutureOr<Either<List<ShortURL>, Failure>> getShortUrls() async {
|
FutureOr<Either<List<ShortURL>, Failure>> getShortUrls() async {
|
||||||
return API_getShortUrls(_api_key, _server_url, apiVersion);
|
return apiGetShortUrls(apiKey, serverUrl, apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets statistics about the Shlink instance
|
||||||
FutureOr<Either<ShlinkStats, Failure>> getShlinkStats() async {
|
FutureOr<Either<ShlinkStats, Failure>> getShlinkStats() async {
|
||||||
return API_getShlinkStats(_api_key, _server_url, apiVersion);
|
return apiGetShlinkStats(apiKey, serverUrl, apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves a new short URL to the server
|
||||||
FutureOr<Either<String, Failure>> submitShortUrl(ShortURLSubmission shortUrl) async {
|
FutureOr<Either<String, Failure>> submitShortUrl(ShortURLSubmission shortUrl) async {
|
||||||
return API_submitShortUrl(shortUrl, _api_key, _server_url, apiVersion);
|
return apiSubmitShortUrl(shortUrl, apiKey, serverUrl, apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes a short URL from the server, identified by its slug
|
||||||
FutureOr<Either<String, Failure>> deleteShortUrl(String shortCode) async {
|
FutureOr<Either<String, Failure>> deleteShortUrl(String shortCode) async {
|
||||||
return API_deleteShortUrl(shortCode, _api_key, _server_url, apiVersion);
|
return apiDeleteShortUrl(shortCode, apiKey, serverUrl, apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets health data about the server
|
||||||
FutureOr<Either<ServerHealthResponse, Failure>> getServerHealth() async {
|
FutureOr<Either<ServerHealthResponse, Failure>> getServerHealth() async {
|
||||||
return API_getServerHealth(_api_key, _server_url, apiVersion);
|
return apiGetServerHealth(apiKey, serverUrl, apiVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets recently created/used short URLs from the server
|
||||||
FutureOr<Either<List<ShortURL>, Failure>> getRecentShortUrls() async {
|
FutureOr<Either<List<ShortURL>, Failure>> getRecentShortUrls() async {
|
||||||
return API_getRecentShortUrls(_api_key, _server_url, apiVersion);
|
return apiGetRecentShortUrls(apiKey, serverUrl, apiVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Server response data type about a page of short URLs from the server
|
||||||
class ShortURLPageResponse {
|
class ShortURLPageResponse {
|
||||||
List<ShortURL> urls;
|
List<ShortURL> urls;
|
||||||
int totalPages;
|
int totalPages;
|
||||||
|
@ -116,6 +128,7 @@ class ShortURLPageResponse {
|
||||||
ShortURLPageResponse(this.urls, this.totalPages);
|
ShortURLPageResponse(this.urls, this.totalPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Server response data type about the health status of the server
|
||||||
class ServerHealthResponse {
|
class ServerHealthResponse {
|
||||||
String status;
|
String status;
|
||||||
String version;
|
String version;
|
||||||
|
@ -123,8 +136,10 @@ class ServerHealthResponse {
|
||||||
ServerHealthResponse({required this.status, required this.version});
|
ServerHealthResponse({required this.status, required this.version});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
class RequestFailure extends Failure {
|
class RequestFailure extends Failure {
|
||||||
int statusCode;
|
int statusCode;
|
||||||
String description;
|
String description;
|
||||||
|
@ -132,6 +147,7 @@ class RequestFailure extends Failure {
|
||||||
RequestFailure(this.statusCode, this.description);
|
RequestFailure(this.statusCode, this.description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains information about an error returned by the Shlink API
|
||||||
class ApiFailure extends Failure {
|
class ApiFailure extends Failure {
|
||||||
String type;
|
String type;
|
||||||
String detail;
|
String detail;
|
|
@ -1,4 +1,4 @@
|
||||||
library dev.abmgrt.shlink_app.globals;
|
library dev.abmgrt.shlink_app.globals;
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/server_manager.dart';
|
||||||
|
|
||||||
ServerManager serverManager = ServerManager();
|
ServerManager serverManager = ServerManager();
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shlink_app/LoginView.dart';
|
import 'package:shlink_app/views/login_view.dart';
|
||||||
import 'package:shlink_app/NavigationBarView.dart';
|
import 'package:shlink_app/views/navigationbar_view.dart';
|
||||||
import 'globals.dart' as globals;
|
import 'globals.dart' as globals;
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ void main() {
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
static final _defaultLightColorScheme =
|
static const _defaultLightColorScheme =
|
||||||
ColorScheme.light();//.fromSwatch(primarySwatch: Colors.blue, backgroundColor: Colors.white);
|
ColorScheme.light();//.fromSwatch(primarySwatch: Colors.blue, backgroundColor: Colors.white);
|
||||||
|
|
||||||
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(
|
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(
|
||||||
|
@ -25,14 +25,14 @@ class MyApp extends StatelessWidget {
|
||||||
title: 'Shlink',
|
title: 'Shlink',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
appBarTheme: 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: AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color(0xff0d0d0d),
|
backgroundColor: Color(0xff0d0d0d),
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
|
@ -40,26 +40,8 @@ class MyApp extends StatelessWidget {
|
||||||
colorScheme: darkColorScheme?.copyWith(background: Colors.black) ?? _defaultDarkColorScheme,
|
colorScheme: darkColorScheme?.copyWith(background: Colors.black) ?? _defaultDarkColorScheme,
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
/*theme: ThemeData(
|
|
||||||
primarySwatch: Colors.blue,
|
|
||||||
brightness: Brightness.light,
|
|
||||||
useMaterial3: true,
|
|
||||||
colorScheme: ColorScheme.fromSwatch().copyWith(
|
|
||||||
secondary: Colors.orange
|
|
||||||
)
|
|
||||||
),*/
|
|
||||||
/*darkTheme: ThemeData(
|
|
||||||
primarySwatch: Colors.blue,
|
|
||||||
brightness: Brightness.dark,
|
|
||||||
useMaterial3: true,
|
|
||||||
colorScheme: ColorScheme.dark(
|
|
||||||
background: Colors.black,
|
|
||||||
surface: Color(0xff0d0d0d),
|
|
||||||
secondaryContainer: Colors.grey[300]
|
|
||||||
)
|
|
||||||
),*/
|
|
||||||
themeMode: ThemeMode.system,
|
themeMode: ThemeMode.system,
|
||||||
home: InitialPage()
|
home: const InitialPage()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -97,7 +79,7 @@ class _InitialPageState extends State<InitialPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return const Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text("")
|
child: Text("")
|
||||||
),
|
),
|
||||||
|
|
|
@ -26,7 +26,7 @@ class LicenseUtil {
|
||||||
|
|
||||||
static List<License> getLicenses() {
|
static List<License> getLicenses() {
|
||||||
return [
|
return [
|
||||||
License(
|
const License(
|
||||||
name: r'cupertino_icons',
|
name: r'cupertino_icons',
|
||||||
license: r'''The MIT License (MIT)
|
license: r'''The MIT License (MIT)
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''',
|
||||||
homepage: null,
|
homepage: null,
|
||||||
repository: r'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons',
|
repository: r'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons',
|
||||||
),
|
),
|
||||||
License(
|
const License(
|
||||||
name: r'dartz',
|
name: r'dartz',
|
||||||
license: r'''The MIT License (MIT)
|
license: r'''The MIT License (MIT)
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ SOFTWARE.
|
||||||
homepage: r'https://github.com/spebbe/dartz',
|
homepage: r'https://github.com/spebbe/dartz',
|
||||||
repository: null,
|
repository: null,
|
||||||
),
|
),
|
||||||
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
|
||||||
|
@ -288,7 +288,7 @@ SOFTWARE.
|
||||||
homepage: null,
|
homepage: null,
|
||||||
repository: r'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color',
|
repository: r'https://github.com/material-foundation/flutter-packages/tree/main/packages/dynamic_color',
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
|
||||||
|
@ -320,7 +320,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
homepage: r'https://flutter.dev/',
|
homepage: r'https://flutter.dev/',
|
||||||
repository: r'https://github.com/flutter/flutter',
|
repository: r'https://github.com/flutter/flutter',
|
||||||
),
|
),
|
||||||
License(
|
const License(
|
||||||
name: r'flutter_launcher_icons',
|
name: r'flutter_launcher_icons',
|
||||||
license: r'''MIT License
|
license: r'''MIT License
|
||||||
|
|
||||||
|
@ -348,7 +348,7 @@ SOFTWARE.
|
||||||
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/',
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
|
||||||
|
@ -380,7 +380,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
homepage: null,
|
homepage: null,
|
||||||
repository: r'https://github.com/flutter/packages/tree/main/packages/flutter_lints',
|
repository: r'https://github.com/flutter/packages/tree/main/packages/flutter_lints',
|
||||||
),
|
),
|
||||||
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
|
||||||
|
|
||||||
|
@ -413,7 +413,7 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
homepage: null,
|
homepage: null,
|
||||||
repository: r'https://github.com/DevsOnFlutter/flutter_process_text',
|
repository: r'https://github.com/DevsOnFlutter/flutter_process_text',
|
||||||
),
|
),
|
||||||
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
|
||||||
|
|
||||||
|
@ -448,7 +448,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
homepage: null,
|
homepage: null,
|
||||||
repository: r'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage',
|
repository: r'https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage',
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
|
||||||
|
@ -480,7 +480,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
homepage: r'https://flutter.dev/',
|
homepage: r'https://flutter.dev/',
|
||||||
repository: r'https://github.com/flutter/flutter',
|
repository: r'https://github.com/flutter/flutter',
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
|
||||||
|
@ -514,7 +514,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
homepage: null,
|
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',
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
|
||||||
|
@ -548,7 +548,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
homepage: null,
|
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',
|
||||||
),
|
),
|
||||||
License(
|
const License(
|
||||||
name: r'license_generator',
|
name: r'license_generator',
|
||||||
license: r'''MIT License
|
license: r'''MIT License
|
||||||
|
|
||||||
|
@ -576,7 +576,7 @@ SOFTWARE.
|
||||||
homepage: r'https://github.com/icapps/flutter-icapps-license',
|
homepage: r'https://github.com/icapps/flutter-icapps-license',
|
||||||
repository: null,
|
repository: null,
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
|
||||||
|
@ -610,7 +610,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
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/',
|
||||||
),
|
),
|
||||||
License(
|
const License(
|
||||||
name: r'qr_flutter',
|
name: r'qr_flutter',
|
||||||
license: r'''BSD 3-Clause License
|
license: r'''BSD 3-Clause License
|
||||||
|
|
||||||
|
@ -646,7 +646,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
homepage: r'https://github.com/theyakka/qr.flutter',
|
homepage: r'https://github.com/theyakka/qr.flutter',
|
||||||
repository: null,
|
repository: null,
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
|
||||||
|
@ -678,7 +678,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
homepage: null,
|
homepage: null,
|
||||||
repository: r'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences',
|
repository: r'https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences',
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
@ -706,7 +706,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
homepage: null,
|
homepage: null,
|
||||||
repository: r'https://github.com/google/tuple.dart',
|
repository: r'https://github.com/google/tuple.dart',
|
||||||
),
|
),
|
||||||
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.
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShlinkStats/ShlinkStats.dart';
|
import 'package:shlink_app/API/Classes/ShlinkStats/shlink_stats.dart';
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/server_manager.dart';
|
||||||
import 'package:shlink_app/LoginView.dart';
|
import 'package:shlink_app/views/short_url_edit_view.dart';
|
||||||
import 'package:shlink_app/ShortURLEditView.dart';
|
import 'package:shlink_app/views/url_list_view.dart';
|
||||||
import 'package:shlink_app/URLListView.dart';
|
import '../API/Classes/ShortURL/short_url.dart';
|
||||||
import 'API/Classes/ShortURL/ShortURL.dart';
|
import '../globals.dart' as globals;
|
||||||
import 'globals.dart' as globals;
|
|
||||||
|
|
||||||
class HomeView extends StatefulWidget {
|
class HomeView extends StatefulWidget {
|
||||||
const HomeView({Key? key}) : super(key: key);
|
const HomeView({Key? key}) : super(key: key);
|
||||||
|
@ -35,8 +34,8 @@ class _HomeViewState extends State<HomeView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadAllData() async {
|
Future<void> loadAllData() async {
|
||||||
var resultStats = await loadShlinkStats();
|
await loadShlinkStats();
|
||||||
var resultShortUrls = await loadRecentShortUrls();
|
await loadRecentShortUrls();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +98,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
title: Column(
|
title: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Shlink", style: TextStyle(fontWeight: FontWeight.bold)),
|
const Text("Shlink", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
Text(globals.serverManager.getServerUrl(), style: TextStyle(fontSize: 16, color: Colors.grey[600]))
|
Text(globals.serverManager.getServerUrl(), style: TextStyle(fontSize: 16, color: Colors.grey[600]))
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -119,12 +118,12 @@ class _HomeViewState extends State<HomeView> {
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(top: 50),
|
padding: const EdgeInsets.only(top: 50),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
|
const Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
|
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -134,9 +133,9 @@ class _HomeViewState extends State<HomeView> {
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
SliverList(delegate: SliverChildBuilderDelegate(
|
SliverList(delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext _context, int index) {
|
(BuildContext context, int index) {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
return Padding(
|
return const Padding(
|
||||||
padding: EdgeInsets.only(top: 16, left: 12, right: 12),
|
padding: EdgeInsets.only(top: 16, left: 12, right: 12),
|
||||||
child: Text("Recent Short URLs", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
child: Text("Recent Short URLs", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||||
);
|
);
|
||||||
|
@ -200,10 +199,10 @@ class _HomeViewState extends State<HomeView> {
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
|
await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
|
||||||
loadRecentShortUrls();
|
loadRecentShortUrls();
|
||||||
},
|
},
|
||||||
child: Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -211,11 +210,11 @@ class _HomeViewState extends State<HomeView> {
|
||||||
|
|
||||||
// stats card widget
|
// stats card widget
|
||||||
class _ShlinkStatsCardWidget extends StatefulWidget {
|
class _ShlinkStatsCardWidget extends StatefulWidget {
|
||||||
const _ShlinkStatsCardWidget({this.text, this.icon, this.borderColor});
|
const _ShlinkStatsCardWidget({required this.text, required this.icon, this.borderColor});
|
||||||
|
|
||||||
final icon;
|
final IconData icon;
|
||||||
final borderColor;
|
final Color? borderColor;
|
||||||
final text;
|
final String text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ShlinkStatsCardWidget> createState() => _ShlinkStatsCardWidgetState();
|
State<_ShlinkStatsCardWidget> createState() => _ShlinkStatsCardWidgetState();
|
||||||
|
@ -226,9 +225,9 @@ class _ShlinkStatsCardWidgetState extends State<_ShlinkStatsCardWidget> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var randomColor = ([...Colors.primaries]..shuffle()).first;
|
var randomColor = ([...Colors.primaries]..shuffle()).first;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: 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)
|
||||||
|
@ -238,8 +237,8 @@ class _ShlinkStatsCardWidgetState extends State<_ShlinkStatsCardWidget> {
|
||||||
children: [
|
children: [
|
||||||
Icon(widget.icon),
|
Icon(widget.icon),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: 4),
|
padding: const EdgeInsets.only(left: 4),
|
||||||
child: Text(widget.text, style: TextStyle(fontWeight: FontWeight.bold)),
|
child: Text(widget.text, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/server_manager.dart';
|
||||||
import 'package:shlink_app/main.dart';
|
import '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({Key? key}) : super(key: key);
|
||||||
|
@ -11,8 +11,8 @@ class LoginView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginViewState extends State<LoginView> {
|
class _LoginViewState extends State<LoginView> {
|
||||||
late TextEditingController _server_url_controller;
|
late TextEditingController _serverUrlController;
|
||||||
late TextEditingController _apikey_controller;
|
late TextEditingController _apiKeyController;
|
||||||
|
|
||||||
bool _isLoggingIn = false;
|
bool _isLoggingIn = false;
|
||||||
String _errorMessage = "";
|
String _errorMessage = "";
|
||||||
|
@ -21,8 +21,8 @@ class _LoginViewState extends State<LoginView> {
|
||||||
void initState() {
|
void initState() {
|
||||||
// TODO: implement initState
|
// TODO: implement initState
|
||||||
super.initState();
|
super.initState();
|
||||||
_server_url_controller = TextEditingController();
|
_serverUrlController = TextEditingController();
|
||||||
_apikey_controller = TextEditingController();
|
_apiKeyController = TextEditingController();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _connect() async {
|
void _connect() async {
|
||||||
|
@ -30,7 +30,7 @@ class _LoginViewState extends State<LoginView> {
|
||||||
_isLoggingIn = true;
|
_isLoggingIn = true;
|
||||||
_errorMessage = "";
|
_errorMessage = "";
|
||||||
});
|
});
|
||||||
final connectResult = await globals.serverManager.initAndConnect(_server_url_controller.text, _apikey_controller.text);
|
final connectResult = await globals.serverManager.initAndConnect(_serverUrlController.text, _apiKeyController.text);
|
||||||
connectResult.fold((l) {
|
connectResult.fold((l) {
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(builder: (context) => const InitialPage())
|
MaterialPageRoute(builder: (context) => const InitialPage())
|
||||||
|
@ -61,8 +61,8 @@ class _LoginViewState extends State<LoginView> {
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar.medium(
|
const SliverAppBar.medium(
|
||||||
title: const Text("Add server", style: TextStyle(fontWeight: FontWeight.bold))
|
title: Text("Add server", style: TextStyle(fontWeight: FontWeight.bold))
|
||||||
),
|
),
|
||||||
SliverFillRemaining(
|
SliverFillRemaining(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -75,10 +75,10 @@ class _LoginViewState extends State<LoginView> {
|
||||||
child: Text("Server URL", style: TextStyle(fontWeight: FontWeight.bold),)),
|
child: Text("Server URL", style: TextStyle(fontWeight: FontWeight.bold),)),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.dns_outlined),
|
const Icon(Icons.dns_outlined),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: TextField(
|
Expanded(child: TextField(
|
||||||
controller: _server_url_controller,
|
controller: _serverUrlController,
|
||||||
keyboardType: TextInputType.url,
|
keyboardType: TextInputType.url,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
|
@ -93,10 +93,10 @@ class _LoginViewState extends State<LoginView> {
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.key),
|
const Icon(Icons.key),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(child: TextField(
|
Expanded(child: TextField(
|
||||||
controller: _apikey_controller,
|
controller: _apiKeyController,
|
||||||
keyboardType: TextInputType.text,
|
keyboardType: TextInputType.text,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
@ -130,7 +130,7 @@ class _LoginViewState extends State<LoginView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Flexible(child: Text(_errorMessage, style: TextStyle(color: Colors.red), textAlign: TextAlign.center))
|
Flexible(child: Text(_errorMessage, style: const TextStyle(color: Colors.red), textAlign: TextAlign.center))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shlink_app/SettingsView.dart';
|
import 'package:shlink_app/views/settings_view.dart';
|
||||||
import 'package:shlink_app/HomeView.dart';
|
import 'package:shlink_app/views/home_view.dart';
|
||||||
import 'package:shlink_app/URLListView.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({Key? key}) : super(key: key);
|
||||||
|
@ -12,7 +12,7 @@ class NavigationBarView extends StatefulWidget {
|
||||||
|
|
||||||
class _NavigationBarViewState extends State<NavigationBarView> {
|
class _NavigationBarViewState extends State<NavigationBarView> {
|
||||||
|
|
||||||
final List<Widget> views = [HomeView(), URLListView(), SettingsView()];
|
final List<Widget> views = [const HomeView(), const URLListView(), const SettingsView()];
|
||||||
int _selectedView = 0;
|
int _selectedView = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -20,7 +20,7 @@ class _NavigationBarViewState extends State<NavigationBarView> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: views.elementAt(_selectedView),
|
body: views.elementAt(_selectedView),
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar: NavigationBar(
|
||||||
destinations: [
|
destinations: const [
|
||||||
NavigationDestination(icon: Icon(Icons.home), label: "Home"),
|
NavigationDestination(icon: Icon(Icons.home), label: "Home"),
|
||||||
NavigationDestination(icon: Icon(Icons.link), label: "Short URLs"),
|
NavigationDestination(icon: Icon(Icons.link), label: "Short URLs"),
|
||||||
NavigationDestination(icon: Icon(Icons.settings), label: "Settings")
|
NavigationDestination(icon: Icon(Icons.settings), label: "Settings")
|
|
@ -15,9 +15,9 @@ class _OpenSourceLicensesViewState extends State<OpenSourceLicensesView> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar.medium(
|
const SliverAppBar.medium(
|
||||||
expandedHeight: 120,
|
expandedHeight: 120,
|
||||||
title: const Text("Open Source Licenses", style: TextStyle(fontWeight: FontWeight.bold),)
|
title: Text("Open Source Licenses", style: TextStyle(fontWeight: FontWeight.bold),)
|
||||||
),
|
),
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
@ -32,23 +32,23 @@ class _OpenSourceLicensesViewState extends State<OpenSourceLicensesView> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
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: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
|
padding: const EdgeInsets.only(left: 12, right: 12, top: 20, bottom: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("${currentLicense.name}", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
|
Text(currentLicense.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
|
||||||
Text("Version: ${currentLicense.version ?? "N/A"}", style: TextStyle(color: Colors.grey)),
|
Text("Version: ${currentLicense.version ?? "N/A"}", style: const TextStyle(color: Colors.grey)),
|
||||||
SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Divider(),
|
const Divider(),
|
||||||
SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text("${currentLicense.license}", textAlign: TextAlign.justify, style: TextStyle(color: Colors.grey)),
|
Text(currentLicense.license, textAlign: TextAlign.justify, style: const TextStyle(color: Colors.grey)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -59,7 +59,7 @@ class _OpenSourceLicensesViewState extends State<OpenSourceLicensesView> {
|
||||||
childCount: LicenseUtil.getLicenses().length
|
childCount: LicenseUtil.getLicenses().length
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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,),
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/server_manager.dart';
|
||||||
import 'package:shlink_app/LoginView.dart';
|
import 'package:shlink_app/views/login_view.dart';
|
||||||
import 'package:shlink_app/OpenSourceLicensesView.dart';
|
import 'package:shlink_app/views/opensource_licenses_view.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
|
|
||||||
class SettingsView extends StatefulWidget {
|
class SettingsView extends StatefulWidget {
|
||||||
const SettingsView({super.key});
|
const SettingsView({super.key});
|
||||||
|
@ -21,9 +21,9 @@ enum ServerStatus {
|
||||||
|
|
||||||
class _SettingsViewState extends State<SettingsView> {
|
class _SettingsViewState extends State<SettingsView> {
|
||||||
|
|
||||||
var _server_version = "---";
|
var _serverVersion = "---";
|
||||||
ServerStatus _server_status = ServerStatus.connecting;
|
ServerStatus _serverStatus = ServerStatus.connecting;
|
||||||
var packageInfo = null;
|
PackageInfo packageInfo = PackageInfo(appName: "", packageName: "", version: "", buildNumber: "");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -34,19 +34,19 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void getServerHealth() async {
|
void getServerHealth() async {
|
||||||
var _packageInfo = await PackageInfo.fromPlatform();
|
var packageInfo = await PackageInfo.fromPlatform();
|
||||||
setState(() {
|
setState(() {
|
||||||
packageInfo = _packageInfo;
|
packageInfo = packageInfo;
|
||||||
});
|
});
|
||||||
final response = await globals.serverManager.getServerHealth();
|
final response = await globals.serverManager.getServerHealth();
|
||||||
response.fold((l) {
|
response.fold((l) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_server_version = l.version;
|
_serverVersion = l.version;
|
||||||
_server_status = ServerStatus.connected;
|
_serverStatus = ServerStatus.connected;
|
||||||
});
|
});
|
||||||
}, (r) {
|
}, (r) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_server_status = ServerStatus.disconnected;
|
_serverStatus = ServerStatus.disconnected;
|
||||||
});
|
});
|
||||||
|
|
||||||
var text = "";
|
var text = "";
|
||||||
|
@ -74,7 +74,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
PopupMenuItem(
|
const PopupMenuItem(
|
||||||
value: 0,
|
value: 0,
|
||||||
child: Text("Log out...", style: TextStyle(color: Colors.red)),
|
child: Text("Log out...", style: TextStyle(color: Colors.red)),
|
||||||
)
|
)
|
||||||
|
@ -105,7 +105,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.dns_outlined, color: (() {
|
Icon(Icons.dns_outlined, color: (() {
|
||||||
switch (_server_status) {
|
switch (_serverStatus) {
|
||||||
case ServerStatus.connected:
|
case ServerStatus.connected:
|
||||||
return Colors.green;
|
return Colors.green;
|
||||||
case ServerStatus.connecting:
|
case ServerStatus.connecting:
|
||||||
|
@ -114,19 +114,19 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
return Colors.red;
|
return Colors.red;
|
||||||
}
|
}
|
||||||
}())),
|
}())),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Connected to", style: TextStyle(color: Colors.grey)),
|
const Text("Connected to", style: TextStyle(color: Colors.grey)),
|
||||||
Text(globals.serverManager.getServerUrl(), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
Text(globals.serverManager.getServerUrl(), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text("API Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
|
const Text("API Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
|
||||||
Text("${globals.serverManager.getApiVersion()}", style: TextStyle(color: Colors.grey)),
|
Text(globals.serverManager.getApiVersion(), style: const TextStyle(color: Colors.grey)),
|
||||||
SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Text("Server Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
|
const Text("Server Version: ", style: TextStyle(color: Colors.grey, fontWeight: FontWeight.w600)),
|
||||||
Text("${_server_version}", style: TextStyle(color: Colors.grey))
|
Text(_serverVersion, style: const TextStyle(color: Colors.grey))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -135,9 +135,9 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Divider(),
|
const Divider(),
|
||||||
SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -149,7 +149,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
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: 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,
|
||||||
|
@ -167,7 +167,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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("https://github.com/rainloreley/shlink-mobile-app");
|
||||||
|
@ -180,7 +180,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
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: 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,
|
||||||
|
@ -198,7 +198,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var url = Uri.parse("https://abmgrt.dev/shlink-manager/privacy");
|
var url = Uri.parse("https://abmgrt.dev/shlink-manager/privacy");
|
||||||
|
@ -211,7 +211,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
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: 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,
|
||||||
|
@ -229,12 +229,12 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (packageInfo != null)
|
if (packageInfo.appName != "")
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text("${packageInfo.appName}, v${packageInfo.version} (${packageInfo.buildNumber})", style: TextStyle(color: Colors.grey),),],
|
Text("${packageInfo.appName}, v${packageInfo.version} (${packageInfo.buildNumber})", style: const TextStyle(color: Colors.grey),),],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShortURLSubmission/ShortURLSubmission.dart';
|
import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.dart';
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/server_manager.dart';
|
||||||
import 'globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
|
|
||||||
class ShortURLEditView extends StatefulWidget {
|
class ShortURLEditView extends StatefulWidget {
|
||||||
const ShortURLEditView({super.key});
|
const ShortURLEditView({super.key});
|
||||||
|
@ -34,7 +34,7 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
void initState() {
|
void initState() {
|
||||||
_customSlugDiceAnimationController = AnimationController(
|
_customSlugDiceAnimationController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
);
|
);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -67,11 +67,11 @@ 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: 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(content: Text("Short URL created!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
|
final snackBar = SnackBar(content: const Text("Short URL created!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
@ -89,8 +89,8 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
text = (r as ApiFailure).detail;
|
text = (r as ApiFailure).detail;
|
||||||
if ((r as ApiFailure).invalidElements != null) {
|
if ((r).invalidElements != null) {
|
||||||
text = text + ": " + (r as ApiFailure).invalidElements.toString();
|
text = "$text: ${(r).invalidElements}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,12 +106,12 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
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: 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,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
@ -119,8 +119,8 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
controller: longUrlController,
|
controller: longUrlController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorText: longUrlError != "" ? longUrlError : null,
|
errorText: longUrlError != "" ? longUrlError : null,
|
||||||
border: OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
label: Row(
|
label: const Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.public),
|
Icon(Icons.public),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
|
@ -129,7 +129,7 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -137,23 +137,25 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
controller: customSlugController,
|
controller: customSlugController,
|
||||||
style: TextStyle(color: randomSlug ? Colors.grey : Theme.of(context).brightness == Brightness.light ? Colors.black : Colors.white),
|
style: TextStyle(color: randomSlug ? Colors.grey : Theme.of(context).brightness == Brightness.light ? Colors.black : Colors.white),
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
if (randomSlug) setState(() {
|
if (randomSlug) {
|
||||||
|
setState(() {
|
||||||
randomSlug = false;
|
randomSlug = false;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
label: Row(
|
label: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.link),
|
const Icon(Icons.link),
|
||||||
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),)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
RotationTransition(
|
RotationTransition(
|
||||||
turns: Tween(begin: 0.0, end: 3.0).animate(CurvedAnimation(parent: _customSlugDiceAnimationController, curve: Curves.easeInOutExpo)),
|
turns: Tween(begin: 0.0, end: 3.0).animate(CurvedAnimation(parent: _customSlugDiceAnimationController, curve: Curves.easeInOutExpo)),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
@ -175,13 +177,13 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (randomSlug)
|
if (randomSlug)
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
if (randomSlug)
|
if (randomSlug)
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("Random slug length"),
|
const Text("Random slug length"),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 100,
|
width: 100,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
@ -189,8 +191,8 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorText: randomSlugLengthError != "" ? "" : null,
|
errorText: randomSlugLengthError != "" ? "" : null,
|
||||||
border: OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
label: Row(
|
label: const Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.tag),
|
Icon(Icons.tag),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
|
@ -201,10 +203,10 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: titleController,
|
controller: titleController,
|
||||||
decoration: InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
label: Row(
|
label: Row(
|
||||||
children: [
|
children: [
|
||||||
|
@ -215,11 +217,11 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("Crawlable"),
|
const Text("Crawlable"),
|
||||||
Switch(
|
Switch(
|
||||||
value: isCrawlable,
|
value: isCrawlable,
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
|
@ -230,11 +232,11 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("Forward query params"),
|
const Text("Forward query params"),
|
||||||
Switch(
|
Switch(
|
||||||
value: forwardQuery,
|
value: forwardQuery,
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
|
@ -245,11 +247,11 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("Copy to clipboard"),
|
const Text("Copy to clipboard"),
|
||||||
Switch(
|
Switch(
|
||||||
value: copyToClipboard,
|
value: copyToClipboard,
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
|
@ -293,7 +295,7 @@ class _ShortURLEditViewState extends State<ShortURLEditView> with SingleTickerPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: isSaving ? Padding(padding: EdgeInsets.all(16), child: CircularProgressIndicator(strokeWidth: 3)) : Icon(Icons.save)
|
child: isSaving ? const Padding(padding: EdgeInsets.all(16), child: CircularProgressIndicator(strokeWidth: 3)) : const Icon(Icons.save)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
|
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/server_manager.dart';
|
||||||
import 'globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
|
|
||||||
class URLDetailView extends StatefulWidget {
|
class URLDetailView extends StatefulWidget {
|
||||||
const URLDetailView({super.key, required this.shortURL});
|
const URLDetailView({super.key, required this.shortURL});
|
||||||
|
@ -26,9 +26,9 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
child: ListBody(
|
child: ListBody(
|
||||||
children: [
|
children: [
|
||||||
const Text("You're about to delete"),
|
const Text("You're about to delete"),
|
||||||
SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text("${widget.shortURL.title ?? widget.shortURL.shortCode}", style: TextStyle(fontStyle: FontStyle.italic),),
|
Text(widget.shortURL.title ?? widget.shortURL.shortCode, style: const TextStyle(fontStyle: FontStyle.italic),),
|
||||||
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)")
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -43,7 +43,7 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Navigator.pop(context, "reload");
|
Navigator.pop(context, "reload");
|
||||||
|
|
||||||
final snackBar = SnackBar(content: Text("Short URL deleted!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
|
final snackBar = SnackBar(content: const Text("Short URL deleted!"), backgroundColor: Colors.green[400], behavior: SnackBarBehavior.floating);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
return true;
|
return true;
|
||||||
}, (r) {
|
}, (r) {
|
||||||
|
@ -60,7 +60,7 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Text("Delete", style: TextStyle(color: Colors.red)),
|
child: const Text("Delete", style: TextStyle(color: Colors.red)),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -74,11 +74,11 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar.medium(
|
SliverAppBar.medium(
|
||||||
title: Text(widget.shortURL.title ?? widget.shortURL.shortCode, style: TextStyle(fontWeight: FontWeight.bold)),
|
title: Text(widget.shortURL.title ?? widget.shortURL.shortCode, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(onPressed: () {
|
IconButton(onPressed: () {
|
||||||
showDeletionConfirmation();
|
showDeletionConfirmation();
|
||||||
}, icon: Icon(Icons.delete, color: Colors.red,))
|
}, icon: const Icon(Icons.delete, color: Colors.red,))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
|
@ -88,9 +88,9 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
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()).first.harmonizeWith(Theme.of(context).colorScheme.primary);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(right: 4, top: 4),
|
padding: const EdgeInsets.only(right: 4, top: 4),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
|
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
color: randomColor,
|
color: randomColor,
|
||||||
|
@ -109,11 +109,11 @@ class _URLDetailViewState extends State<URLDetailView> {
|
||||||
_ListCell(title: "Android", content: widget.shortURL.deviceLongUrls.android, sub: true),
|
_ListCell(title: "Android", content: widget.shortURL.deviceLongUrls.android, sub: true),
|
||||||
_ListCell(title: "Desktop", content: widget.shortURL.deviceLongUrls.desktop, sub: true),
|
_ListCell(title: "Desktop", content: widget.shortURL.deviceLongUrls.desktop, sub: true),
|
||||||
_ListCell(title: "Creation Date", content: widget.shortURL.dateCreated),
|
_ListCell(title: "Creation Date", content: widget.shortURL.dateCreated),
|
||||||
_ListCell(title: "Visits", content: ""),
|
const _ListCell(title: "Visits", content: ""),
|
||||||
_ListCell(title: "Total", content: widget.shortURL.visitsSummary.total, sub: true),
|
_ListCell(title: "Total", content: widget.shortURL.visitsSummary.total, sub: true),
|
||||||
_ListCell(title: "Non-Bots", content: widget.shortURL.visitsSummary.nonBots, sub: true),
|
_ListCell(title: "Non-Bots", content: widget.shortURL.visitsSummary.nonBots, sub: true),
|
||||||
_ListCell(title: "Bots", content: widget.shortURL.visitsSummary.bots, sub: true),
|
_ListCell(title: "Bots", content: widget.shortURL.visitsSummary.bots, sub: true),
|
||||||
_ListCell(title: "Meta", content: ""),
|
const _ListCell(title: "Meta", content: ""),
|
||||||
_ListCell(title: "Valid Since", content: widget.shortURL.meta.validSince, sub: true),
|
_ListCell(title: "Valid Since", content: widget.shortURL.meta.validSince, sub: true),
|
||||||
_ListCell(title: "Valid Until", content: widget.shortURL.meta.validUntil, 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: "Max Visits", content: widget.shortURL.meta.maxVisits, sub: true),
|
||||||
|
@ -146,7 +146,7 @@ class _ListCellState extends State<_ListCell> {
|
||||||
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: 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(width: 1, color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.grey[800]! : Colors.grey[300]!)),
|
||||||
),
|
),
|
||||||
|
@ -157,7 +157,7 @@ class _ListCellState extends State<_ListCell> {
|
||||||
children: [
|
children: [
|
||||||
if (widget.sub)
|
if (widget.sub)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(right: 4),
|
padding: const EdgeInsets.only(right: 4),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 6,
|
height: 6,
|
||||||
|
@ -169,7 +169,7 @@ class _ListCellState extends State<_ListCell> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(widget.title, style: TextStyle(fontWeight: FontWeight.bold),)],
|
Text(widget.title, style: const TextStyle(fontWeight: FontWeight.bold),)],
|
||||||
),
|
),
|
||||||
if (widget.content is bool)
|
if (widget.content is bool)
|
||||||
Icon(widget.content ? Icons.check : Icons.close, color: widget.content ? Colors.green : Colors.red)
|
Icon(widget.content ? Icons.check : Icons.close, color: widget.content ? Colors.green : Colors.red)
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:shlink_app/API/Classes/ShortURL/ShortURL.dart';
|
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
|
||||||
import 'package:shlink_app/API/ServerManager.dart';
|
import 'package:shlink_app/API/server_manager.dart';
|
||||||
import 'package:shlink_app/ShortURLEditView.dart';
|
import 'package:shlink_app/views/short_url_edit_view.dart';
|
||||||
import 'package:shlink_app/URLDetailView.dart';
|
import 'package:shlink_app/views/url_detail_view.dart';
|
||||||
import 'globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class URLListView extends StatefulWidget {
|
class URLListView extends StatefulWidget {
|
||||||
|
@ -59,10 +59,10 @@ class _URLListViewState extends State<URLListView> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => ShortURLEditView()));
|
await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ShortURLEditView()));
|
||||||
loadAllShortUrls();
|
loadAllShortUrls();
|
||||||
},
|
},
|
||||||
child: Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
@ -74,19 +74,19 @@ class _URLListViewState extends State<URLListView> {
|
||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar.medium(
|
const SliverAppBar.medium(
|
||||||
title: Text("Short URLs", style: TextStyle(fontWeight: FontWeight.bold))
|
title: Text("Short URLs", style: TextStyle(fontWeight: FontWeight.bold))
|
||||||
),
|
),
|
||||||
if (shortUrlsLoaded && shortUrls.length == 0)
|
if (shortUrlsLoaded && shortUrls.isEmpty)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(top: 50),
|
padding: const EdgeInsets.only(top: 50),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
|
const Text("No Short URLs", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
|
child: Text('Create one by tapping the "+" button below', style: TextStyle(fontSize: 16, color: Colors.grey[600]),),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -96,7 +96,7 @@ class _URLListViewState extends State<URLListView> {
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
SliverList(delegate: SliverChildBuilderDelegate(
|
SliverList(delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext _context, int index) {
|
(BuildContext context, int index) {
|
||||||
final shortURL = shortUrls[index];
|
final shortURL = shortUrls[index];
|
||||||
return ShortURLCell(shortURL: shortURL, reload: () {
|
return ShortURLCell(shortURL: shortURL, reload: () {
|
||||||
loadAllShortUrls();
|
loadAllShortUrls();
|
||||||
|
@ -181,7 +181,7 @@ class _ShortURLCellState extends State<ShortURLCell> {
|
||||||
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: EdgeInsets.only(left: 8, right: 8, bottom: 16, top: 16),
|
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 16, top: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(bottom: BorderSide(color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.grey[800]! : Colors.grey[300]!)),
|
border: Border(bottom: BorderSide(color: MediaQuery.of(context).platformBrightness == Brightness.dark ? Colors.grey[800]! : Colors.grey[300]!)),
|
||||||
),
|
),
|
||||||
|
@ -193,16 +193,16 @@ class _ShortURLCellState extends State<ShortURLCell> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("${widget.shortURL.title ?? widget.shortURL.shortCode}", textScaleFactor: 1.4, style: TextStyle(fontWeight: FontWeight.bold),),
|
Text(widget.shortURL.title ?? widget.shortURL.shortCode, textScaleFactor: 1.4, style: const TextStyle(fontWeight: FontWeight.bold),),
|
||||||
Text("${widget.shortURL.longUrl}",maxLines: 1, overflow: TextOverflow.ellipsis, textScaleFactor: 0.9, style: TextStyle(color: Colors.grey[600]),),
|
Text(widget.shortURL.longUrl,maxLines: 1, overflow: TextOverflow.ellipsis, textScaleFactor: 0.9, style: TextStyle(color: Colors.grey[600]),),
|
||||||
// List tags in a row
|
// List tags in a row
|
||||||
Wrap(
|
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()).first.harmonizeWith(Theme.of(context).colorScheme.primary);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(right: 4, top: 4),
|
padding: const EdgeInsets.only(right: 4, top: 4),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
|
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 12, right: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
color: randomColor,
|
color: randomColor,
|
||||||
|
@ -218,12 +218,12 @@ class _ShortURLCellState extends State<ShortURLCell> {
|
||||||
),
|
),
|
||||||
IconButton(onPressed: () async {
|
IconButton(onPressed: () async {
|
||||||
await Clipboard.setData(ClipboardData(text: widget.shortURL.shortUrl));
|
await Clipboard.setData(ClipboardData(text: widget.shortURL.shortUrl));
|
||||||
final snackBar = SnackBar(content: Text("Copied to clipboard!"), behavior: SnackBarBehavior.floating, backgroundColor: Colors.green[400]);
|
final snackBar = SnackBar(content: const Text("Copied to clipboard!"), behavior: SnackBarBehavior.floating, backgroundColor: Colors.green[400]);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}, icon: Icon(Icons.copy)),
|
}, icon: const Icon(Icons.copy)),
|
||||||
IconButton(onPressed: () {
|
IconButton(onPressed: () {
|
||||||
widget.showQRCode(widget.shortURL.shortUrl);
|
widget.showQRCode(widget.shortURL.shortUrl);
|
||||||
}, icon: Icon(Icons.qr_code))
|
}, icon: const Icon(Icons.qr_code))
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
),
|
),
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.9.0+4
|
version: 0.9.1+5
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^2.19.0
|
sdk: ^2.19.0
|
||||||
|
|
Loading…
Reference in New Issue
Block a user