update for get listview update

This commit is contained in:
Mohamed Nouffer
2022-06-29 18:25:19 +05:30
parent 7b9d029bb0
commit 4db26e5490
61 changed files with 121 additions and 68 deletions

BIN
assets/images/Zoo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/dora.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/images/⾷事処.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/images/⾷材店.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/images/スキー.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/images/テニス.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1011 B

BIN
assets/images/役所.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/文化2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/images/用品.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/images/競馬場.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/images/買い物.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -21,6 +21,7 @@ class DestinationController extends GetxController {
late LocationSettings locationSettings; late LocationSettings locationSettings;
var destinationCount = 0.obs;
List<dynamic> destinations = <dynamic>[].obs; List<dynamic> destinations = <dynamic>[].obs;
List<Map<String, dynamic>> destination_index_data = <Map<String, dynamic>>[].obs; List<Map<String, dynamic>> destination_index_data = <Map<String, dynamic>>[].obs;
@ -101,58 +102,57 @@ class DestinationController extends GetxController {
@override @override
void onInit() { void onInit() {
super.onInit();
PopulateDestinations(); PopulateDestinations();
print("------ in iniit"); print("------ in iniit");
if (defaultTargetPlatform == TargetPlatform.android) { // if (defaultTargetPlatform == TargetPlatform.android) {
locationSettings = AndroidSettings( // locationSettings = AndroidSettings(
accuracy: LocationAccuracy.high, // accuracy: LocationAccuracy.high,
distanceFilter: 30, // distanceFilter: 30,
forceLocationManager: true, // forceLocationManager: true,
intervalDuration: const Duration(seconds: 10), // intervalDuration: const Duration(seconds: 10),
//(Optional) Set foreground notification config to keep the app alive // //(Optional) Set foreground notification config to keep the app alive
//when going to the background // //when going to the background
foregroundNotificationConfig: const ForegroundNotificationConfig( // foregroundNotificationConfig: const ForegroundNotificationConfig(
notificationText: // notificationText:
"Example app will continue to receive your location even when you aren't using it", // "Example app will continue to receive your location even when you aren't using it",
notificationTitle: "Running in Background", // notificationTitle: "Running in Background",
enableWakeLock: true, // enableWakeLock: true,
) // )
); // );
} else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { // } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) {
locationSettings = AppleSettings( // locationSettings = AppleSettings(
accuracy: LocationAccuracy.high, // accuracy: LocationAccuracy.high,
activityType: ActivityType.fitness, // activityType: ActivityType.fitness,
distanceFilter: 1, // distanceFilter: 1,
pauseLocationUpdatesAutomatically: false, // pauseLocationUpdatesAutomatically: false,
// Only set to true if our app will be started up in the background. // // Only set to true if our app will be started up in the background.
showBackgroundLocationIndicator: true // showBackgroundLocationIndicator: true
); // );
} else { // } else {
locationSettings = LocationSettings( // locationSettings = LocationSettings(
accuracy: LocationAccuracy.high, // accuracy: LocationAccuracy.high,
distanceFilter: 30, // distanceFilter: 30,
); // );
} // }
StreamSubscription<Position> positionStream = Geolocator.getPositionStream(locationSettings: locationSettings).listen( // StreamSubscription<Position> positionStream = Geolocator.getPositionStream(locationSettings: locationSettings).listen(
(Position? position) { // (Position? position) {
if(isSelected[0]){ // if(isSelected[0]){
String user_id = indexController.currentUser[0]["user"]["id"].toString(); // String user_id = indexController.currentUser[0]["user"]["id"].toString();
TrackingService.addTrack(user_id, position!.latitude, position.longitude).then((val){ // TrackingService.addTrack(user_id, position!.latitude, position.longitude).then((val){
//checkForCheckin(position!.latitude, position.longitude); // //checkForCheckin(position!.latitude, position.longitude);
}); // });
} // }
print(position == null ? 'Unknown' : 'current position is ${position.latitude.toString()}, ${position.longitude.toString()}'); // print(position == null ? 'Unknown' : 'current position is ${position.latitude.toString()}, ${position.longitude.toString()}');
}); // });
super.onInit();
} }
void deleteDestination(int index){ void deleteDestination(int index){
@ -165,18 +165,20 @@ class DestinationController extends GetxController {
} }
void PopulateDestinations(){ void PopulateDestinations(){
print("--------- populsting destinations -----------");
if(indexController.currentUser.isNotEmpty){ if(indexController.currentUser.isNotEmpty){
int user_id = indexController.currentUser[0]["user"]["id"]; int user_id = indexController.currentUser[0]["user"]["id"];
print(user_id); print(user_id);
DestinationService.getDestinations(user_id).then((value){ DestinationService.getDestinations(user_id).then((value){
destinations.clear();
destinations = value;
destinationCount.value = 0;
destinationCount.value = destinations.length;
MatrixService.getDestinations(value).then((mat){ MatrixService.getDestinations(value).then((mat){
print(mat); print(mat);
matrix = mat; matrix = mat;
destinations.clear();
destinations = value;
}); });
//var val = value[2]["location"]["id"]; //var val = value[2]["location"]["id"];

View File

@ -49,6 +49,7 @@ class _DestinationPageState extends State<DestinationPage> {
@override @override
void initState() { void initState() {
destinationController.context = context; destinationController.context = context;
destinationController.PopulateDestinations();
super.initState(); super.initState();
} }

View File

@ -164,7 +164,15 @@ void login(String email, String password, BuildContext context){
Get.toNamed(AppPages.INITIAL); Get.toNamed(AppPages.INITIAL);
}else{ }else{
is_loading.value = false; is_loading.value = false;
Get.snackbar("Failed", "User login failed, please try again."); Get.snackbar(
"Failed",
"User login failed, please try again.",
icon: Icon(Icons.error, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: Duration(milliseconds: 800),
backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
} }
}); });
@ -181,7 +189,15 @@ void login(String email, String password, BuildContext context){
Get.toNamed(AppPages.INITIAL); Get.toNamed(AppPages.INITIAL);
}else{ }else{
is_loading.value = false; is_loading.value = false;
Get.snackbar("Failed", "User registration failed, please try again."); Get.snackbar(
"Failed",
"User registration failed, please try again.",
icon: Icon(Icons.error, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: Duration(milliseconds: 800),
backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
} }
}); });
} }
@ -331,6 +347,8 @@ void login(String email, String password, BuildContext context){
void loadLocationsBound(){ void loadLocationsBound(){
String cat = currentCat.isNotEmpty ? currentCat[0] : ""; String cat = currentCat.isNotEmpty ? currentCat[0] : "";
LatLngBounds bounds = mapController!.bounds!; LatLngBounds bounds = mapController!.bounds!;
currentBound.clear();
currentBound.add(bounds);
//print(currentCat); //print(currentCat);
if(bounds.southEast != null && bounds.southWest != null && bounds.northEast != null && bounds.southEast != null ){ if(bounds.southEast != null && bounds.southWest != null && bounds.northEast != null && bounds.southEast != null ){
LocationService.loadLocationsBound(bounds.southWest!.latitude, bounds.southWest!.longitude, bounds.northWest.latitude, bounds.northWest.longitude, bounds.northEast!.latitude, bounds.northEast!.longitude, bounds.southEast.latitude, bounds.southEast.longitude, cat).then((value){ LocationService.loadLocationsBound(bounds.southWest!.latitude, bounds.southWest!.longitude, bounds.northWest.latitude, bounds.northWest.longitude, bounds.northEast!.latitude, bounds.northEast!.longitude, bounds.southEast.latitude, bounds.southEast.longitude, cat).then((value){
@ -338,20 +356,21 @@ void login(String email, String password, BuildContext context){
if(value == null){ if(value == null){
return; return;
} }
locations.clear();
if(value != null && value.collection.isEmpty){ if(value != null && value.collection.isEmpty){
Get.snackbar( Get.snackbar(
"Too many Points", "Too many Points",
"please zoom in", "please zoom in",
icon: Icon(Icons.person, color: Colors.white), icon: Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.BOTTOM, snackPosition: SnackPosition.TOP,
duration: Duration(milliseconds: 800), duration: Duration(milliseconds: 800),
backgroundColor: Colors.yellow, backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
); );
//Get.showSnackbar(GetSnackBar(message: "Too many points, please zoom in",)); //Get.showSnackbar(GetSnackBar(message: "Too many points, please zoom in",));
} }
if(value != null && value.collection.isNotEmpty){ if(value != null && value.collection.isNotEmpty){
//print("---- added---"); //print("---- added---");
locations.clear();
locations.add(value); locations.add(value);
loadCatsv2(); loadCatsv2();
} }

View File

@ -89,7 +89,15 @@ class LoginPage extends StatelessWidget {
height:60, height:60,
onPressed: (){ onPressed: (){
if(emailController.text.isEmpty || passwordController.text.isEmpty){ if(emailController.text.isEmpty || passwordController.text.isEmpty){
Get.snackbar("No values", "Email and password required"); Get.snackbar(
"No values",
"Email and password required",
icon: Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: Duration(milliseconds: 800),
backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
return; return;
} }
indexController.is_loading.value = true; indexController.is_loading.value = true;

View File

@ -82,10 +82,26 @@ class RegisterPage extends StatelessWidget {
height:60, height:60,
onPressed: (){ onPressed: (){
if(passwordController.text != confirmPasswordController.text){ if(passwordController.text != confirmPasswordController.text){
Get.snackbar("No match", "Passwords does not match"); Get.snackbar(
"No match",
"Passwords does not match",
icon: Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: Duration(milliseconds: 800),
backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
} }
if(emailController.text.isEmpty || passwordController.text.isEmpty){ if(emailController.text.isEmpty || passwordController.text.isEmpty){
Get.snackbar("No values", "Email and password required"); Get.snackbar(
"No values",
"Email and password required",
icon: Icon(Icons.assistant_photo_outlined, size: 40.0, color: Colors.blue),
snackPosition: SnackPosition.TOP,
duration: Duration(milliseconds: 800),
backgroundColor: Colors.yellow,
//icon:Image(image:AssetImage("assets/images/dora.png"))
);
return; return;
} }
indexController.is_loading.value = true; indexController.is_loading.value = true;

View File

@ -11,6 +11,7 @@ class ActionService{
String server_url = ConstValues.currentServer(); String server_url = ConstValues.currentServer();
String url = "${server_url}/api/makeaction/?user_id=${user_id}&location_id=${location_id}&wanttogo=${wanttogo}&like=${like}&checkin=${checkin}"; String url = "${server_url}/api/makeaction/?user_id=${user_id}&location_id=${location_id}&wanttogo=${wanttogo}&like=${like}&checkin=${checkin}";
//String url = "http://localhost:8100/api/makeaction/?user_id=${user_id}&location_id=${location_id}&wanttogo=${wanttogo}&like=${like}&checkin=${checkin}"; //String url = "http://localhost:8100/api/makeaction/?user_id=${user_id}&location_id=${location_id}&wanttogo=${wanttogo}&like=${like}&checkin=${checkin}";
print("url is ------ ${url}");
final http.Response response = await http.get( final http.Response response = await http.get(
Uri.parse(url), Uri.parse(url),
headers: <String, String>{ headers: <String, String>{
@ -29,6 +30,7 @@ class ActionService{
List<dynamic> cats = []; List<dynamic> cats = [];
String server_url = ConstValues.currentServer(); String server_url = ConstValues.currentServer();
String url = '${server_url}/api/useraction/?user_id=${user_id}&location_id=${location_id}'; String url = '${server_url}/api/useraction/?user_id=${user_id}&location_id=${location_id}';
print("url is ------ ${url}");
//String url = 'http://localhost:8100/api/useraction/?user_id=${user_id}&location_id=${location_id}'; //String url = 'http://localhost:8100/api/useraction/?user_id=${user_id}&location_id=${location_id}';
final response = await http.get(Uri.parse(url), final response = await http.get(Uri.parse(url),
headers: <String, String>{ headers: <String, String>{

View File

@ -7,6 +7,6 @@ class ConstValues{
static const dev_ip_server = "http://192.168.8.100:8100"; static const dev_ip_server = "http://192.168.8.100:8100";
static String currentServer(){ static String currentServer(){
return server_uri; return dev_ip_server;
} }
} }

View File

@ -58,7 +58,6 @@ class DestinationWidget extends StatelessWidget {
//print(element["index"]); //print(element["index"]);
int action_id = destinationController.destinations[element["index"]]["id"] as int; int action_id = destinationController.destinations[element["index"]]["id"] as int;
destinationController.makeOrder(action_id, (element["index"] as int) - 1, "up"); destinationController.makeOrder(action_id, (element["index"] as int) - 1, "up");
}); });
} }
@ -67,7 +66,6 @@ class DestinationWidget extends StatelessWidget {
//print(element["index"]); //print(element["index"]);
int action_id = destinationController.destinations[element["index"]]["id"] as int; int action_id = destinationController.destinations[element["index"]]["id"] as int;
destinationController.makeOrder(action_id, (element["index"] as int) + 1, "up"); destinationController.makeOrder(action_id, (element["index"] as int) + 1, "up");
}); });
} }
@ -90,7 +88,8 @@ class DestinationWidget extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(top:45.0), padding: const EdgeInsets.only(top:45.0),
child: ListView.builder( child: ListView.builder(
itemCount: destinationController.destinations.length, itemCount: destinationController.destinationCount.value,
//itemCount: destinationController.destinations.length,
// onReorder: (int oldIndex, int newIndex){ // onReorder: (int oldIndex, int newIndex){
// int action_id = destinationController.destinations[oldIndex]["id"] as int; // int action_id = destinationController.destinations[oldIndex]["id"] as int;
// //print(action_id); // //print(action_id);
@ -165,13 +164,17 @@ class DestinationWidget extends StatelessWidget {
), ),
), ),
), ),
startChild: Column( startChild:
mainAxisAlignment: MainAxisAlignment.spaceEvenly, destinationController.matrix["rows"] != null ?
children: [ Column(
Text(destinationController.matrix["rows"][0]["elements"][index]["distance"]["text"].toString()), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Text(destinationController.matrix["rows"][0]["elements"][index]["duration"]["text"].toString()) children: [
], Text(destinationController.matrix["rows"][0]["elements"][index]["distance"]["text"].toString()),
), Text(destinationController.matrix["rows"][0]["elements"][index]["duration"]["text"].toString())
],
):
Container()
,
); );
} }

View File

@ -161,14 +161,15 @@ class MapWidget extends StatelessWidget {
print("lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}"); print("lat is ${p.geoSerie!.geoPoints[0].latitude} and lon is ${p.geoSerie!.geoPoints[0].longitude}");
return Marker( return Marker(
anchorPos: AnchorPos.align(AnchorAlign.center), anchorPos: AnchorPos.align(AnchorAlign.center),
height: 70.0, height: 22.0,
width: 70.0, width: 22.0,
point: LatLng(p.geoSerie!.geoPoints[0].latitude, p.geoSerie!.geoPoints[0].longitude), point: LatLng(p.geoSerie!.geoPoints[0].latitude, p.geoSerie!.geoPoints[0].longitude),
builder: (ctx) => Icon(Icons.pin_drop), builder: (ctx) => Icon(Icons.pin_drop),
// builder: (ctx) => i.properties!["category"] != null ? // builder: (ctx) => i.properties!["category"] != null ?
// ImageIcon( // ImageIcon(
// AssetImage("assets/images/${i.properties!["category"]}.png"), // AssetImage("assets/images/${i.properties!["category"]}.png"),
// color: Color(0xFF3A5A98), // color: Color(0xFF3A5A98),
// size:12.0,
// ) // )
// : Icon(Icons.pin_drop), // : Icon(Icons.pin_drop),

View File

@ -253,7 +253,7 @@ packages:
name: get name: get
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.6.1" version: "4.6.5"
google_api_availability: google_api_availability:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -35,7 +35,7 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
sqflite: ^2.0.1 sqflite: ^2.0.1
get: ^4.6.1 get: ^4.6.5
flutter_map: any flutter_map: any
geolocator: ^8.0.1 geolocator: ^8.0.1
permission_handler: ^8.3.0 permission_handler: ^8.3.0
@ -94,6 +94,7 @@ flutter:
- assets/images/ - assets/images/
- assets/images/empty_image.png - assets/images/empty_image.png
- assets/gradient_japanese_temple.jpg - assets/gradient_japanese_temple.jpg
- assets/images/japanese_fun.jpeg
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see