diff --git a/lib/API/v1/API.dart b/lib/API/v1/API.dart deleted file mode 100644 index 22b2b7b..0000000 --- a/lib/API/v1/API.dart +++ /dev/null @@ -1,276 +0,0 @@ -// To parse this JSON data, do -// -// final derpibooru = derpibooruFromJson(jsonString); - -import 'dart:convert'; - -Derpibooru derpibooruFromJson(String str) { - final jsonData = json.decode(str); - return Derpibooru.fromJson(jsonData); -} - -String derpibooruToJson(Derpibooru data) { - final dyn = data.toJson(); - return json.encode(dyn); -} - -class Derpibooru { - List search; - int total; - List interactions; - - Derpibooru({ - this.search, - this.total, - this.interactions, - }); - - factory Derpibooru.fromJson(Map json) => new Derpibooru( - search: new List.from(json["search"].map((x) => Derpi.fromJson(x))), - total: json["total"], - interactions: new List.from(json["interactions"].map((x) => x)), - ); - - Map toJson() => { - "search": new List.from(search.map((x) => x.toJson())), - "total": total, - "interactions": new List.from(interactions.map((x) => x)), - }; -} - -class Derpi { - int id; - String createdAt; - String updatedAt; - String firstSeenAt; - int score; - int commentCount; - int width; - int height; - String fileName; - String description; - String uploader; - int uploaderId; - String image; - int upvotes; - int downvotes; - int faves; - List tags; - List tagIds; - double aspectRatio; - OriginalFormat originalFormat; - MimeType mimeType; - String sha512Hash; - String origSha512Hash; - String sourceUrl; - Representations representations; - bool isRendered; - bool isOptimized; - - Derpi({ - this.id, - this.createdAt, - this.updatedAt, - this.firstSeenAt, - this.score, - this.commentCount, - this.width, - this.height, - this.fileName, - this.description, - this.uploader, - this.uploaderId, - this.image, - this.upvotes, - this.downvotes, - this.faves, - this.tags, - this.tagIds, - this.aspectRatio, - this.originalFormat, - this.mimeType, - this.sha512Hash, - this.origSha512Hash, - this.sourceUrl, - this.representations, - this.isRendered, - this.isOptimized, - }); - - factory Derpi.fromJson(Map json) => new Derpi( - id: json["id"], - createdAt: json["created_at"], - updatedAt: json["updated_at"], - firstSeenAt: json["first_seen_at"], - score: json["score"], - commentCount: json["comment_count"], - width: json["width"], - height: json["height"], - fileName: json["file_name"], - description: json["description"], - uploader: json["uploader"], - uploaderId: json["uploader_id"] == null ? null : json["uploader_id"], - image: json["image"], - upvotes: json["upvotes"], - downvotes: json["downvotes"], - faves: json["faves"], - tags: Tag.parse(json["tags"]), - tagIds: new List.from(json["tag_ids"].map((x) => x)), - aspectRatio: json["aspect_ratio"].toDouble(), - originalFormat: originalFormatValues.map[json["original_format"]], - mimeType: mimeTypeValues.map[json["mime_type"]], - sha512Hash: json["sha512_hash"], - origSha512Hash: json["orig_sha512_hash"], - sourceUrl: json["source_url"], - representations: Representations.fromJson(json["representations"]), - isRendered: json["is_rendered"], - isOptimized: json["is_optimized"], - ); - - Map toJson() => { - "id": id, - "created_at": createdAt, - "updated_at": updatedAt, - "first_seen_at": firstSeenAt, - "score": score, - "comment_count": commentCount, - "width": width, - "height": height, - "file_name": fileName, - "description": description, - "uploader": uploader, - "uploader_id": uploaderId == null ? null : uploaderId, - "image": image, - "upvotes": upvotes, - "downvotes": downvotes, - "faves": faves, - "tags": tags, - "tag_ids": new List.from(tagIds.map((x) => x)), - "aspect_ratio": aspectRatio, - "original_format": originalFormatValues.reverse[originalFormat], - "mime_type": mimeTypeValues.reverse[mimeType], - "sha512_hash": sha512Hash, - "orig_sha512_hash": origSha512Hash, - "source_url": sourceUrl, - "representations": representations.toJson(), - "is_rendered": isRendered, - "is_optimized": isOptimized, - }; -} - -enum MimeType { IMAGE_JPEG, IMAGE_PNG, VIDEO_WEBM } - -final mimeTypeValues = new EnumValues({ - "image/jpeg": MimeType.IMAGE_JPEG, - "image/png": MimeType.IMAGE_PNG, - "video/webm": MimeType.VIDEO_WEBM -}); - -enum OriginalFormat { JPEG, PNG, WEBM } - -final originalFormatValues = new EnumValues({ - "jpeg": OriginalFormat.JPEG, - "png": OriginalFormat.PNG, - "webm": OriginalFormat.WEBM -}); - -enum TagType { ARTIST, SPOILER, OC } - -final tagTypeValues = new EnumValues({ - "artist": TagType.ARTIST, - "spoiler": TagType.SPOILER, - "oc": TagType.OC -}); - -class Representations { - String thumbTiny; - String thumbSmall; - String thumb; - String small; - String medium; - String large; - String tall; - String full; - String webm; - String mp4; - - Representations({ - this.thumbTiny, - this.thumbSmall, - this.thumb, - this.small, - this.medium, - this.large, - this.tall, - this.full, - this.webm, - this.mp4, - }); - - factory Representations.fromJson(Map json) => new Representations( - thumbTiny: json["thumb_tiny"], - thumbSmall: json["thumb_small"], - thumb: json["thumb"], - small: json["small"], - medium: json["medium"], - large: json["large"], - tall: json["tall"], - full: json["full"], - webm: json["webm"] == null ? null : json["webm"], - mp4: json["mp4"] == null ? null : json["mp4"], - ); - - Map toJson() => { - "thumb_tiny": thumbTiny, - "thumb_small": thumbSmall, - "thumb": thumb, - "small": small, - "medium": medium, - "large": large, - "tall": tall, - "full": full, - "webm": webm == null ? null : webm, - "mp4": mp4 == null ? null : mp4, - }; -} - -class Tag { - TagType type; - String label; - - Tag(String tag) { - var splitTag = tag.split(':'); - if (splitTag.length >= 2) { - this.type = tagTypeValues.map[splitTag[0]]; - this.label = tag; - } else { - this.label = tag; - } - } - - static List parse(String tags) { - List outTags = new List(); - if (tags != null){ - for (String t in tags.split(', ')) { - outTags.add(new Tag(t)); - } - } else { - outTags.add(new Tag('No tags ¯\\_(ツ)_/¯')); - } - return outTags; - } -} - -class EnumValues { - Map map; - Map reverseMap; - - EnumValues(this.map); - - Map get reverse { - if (reverseMap == null) { - reverseMap = map.map((k, v) => new MapEntry(v, k)); - } - return reverseMap; - } -} diff --git a/lib/API/v1/ImageList.dart b/lib/API/v1/ImageList.dart deleted file mode 100644 index 8fccf02..0000000 --- a/lib/API/v1/ImageList.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:chryssibooru/API/v2/API.dart'; -import 'package:chryssibooru/Connect.dart'; -import 'package:flutter/foundation.dart'; - - -Future> searchImages(String query, bool s, bool q, bool e, String key, [int page = 1, int limit = 30]) { - const api_url = "https://derpibooru.org/search.json?"; - - var ratingsArr = []; - if (s) ratingsArr.add("safe"); - if (q) ratingsArr.add("questionable"); - if (e) ratingsArr.add("explicit"); - var ratings = ratingsArr.join(" OR "); - - var queryString = api_url + "q=" + query; - - if ( !( (s&&q&&e) || (!s&&!q&&!e) ) ) queryString += "%2C (" + ratings + ")"; - queryString += "&page=" + page.toString(); - if (key != "" || key != null) queryString += "&key=" + key; - if (key == "" || key == null) queryString += "&perpage=" + limit.toString(); - - var escapedQuery = queryString.replaceAll(" ", "+"); - - var derpis = fetchDerpi(escapedQuery); - - debugPrint(derpis != null ? escapedQuery : 'End of results'); - - return derpis; -} - -Future getRandomImage(String query) { - String apiUrl = "https://derpibooru.org/search.json?q="; - apiUrl += query.replaceAll(' ', '+'); - apiUrl += '%2Cscore.gt:10&random_image=y'; - return fetchSingleDerpi(apiUrl); -} \ No newline at end of file diff --git a/lib/API/v2/API.dart b/lib/API/v2/API.dart index 253a7f3..57f7508 100644 --- a/lib/API/v2/API.dart +++ b/lib/API/v2/API.dart @@ -4,6 +4,8 @@ import 'dart:convert'; +import 'package:chryssibooru/Helpers.dart'; + Derpibooru derpibooruFromJson(String str) { final jsonData = json.decode(str); return Derpibooru.fromJson(jsonData); @@ -237,7 +239,9 @@ enum MimeType { IMAGE_PNG, IMAGE_JPEG, VIDEO_WEBM, VIDEO_MP4 } final mimeTypeValues = EnumValues({ "image/jpeg": MimeType.IMAGE_JPEG, - "image/png": MimeType.IMAGE_PNG + "image/png": MimeType.IMAGE_PNG, + "video/webm": MimeType.VIDEO_WEBM, + "video/mp4": MimeType.VIDEO_MP4 }); class Representations { @@ -261,6 +265,35 @@ class Representations { this.thumbTiny, }); + String fromEnum(ERepresentations representation) { + switch (representation) { + case ERepresentations.Full: + return this.full; + break; + case ERepresentations.Large: + return this.large; + break; + case ERepresentations.Medium: + return this.medium; + break; + case ERepresentations.Small: + return this.small; + break; + case ERepresentations.Thumb: + return this.thumb; + break; + case ERepresentations.ThumbSmall: + return this.thumbSmall; + break; + case ERepresentations.ThumbTiny: + return this.thumbTiny; + break; + default: + return this.thumbTiny; + break; + } + } + factory Representations.fromRawJson(String str) => Representations.fromJson(json.decode(str)); String toRawJson() => json.encode(toJson()); diff --git a/lib/API/v2/ImageList.dart b/lib/API/v2/ImageList.dart index 8901582..b097ec2 100644 --- a/lib/API/v2/ImageList.dart +++ b/lib/API/v2/ImageList.dart @@ -1,11 +1,17 @@ +import 'dart:math'; + import 'package:chryssibooru/API/v2/API.dart'; import 'package:chryssibooru/Connect.dart'; +import 'package:chryssibooru/Helpers.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; -Future> searchImages(String query, bool s, bool q, bool e, String key, [int page = 1, int limit = 30]) { +Future> searchImages(String query, bool s, bool q, bool e, String key, {int page = 1, int limit = 30, ESortMethod sortMethod = ESortMethod.ID_DESC}) { const api_url = "https://derpibooru.org/api/v1/json/search/images?"; + var sortData = sortDataFromEnum(sortMethod); + var ratingsArr = []; if (s) ratingsArr.add("safe"); if (q) ratingsArr.add("questionable"); @@ -18,6 +24,10 @@ Future> searchImages(String query, bool s, bool q, bool e, String ke queryString += "&page=" + page.toString(); if (key != "" || key != null) queryString += "&key=" + key; if (key == "" || key == null) queryString += "&per_page=" + limit.toString(); + if (sortData.length > 0 && sortData[0].isNotEmpty) + queryString += "&sf=" + sortData[0]; + if (sortData.length > 1 && sortData[1].isNotEmpty) + queryString += "&sd=" + sortData[1]; var escapedQuery = queryString.replaceAll(" ", "+"); @@ -29,8 +39,9 @@ Future> searchImages(String query, bool s, bool q, bool e, String ke } Future getRandomImage(String query) { - String apiUrl = "https://derpibooru.org/search.json?q="; + var rnd = (new Random()).nextInt(10000).toString(); + String apiUrl = "https://derpibooru.org/api/v1/json/search/images?q="; apiUrl += query.replaceAll(' ', '+'); - apiUrl += '%2Cscore.gt:10&random_image=y'; + apiUrl += ',score.gt:10&sf=random:'+rnd; return fetchSingleDerpi(apiUrl); } \ No newline at end of file diff --git a/lib/Connect.dart b/lib/Connect.dart index b05f37a..3cd813d 100644 --- a/lib/Connect.dart +++ b/lib/Connect.dart @@ -22,10 +22,5 @@ Future> fetchDerpi(String url) async { Future fetchSingleDerpi(String url) async { final response = await http.get(url); var idObject = jsonDecode(response.body); - String imageUrl = "https://derpibooru.org/"+idObject['id']+".json"; - final imageJson = await http.get(imageUrl); - var imageObject = jsonDecode(imageJson.body); - - - return Derpi.fromJson(imageObject); + return Derpi.fromJson(idObject['images'][0]); } \ No newline at end of file diff --git a/lib/DerpisRepo.dart b/lib/DerpisRepo.dart index d404a1f..01b080b 100644 --- a/lib/DerpisRepo.dart +++ b/lib/DerpisRepo.dart @@ -1,5 +1,6 @@ import 'package:chryssibooru/API/v2/API.dart'; import 'package:chryssibooru/API/v2/ImageList.dart'; +import 'package:chryssibooru/Helpers.dart'; import 'package:flutter/material.dart'; class DerpisRepo extends ChangeNotifier { @@ -14,11 +15,13 @@ class DerpisRepo extends ChangeNotifier { String query = ""; + ESortMethod sortMethod = ESortMethod.ID_DESC; + int page = 1; Future loadDerpis() async { loaded = false; - final newImages = await searchImages(query, safe, questionable, explicit, key, page); + final newImages = await searchImages(query, safe, questionable, explicit, key, page: page, sortMethod: sortMethod); if (newImages != null) { derpis.addAll(newImages); notifyListeners(); diff --git a/lib/Elements/DetailsSheet.dart b/lib/Elements/DetailsSheet.dart new file mode 100644 index 0000000..6dc0748 --- /dev/null +++ b/lib/Elements/DetailsSheet.dart @@ -0,0 +1,351 @@ +import 'package:chryssibooru/API/v2/API.dart'; +import 'package:chryssibooru/DerpisRepo.dart'; +import 'package:chryssibooru/Elements/IcoText.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:share/share.dart'; +import 'package:provider/provider.dart' as p; + +import '../Helpers.dart'; + +class DetailsSheet extends StatefulWidget { + DetailsSheet({ + @required this.derpi + }); + + final Derpi derpi; + + @override + _DetailsSheet createState() => _DetailsSheet(); +} + +class _DetailsSheet extends State { + + Derpi _derpi; + List _tags; + DerpisRepo _repo; + + + @override + void didChangeDependencies() { + _repo = p.Provider.of(context); + super.didChangeDependencies(); + } + + @override + void initState(){ + _derpi = widget.derpi; + _tags = widget.derpi.tags; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return new Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 1.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(_derpi.id.toString()), + ButtonBar( + children: [ + Container( + height: 30, + width: 30, + child: InkWell( + borderRadius: + BorderRadius.all(Radius.circular(50.0)), + child: Icon(Icons.share), + onTap: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Container( + height: 100.0, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + FlatButton( + onPressed: () => Share.share("https://derpibooru.org/" +_derpi.id.toString()), + child:Text("Derpibooru post"), + ), + FlatButton( + onPressed: () => Share.share("https:" + _derpi.representations.full), + child: Text("Direct image"), + ) + ], + ), + ); + }); + }, + onLongPress: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text("Share"), + ); + }); + }, + ), + ), + Container( + height: 30, + width: 30, + child: InkWell( + borderRadius: + BorderRadius.all(Radius.circular(50.0)), + child: Icon(Icons.link), + onTap: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Container( + height: 100.0, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + FlatButton( + onPressed: () => openInBrowser("https://derpibooru.org/" +_derpi.id.toString()), + child:Text("Derpibooru post"), + ), + FlatButton( + onPressed: () => openInBrowser("https:" +_derpi.representations.full), + child: Text("Direct image"), + ) + ], + ), + ); + }); + }, + onLongPress: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text("Open in browser"), + ); + }); + }, + ), + ), + Container( + height: 30, + width: 30, + child: InkWell( + borderRadius: + BorderRadius.all(Radius.circular(50.0)), + child: Icon(Icons.content_copy), + onTap: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Container( + height: 100.0, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + FlatButton( + onPressed: () => Clipboard.setData(new ClipboardData( + text: "https://derpibooru.org/" +_derpi.id.toString()) + ), + child: + Text("Derpibooru post"), + ), + FlatButton( + onPressed: () => Clipboard.setData(new ClipboardData( + text: "https:" + _derpi.representations.full) + ), + child: Text("Direct image"), + ) + ], + ), + ); + }); + }, + onLongPress: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text("Copy direct link"), + ); + }); + }, + ), + ), + Container( + height: 30, + width: 30, + child: InkWell( + borderRadius: + BorderRadius.all(Radius.circular(50.0)), + child: Icon(Icons.file_download), + onTap: () => + downloadImage(_derpi.representations.full), + onLongPress: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text("Download"), + ); + }); + }, + ), + ), + ], + ) + ], + ), + ), + ConstrainedBox( + constraints: new BoxConstraints(maxHeight: 100.0), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 4.0), + child: Row( + children: [ + Spacer(flex: 10), + IcoText( + text: _derpi.commentCount.toString(), + icon: Icons.chat_bubble_outline, + color: Color.fromARGB(255, 176, 153, 221) + ), + Spacer(), + IcoText( + text: _derpi.faves.toString(), + icon: Icons.star_border, + color: Colors.amber, + ), + Spacer(), + IcoText( + text: (_derpi.score - _derpi.downvotes).toString(), + icon: Icons.arrow_upward, + color: Colors.green, + reverse: true, + ), + Text(_derpi.score.toString()), + IcoText( + text: _derpi.downvotes.toString(), + icon: Icons.arrow_downward, + color: Colors.red + ), + Spacer(flex: 10), + ], + ), + ), + ), + ConstrainedBox( + constraints: new BoxConstraints(maxHeight: 100.0), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 4.0), + child: SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Text(_derpi.description), + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4.0, vertical: 1.0), + child: SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Wrap( + spacing: 5, + runSpacing: -5, + alignment: WrapAlignment.center, + children: [ + for (final tag in _tags) + GestureDetector( + child: Chip( + padding: EdgeInsets.all(0), + label: Text(tag.label), + backgroundColor: { + TagType.ARTIST: Colors.blue, + TagType.OC: Colors.green, + TagType.SPOILER: Colors.red, + }[tag.type] ?? + Colors.grey, + ), + onTap: () { + showModalBottomSheet( + context: context, + builder: (BuildContext c) { + return Container( + height: 150.0, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + FlatButton( + onPressed: () async { + _repo.derpis = new List(); + _repo.addTag(tag.label); + await _repo.loadDerpis(); + // setState(() { + // _id = 0; + // _pageController + // .jumpToPage(0); + // }); + }, + child: Text("Add to search"), + ), + FlatButton( + onPressed: () async { + _repo.derpis = new List(); + _repo.removeTag(tag.label); + await _repo.loadDerpis(); + // setState(() { + // _id = 0; + // _pageController + // .jumpToPage(0); + // }); + }, + child: Text("Remove from search"), + ), + FlatButton( + onPressed: () async { + _repo.derpis = new List(); + _repo.query = tag.label; + await _repo.loadDerpis(); + // setState(() { + // _id = 0; + // _pageController + // .jumpToPage(0); + // }); + }, + child: Text("New search"), + ) + ], + ), + ); + }); + }, + ) + ], + ), + ), + ), + ), + ], + ); + } + + + +} \ No newline at end of file diff --git a/lib/Elements/FilterSheet.dart b/lib/Elements/FilterSheet.dart index 2166c7c..1ea66f0 100644 --- a/lib/Elements/FilterSheet.dart +++ b/lib/Elements/FilterSheet.dart @@ -5,7 +5,8 @@ class FilterSheet extends StatefulWidget { FilterSheet({ @required this.safe, @required this.questionable, @required this.explicit, @required this.safeChanged, @required this.questionableChanged, @required this.explicitChanged, - @required this.quality, @required this.qualityChanged + @required this.quality, @required this.qualityChanged, + @required this.sortMethod, @required this.sortMethodChanged }); final bool safe; @@ -19,6 +20,9 @@ class FilterSheet extends StatefulWidget { final Quality quality; final ValueChanged qualityChanged; + final ESortMethod sortMethod; + final ValueChanged sortMethodChanged; + @override _FilterSheet createState() => _FilterSheet(); } @@ -29,6 +33,7 @@ class _FilterSheet extends State { bool _explicit; Quality _quality; + ESortMethod _sortMethod; @override void initState() { @@ -36,6 +41,7 @@ class _FilterSheet extends State { _questionable = widget.questionable; _explicit = widget.explicit; _quality = widget.quality; + _sortMethod = widget.sortMethod; super.initState(); } @@ -80,10 +86,12 @@ class _FilterSheet extends State { Row( mainAxisAlignment: MainAxisAlignment.start, children: [ + Padding( padding: const EdgeInsets.fromLTRB(16, 8, 8, 8), child: Text('Image quality:'), ), + new DropdownButton( value: _quality, items: Quality.values.map((Quality q) { @@ -101,6 +109,32 @@ class _FilterSheet extends State { ), ], ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + + Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 8, 8), + child: Text('Sort by:'), + ), + + new DropdownButton( + value: _sortMethod, + items: ESortMethod.values.map((ESortMethod s) { + return DropdownMenuItem ( + value: s, + child: Text(s.toString().split('.')[1]), + ); + }).toList(), + onChanged: (ESortMethod s) { + setState(() { + _sortMethod = s; + widget.sortMethodChanged(s); + }); + } + ), + ], + ), new ListTile( ), diff --git a/lib/Elements/IcoText.dart b/lib/Elements/IcoText.dart new file mode 100644 index 0000000..2eae5fe --- /dev/null +++ b/lib/Elements/IcoText.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class IcoText extends StatelessWidget { + const IcoText( + {Key key, + @required this.text, + @required this.icon, + @required this.color, + this.reverse = false}) + : super(key: key); + + final String text; + final IconData icon; + final Color color; + + final bool reverse; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Icon( + icon, + color: color, + ), + Text( + text, + style: TextStyle(color: color), + ), + ], + textDirection: reverse + ? TextDirection.rtl + : TextDirection.ltr, + ); + } +} diff --git a/lib/Helpers.dart b/lib/Helpers.dart index bd94e26..2f2ff74 100644 --- a/lib/Helpers.dart +++ b/lib/Helpers.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:chryssibooru/DerpisRepo.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -44,6 +46,75 @@ enum Quality { Source } +enum ESortMethod { + SCORE_ASC, + SCORE_DESC, + ID_ASC, + ID_DESC, + FAVES_ASC, + FAVES_DESC, + UPVOTES_ASC, + UPVOTES_DESC, + RANDOM +} + +List sortDataFromEnum(ESortMethod method) { + switch (method) { + case ESortMethod.SCORE_ASC: + return ["score", "asc"]; + break; + case ESortMethod.SCORE_DESC: + return ["score", "desc"]; + break; + case ESortMethod.ID_ASC: + return ["id", "asc"]; + break; + case ESortMethod.ID_DESC: + return ["id", "desc"]; + break; + case ESortMethod.FAVES_ASC: + return ["faves", "asc"]; + break; + case ESortMethod.FAVES_DESC: + return ["faves", "desc"]; + break; + case ESortMethod.UPVOTES_ASC: + return ["upvotes", "asc"]; + break; + case ESortMethod.UPVOTES_DESC: + return ["upvotes", "desc"]; + break; + case ESortMethod.RANDOM: + var rnd = (new Random()).nextInt(10000).toString(); + return ["random:"+rnd, "desc"]; + break; + default: + return []; + break; + } +} + +enum ERepresentations { + Full, + Large, + Medium, + Small, + Thumb, + ThumbSmall, + ThumbTiny +} + +ERepresentations representationFromWidth(int width) { + if (width <= 50) return ERepresentations.ThumbTiny; + if (width <= 150) return ERepresentations.ThumbSmall; + if (width <= 250) return ERepresentations.Thumb; + if (width <= 320) return ERepresentations.Small; + if (width <= 800) return ERepresentations.Medium; + if (width <= 1280) return ERepresentations.Large; + return ERepresentations.Full; +} + + String getImageOfQuality(Quality quality, DerpisRepo repo, int index) { switch (quality) { case Quality.Low: @@ -58,66 +129,4 @@ String getImageOfQuality(Quality quality, DerpisRepo repo, int index) { default: return repo.derpis[index].representations.medium; } -} - -class Semver { - int major; - int minor; - int hotfix; - - Semver(this.major, this.minor, this.hotfix); - - Semver.fromString(String version) { - var parts = version.split('.'); - List iParts = List(); - - if (parts.length > 3) throw ArgumentError('Incorrect semver format'); - - for (var p in parts) { - int i = int.parse(p); - if (i == null) throw ArgumentError('Incorrect semver format'); - else iParts.add(i); - } - - this.major = iParts[0]; - this.minor = iParts[1]; - this.hotfix = iParts[2]; - } - - @override - String toString() => '$major.$minor.$hotfix'; - - bool operator < (Semver other) { - if (this.major < other.major) return true; - if (this.minor < other.minor) return true; - if (this.hotfix < other.hotfix) return true; - else return false; - } - - bool operator > (Semver other) { - if (this.major > other.major) return true; - if (this.minor > other.minor) return true; - if (this.hotfix > other.hotfix) return true; - else return false; - } - - bool operator <= (Semver other) => (this < other) || (this == other); - - bool operator >= (Semver other) => (this > other) || (this == other); - - @override - bool operator == (Object other) => - identical(this, other) || - other is Semver && - runtimeType == other.runtimeType && - major == other.major && - minor == other.minor && - hotfix == other.hotfix; - - @override - int get hashCode => - major.hashCode ^ - minor.hashCode ^ - hotfix.hashCode; - } \ No newline at end of file diff --git a/lib/Views/HomePage.dart b/lib/Views/HomePage.dart index 5021b09..71054ff 100644 --- a/lib/Views/HomePage.dart +++ b/lib/Views/HomePage.dart @@ -3,7 +3,7 @@ import 'package:chryssibooru/DerpisRepo.dart'; import 'package:chryssibooru/Elements/FavouritesModal.dart'; import 'package:chryssibooru/Elements/FilterSheet.dart'; import 'package:chryssibooru/Elements/HistoryModal.dart'; -import 'package:chryssibooru/API/v1/ImageList.dart'; +import 'package:chryssibooru/API/v2/ImageList.dart'; import 'package:chryssibooru/Views/ImageViewer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_advanced_networkimage/provider.dart'; @@ -44,6 +44,7 @@ class HomePageState extends State { bool _e = false; Quality _quality; + ESortMethod _sortMethod; String _headerImage; @@ -110,6 +111,18 @@ class HomePageState extends State { prefs.setInt('quality', _quality.index); } + // void _getSortMethodPrefs() async { + // SharedPreferences prefs = await SharedPreferences.getInstance(); + // setState(() { + // _sortMethod = ESortMethod.values[prefs.getInt('sortMethod') ?? ESortMethod.ID_DESC.index]; + // }); + // } + // + // void _saveSortMethodPrefs() async { + // SharedPreferences prefs = await SharedPreferences.getInstance(); + // prefs.setInt('sortMethod', _sortMethod.index); + // } + void _getRatingPrefs() async { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { @@ -153,8 +166,12 @@ class HomePageState extends State { prefs.setStringList("history", history); } + bool _isHeaderLoading = false; void _setHeaderImage() async { - var derp = await getRandomImage('chrysalis, solo, transparent background, -webm'); + setState(() { + _isHeaderLoading = true; + }); + Derpi derp = await getRandomImage('chrysalis,solo,transparent background,-webm'); setState(() { _headerImage = derp.representations.medium; }); @@ -194,19 +211,19 @@ class HomePageState extends State { child: Padding( padding: EdgeInsets.all(0.2), child: ClipRRect( - borderRadius:new BorderRadius.all(Radius.circular(10.0)), + borderRadius: new BorderRadius.all(Radius.circular(10.0)), child: (){ - String url = repo.derpis[index].representations.thumb; - if(repo.derpis[index].mimeType == MimeType.VIDEO_WEBM){ + String url = repo.derpis[index].representations.fromEnum(ERepresentations.Thumb); + if (repo.derpis[index].mimeType == MimeType.VIDEO_WEBM){ List parts = url.split('.'); parts[parts.length-1] = 'gif'; url = parts.join('.'); } - return new TransitionToImage( + return TransitionToImage( image: AdvancedNetworkImage( - url, - useDiskCache: true, - cacheRule: CacheRule(maxAge: const Duration(days: 7)) + url, + useDiskCache: true, + cacheRule: CacheRule(maxAge: const Duration(days: 7)) ), placeholder: SvgPicture.asset('assets/logo.svg'), loadingWidgetBuilder: (BuildContext ctx, double progress, _) => Padding( @@ -266,6 +283,7 @@ class HomePageState extends State { repo.derpis = new List(); repo.page = 1; repo.query = text; + repo.sortMethod = _sortMethod; repo.setRatings(_s, _q, _e); _saveSearch(text); setState(() { @@ -302,6 +320,12 @@ class HomePageState extends State { _quality = q; _saveQualityPrefs(); }, + sortMethod: _sortMethod, + sortMethodChanged: (ESortMethod s) { + setState(() { + _sortMethod = s; + }); + }, ); }, ); @@ -323,28 +347,50 @@ class HomePageState extends State { if (_headerImage != null) { return new TransitionToImage( image: AdvancedNetworkImage( - 'https:'+_headerImage, + _headerImage, useDiskCache: true, cacheRule: CacheRule(maxAge: const Duration(days: 7)), ), placeholder: SvgPicture.asset('assets/logo.svg'), fit: BoxFit.cover, + loadedCallback: () { + setState(() { + _isHeaderLoading = false; + }); + }, ); } else { return SvgPicture.asset('assets/logo.svg'); } }(), Positioned( - child: IconButton( - icon: Icon(Icons.refresh), - onPressed: () => _setHeaderImage(), - splashColor: Colors.transparent, - color: Color.fromARGB(50, 255, 255, 255), - iconSize: 20, + child: GestureDetector( + child: Icon( + Icons.shuffle, + color: Color.fromARGB(50, 255, 255, 255), + size: 20, + ), + onTap: () => _setHeaderImage(), + onLongPress: () { + setState(() { + _headerImage = null; + }); + }, ), right: 0.0, bottom: 0.0, - ) + ), + (){ + if (_isHeaderLoading) { + return Positioned( + child: Text("Loading...", style: TextStyle(color: Color.fromARGB(50, 255, 255, 255)),), + left: 0.0, + bottom: 0.0, + ); + } else { + return SizedBox.shrink(); + } + }() ], ), decoration: BoxDecoration( diff --git a/lib/Views/ImageViewer.dart b/lib/Views/ImageViewer.dart index 56dd1bd..facb4d6 100644 --- a/lib/Views/ImageViewer.dart +++ b/lib/Views/ImageViewer.dart @@ -1,3 +1,4 @@ +import 'package:chryssibooru/Elements/DetailsSheet.dart'; import 'package:flutter/services.dart'; import 'package:flutter_advanced_networkimage/provider.dart'; import 'package:flutter_advanced_networkimage/transition.dart'; @@ -240,304 +241,47 @@ class ImageViewerState extends State { }, ), ), - bottomNavigationBar: GestureDetector( - child: BottomAppBar( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 8.0), - child: Container( - height: 30, - child: Stack( + bottomNavigationBar: BottomAppBar( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 8.0), + child: Container( + height: 30, + child: Stack( // mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Align( - alignment: Alignment.centerLeft, - child: Text((_id + 1).toString() + - '/' + - repo.derpis.length.toString())), - Align( - alignment: Alignment.center, + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text((_id + 1).toString() + '/' + repo.derpis.length.toString())), + Align( + alignment: Alignment.center, + child: GestureDetector( child: Icon(Icons.keyboard_arrow_up), + onTap: () { + showModalBottomSheet( + context: mainContext, + builder: (context) { + Derpi derpi = repo.derpis[_id]; + return DetailsSheet(derpi: derpi); + } + ); + }, ), - Align( - alignment: Alignment.centerRight, - child: Text(repo.derpis[_id].score.toString())) - ], - ), + ), + Align( + alignment: Alignment.centerRight, + child: Text( + repo.derpis[_id].score.toString(), + style: TextStyle( + color: repo.derpis[_id].score >= 0 + ? Color.fromARGB(255, 0, 255, 0) + : Color.fromARGB(255, 255, 0, 0) + ) + ) + ) + ], ), ), ), - onVerticalDragUpdate: (details) { - showModalBottomSheet( - context: mainContext, - builder: (context) { - Derpi derpi = repo.derpis[_id]; - List tags = derpi.tags; - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 1.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(derpi.id.toString()), - ButtonBar( - children: [ - Container( - height: 30, - width: 30, - child: InkWell( - borderRadius: - BorderRadius.all(Radius.circular(50.0)), - child: Icon(Icons.share), - onTap: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Container( - height: 100.0, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - FlatButton( - onPressed: () => Share.share("https://derpibooru.org/" +derpi.id.toString()), - child:Text("Derpibooru post"), - ), - FlatButton( - onPressed: () => Share.share("https:" + derpi.representations.full), - child: Text("Direct image"), - ) - ], - ), - ); - }); - }, - onLongPress: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text("Share"), - ); - }); - }, - ), - ), - Container( - height: 30, - width: 30, - child: InkWell( - borderRadius: - BorderRadius.all(Radius.circular(50.0)), - child: Icon(Icons.link), - onTap: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Container( - height: 100.0, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - FlatButton( - onPressed: () => openInBrowser("https://derpibooru.org/" +derpi.id.toString()), - child:Text("Derpibooru post"), - ), - FlatButton( - onPressed: () => openInBrowser("https:" +derpi.representations.full), - child: Text("Direct image"), - ) - ], - ), - ); - }); - }, - onLongPress: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text("Open in browser"), - ); - }); - }, - ), - ), - Container( - height: 30, - width: 30, - child: InkWell( - borderRadius: - BorderRadius.all(Radius.circular(50.0)), - child: Icon(Icons.content_copy), - onTap: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Container( - height: 100.0, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - FlatButton( - onPressed: () => Clipboard.setData(new ClipboardData( - text: "https://derpibooru.org/" +derpi.id.toString()) - ), - child: - Text("Derpibooru post"), - ), - FlatButton( - onPressed: () => Clipboard.setData(new ClipboardData( - text: "https:" + derpi.representations.full) - ), - child: Text("Direct image"), - ) - ], - ), - ); - }); - }, - onLongPress: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text("Copy direct link"), - ); - }); - }, - ), - ), - Container( - height: 30, - width: 30, - child: InkWell( - borderRadius: - BorderRadius.all(Radius.circular(50.0)), - child: Icon(Icons.file_download), - onTap: () => - downloadImage(derpi.representations.full), - onLongPress: () { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text("Download"), - ); - }); - }, - ), - ), - ], - ) - ], - ), - ), - ConstrainedBox( - constraints: new BoxConstraints(maxHeight: 100.0), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 4.0), - child: SingleChildScrollView( - physics: BouncingScrollPhysics(), - child: Text(derpi.description), - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4.0, vertical: 1.0), - child: SingleChildScrollView( - physics: BouncingScrollPhysics(), - child: Wrap( - spacing: 5, - runSpacing: -5, - alignment: WrapAlignment.center, - children: [ - for (final tag in tags) - GestureDetector( - child: Chip( - padding: EdgeInsets.all(0), - label: Text(tag.label), - backgroundColor: { - TagType.ARTIST: Colors.blue, - TagType.OC: Colors.green, - TagType.SPOILER: Colors.red, - }[tag.type] ?? - Colors.grey, - ), - onTap: () { - showModalBottomSheet( - context: context, - builder: (BuildContext c) { - return Container( - height: 150.0, - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - FlatButton( - onPressed: () async { - repo.derpis = new List(); - repo.addTag(tag.label); - await repo.loadDerpis(); - setState(() { - _id = 0; - _pageController - .jumpToPage(0); - }); - }, - child: Text("Add to search"), - ), - FlatButton( - onPressed: () async { - repo.derpis = new List(); - repo.removeTag(tag.label); - await repo.loadDerpis(); - setState(() { - _id = 0; - _pageController - .jumpToPage(0); - }); - }, - child: Text("Remove from search"), - ), - FlatButton( - onPressed: () async { - repo.derpis = new List(); - repo.query = tag.label; - await repo.loadDerpis(); - setState(() { - _id = 0; - _pageController - .jumpToPage(0); - }); - }, - child: Text("New search"), - ) - ], - ), - ); - }); - }, - ) - ], - ), - ), - ), - ), - ], - ); - }); - }, ), ); }