Compare commits

...

3 Commits

Author SHA1 Message Date
6c1ad94b4f Fixed build number 2022-09-17 19:09:32 -04:00
7d7986f8bf FIxed a typo 2022-09-17 19:05:55 -04:00
3ddf9ea736 Fixed incorrect background colours 2022-09-17 18:57:14 -04:00
5 changed files with 546 additions and 515 deletions

View File

@ -12,7 +12,7 @@ import 'package:dynamic_color/dynamic_color.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
const String currentReleaseTag = const String currentReleaseTag =
'v0.2.2-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES 'v0.2.4-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
@pragma('vm:entry-point') @pragma('vm:entry-point')
void bgTaskCallback() { void bgTaskCallback() {

View File

@ -23,125 +23,136 @@ class _AddAppPageState extends State<AddAppPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SourceProvider sourceProvider = SourceProvider(); SourceProvider sourceProvider = SourceProvider();
return CustomScrollView(slivers: <Widget>[ return Scaffold(
const CustomAppBar(title: 'Add App'), backgroundColor: Theme.of(context).colorScheme.surface,
SliverFillRemaining( body: CustomScrollView(slivers: <Widget>[
hasScrollBody: false, const CustomAppBar(title: 'Add App'),
child: Center( SliverFillRemaining(
child: Form( hasScrollBody: false,
key: _formKey, child: Center(
child: Column( child: Form(
mainAxisAlignment: MainAxisAlignment.spaceBetween, key: _formKey,
crossAxisAlignment: CrossAxisAlignment.stretch, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
Container(), crossAxisAlignment: CrossAxisAlignment.stretch,
Padding( children: [
padding: const EdgeInsets.all(16), Container(),
child: Column( Padding(
crossAxisAlignment: CrossAxisAlignment.stretch, padding: const EdgeInsets.all(16),
children: [ child: Column(
TextFormField( crossAxisAlignment: CrossAxisAlignment.stretch,
decoration: const InputDecoration( children: [
hintText: 'https://github.com/Author/Project', TextFormField(
helperText: 'Enter the App source URL'), decoration: const InputDecoration(
controller: urlInputController, hintText:
validator: (value) { 'https://github.com/Author/Project',
if (value == null || helperText: 'Enter the App source URL'),
value.isEmpty || controller: urlInputController,
Uri.tryParse(value) == null) { validator: (value) {
return 'Please enter a supported source URL'; if (value == null ||
} value.isEmpty ||
return null; Uri.tryParse(value) == null) {
}, return 'Please enter a supported source URL';
), }
Padding( return null;
padding: const EdgeInsets.symmetric(vertical: 16.0), },
child: ElevatedButton( ),
onPressed: gettingAppInfo Padding(
? null padding:
: () { const EdgeInsets.symmetric(vertical: 16.0),
HapticFeedback.selectionClick(); child: ElevatedButton(
if (_formKey.currentState!.validate()) { onPressed: gettingAppInfo
setState(() { ? null
gettingAppInfo = true; : () {
}); HapticFeedback.selectionClick();
sourceProvider if (_formKey.currentState!
.getApp( .validate()) {
urlInputController.value.text) setState(() {
.then((app) { gettingAppInfo = true;
var appsProvider =
context.read<AppsProvider>();
var settingsProvider =
context.read<SettingsProvider>();
if (appsProvider.apps
.containsKey(app.id)) {
throw 'App already added';
}
settingsProvider
.getInstallPermission()
.then((_) {
appsProvider.saveApp(app).then((_) {
urlInputController.clear();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AppPage(
appId: app.id)));
}); });
}); sourceProvider
}).catchError((e) { .getApp(urlInputController
ScaffoldMessenger.of(context) .value.text)
.showSnackBar( .then((app) {
SnackBar( var appsProvider =
content: Text(e.toString())), context.read<AppsProvider>();
); var settingsProvider = context
}).whenComplete(() { .read<SettingsProvider>();
setState(() { if (appsProvider.apps
gettingAppInfo = false; .containsKey(app.id)) {
}); throw 'App already added';
}); }
} settingsProvider
}, .getInstallPermission()
child: const Text('Add'), .then((_) {
), appsProvider
.saveApp(app)
.then((_) {
urlInputController.clear();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AppPage(
appId:
app.id)));
});
});
}).catchError((e) {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content:
Text(e.toString())),
);
}).whenComplete(() {
setState(() {
gettingAppInfo = false;
});
});
}
},
child: const Text('Add'),
),
),
],
), ),
], ),
), Column(
), crossAxisAlignment: CrossAxisAlignment.center,
Column( children: [
crossAxisAlignment: CrossAxisAlignment.center, const Text(
children: [ 'Supported Sources:',
const Text( // style: TextStyle(fontWeight: FontWeight.bold),
'Supported Sources:', // style: Theme.of(context).textTheme.bodySmall,
// style: TextStyle(fontWeight: FontWeight.bold), ),
// style: Theme.of(context).textTheme.bodySmall, const SizedBox(
), height: 8,
const SizedBox( ),
height: 8, ...sourceProvider
), .getSourceHosts()
...sourceProvider .map((e) => GestureDetector(
.getSourceHosts() onTap: () {
.map((e) => GestureDetector( launchUrlString('https://$e',
onTap: () { mode:
launchUrlString('https://$e', LaunchMode.externalApplication);
mode: LaunchMode.externalApplication); },
}, child: Text(
child: Text( e,
e, style: const TextStyle(
style: const TextStyle( decoration:
decoration: TextDecoration.underline, TextDecoration.underline,
fontStyle: FontStyle.italic), fontStyle: FontStyle.italic),
))) )))
.toList() .toList()
]), ]),
if (gettingAppInfo) if (gettingAppInfo)
const LinearProgressIndicator() const LinearProgressIndicator()
else else
Container(), Container(),
], ],
)), )),
)) ))
]); ]));
} }
} }

View File

@ -54,74 +54,146 @@ class _ImportExportPageState extends State<ImportExportPage> {
return errors; return errors;
} }
return CustomScrollView(slivers: <Widget>[ return Scaffold(
const CustomAppBar(title: 'Import/Export'), backgroundColor: Theme.of(context).colorScheme.surface,
SliverFillRemaining( body: CustomScrollView(slivers: <Widget>[
hasScrollBody: false, const CustomAppBar(title: 'Import/Export'),
child: Padding( SliverFillRemaining(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), hasScrollBody: false,
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.stretch, padding:
children: [ const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
Row( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Expanded( Row(
child: TextButton( children: [
style: outlineButtonStyle, Expanded(
onPressed: appsProvider.apps.isEmpty || child: TextButton(
importInProgress style: outlineButtonStyle,
? null onPressed: appsProvider.apps.isEmpty ||
: () { importInProgress
HapticFeedback.selectionClick(); ? null
appsProvider : () {
.exportApps() HapticFeedback.selectionClick();
.then((String path) {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content:
Text('Exported to $path')),
);
});
},
child: const Text('Obtainium Export'))),
const SizedBox(
width: 16,
),
Expanded(
child: TextButton(
style: outlineButtonStyle,
onPressed: importInProgress
? null
: () {
HapticFeedback.selectionClick();
FilePicker.platform
.pickFiles()
.then((result) {
setState(() {
importInProgress = true;
});
if (result != null) {
String data =
File(result.files.single.path!)
.readAsStringSync();
try {
jsonDecode(data);
} catch (e) {
throw 'Invalid input';
}
appsProvider appsProvider
.importApps(data) .exportApps()
.then((value) { .then((String path) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
.showSnackBar( .showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
'$value App${value == 1 ? '' : 's'} Imported')), 'Exported to $path')),
); );
}); });
},
child: const Text('Obtainium Export'))),
const SizedBox(
width: 16,
),
Expanded(
child: TextButton(
style: outlineButtonStyle,
onPressed: importInProgress
? null
: () {
HapticFeedback.selectionClick();
FilePicker.platform
.pickFiles()
.then((result) {
setState(() {
importInProgress = true;
});
if (result != null) {
String data = File(
result.files.single.path!)
.readAsStringSync();
try {
jsonDecode(data);
} catch (e) {
throw 'Invalid input';
}
appsProvider
.importApps(data)
.then((value) {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(
'$value App${value == 1 ? '' : 's'} Imported')),
);
});
} else {
// User canceled the picker
}
}).catchError((e) {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(e.toString())),
);
}).whenComplete(() {
setState(() {
importInProgress = false;
});
});
},
child: const Text('Obtainium Import')))
],
),
if (importInProgress)
Column(
children: const [
SizedBox(
height: 14,
),
LinearProgressIndicator(),
SizedBox(
height: 14,
),
],
)
else
const Divider(
height: 32,
),
TextButton(
onPressed: importInProgress
? null
: () {
showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: 'Import from URL List',
items: [
GeneratedFormItem(
'App URL List', true, 7)
],
);
}).then((values) {
if (values != null) {
var urls =
(values[0] as String).split('\n');
setState(() {
importInProgress = true;
});
addApps(urls).then((errors) {
if (errors.isEmpty) {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(
'Imported ${urls.length} Apps')),
);
} else { } else {
// User canceled the picker showDialog(
context: context,
builder: (BuildContext ctx) {
return ImportErrorDialog(
urlsLength: urls.length,
errors: errors);
});
} }
}).catchError((e) { }).catchError((e) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
@ -133,148 +205,92 @@ class _ImportExportPageState extends State<ImportExportPage> {
importInProgress = false; importInProgress = false;
}); });
}); });
},
child: const Text('Obtainium Import')))
],
),
if (importInProgress)
Column(
children: const [
SizedBox(
height: 14,
),
LinearProgressIndicator(),
SizedBox(
height: 14,
),
],
)
else
const Divider(
height: 32,
),
TextButton(
onPressed: importInProgress
? null
: () {
showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: 'Import from URL List',
items: [
GeneratedFormItem(
'App URL List', true, 7)
],
);
}).then((values) {
if (values != null) {
var urls = (values[0] as String).split('\n');
setState(() {
importInProgress = true;
});
addApps(urls).then((errors) {
if (errors.isEmpty) {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(
'Imported ${urls.length} Apps')),
);
} else {
showDialog(
context: context,
builder: (BuildContext ctx) {
return ImportErrorDialog(
urlsLength: urls.length,
errors: errors);
});
} }
}).catchError((e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
}).whenComplete(() {
setState(() {
importInProgress = false;
});
}); });
} },
}); child: const Text(
}, 'Import from URL List',
child: const Text( )),
'Import from URL List', ...sourceProvider.massSources
)), .map((source) => Column(
...sourceProvider.massSources crossAxisAlignment:
.map((source) => Column( CrossAxisAlignment.stretch,
crossAxisAlignment: CrossAxisAlignment.stretch, children: [
children: [ const SizedBox(height: 8),
const SizedBox(height: 8), TextButton(
TextButton( onPressed: importInProgress
onPressed: importInProgress ? null
? null : () {
: () { showDialog(
showDialog( context: context,
context: context, builder:
builder: (BuildContext ctx) { (BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(
title: title:
'Import ${source.name}', 'Import ${source.name}',
items: source.requiredArgs items: source
.map((e) => .requiredArgs
GeneratedFormItem( .map((e) =>
e, true, 1)) GeneratedFormItem(
.toList()); e,
}).then((values) { true,
if (values != null) { 1))
source .toList());
.getUrls(values) }).then((values) {
.then((urls) { if (values != null) {
setState(() { source
importInProgress = true; .getUrls(values)
}); .then((urls) {
addApps(urls).then((errors) { setState(() {
if (errors.isEmpty) { importInProgress = true;
});
addApps(urls)
.then((errors) {
if (errors.isEmpty) {
ScaffoldMessenger.of(
context)
.showSnackBar(
SnackBar(
content: Text(
'Imported ${urls.length} Apps')),
);
} else {
showDialog(
context: context,
builder:
(BuildContext
ctx) {
return ImportErrorDialog(
urlsLength: urls
.length,
errors:
errors);
});
}
}).whenComplete(() {
setState(() {
importInProgress =
false;
});
});
}).catchError((e) {
ScaffoldMessenger.of( ScaffoldMessenger.of(
context) context)
.showSnackBar( .showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
'Imported ${urls.length} Apps')), e.toString())),
); );
} else {
showDialog(
context: context,
builder: (BuildContext
ctx) {
return ImportErrorDialog(
urlsLength:
urls.length,
errors: errors);
});
}
}).whenComplete(() {
setState(() {
importInProgress = false;
}); });
}); }
}).catchError((e) {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content:
Text(e.toString())),
);
}); });
} },
}); child: Text('Import ${source.name}'))
}, ]))
child: Text('Import ${source.name}')) .toList()
])) ],
.toList() )))
], ]));
)))
]);
} }
} }

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/components/custom_app_bar.dart'; import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -19,214 +18,219 @@ class _SettingsPageState extends State<SettingsPage> {
if (settingsProvider.prefs == null) { if (settingsProvider.prefs == null) {
settingsProvider.initializeSettings(); settingsProvider.initializeSettings();
} }
return CustomScrollView(slivers: <Widget>[ return Scaffold(
const CustomAppBar(title: 'Add App'), backgroundColor: Theme.of(context).colorScheme.surface,
SliverFillRemaining( body: CustomScrollView(slivers: <Widget>[
hasScrollBody: true, const CustomAppBar(title: 'Settings'),
child: Padding( SliverFillRemaining(
padding: const EdgeInsets.all(16), hasScrollBody: true,
child: settingsProvider.prefs == null child: Padding(
? Container() padding: const EdgeInsets.all(16),
: Column( child: settingsProvider.prefs == null
crossAxisAlignment: CrossAxisAlignment.start, ? Container()
children: [ : Column(
Text(
'Appearance',
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
DropdownButtonFormField(
decoration:
const InputDecoration(labelText: 'Theme'),
value: settingsProvider.theme,
items: const [
DropdownMenuItem(
value: ThemeSettings.dark,
child: Text('Dark'),
),
DropdownMenuItem(
value: ThemeSettings.light,
child: Text('Light'),
),
DropdownMenuItem(
value: ThemeSettings.system,
child: Text('Follow System'),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.theme = value;
}
}),
const SizedBox(
height: 16,
),
DropdownButtonFormField(
decoration:
const InputDecoration(labelText: 'Colour'),
value: settingsProvider.colour,
items: const [
DropdownMenuItem(
value: ColourSettings.basic,
child: Text('Obtainium'),
),
DropdownMenuItem(
value: ColourSettings.materialYou,
child: Text('Material You'),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.colour = value;
}
}),
const SizedBox(
height: 16,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Text(
child: DropdownButtonFormField( 'Appearance',
decoration: const InputDecoration( style: TextStyle(
labelText: 'App Sort By'), color: Theme.of(context).colorScheme.primary),
value: settingsProvider.sortColumn,
items: const [
DropdownMenuItem(
value: SortColumnSettings.authorName,
child: Text('Author/Name'),
),
DropdownMenuItem(
value: SortColumnSettings.nameAuthor,
child: Text('Name/Author'),
),
DropdownMenuItem(
value: SortColumnSettings.added,
child: Text('As Added'),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.sortColumn = value;
}
})),
const SizedBox(
width: 16,
), ),
Expanded( DropdownButtonFormField(
child: DropdownButtonFormField( decoration:
decoration: const InputDecoration( const InputDecoration(labelText: 'Theme'),
labelText: 'App Sort Order'), value: settingsProvider.theme,
value: settingsProvider.sortOrder, items: const [
items: const [ DropdownMenuItem(
DropdownMenuItem( value: ThemeSettings.dark,
value: SortOrderSettings.ascending, child: Text('Dark'),
child: Text('Ascending'), ),
), DropdownMenuItem(
DropdownMenuItem( value: ThemeSettings.light,
value: SortOrderSettings.descending, child: Text('Light'),
child: Text('Descending'), ),
), DropdownMenuItem(
], value: ThemeSettings.system,
onChanged: (value) { child: Text('Follow System'),
if (value != null) { )
settingsProvider.sortOrder = value; ],
}
})),
],
),
const SizedBox(
height: 16,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Show Source Webpage in App View'),
Switch(
value: settingsProvider.showAppWebpage,
onChanged: (value) { onChanged: (value) {
settingsProvider.showAppWebpage = value; if (value != null) {
}) settingsProvider.theme = value;
], }
),
const Divider(
height: 16,
),
const SizedBox(
height: 16,
),
Text(
'More',
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
DropdownButtonFormField(
decoration: const InputDecoration(
labelText:
'Background Update Checking Interval'),
value: settingsProvider.updateInterval,
items: const [
DropdownMenuItem(
value: 15,
child: Text('15 Minutes'),
),
DropdownMenuItem(
value: 30,
child: Text('30 Minutes'),
),
DropdownMenuItem(
value: 60,
child: Text('1 Hour'),
),
DropdownMenuItem(
value: 360,
child: Text('6 Hours'),
),
DropdownMenuItem(
value: 720,
child: Text('12 Hours'),
),
DropdownMenuItem(
value: 1440,
child: Text('1 Day'),
),
DropdownMenuItem(
value: 0,
child: Text('Never - Manual Only'),
),
],
onChanged: (value) {
if (value != null) {
settingsProvider.updateInterval = value;
}
}),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton.icon(
style: ButtonStyle(
foregroundColor:
MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
return Colors.grey;
}), }),
), const SizedBox(
onPressed: () { height: 16,
launchUrlString(settingsProvider.sourceUrl, ),
mode: LaunchMode.externalApplication); DropdownButtonFormField(
}, decoration:
icon: const Icon(Icons.code), const InputDecoration(labelText: 'Colour'),
label: Text( value: settingsProvider.colour,
'Source', items: const [
style: Theme.of(context).textTheme.bodySmall, DropdownMenuItem(
), value: ColourSettings.basic,
) child: Text('Obtainium'),
),
DropdownMenuItem(
value: ColourSettings.materialYou,
child: Text('Material You'),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.colour = value;
}
}),
const SizedBox(
height: 16,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: DropdownButtonFormField(
decoration: const InputDecoration(
labelText: 'App Sort By'),
value: settingsProvider.sortColumn,
items: const [
DropdownMenuItem(
value:
SortColumnSettings.authorName,
child: Text('Author/Name'),
),
DropdownMenuItem(
value:
SortColumnSettings.nameAuthor,
child: Text('Name/Author'),
),
DropdownMenuItem(
value: SortColumnSettings.added,
child: Text('As Added'),
)
],
onChanged: (value) {
if (value != null) {
settingsProvider.sortColumn = value;
}
})),
const SizedBox(
width: 16,
),
Expanded(
child: DropdownButtonFormField(
decoration: const InputDecoration(
labelText: 'App Sort Order'),
value: settingsProvider.sortOrder,
items: const [
DropdownMenuItem(
value: SortOrderSettings.ascending,
child: Text('Ascending'),
),
DropdownMenuItem(
value: SortOrderSettings.descending,
child: Text('Descending'),
),
],
onChanged: (value) {
if (value != null) {
settingsProvider.sortOrder = value;
}
})),
],
),
const SizedBox(
height: 16,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Show Source Webpage in App View'),
Switch(
value: settingsProvider.showAppWebpage,
onChanged: (value) {
settingsProvider.showAppWebpage = value;
})
],
),
const Divider(
height: 16,
),
const SizedBox(
height: 16,
),
Text(
'More',
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
DropdownButtonFormField(
decoration: const InputDecoration(
labelText:
'Background Update Checking Interval'),
value: settingsProvider.updateInterval,
items: const [
DropdownMenuItem(
value: 15,
child: Text('15 Minutes'),
),
DropdownMenuItem(
value: 30,
child: Text('30 Minutes'),
),
DropdownMenuItem(
value: 60,
child: Text('1 Hour'),
),
DropdownMenuItem(
value: 360,
child: Text('6 Hours'),
),
DropdownMenuItem(
value: 720,
child: Text('12 Hours'),
),
DropdownMenuItem(
value: 1440,
child: Text('1 Day'),
),
DropdownMenuItem(
value: 0,
child: Text('Never - Manual Only'),
),
],
onChanged: (value) {
if (value != null) {
settingsProvider.updateInterval = value;
}
}),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton.icon(
style: ButtonStyle(
foregroundColor:
MaterialStateProperty.resolveWith<
Color>((Set<MaterialState> states) {
return Colors.grey;
}),
),
onPressed: () {
launchUrlString(settingsProvider.sourceUrl,
mode: LaunchMode.externalApplication);
},
icon: const Icon(Icons.code),
label: Text(
'Source',
style:
Theme.of(context).textTheme.bodySmall,
),
)
],
),
], ],
), )))
], ]));
)))
]);
} }
} }

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.2.2+13 # When changing this, update the tag in main() accordingly version: 0.2.4+15 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.19.0-79.0.dev <3.0.0' sdk: '>=2.19.0-79.0.dev <3.0.0'