formatting

This commit is contained in:
Adrian Baumgart 2024-07-25 17:37:17 +02:00
parent 0eea6ee9a2
commit ba058e2af3
No known key found for this signature in database
11 changed files with 216 additions and 192 deletions

View File

@ -14,11 +14,9 @@ enum ConditionDeviceType {
} }
throw ArgumentError("Invalid type $api"); throw ArgumentError("Invalid type $api");
} }
} }
extension ConditionTypeExtension on ConditionDeviceType { extension ConditionTypeExtension on ConditionDeviceType {
String get api { String get api {
switch (this) { switch (this) {
case ConditionDeviceType.IOS: case ConditionDeviceType.IOS:
@ -29,6 +27,7 @@ extension ConditionTypeExtension on ConditionDeviceType {
return "desktop"; return "desktop";
} }
} }
String get humanReadable { String get humanReadable {
switch (this) { switch (this) {
case ConditionDeviceType.IOS: case ConditionDeviceType.IOS:

View File

@ -9,10 +9,11 @@ class RedirectRule {
RedirectRule(this.longUrl, this.priority, this.conditions); RedirectRule(this.longUrl, this.priority, this.conditions);
RedirectRule.fromJson(Map<String, dynamic> json) RedirectRule.fromJson(Map<String, dynamic> json)
: longUrl = json["longUrl"], : longUrl = json["longUrl"],
priority = json["priority"], priority = json["priority"],
conditions = (json["conditions"] as List<dynamic>).map((e) conditions = (json["conditions"] as List<dynamic>)
=> RedirectRuleCondition.fromJson(e)).toList(); .map((e) => RedirectRuleCondition.fromJson(e))
.toList();
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {

View File

@ -5,19 +5,15 @@ class RedirectRuleCondition {
String matchValue; String matchValue;
String? matchKey; String? matchKey;
RedirectRuleCondition(String type, this.matchValue, this.matchKey) : RedirectRuleCondition(String type, this.matchValue, this.matchKey)
type = RedirectRuleConditionType.fromApi(type); : type = RedirectRuleConditionType.fromApi(type);
RedirectRuleCondition.fromJson(Map<String, dynamic> json) RedirectRuleCondition.fromJson(Map<String, dynamic> json)
: type = RedirectRuleConditionType.fromApi(json["type"]), : type = RedirectRuleConditionType.fromApi(json["type"]),
matchValue = json["matchValue"], matchValue = json["matchValue"],
matchKey = json["matchKey"]; matchKey = json["matchKey"];
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {"type": type.api, "matchValue": matchValue, "matchKey": matchKey};
"type": type.api,
"matchValue": matchValue,
"matchKey": matchKey
};
} }
} }

View File

@ -14,11 +14,9 @@ enum RedirectRuleConditionType {
} }
throw ArgumentError("Invalid type $api"); throw ArgumentError("Invalid type $api");
} }
} }
extension ConditionTypeExtension on RedirectRuleConditionType { extension ConditionTypeExtension on RedirectRuleConditionType {
String get api { String get api {
switch (this) { switch (this) {
case RedirectRuleConditionType.DEVICE: case RedirectRuleConditionType.DEVICE:
@ -29,6 +27,7 @@ extension ConditionTypeExtension on RedirectRuleConditionType {
return "query-param"; return "query-param";
} }
} }
String get humanReadable { String get humanReadable {
switch (this) { switch (this) {
case RedirectRuleConditionType.DEVICE: case RedirectRuleConditionType.DEVICE:

View File

@ -12,8 +12,9 @@ FutureOr<Either<List<RedirectRule>, Failure>> apiGetRedirectRules(
String? serverUrl, String? serverUrl,
String apiVersion) async { String apiVersion) async {
try { try {
final response = final response = await http.get(
await http.get(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"), Uri.parse(
"$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"),
headers: { headers: {
"X-Api-Key": apiKey ?? "", "X-Api-Key": apiKey ?? "",
}); });
@ -22,9 +23,10 @@ FutureOr<Either<List<RedirectRule>, Failure>> apiGetRedirectRules(
var jsonBody = jsonDecode(response.body) as Map<String, dynamic>; var jsonBody = jsonDecode(response.body) as Map<String, dynamic>;
// convert json array to object array // convert json array to object array
List<RedirectRule> redirectRules = (jsonBody["redirectRules"] List<RedirectRule> redirectRules =
as List<dynamic>).map((e) (jsonBody["redirectRules"] as List<dynamic>)
=> RedirectRule.fromJson(e)).toList(); .map((e) => RedirectRule.fromJson(e))
.toList();
return left(redirectRules); return left(redirectRules);
} else { } else {

View File

@ -14,13 +14,16 @@ FutureOr<Either<bool, Failure>> apiSetRedirectRules(
String apiVersion) async { String apiVersion) async {
try { try {
Map<String, dynamic> body = {}; Map<String, dynamic> body = {};
List<Map<String, dynamic>> redirectRulesJson = redirectRules.map((e) => e.toJson()).toList(); List<Map<String, dynamic>> redirectRulesJson =
redirectRules.map((e) => e.toJson()).toList();
body["redirectRules"] = redirectRulesJson; body["redirectRules"] = redirectRulesJson;
final response = final response = await http.post(
await http.post(Uri.parse("$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"), Uri.parse(
"$serverUrl/rest/v$apiVersion/short-urls/$shortCode/redirect-rules"),
headers: { headers: {
"X-Api-Key": apiKey ?? "", "X-Api-Key": apiKey ?? "",
}, body: jsonEncode(body)); },
body: jsonEncode(body));
if (response.statusCode == 200) { if (response.statusCode == 200) {
return left(true); return left(true);
} else { } else {

View File

@ -127,6 +127,7 @@ 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) /// Gets redirect rules for a given short URL (code)
FutureOr<Either<List<RedirectRule>, Failure>> getRedirectRules( FutureOr<Either<List<RedirectRule>, Failure>> getRedirectRules(
String shortCode) async { String shortCode) async {
@ -136,7 +137,8 @@ class ServerManager {
/// Sets redirect rules for a given short URL (code) /// Sets redirect rules for a given short URL (code)
FutureOr<Either<bool, Failure>> setRedirectRules( FutureOr<Either<bool, Failure>> setRedirectRules(
String shortCode, List<RedirectRule> redirectRules) async { String shortCode, List<RedirectRule> redirectRules) async {
return apiSetRedirectRules(shortCode, redirectRules, apiKey, serverUrl, apiVersion); return apiSetRedirectRules(
shortCode, redirectRules, apiKey, serverUrl, apiVersion);
} }
} }

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

View File

@ -50,7 +50,8 @@ 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, 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',
@ -286,7 +287,8 @@ SOFTWARE.
''', ''',
version: r'^1.6.6', version: r'^1.6.6',
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',
), ),
const License( const License(
name: r'flutter', name: r'flutter',
@ -346,7 +348,8 @@ SOFTWARE.
''', ''',
version: r'^0.13.1', version: r'^0.13.1',
homepage: r'https://github.com/fluttercommunity/flutter_launcher_icons', homepage: r'https://github.com/fluttercommunity/flutter_launcher_icons',
repository: r'https://github.com/fluttercommunity/flutter_launcher_icons/', repository:
r'https://github.com/fluttercommunity/flutter_launcher_icons/',
), ),
const License( const License(
name: r'flutter_lints', name: r'flutter_lints',
@ -378,7 +381,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^3.0.1', version: r'^3.0.1',
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',
), ),
const License( const License(
name: r'flutter_process_text', name: r'flutter_process_text',
@ -446,7 +450,8 @@ 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, 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',
@ -652,7 +657,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
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, repository: null,
), ),
const License( const License(
@ -815,7 +821,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^4.0.2', version: r'^4.0.2',
homepage: r'https://plus.fluttercommunity.dev/', homepage: r'https://plus.fluttercommunity.dev/',
repository: r'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/package_info_plus', repository:
r'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/package_info_plus',
), ),
const License( const License(
name: r'qr_flutter', name: r'qr_flutter',
@ -883,7 +890,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^2.2.2', version: r'^2.2.2',
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',
), ),
const License( const License(
name: r'tuple', name: r'tuple',
@ -943,7 +951,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''', ''',
version: r'^6.2.4', version: r'^6.2.4',
homepage: null, 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

@ -13,7 +13,8 @@ class RedirectRulesDetailView extends StatefulWidget {
final ShortURL shortURL; final ShortURL shortURL;
@override @override
State<RedirectRulesDetailView> createState() => _RedirectRulesDetailViewState(); State<RedirectRulesDetailView> createState() =>
_RedirectRulesDetailViewState();
} }
class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> { class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
@ -30,7 +31,8 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
} }
Future<void> loadRedirectRules() async { Future<void> loadRedirectRules() async {
final response = await globals.serverManager.getRedirectRules(widget.shortURL.shortCode); final response =
await globals.serverManager.getRedirectRules(widget.shortURL.shortCode);
response.fold((l) { response.fold((l) {
setState(() { setState(() {
redirectRules = l; redirectRules = l;
@ -56,7 +58,8 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
} }
void _saveRedirectRules() async { void _saveRedirectRules() async {
final response = await globals.serverManager.setRedirectRules(widget.shortURL.shortCode, redirectRules); final response = await globals.serverManager
.setRedirectRules(widget.shortURL.shortCode, redirectRules);
response.fold((l) { response.fold((l) {
Navigator.pop(context); Navigator.pop(context);
}, (r) { }, (r) {
@ -97,7 +100,7 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
spacing: 16, spacing: 16,
children: [ children: [
FloatingActionButton( FloatingActionButton(
onPressed: () { onPressed: () {
if (!isSaving & redirectRulesLoaded) { if (!isSaving & redirectRulesLoaded) {
setState(() { setState(() {
isSaving = true; isSaving = true;
@ -106,10 +109,10 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
} }
}, },
child: isSaving child: isSaving
? const Padding( ? const Padding(
padding: EdgeInsets.all(16), padding: EdgeInsets.all(16),
child: CircularProgressIndicator(strokeWidth: 3)) child: CircularProgressIndicator(strokeWidth: 3))
: const Icon(Icons.save)) : const Icon(Icons.save))
], ],
), ),
body: CustomScrollView( body: CustomScrollView(
@ -131,16 +134,14 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
const Text( const Text(
"No Redirect Rules", "No Redirect Rules",
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24, fontWeight: FontWeight.bold),
fontWeight: FontWeight.bold),
), ),
Padding( Padding(
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
child: Text( child: Text(
'Adding redirect rules will be supported soon!', 'Adding redirect rules will be supported soon!',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16, color: Colors.grey[600]),
color: Colors.grey[600]),
), ),
) )
], ],
@ -149,30 +150,34 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
SliverList( SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
return _ListCell( return _ListCell(
redirectRule: redirectRules[index], redirectRule: redirectRules[index],
moveUp: index == 0 ? null : () { moveUp: index == 0
setState(() { ? null
redirectRules[index].priority -= 1; : () {
redirectRules[index - 1].priority += 1; setState(() {
}); redirectRules[index].priority -= 1;
_sortListByPriority(); redirectRules[index - 1].priority += 1;
}, });
moveDown: index == (redirectRules.length - 1) ? null : () { _sortListByPriority();
setState(() { },
redirectRules[index].priority += 1; moveDown: index == (redirectRules.length - 1)
redirectRules[index + 1].priority -= 1; ? null
}); : () {
_sortListByPriority(); setState(() {
}, redirectRules[index].priority += 1;
delete: () { redirectRules[index + 1].priority -= 1;
setState(() { });
redirectRules.removeAt(index); _sortListByPriority();
}); },
_fixPriorities(); delete: () {
}, setState(() {
); redirectRules.removeAt(index);
}, childCount: redirectRules.length)) });
_fixPriorities();
},
);
}, childCount: redirectRules.length))
], ],
), ),
); );
@ -180,11 +185,12 @@ class _RedirectRulesDetailViewState extends State<RedirectRulesDetailView> {
} }
class _ListCell extends StatefulWidget { class _ListCell extends StatefulWidget {
const _ListCell({super.key, const _ListCell(
required this.redirectRule, {super.key,
required this.moveUp, required this.redirectRule,
required this.moveDown, required this.moveUp,
required this.delete}); required this.moveDown,
required this.delete});
final VoidCallback? moveUp; final VoidCallback? moveUp;
final VoidCallback? moveDown; final VoidCallback? moveDown;
@ -196,7 +202,6 @@ class _ListCell extends StatefulWidget {
} }
class _ListCellState extends State<_ListCell> { class _ListCellState extends State<_ListCell> {
String _conditionToTagString(RedirectRuleCondition condition) { String _conditionToTagString(RedirectRuleCondition condition) {
switch (condition.type) { switch (condition.type) {
case RedirectRuleConditionType.DEVICE: case RedirectRuleConditionType.DEVICE:
@ -210,76 +215,84 @@ class _ListCellState extends State<_ListCell> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding(padding: EdgeInsets.only( return Padding(
left: 8, right: 8 padding: EdgeInsets.only(left: 8, right: 8),
), child: Container( child: Container(
padding: EdgeInsets.only(left: 8, right: 8, top: 16, bottom: 16), padding: EdgeInsets.only(left: 8, right: 8, top: 16, bottom: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
color: MediaQuery.of(context).platformBrightness == color: MediaQuery.of(context).platformBrightness ==
Brightness.dark Brightness.dark
? Colors.grey[800]! ? Colors.grey[800]!
: Colors.grey[300]!)), : Colors.grey[300]!)),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
children: [ children: [
const Text("Long URL ", style: TextStyle(fontWeight: FontWeight.bold)), const Text("Long URL ",
Text(widget.redirectRule.longUrl) style: TextStyle(fontWeight: FontWeight.bold)),
], Text(widget.redirectRule.longUrl)
), ],
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(),
), ),
) Text("Conditions:",
], style: TextStyle(fontWeight: FontWeight.bold)),
), Row(
Wrap( children: [
children: [ Expanded(
IconButton( child: Wrap(
disabledColor: MediaQuery.of(context).platformBrightness children:
== Brightness.dark ? Colors.grey[700] : Colors.grey[400], widget.redirectRule.conditions.map((condition) {
onPressed: widget.moveUp, return Padding(
icon: Icon(Icons.arrow_upward), padding: const EdgeInsets.only(right: 4, top: 4),
), child: Container(
IconButton( padding: const EdgeInsets.only(
disabledColor: MediaQuery.of(context).platformBrightness top: 4, bottom: 4, left: 12, right: 12),
== Brightness.dark ? Colors.grey[700] : Colors.grey[400], decoration: BoxDecoration(
onPressed: widget.moveDown, borderRadius: BorderRadius.circular(4),
icon: Icon(Icons.arrow_downward), color:
), MediaQuery.of(context).platformBrightness ==
IconButton( Brightness.dark
onPressed: widget.delete, ? Colors.grey[900]
icon: Icon(Icons.delete, color: Colors.red), : 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: Icon(Icons.arrow_upward),
),
IconButton(
disabledColor:
MediaQuery.of(context).platformBrightness ==
Brightness.dark
? Colors.grey[700]
: Colors.grey[400],
onPressed: widget.moveDown,
icon: Icon(Icons.arrow_downward),
),
IconButton(
onPressed: widget.delete,
icon: Icon(Icons.delete, color: Colors.red),
)
],
)
],
)));
} }
} }

View File

@ -130,7 +130,9 @@ 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, _ListCell(
title: "Redirect Rules",
content: null,
clickableDetailView: RedirectRulesDetailView(shortURL: shortURL)), clickableDetailView: RedirectRulesDetailView(shortURL: shortURL)),
const _ListCell(title: "Visits", content: ""), const _ListCell(title: "Visits", content: ""),
_ListCell( _ListCell(
@ -189,10 +191,8 @@ class _ListCellState extends State<_ListCell> {
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
if (widget.clickableDetailView != null) { if (widget.clickableDetailView != null) {
Navigator.of(context) Navigator.of(context).push(MaterialPageRoute(
.push(MaterialPageRoute( builder: (context) => widget.clickableDetailView!));
builder: (context) =>
widget.clickableDetailView!));
} else if (widget.content is String) { } else if (widget.content is String) {
Uri? parsedUrl = Uri.tryParse(widget.content); Uri? parsedUrl = Uri.tryParse(widget.content);
if (widget.isUrl && if (widget.isUrl &&
@ -258,7 +258,7 @@ class _ListCellState extends State<_ListCell> {
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) else if (widget.clickableDetailView != null)
const Icon(Icons.chevron_right) const Icon(Icons.chevron_right)
else else
const Text("N/A") const Text("N/A")
], ],