Compare commits

...

13 Commits

Author SHA1 Message Date
Adrian Baumgart e1c9bc4d80
bump version to 1.3.1 2024-07-25 20:17:55 +02:00
Adrian Baumgart 413275df38
fixed bug where server list stays empty 2024-07-25 20:17:27 +02:00
Adrian Baumgart 975b2ea3d8
cleaning and bump version to 1.3.0 2024-07-25 19:23:21 +02:00
rainloreley f9c6a58db1 Apply automatic changes 2024-07-25 17:12:51 +00:00
Adrian Baumgart 69c8870997
support multiple shlink instances 2024-07-25 19:11:24 +02:00
Adrian Baumgart ce28066d29
fixed license 2024-07-25 17:40:17 +02:00
Adrian Baumgart e9f98c2171
edited README 2024-07-25 17:39:42 +02:00
rainloreley 39483dca54 Apply automatic changes 2024-07-25 15:38:31 +00:00
Adrian Baumgart ba058e2af3
formatting 2024-07-25 17:37:17 +02:00
Adrian Baumgart 0eea6ee9a2
dependency 2024-07-25 17:35:12 +02:00
Adrian Baumgart 0ade61faea
added support for redirect rules 2024-07-25 17:31:54 +02:00
rainloreley 00bb9af82e Apply automatic changes 2024-07-25 12:51:42 +00:00
Adrian Baumgart 6a5cebdb68
change view title when editing short URL 2024-07-25 14:50:16 +02:00
21 changed files with 1048 additions and 302 deletions

View File

@ -1,42 +0,0 @@
name: Fix formatting and license file
on:
push:
branches: [ "main" ]
paths:
- '**.dart'
- 'pubspec.yaml'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'oracle'
java-version: '21'
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Setup Flutter SDK
uses: flutter-actions/setup-flutter@v2
with:
channel: stable
version: 3.13.7
- name: Install dependencies
run: dart pub get
- name: Verify formatting
run: dart format --output=none .
- name: Update license file
run: flutter packages pub run license_generator generate
- name: Commit and push
uses: stefanzweifel/git-auto-commit-action@v5

View File

@ -17,20 +17,18 @@ Shlink Manager is an app for Android to see and manage all shortened URLs create
## 📱 Current Features ## 📱 Current Features
✅ List all short URLs<br/> ✅ List all short URLs<br/>
✅ Create new short URLs<br/> ✅ Create, edit and delete short URLs<br/>
✅ Delete short URLs<br/>
✅ See overall statistics<br/> ✅ See overall statistics<br/>
✅ Detailed statistics for each short URL<br/> ✅ Detailed statistics for each short URL<br/>
✅ Display tags<br/> ✅ Display tags & QR code<br/>
✅ Display QR code<br/>
✅ Dark mode support<br/> ✅ Dark mode support<br/>
✅ Edit existing short URLs<br/>
✅ Quickly create Short URL via Share Sheet<br/> ✅ Quickly create Short URL via Share Sheet<br/>
✅ View rule-based redirects (no editing yet)<br/>
✅ Use multiple Shlink instances<br/>
## 🔨 To Do ## 🔨 To Do
- [ ] Add support for iOS (maybe in the future) - [ ] Add support for iOS (maybe in the future)
- [ ] add tags - [ ] add tags
- [ ] Add dynamic rule-based redirects system (Shlink v4.0.0)
- [ ] improve app icon - [ ] improve app icon
- [ ] Refactor code - [ ] Refactor code
- [ ] ...and more - [ ] ...and more

View File

@ -52,10 +52,12 @@ android {
applicationId "dev.abmgrt.shlink_app" applicationId "dev.abmgrt.shlink_app"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 19 //flutter.minSdkVersion minSdkVersion flutter.minSdkVersion //flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
multiDexEnabled true
} }
signingConfigs { signingConfigs {
@ -79,4 +81,5 @@ flutter {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.multidex:multidex:2.0.1'
} }

View File

@ -0,0 +1,41 @@
enum ConditionDeviceType {
IOS,
ANDROID,
DESKTOP;
static ConditionDeviceType fromApi(String api) {
switch (api) {
case "ios":
return ConditionDeviceType.IOS;
case "android":
return ConditionDeviceType.ANDROID;
case "desktop":
return ConditionDeviceType.DESKTOP;
}
throw ArgumentError("Invalid type $api");
}
}
extension ConditionTypeExtension on ConditionDeviceType {
String get api {
switch (this) {
case ConditionDeviceType.IOS:
return "ios";
case ConditionDeviceType.ANDROID:
return "android";
case ConditionDeviceType.DESKTOP:
return "desktop";
}
}
String get humanReadable {
switch (this) {
case ConditionDeviceType.IOS:
return "iOS";
case ConditionDeviceType.ANDROID:
return "Android";
case ConditionDeviceType.DESKTOP:
return "Desktop";
}
}
}

View File

@ -0,0 +1,24 @@
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/redirect_rule_condition.dart';
/// Single redirect rule for a short URL.
class RedirectRule {
String longUrl;
int priority;
List<RedirectRuleCondition> conditions;
RedirectRule(this.longUrl, this.priority, this.conditions);
RedirectRule.fromJson(Map<String, dynamic> json)
: longUrl = json["longUrl"],
priority = json["priority"],
conditions = (json["conditions"] as List<dynamic>)
.map((e) => RedirectRuleCondition.fromJson(e))
.toList();
Map<String, dynamic> toJson() {
return {
"longUrl": longUrl,
"conditions": conditions.map((e) => e.toJson()).toList()
};
}
}

View File

@ -0,0 +1,19 @@
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/redirect_rule_condition_type.dart';
class RedirectRuleCondition {
RedirectRuleConditionType type;
String matchValue;
String? matchKey;
RedirectRuleCondition(String type, this.matchValue, this.matchKey)
: type = RedirectRuleConditionType.fromApi(type);
RedirectRuleCondition.fromJson(Map<String, dynamic> json)
: type = RedirectRuleConditionType.fromApi(json["type"]),
matchValue = json["matchValue"],
matchKey = json["matchKey"];
Map<String, dynamic> toJson() {
return {"type": type.api, "matchValue": matchValue, "matchKey": matchKey};
}
}

View File

@ -0,0 +1,41 @@
enum RedirectRuleConditionType {
DEVICE,
LANGUAGE,
QUERY_PARAM;
static RedirectRuleConditionType fromApi(String api) {
switch (api) {
case "device":
return RedirectRuleConditionType.DEVICE;
case "language":
return RedirectRuleConditionType.LANGUAGE;
case "query-param":
return RedirectRuleConditionType.QUERY_PARAM;
}
throw ArgumentError("Invalid type $api");
}
}
extension ConditionTypeExtension on RedirectRuleConditionType {
String get api {
switch (this) {
case RedirectRuleConditionType.DEVICE:
return "device";
case RedirectRuleConditionType.LANGUAGE:
return "language";
case RedirectRuleConditionType.QUERY_PARAM:
return "query-param";
}
}
String get humanReadable {
switch (this) {
case RedirectRuleConditionType.DEVICE:
return "Device";
case RedirectRuleConditionType.LANGUAGE:
return "Language";
case RedirectRuleConditionType.QUERY_PARAM:
return "Query parameter";
}
}
}

View File

@ -1,6 +1,8 @@
import 'package:shlink_app/API/Classes/ShortURL/short_url_meta.dart'; import 'package:shlink_app/API/Classes/ShortURL/short_url_meta.dart';
import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart'; import 'package:shlink_app/API/Classes/ShortURL/visits_summary.dart';
import 'RedirectRule/redirect_rule.dart';
/// Data about a short URL /// Data about a short URL
class ShortURL { class ShortURL {
/// Slug of the short URL used in the URL /// Slug of the short URL used in the URL
@ -33,6 +35,8 @@ class ShortURL {
/// Whether the short URL is crawlable by a web crawler /// Whether the short URL is crawlable by a web crawler
bool crawlable; bool crawlable;
List<RedirectRule>? redirectRules;
ShortURL( ShortURL(
this.shortCode, this.shortCode,
this.shortUrl, this.shortUrl,

View File

@ -0,0 +1,48 @@
import 'dart:async';
import 'dart:convert';
import 'package:dartz/dartz.dart';
import 'package:http/http.dart' as http;
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/redirect_rule.dart';
import '../server_manager.dart';
/// Gets redirect rules for a given short URL (code).
FutureOr<Either<List<RedirectRule>, Failure>> apiGetRedirectRules(
String shortCode,
String? apiKey,
String? serverUrl,
String apiVersion) async {
try {
final response = await http.get(
Uri.parse(
"$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"),
headers: {
"X-Api-Key": apiKey ?? "",
});
if (response.statusCode == 200) {
// get returned redirect rules
var jsonBody = jsonDecode(response.body) as Map<String, dynamic>;
// convert json array to object array
List<RedirectRule> redirectRules =
(jsonBody["redirectRules"] as List<dynamic>)
.map((e) => RedirectRule.fromJson(e))
.toList();
return left(redirectRules);
} else {
try {
var jsonBody = jsonDecode(response.body);
return right(ApiFailure(
type: jsonBody["type"],
detail: jsonBody["detail"],
title: jsonBody["title"],
status: jsonBody["status"],
invalidElements: jsonBody["invalidElements"]));
} catch (resErr) {
return right(RequestFailure(response.statusCode, resErr.toString()));
}
}
} catch (reqErr) {
return right(RequestFailure(0, reqErr.toString()));
}
}

View File

@ -0,0 +1,45 @@
import 'dart:async';
import 'dart:convert';
import 'package:dartz/dartz.dart';
import 'package:http/http.dart' as http;
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/redirect_rule.dart';
import '../server_manager.dart';
/// Saves the redirect rules for a given short URL (code).
FutureOr<Either<bool, Failure>> apiSetRedirectRules(
String shortCode,
List<RedirectRule> redirectRules,
String? apiKey,
String? serverUrl,
String apiVersion) async {
try {
Map<String, dynamic> body = {};
List<Map<String, dynamic>> redirectRulesJson =
redirectRules.map((e) => e.toJson()).toList();
body["redirectRules"] = redirectRulesJson;
final response = await http.post(
Uri.parse(
"$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"),
headers: {
"X-Api-Key": apiKey ?? "",
},
body: jsonEncode(body));
if (response.statusCode == 200) {
return left(true);
} else {
try {
var jsonBody = jsonDecode(response.body);
return right(ApiFailure(
type: jsonBody["type"],
detail: jsonBody["detail"],
title: jsonBody["title"],
status: jsonBody["status"],
invalidElements: jsonBody["invalidElements"]));
} catch (resErr) {
return right(RequestFailure(response.statusCode, resErr.toString()));
}
}
} catch (reqErr) {
return right(RequestFailure(0, reqErr.toString()));
}
}

View File

@ -1,15 +1,19 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
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/shlink_stats.dart'; import 'package:shlink_app/API/Classes/ShlinkStats/shlink_stats.dart';
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/redirect_rule.dart';
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart'; import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
import 'package:shlink_app/API/Classes/ShortURLSubmission/short_url_submission.dart'; import 'package:shlink_app/API/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/get_recent_short_urls.dart'; import 'package:shlink_app/API/Methods/get_recent_short_urls.dart';
import 'package:shlink_app/API/Methods/get_redirect_rules.dart';
import 'package:shlink_app/API/Methods/get_server_health.dart'; import 'package:shlink_app/API/Methods/get_server_health.dart';
import 'package:shlink_app/API/Methods/get_shlink_stats.dart'; import 'package:shlink_app/API/Methods/get_shlink_stats.dart';
import 'package:shlink_app/API/Methods/get_short_urls.dart'; import 'package:shlink_app/API/Methods/get_short_urls.dart';
import 'package:shlink_app/API/Methods/set_redirect_rules.dart';
import 'package:shlink_app/API/Methods/update_short_url.dart'; import 'package:shlink_app/API/Methods/update_short_url.dart';
import 'Methods/delete_short_url.dart'; import 'Methods/delete_short_url.dart';
@ -41,10 +45,41 @@ class ServerManager {
} }
/// Logs out the user and removes data about the Shlink server /// Logs out the user and removes data about the Shlink server
Future<void> logOut() async { Future<void> logOut(String url) async {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
await storage.delete(key: "shlink_url"); final prefs = await SharedPreferences.getInstance();
await storage.delete(key: "shlink_apikey");
String? serverMapSerialized = await storage.read(key: "server_map");
if (serverMapSerialized != null) {
Map<String, String> serverMap =
Map.castFrom(jsonDecode(serverMapSerialized));
serverMap.remove(url);
if (serverMap.isEmpty) {
storage.delete(key: "server_map");
} else {
storage.write(key: "server_map", value: jsonEncode(serverMap));
}
if (serverUrl == url) {
serverUrl = null;
apiKey = null;
prefs.remove("lastusedserver");
}
}
}
/// Returns all servers saved in the app
Future<List<String>> getAvailableServers() async {
const storage = FlutterSecureStorage();
String? serverMapSerialized = await storage.read(key: "server_map");
if (serverMapSerialized != null) {
Map<String, String> serverMap =
Map.castFrom(jsonDecode(serverMapSerialized));
return serverMap.keys.toList();
} else {
return [];
}
} }
/// Loads the server credentials from [FlutterSecureStorage] /// Loads the server credentials from [FlutterSecureStorage]
@ -57,16 +92,68 @@ class ServerManager {
prefs.setBool('first_run', false); prefs.setBool('first_run', false);
} else { } else {
serverUrl = await storage.read(key: "shlink_url"); if (await _replaceDeprecatedStorageMethod()) {
apiKey = await storage.read(key: "shlink_apikey"); _loadCredentials();
return;
}
String? serverMapSerialized = await storage.read(key: "server_map");
String? lastUsedServer = prefs.getString("lastusedserver");
if (serverMapSerialized != null) {
Map<String, String> serverMap =
Map.castFrom(jsonDecode(serverMapSerialized));
if (lastUsedServer != null) {
serverUrl = lastUsedServer;
apiKey = serverMap[lastUsedServer]!;
} else {
List<String> availableServers = serverMap.keys.toList();
if (availableServers.isNotEmpty) {
serverUrl = availableServers.first;
apiKey = serverMap[serverUrl];
prefs.setString("lastusedserver", serverUrl!);
}
}
}
}
}
Future<bool> _replaceDeprecatedStorageMethod() async {
const storage = FlutterSecureStorage();
// deprecated data storage method, replaced because of multi-server support
var v1DataServerurl = await storage.read(key: "shlink_url");
var v1DataApikey = await storage.read(key: "shlink_apikey");
if (v1DataServerurl != null && v1DataApikey != null) {
// conversion to new data storage method
Map<String, String> serverMap = {};
serverMap[v1DataServerurl] = v1DataApikey;
storage.write(key: "server_map", value: jsonEncode(serverMap));
storage.delete(key: "shlink_url");
storage.delete(key: "shlink_apikey");
return true;
} else {
return false;
} }
} }
/// Saves the provided server credentials to [FlutterSecureStorage] /// 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); final prefs = await SharedPreferences.getInstance();
storage.write(key: "shlink_apikey", value: apiKey); String? serverMapSerialized = await storage.read(key: "server_map");
Map<String, String> serverMap;
if (serverMapSerialized != null) {
serverMap = Map.castFrom(jsonDecode(serverMapSerialized));
} else {
serverMap = {};
}
serverMap[url] = apiKey;
storage.write(key: "server_map", value: jsonEncode(serverMap));
prefs.setString("lastusedserver", url);
} }
/// Saves provided server credentials and tries to establish a connection /// Saves provided server credentials and tries to establish a connection
@ -78,7 +165,7 @@ class ServerManager {
_saveCredentials(url, apiKey); _saveCredentials(url, apiKey);
final result = await connect(); final result = await connect();
result.fold((l) => null, (r) { result.fold((l) => null, (r) {
logOut(); logOut(url);
}); });
return result; return result;
} }
@ -124,6 +211,19 @@ class ServerManager {
FutureOr<Either<List<ShortURL>, Failure>> getRecentShortUrls() async { FutureOr<Either<List<ShortURL>, Failure>> getRecentShortUrls() async {
return apiGetRecentShortUrls(apiKey, serverUrl, apiVersion); return apiGetRecentShortUrls(apiKey, serverUrl, apiVersion);
} }
/// Gets redirect rules for a given short URL (code)
FutureOr<Either<List<RedirectRule>, Failure>> getRedirectRules(
String shortCode) async {
return apiGetRedirectRules(shortCode, apiKey, serverUrl, apiVersion);
}
/// Sets redirect rules for a given short URL (code)
FutureOr<Either<bool, Failure>> setRedirectRules(
String shortCode, List<RedirectRule> redirectRules) async {
return apiSetRedirectRules(
shortCode, redirectRules, apiKey, serverUrl, apiVersion);
}
} }
/// Server response data type about a page of short URLs from the server /// Server response data type about a page of short URLs from the server

View File

@ -11,13 +11,13 @@ void main() {
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
static final ColorScheme _defaultLightColorScheme = ColorScheme static final ColorScheme _defaultLightColorScheme =
.fromSeed(seedColor: Colors.blue); ColorScheme.fromSeed(seedColor: Colors.blue);
static final _defaultDarkColorScheme = ColorScheme.fromSeed( static final _defaultDarkColorScheme = ColorScheme.fromSeed(
brightness: Brightness.dark, brightness: Brightness.dark,
seedColor: Colors.blue, seedColor: Colors.blue,
background: Colors.black); background: Colors.black);
// This widget is the root of your application. // This widget is the root of your application.
@override @override
@ -38,7 +38,7 @@ class MyApp extends StatelessWidget {
foregroundColor: Colors.white, foregroundColor: Colors.white,
elevation: 0, elevation: 0,
), ),
colorScheme: darkColorScheme?.copyWith(background: Colors.black) ?? colorScheme: darkColorScheme?.copyWith(surface: Colors.black) ??
_defaultDarkColorScheme, _defaultDarkColorScheme,
useMaterial3: true, useMaterial3: true,
), ),
@ -64,11 +64,13 @@ class _InitialPageState extends State<InitialPage> {
void checkLogin() async { void checkLogin() async {
bool result = await globals.serverManager.checkLogin(); bool result = await globals.serverManager.checkLogin();
if (result) { if (result) {
Navigator.of(context).pushReplacement( Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const NavigationBarView())); MaterialPageRoute(builder: (context) => const NavigationBarView()),
(Route<dynamic> route) => false);
} else { } else {
Navigator.of(context).pushReplacement( Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const LoginView())); MaterialPageRoute(builder: (context) => const LoginView()),
(Route<dynamic> route) => false);
} }
} }

View File

@ -28,7 +28,8 @@ class LicenseUtil {
return [ return [
const License( const License(
name: r'cupertino_icons', name: r'cupertino_icons',
license: r'''The MIT License (MIT) license: r'''
The MIT License (MIT)
Copyright (c) 2016 Vladimir Kharlampidi Copyright (c) 2016 Vladimir Kharlampidi
@ -49,12 +50,12 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''',
version: r'^1.0.5', version: r'^1.0.5',
homepage: null,
repository: 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',
), ),
const License( const License(
name: r'dartz', name: r'dartz',
license: r'''The MIT License (MIT) license: r'''
The MIT License (MIT)
Copyright (c) 2016, 2017, 2018, 2019, 2020, 2021 Björn Sperber Copyright (c) 2016, 2017, 2018, 2019, 2020, 2021 Björn Sperber
@ -78,11 +79,11 @@ SOFTWARE.
''', ''',
version: r'^0.10.1', version: r'^0.10.1',
homepage: r'https://github.com/spebbe/dartz', homepage: r'https://github.com/spebbe/dartz',
repository: null,
), ),
const License( const License(
name: r'dynamic_color', name: r'dynamic_color',
license: r''' Apache License license: r'''
Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/
@ -285,12 +286,12 @@ SOFTWARE.
limitations under the License. limitations under the License.
''', ''',
version: r'^1.6.6', version: r'^1.6.6',
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',
), ),
const License( const License(
name: r'flutter', name: r'flutter',
license: r'''Copyright 2014 The Flutter Authors. All rights reserved. license: r'''
Copyright 2014 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
@ -316,13 +317,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: null,
homepage: r'https://flutter.dev/', homepage: r'https://flutter.dev/',
repository: r'https://github.com/flutter/flutter', repository: r'https://github.com/flutter/flutter',
), ),
const License( const License(
name: r'flutter_launcher_icons', name: r'flutter_launcher_icons',
license: r'''MIT License license: r'''
MIT License
Copyright (c) 2019 Mark O'Sullivan Copyright (c) 2019 Mark O'Sullivan
@ -350,7 +351,8 @@ SOFTWARE.
), ),
const License( const License(
name: r'flutter_lints', name: r'flutter_lints',
license: r'''Copyright 2013 The Flutter Authors. All rights reserved. license: r'''
Copyright 2013 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
@ -377,12 +379,12 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^3.0.1', version: r'^3.0.1',
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',
), ),
const License( const License(
name: r'flutter_process_text', name: r'flutter_process_text',
license: r'''BSD 3-Clause License license: r'''
BSD 3-Clause License
(c) Copyright 2021 divshekhar (Divyanshu Shekhar) (c) Copyright 2021 divshekhar (Divyanshu Shekhar)
@ -410,12 +412,12 @@ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
version: r'^1.1.2', version: r'^1.1.2',
homepage: null,
repository: r'https://github.com/DevsOnFlutter/flutter_process_text', repository: r'https://github.com/DevsOnFlutter/flutter_process_text',
), ),
const License( const License(
name: r'flutter_secure_storage', name: r'flutter_secure_storage',
license: r'''BSD 3-Clause License license: r'''
BSD 3-Clause License
Copyright 2017 German Saprykin Copyright 2017 German Saprykin
All rights reserved. All rights reserved.
@ -445,12 +447,12 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
version: r'^9.0.0', version: r'^9.0.0',
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',
), ),
const License( const License(
name: r'flutter_sharing_intent', name: r'flutter_sharing_intent',
license: r''' Apache License license: r'''
Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/
@ -653,11 +655,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
limitations under the License.''', limitations under the License.''',
version: r'^1.1.1', version: r'^1.1.1',
homepage: r'https://github.com/bhagat-techind/flutter_sharing_intent.git', homepage: r'https://github.com/bhagat-techind/flutter_sharing_intent.git',
repository: null,
), ),
const License( const License(
name: r'flutter_test', name: r'flutter_test',
license: r'''Copyright 2014 The Flutter Authors. All rights reserved. license: r'''
Copyright 2014 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
@ -683,13 +685,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: null,
homepage: r'https://flutter.dev/', homepage: r'https://flutter.dev/',
repository: r'https://github.com/flutter/flutter', repository: r'https://github.com/flutter/flutter',
), ),
const License( const License(
name: r'http', name: r'http',
license: r'''Copyright 2014, the Dart project authors. license: r'''
Copyright 2014, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -718,12 +720,12 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^1.1.0', version: r'^1.1.0',
homepage: null,
repository: r'https://github.com/dart-lang/http/tree/master/pkgs/http', repository: r'https://github.com/dart-lang/http/tree/master/pkgs/http',
), ),
const License( const License(
name: r'intl', name: r'intl',
license: r'''Copyright 2013, the Dart project authors. license: r'''
Copyright 2013, the Dart project authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -752,12 +754,12 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^0.19.0', version: r'^0.19.0',
homepage: null,
repository: r'https://github.com/dart-lang/i18n/tree/main/pkgs/intl', repository: r'https://github.com/dart-lang/i18n/tree/main/pkgs/intl',
), ),
const License( const License(
name: r'license_generator', name: r'license_generator',
license: r'''MIT License license: r'''
MIT License
Copyright (c) 2022 icapps Copyright (c) 2022 icapps
@ -781,11 +783,11 @@ SOFTWARE.
''', ''',
version: r'^2.0.0', version: r'^2.0.0',
homepage: r'https://github.com/icapps/flutter-icapps-license', homepage: r'https://github.com/icapps/flutter-icapps-license',
repository: null,
), ),
const License( const License(
name: r'package_info_plus', name: r'package_info_plus',
license: r'''Copyright 2017 The Chromium Authors. All rights reserved. license: r'''
Copyright 2017 The Chromium Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -819,7 +821,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
), ),
const License( const License(
name: r'qr_flutter', name: r'qr_flutter',
license: r'''BSD 3-Clause License license: r'''
BSD 3-Clause License
Copyright (c) 2020, Luke Freeman. Copyright (c) 2020, Luke Freeman.
All rights reserved. All rights reserved.
@ -851,11 +854,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^4.1.0', version: r'^4.1.0',
homepage: r'https://github.com/theyakka/qr.flutter', homepage: r'https://github.com/theyakka/qr.flutter',
repository: null,
), ),
const License( const License(
name: r'shared_preferences', name: r'shared_preferences',
license: r'''Copyright 2013 The Flutter Authors. All rights reserved. license: r'''
Copyright 2013 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
@ -882,12 +885,12 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^2.2.2', version: r'^2.2.2',
homepage: null,
repository: 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',
), ),
const License( const License(
name: r'tuple', name: r'tuple',
license: r'''Copyright (c) 2014, the tuple project authors. license: r'''
Copyright (c) 2014, the tuple project authors.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@ -910,12 +913,12 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
version: r'^2.0.2', version: r'^2.0.2',
homepage: null,
repository: r'https://github.com/google/tuple.dart', repository: r'https://github.com/google/tuple.dart',
), ),
const License( const License(
name: r'url_launcher', name: r'url_launcher',
license: r'''Copyright 2013 The Flutter Authors. All rights reserved. license: r'''
Copyright 2013 The Flutter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
@ -942,7 +945,6 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^6.2.4', version: r'^6.2.4',
homepage: null,
repository: r'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher', repository: r'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher',
), ),
]; ];

View File

@ -8,6 +8,7 @@ import 'package:shlink_app/API/Classes/ShlinkStats/shlink_stats.dart';
import 'package:shlink_app/API/server_manager.dart'; import 'package:shlink_app/API/server_manager.dart';
import 'package:shlink_app/views/short_url_edit_view.dart'; import 'package:shlink_app/views/short_url_edit_view.dart';
import 'package:shlink_app/views/url_list_view.dart'; import 'package:shlink_app/views/url_list_view.dart';
import 'package:shlink_app/widgets/available_servers_bottom_sheet.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import '../API/Classes/ShortURL/short_url.dart'; import '../API/Classes/ShortURL/short_url.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
@ -121,15 +122,25 @@ class _HomeViewState extends State<HomeView> {
child: CustomScrollView( child: CustomScrollView(
slivers: [ slivers: [
SliverAppBar.medium( SliverAppBar.medium(
automaticallyImplyLeading: false,
expandedHeight: 160, expandedHeight: 160,
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text("Shlink", const Text("Shlink",
style: TextStyle(fontWeight: FontWeight.bold)), style: TextStyle(fontWeight: FontWeight.bold)),
Text(globals.serverManager.getServerUrl(), GestureDetector(
style: TextStyle( onTap: () {
fontSize: 16, color: Colors.grey[600])) showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return const AvailableServerBottomSheet();
});
},
child: Text(globals.serverManager.getServerUrl(),
style: TextStyle(
fontSize: 16, color: Colors.grey[600])),
)
], ],
)), )),
SliverToBoxAdapter( SliverToBoxAdapter(

View File

@ -0,0 +1,297 @@
import 'package:flutter/material.dart';
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/condition_device_type.dart';
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/redirect_rule_condition.dart';
import 'package:shlink_app/API/Classes/ShortURL/RedirectRule/redirect_rule_condition_type.dart';
import 'package:shlink_app/API/Classes/ShortURL/short_url.dart';
import 'package:shlink_app/API/server_manager.dart';
import '../globals.dart' as globals;
import '../API/Classes/ShortURL/RedirectRule/redirect_rule.dart';
class RedirectRulesDetailView extends StatefulWidget {
const RedirectRulesDetailView({super.key, required this.shortURL});
final ShortURL shortURL;
@override
State<RedirectRulesDetailView> createState() =>
_RedirectRulesDetailViewState();
}
class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
List<RedirectRule> redirectRules = [];
bool redirectRulesLoaded = false;
bool isSaving = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => loadRedirectRules());
}
Future<void> loadRedirectRules() async {
final response =
await globals.serverManager.getRedirectRules(widget.shortURL.shortCode);
response.fold((l) {
setState(() {
redirectRules = l;
redirectRulesLoaded = true;
});
_sortListByPriority();
return true;
}, (r) {
var text = "";
if (r is RequestFailure) {
text = r.description;
} else {
text = (r as ApiFailure).detail;
}
final snackBar = SnackBar(
content: Text(text),
backgroundColor: Colors.red[400],
behavior: SnackBarBehavior.floating);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return false;
});
}
void _saveRedirectRules() async {
final response = await globals.serverManager
.setRedirectRules(widget.shortURL.shortCode, redirectRules);
response.fold((l) {
Navigator.pop(context);
}, (r) {
var text = "";
if (r is RequestFailure) {
text = r.description;
} else {
text = (r as ApiFailure).detail;
}
final snackBar = SnackBar(
content: Text(text),
backgroundColor: Colors.red[400],
behavior: SnackBarBehavior.floating);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
return false;
});
}
void _sortListByPriority() {
setState(() {
redirectRules.sort((a, b) => a.priority - b.priority);
});
}
void _fixPriorities() {
for (int i = 0; i < redirectRules.length; i++) {
setState(() {
redirectRules[i].priority = i + 1;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: Wrap(
spacing: 16,
children: [
FloatingActionButton(
onPressed: () {
if (!isSaving & redirectRulesLoaded) {
setState(() {
isSaving = true;
});
_saveRedirectRules();
}
},
child: isSaving
? const Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(strokeWidth: 3))
: const Icon(Icons.save))
],
),
body: CustomScrollView(
slivers: [
const SliverAppBar.medium(
expandedHeight: 120,
title: Text(
"Redirect Rules",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
if (redirectRulesLoaded && redirectRules.isEmpty)
SliverToBoxAdapter(
child: Center(
child: Padding(
padding: const EdgeInsets.only(top: 50),
child: Column(
children: [
const Text(
"No Redirect Rules",
style: TextStyle(
fontSize: 24, fontWeight: FontWeight.bold),
),
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(
'Adding redirect rules will be supported soon!',
style: TextStyle(
fontSize: 16, color: Colors.grey[600]),
),
)
],
))))
else
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _ListCell(
redirectRule: redirectRules[index],
moveUp: index == 0
? null
: () {
setState(() {
redirectRules[index].priority -= 1;
redirectRules[index - 1].priority += 1;
});
_sortListByPriority();
},
moveDown: index == (redirectRules.length - 1)
? null
: () {
setState(() {
redirectRules[index].priority += 1;
redirectRules[index + 1].priority -= 1;
});
_sortListByPriority();
},
delete: () {
setState(() {
redirectRules.removeAt(index);
});
_fixPriorities();
},
);
}, childCount: redirectRules.length))
],
),
);
}
}
class _ListCell extends StatefulWidget {
const _ListCell(
{required this.redirectRule,
required this.moveUp,
required this.moveDown,
required this.delete});
final VoidCallback? moveUp;
final VoidCallback? moveDown;
final VoidCallback delete;
final RedirectRule redirectRule;
@override
State<_ListCell> createState() => _ListCellState();
}
class _ListCellState extends State<_ListCell> {
String _conditionToTagString(RedirectRuleCondition condition) {
switch (condition.type) {
case RedirectRuleConditionType.DEVICE:
return "Device is ${ConditionDeviceType.fromApi(condition.matchValue).humanReadable}";
case RedirectRuleConditionType.LANGUAGE:
return "Language is ${condition.matchValue}";
case RedirectRuleConditionType.QUERY_PARAM:
return "Query string contains ${condition.matchKey}=${condition.matchValue}";
}
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: Container(
padding: const EdgeInsets.only(left: 8, right: 8, top: 16, bottom: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[800]!
: Colors.grey[300]!)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text("Long URL ",
style: TextStyle(fontWeight: FontWeight.bold)),
Text(widget.redirectRule.longUrl)
],
),
const Text("Conditions:",
style: TextStyle(fontWeight: FontWeight.bold)),
Row(
children: [
Expanded(
child: Wrap(
children:
widget.redirectRule.conditions.map((condition) {
return Padding(
padding: const EdgeInsets.only(right: 4, top: 4),
child: Container(
padding: const EdgeInsets.only(
top: 4, bottom: 4, left: 12, right: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color:
MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[900]
: Colors.grey[300],
),
child: Text(_conditionToTagString(condition)),
),
);
}).toList(),
),
)
],
),
Wrap(
children: [
IconButton(
disabledColor:
MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[700]
: Colors.grey[400],
onPressed: widget.moveUp,
icon: const Icon(Icons.arrow_upward),
),
IconButton(
disabledColor:
MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[700]
: Colors.grey[400],
onPressed: widget.moveDown,
icon: const Icon(Icons.arrow_downward),
),
IconButton(
onPressed: widget.delete,
icon: const Icon(Icons.delete, color: Colors.red),
)
],
)
],
)));
}
}

View File

@ -1,8 +1,8 @@
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/server_manager.dart'; import 'package:shlink_app/API/server_manager.dart';
import 'package:shlink_app/views/login_view.dart';
import 'package:shlink_app/views/opensource_licenses_view.dart'; import 'package:shlink_app/views/opensource_licenses_view.dart';
import 'package:shlink_app/widgets/available_servers_bottom_sheet.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;
@ -64,91 +64,81 @@ class _SettingsViewState extends State<SettingsView> {
return Scaffold( return Scaffold(
body: CustomScrollView( body: CustomScrollView(
slivers: [ slivers: [
SliverAppBar.medium( const SliverAppBar.medium(
expandedHeight: 120, expandedHeight: 120,
title: const Text( title: Text(
"Settings", "Settings",
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
actions: [
PopupMenuButton(
itemBuilder: (context) {
return [
const PopupMenuItem(
value: 0,
child: Text("Log out", style: TextStyle(color: Colors.red)),
)
];
},
onSelected: (value) {
if (value == 0) {
globals.serverManager.logOut().then((value) =>
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => const LoginView())));
}
},
)
],
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Column( child: Column(
children: [ children: [
Container( GestureDetector(
decoration: BoxDecoration( onTap: () {
borderRadius: BorderRadius.circular(8), showModalBottomSheet(
color: Theme.of(context).brightness == Brightness.light context: context,
? Colors.grey[100] builder: (BuildContext context) {
: Colors.grey[900], return const AvailableServerBottomSheet();
), });
child: Padding( },
padding: const EdgeInsets.all(12.0), child: Container(
child: Row( decoration: BoxDecoration(
children: [ borderRadius: BorderRadius.circular(8),
Icon(Icons.dns_outlined, color: Theme.of(context).brightness == Brightness.light
color: (() { ? Colors.grey[100]
switch (_serverStatus) { : Colors.grey[900],
case ServerStatus.connected: ),
return Colors.green; child: Padding(
case ServerStatus.connecting: padding: const EdgeInsets.all(12.0),
return Colors.orange; child: Row(
case ServerStatus.disconnected: children: [
return Colors.red; Icon(Icons.dns_outlined,
} color: (() {
}())), switch (_serverStatus) {
const SizedBox(width: 8), case ServerStatus.connected:
Column( return Colors.green;
crossAxisAlignment: CrossAxisAlignment.start, case ServerStatus.connecting:
children: [ return Colors.orange;
const Text("Connected to", case ServerStatus.disconnected:
style: TextStyle(color: Colors.grey)), return Colors.red;
Text(globals.serverManager.getServerUrl(), }
style: const TextStyle( }())),
fontWeight: FontWeight.bold, const SizedBox(width: 8),
fontSize: 16)), Column(
Row( crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text("API Version: ", const Text("Connected to",
style: TextStyle( style: TextStyle(color: Colors.grey)),
color: Colors.grey, Text(globals.serverManager.getServerUrl(),
fontWeight: FontWeight.w600)), style: const TextStyle(
Text(globals.serverManager.getApiVersion(), fontWeight: FontWeight.bold,
style: fontSize: 16)),
const TextStyle(color: Colors.grey)), Row(
const SizedBox(width: 16), children: [
const Text("Server Version: ", const Text("API Version: ",
style: TextStyle( style: TextStyle(
color: Colors.grey, color: Colors.grey,
fontWeight: FontWeight.w600)), fontWeight: FontWeight.w600)),
Text(_serverVersion, Text(globals.serverManager.getApiVersion(),
style: style: const TextStyle(
const TextStyle(color: Colors.grey)) color: Colors.grey)),
], const SizedBox(width: 16),
), const Text("Server Version: ",
], style: TextStyle(
) color: Colors.grey,
], fontWeight: FontWeight.w600)),
Text(_serverVersion,
style:
const TextStyle(color: Colors.grey))
],
),
],
)
],
),
), ),
), ),
), ),

View File

@ -144,9 +144,9 @@ class _ShortURLEditViewState extends State<ShortURLEditView>
return Scaffold( return Scaffold(
body: CustomScrollView( body: CustomScrollView(
slivers: [ slivers: [
const SliverAppBar.medium( SliverAppBar.medium(
title: Text("New Short URL", title: Text("${disableSlugEditor ? "Edit" : "New"} Short URL",
style: TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(fontWeight: FontWeight.bold)),
), ),
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:shlink_app/API/Classes/ShortURL/short_url.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/server_manager.dart'; import 'package:shlink_app/API/server_manager.dart';
import 'package:shlink_app/views/redirect_rules_detail_view.dart';
import 'package:shlink_app/views/short_url_edit_view.dart'; import 'package:shlink_app/views/short_url_edit_view.dart';
import 'package:shlink_app/widgets/url_tags_list_widget.dart'; import 'package:shlink_app/widgets/url_tags_list_widget.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -129,6 +130,10 @@ class _URLDetailViewState extends State<URLDetailView> {
title: "Short URL", content: shortURL.shortUrl, isUrl: true), title: "Short URL", content: shortURL.shortUrl, isUrl: true),
_ListCell(title: "Long URL", content: shortURL.longUrl, isUrl: true), _ListCell(title: "Long URL", content: shortURL.longUrl, isUrl: true),
_ListCell(title: "Creation Date", content: shortURL.dateCreated), _ListCell(title: "Creation Date", content: shortURL.dateCreated),
_ListCell(
title: "Redirect Rules",
content: null,
clickableDetailView: RedirectRulesDetailView(shortURL: shortURL)),
const _ListCell(title: "Visits", content: ""), const _ListCell(title: "Visits", content: ""),
_ListCell( _ListCell(
title: "Total", content: shortURL.visitsSummary.total, sub: true), title: "Total", content: shortURL.visitsSummary.total, sub: true),
@ -163,13 +168,15 @@ class _ListCell extends StatefulWidget {
required this.content, required this.content,
this.sub = false, this.sub = false,
this.last = false, this.last = false,
this.isUrl = false}); this.isUrl = false,
this.clickableDetailView});
final String title; final String title;
final dynamic content; final dynamic content;
final bool sub; final bool sub;
final bool last; final bool last;
final bool isUrl; final bool isUrl;
final Widget? clickableDetailView;
@override @override
State<_ListCell> createState() => _ListCellState(); State<_ListCell> createState() => _ListCellState();
@ -183,11 +190,16 @@ class _ListCellState extends State<_ListCell> {
padding: EdgeInsets.only(top: 16, bottom: widget.last ? 30 : 0), padding: EdgeInsets.only(top: 16, bottom: widget.last ? 30 : 0),
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
Uri? parsedUrl = Uri.tryParse(widget.content); if (widget.clickableDetailView != null) {
if (widget.isUrl && Navigator.of(context).push(MaterialPageRoute(
parsedUrl != null && builder: (context) => widget.clickableDetailView!));
await canLaunchUrl(parsedUrl)) { } else if (widget.content is String) {
launchUrl(parsedUrl); Uri? parsedUrl = Uri.tryParse(widget.content);
if (widget.isUrl &&
parsedUrl != null &&
await canLaunchUrl(parsedUrl)) {
launchUrl(parsedUrl);
}
} }
}, },
child: Container( child: Container(
@ -245,6 +257,8 @@ class _ListCellState extends State<_ListCell> {
else if (widget.content is DateTime) else if (widget.content is DateTime)
Text(DateFormat('yyyy-MM-dd - HH:mm') Text(DateFormat('yyyy-MM-dd - HH:mm')
.format(widget.content)) .format(widget.content))
else if (widget.clickableDetailView != null)
const Icon(Icons.chevron_right)
else else
const Text("N/A") const Text("N/A")
], ],

View File

@ -0,0 +1,133 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shlink_app/main.dart';
import 'package:shlink_app/views/login_view.dart';
import '../globals.dart' as globals;
class AvailableServerBottomSheet extends StatefulWidget {
const AvailableServerBottomSheet({super.key});
@override
State<AvailableServerBottomSheet> createState() =>
_AvailableServerBottomSheetState();
}
class _AvailableServerBottomSheetState
extends State<AvailableServerBottomSheet> {
List<String> availableServers = [];
@override
void initState() {
super.initState();
_loadServers();
}
Future<void> _loadServers() async {
List<String> savedServers =
await globals.serverManager.getAvailableServers();
setState(() {
availableServers = savedServers;
});
}
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
const SliverAppBar.medium(
expandedHeight: 120,
automaticallyImplyLeading: false,
title: Text(
"Available Servers",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
SliverList(
delegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
return GestureDetector(
onTap: () async {
final prefs = await SharedPreferences.getInstance();
prefs.setString("lastusedserver", availableServers[index]);
await Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => const InitialPage()),
(Route<dynamic> route) => false);
},
child: Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: Container(
padding:
const EdgeInsets.only(left: 8, right: 8, top: 16, bottom: 16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color:
MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[800]!
: Colors.grey[300]!)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
const Icon(Icons.dns_outlined),
Text(availableServers[index])
],
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
if (availableServers[index] ==
globals.serverManager.serverUrl)
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(4)),
),
IconButton(
onPressed: () async {
globals.serverManager
.logOut(availableServers[index]);
if (availableServers[index] ==
globals.serverManager.serverUrl) {
await Navigator.of(context)
.pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) =>
const InitialPage()),
(Route<dynamic> route) => false);
} else {
Navigator.pop(context);
}
},
icon: const Icon(Icons.logout, color: Colors.red),
)
],
)
],
))),
);
}, childCount: availableServers.length)),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 8),
child: Center(
child: ElevatedButton(
onPressed: () async {
await Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => const LoginView()));
},
child: const Text("Add server..."),
),
),
))
],
);
}
}

View File

@ -5,18 +5,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.10" version: "3.6.1"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -69,18 +69,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.2" version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -93,10 +85,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.6" version: "1.0.8"
dartz: dartz:
dependency: "direct main" dependency: "direct main"
description: description:
@ -125,10 +117,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -154,10 +146,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "4.0.0"
flutter_process_text: flutter_process_text:
dependency: "direct main" dependency: "direct main"
description: description:
@ -170,50 +162,50 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_secure_storage name: flutter_secure_storage
sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.0" version: "9.2.2"
flutter_secure_storage_linux: flutter_secure_storage_linux:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_linux name: flutter_secure_storage_linux
sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
flutter_secure_storage_macos: flutter_secure_storage_macos:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_macos name: flutter_secure_storage_macos
sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.1.2"
flutter_secure_storage_platform_interface: flutter_secure_storage_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_platform_interface name: flutter_secure_storage_platform_interface
sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.1.2"
flutter_secure_storage_web: flutter_secure_storage_web:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_web name: flutter_secure_storage_web
sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.2.1"
flutter_secure_storage_windows: flutter_secure_storage_windows:
dependency: transitive dependency: transitive
description: description:
name: flutter_secure_storage_windows name: flutter_secure_storage_windows
sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.1.2"
flutter_sharing_intent: flutter_sharing_intent:
dependency: "direct main" dependency: "direct main"
description: description:
@ -236,10 +228,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.2.2"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -252,10 +244,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e" sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.7" version: "4.2.0"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -276,10 +268,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: json_annotation name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.1" version: "4.9.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
license_generator: license_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -292,82 +308,82 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "4.0.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.16" version: "0.12.16+1"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.8.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.12.0"
package_info_plus: package_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.0" version: "8.0.0"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.9.0"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
name: path_provider name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.7"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -388,26 +404,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.3.0"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.4.0" version: "6.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.4" version: "3.1.5"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -416,22 +432,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev"
source: hosted
version: "3.7.4"
qr: qr:
dependency: transitive dependency: transitive
description: description:
name: qr name: qr
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
qr_flutter: qr_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@ -444,26 +452,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.3"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.5" version: "2.4.0"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -476,18 +484,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" sha256: "034650b71e73629ca08a0bd789fd1d83cc63c2d1e405946f7cef7bc37432f93a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.0"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.3.0"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@ -513,18 +521,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -545,10 +553,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.0" version: "0.7.0"
tuple: tuple:
dependency: "direct main" dependency: "direct main"
description: description:
@ -569,26 +577,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.4" version: "6.3.0"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 sha256: "95d8027db36a0e52caf55680f91e33ea6aa12a3ce608c90b06f4e429a21067ac"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.0" version: "6.3.5"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.4" version: "6.3.1"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@ -601,10 +609,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.2.0"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -617,18 +625,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.1"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -637,22 +645,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.1"
web: web:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.4-beta" version: "0.5.1"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "5.5.1"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -665,10 +681,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.0" version: "6.5.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -678,5 +694,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.1.0 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.13.0" flutter: ">=3.22.0"

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # 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: 1.2.0+8 version: 1.3.1+10
environment: environment:
sdk: ^3.0.0 sdk: ^3.0.0
@ -40,7 +40,7 @@ dependencies:
intl: ^0.19.0 intl: ^0.19.0
dynamic_color: ^1.6.6 dynamic_color: ^1.6.6
url_launcher: ^6.2.4 url_launcher: ^6.2.4
package_info_plus: ^4.0.2 package_info_plus: ^8.0.0
shared_preferences: ^2.2.2 shared_preferences: ^2.2.2
flutter_sharing_intent: ^1.1.1 flutter_sharing_intent: ^1.1.1
@ -50,7 +50,7 @@ dev_dependencies:
license_generator: ^2.0.0 license_generator: ^2.0.0
flutter_launcher_icons: ^0.13.1 flutter_launcher_icons: ^0.13.1
flutter_lints: ^3.0.1 flutter_lints: ^4.0.0
flutter: flutter:
uses-material-design: true uses-material-design: true