feat: update app icon
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 1022 B |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -1,6 +1,6 @@
|
||||
# flutter pub run flutter_launcher_icons
|
||||
flutter_launcher_icons:
|
||||
image_path: "assets/icons/kooltap_logo.png"
|
||||
image_path: "assets/icons/play_store_512.png"
|
||||
|
||||
android: "launcher_icon"
|
||||
# image_path_android: "assets/icons/kooltap_logo.png"
|
||||
@@ -26,7 +26,7 @@ flutter_launcher_icons:
|
||||
|
||||
windows:
|
||||
generate: true
|
||||
image_path: "assets/icons/kooltap_logo.png"
|
||||
image_path: "assets/icons/play_store_512.png"
|
||||
icon_size: 48 # min:48, max:256, default: 48
|
||||
|
||||
macos:
|
||||
|
||||
@@ -85,12 +85,32 @@ class SyncResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/// One line on a tab as shown in the dashboard, e.g. "10× Cola".
|
||||
class SnapshotTabItem {
|
||||
final String productName;
|
||||
final int quantity;
|
||||
final int unitPriceInCents;
|
||||
|
||||
const SnapshotTabItem({
|
||||
required this.productName,
|
||||
required this.quantity,
|
||||
required this.unitPriceInCents,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'productName': productName,
|
||||
'quantity': quantity,
|
||||
'unitPriceInCents': unitPriceInCents,
|
||||
};
|
||||
}
|
||||
|
||||
class SnapshotTab {
|
||||
final String id;
|
||||
final String customerName;
|
||||
final int version;
|
||||
final int totalInCents;
|
||||
final int itemCount;
|
||||
final List<SnapshotTabItem> items;
|
||||
|
||||
const SnapshotTab({
|
||||
required this.id,
|
||||
@@ -98,6 +118,7 @@ class SnapshotTab {
|
||||
required this.version,
|
||||
required this.totalInCents,
|
||||
required this.itemCount,
|
||||
this.items = const [],
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@@ -106,6 +127,7 @@ class SnapshotTab {
|
||||
'version': version,
|
||||
'totalInCents': totalInCents,
|
||||
'itemCount': itemCount,
|
||||
'items': items.map((item) => item.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -239,6 +239,14 @@ class RemoteSyncService {
|
||||
version: tab.version,
|
||||
totalInCents: tab.totalInCents,
|
||||
itemCount: tab.itemCount,
|
||||
items: [
|
||||
for (final item in tab.items)
|
||||
SnapshotTabItem(
|
||||
productName: item.productName,
|
||||
quantity: item.quantity,
|
||||
unitPriceInCents: item.unitPriceInCents,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +1,3 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions DisableDelayedExpansion
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo.
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"$content = [IO.File]::ReadAllText('pubspec.yaml'); " ^
|
||||
"$match = [regex]::Match($content, '(?m)(?:\A|\r?\n)version:[ \t]*([^\r\n]*)'); " ^
|
||||
"if (-not $match.Success) { Write-Error 'Could not find the version line in pubspec.yaml.'; exit 1 }; " ^
|
||||
"Write-Output ('Current version: ' + $match.Groups[1].Value.Trim())"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo Could not read the current version. Release cancelled.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set "APP_VERSION="
|
||||
set /p "APP_VERSION=Enter release version (for example 1.0.10 or 1.0.10+2): "
|
||||
|
||||
if not defined APP_VERSION (
|
||||
echo No version entered. Release cancelled.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"$version = $env:APP_VERSION; " ^
|
||||
"if ($version -notmatch '\A[0-9]+\.[0-9]+\.[0-9]+(\+[0-9]+)?\z') { " ^
|
||||
" Write-Error 'Use a version like 1.0.10 or 1.0.10+2.'; exit 1 " ^
|
||||
"}; " ^
|
||||
"$path = 'pubspec.yaml'; " ^
|
||||
"$content = [IO.File]::ReadAllText($path); " ^
|
||||
"$updated = ([regex]::new('(?m)(\A|\r?\n)version:[^\r\n]*')).Replace($content, ('${1}version: ' + $version), 1); " ^
|
||||
"if ($updated -eq $content) { Write-Error 'Could not find the version line in pubspec.yaml.'; exit 1 }; " ^
|
||||
"[IO.File]::WriteAllText($path, $updated)"
|
||||
|
||||
if errorlevel 1 (
|
||||
echo Version update failed. Release cancelled.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Updated pubspec.yaml to version %APP_VERSION%.
|
||||
echo Building release APK...
|
||||
flutter build apk --release
|
||||
|
||||
if errorlevel 1 (
|
||||
echo Release APK build failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Release APK created at build\app\outputs\flutter-apk\app-release.apk
|
||||
endlocal
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0make_release.ps1" %*
|
||||
exit /b %errorlevel%
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
param(
|
||||
[switch]$SkipTests,
|
||||
[switch]$SkipUpload
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-Location $PSScriptRoot
|
||||
|
||||
$updateServer = 'https://updater.kooltap.com'
|
||||
$apkPath = 'build\app\outputs\flutter-apk\app-release.apk'
|
||||
|
||||
function Fail([string]$message) {
|
||||
Write-Host $message -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
$pubspec = [IO.File]::ReadAllText('pubspec.yaml')
|
||||
$match = [regex]::Match(
|
||||
$pubspec,
|
||||
'(?m)^version:[ \t]*([0-9]+\.[0-9]+\.[0-9]+)\+([0-9]+)'
|
||||
)
|
||||
if (-not $match.Success) {
|
||||
Fail 'Could not find a version like 1.2.3+4 in pubspec.yaml.'
|
||||
}
|
||||
|
||||
$currentName = $match.Groups[1].Value
|
||||
$currentBuild = [int]$match.Groups[2].Value
|
||||
Write-Host "Current version: $currentName+$currentBuild"
|
||||
|
||||
# The server picks the latest release by build number, so every release needs
|
||||
# a higher one than anything already published
|
||||
$publishedName = $null
|
||||
$publishedBuild = 0
|
||||
try {
|
||||
foreach ($release in Invoke-RestMethod "$updateServer/api/releases") {
|
||||
if ($release.VersionCode -gt $publishedBuild) {
|
||||
$publishedName = $release.Version
|
||||
$publishedBuild = [int]$release.VersionCode
|
||||
}
|
||||
}
|
||||
if ($publishedName) {
|
||||
Write-Host "Latest published: $publishedName+$publishedBuild"
|
||||
}
|
||||
} catch {
|
||||
Write-Host "Could not reach $updateServer, using pubspec.yaml only." -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$name = Read-Host 'Release version (for example 1.2.2)'
|
||||
if ($name -notmatch '\A[0-9]+\.[0-9]+\.[0-9]+\z') {
|
||||
Fail 'Use a version like 1.2.2.'
|
||||
}
|
||||
if ($publishedName -and [version]$name -le [version]$publishedName) {
|
||||
Fail "$name must be higher than the published $publishedName, or tablets will not see it."
|
||||
}
|
||||
|
||||
$build = [Math]::Max($currentBuild, $publishedBuild) + 1
|
||||
$version = "$name+$build"
|
||||
|
||||
$versionStart = $match.Groups[1].Index
|
||||
$versionEnd = $match.Groups[2].Index + $match.Groups[2].Length
|
||||
$pubspec = $pubspec.Substring(0, $versionStart) + $version + $pubspec.Substring($versionEnd)
|
||||
[IO.File]::WriteAllText((Resolve-Path 'pubspec.yaml'), $pubspec)
|
||||
Write-Host "Updated pubspec.yaml to version $version."
|
||||
|
||||
if (-not $SkipTests) {
|
||||
Write-Host 'Running tests...'
|
||||
flutter test
|
||||
if ($LASTEXITCODE -ne 0) { Fail 'Tests failed. Release cancelled.' }
|
||||
}
|
||||
|
||||
Write-Host 'Building release APK...'
|
||||
flutter build apk --release
|
||||
if ($LASTEXITCODE -ne 0) { Fail 'Release APK build failed.' }
|
||||
Write-Host "Release APK created at $apkPath"
|
||||
|
||||
if ($SkipUpload) { exit 0 }
|
||||
|
||||
$token = $env:KOOLTAP_ADMIN_TOKEN
|
||||
if (-not $token) {
|
||||
Write-Host 'KOOLTAP_ADMIN_TOKEN is not set, skipping the upload.' -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
$notes = Read-Host 'Release notes'
|
||||
$mandatory = if ((Read-Host 'Mandatory update? (y/N)') -match '\A[yY]') { 'true' } else { 'false' }
|
||||
|
||||
Write-Host "Uploading $version to $updateServer..."
|
||||
|
||||
Add-Type -AssemblyName System.Net.Http
|
||||
$client = [Net.Http.HttpClient]::new()
|
||||
$client.Timeout = [TimeSpan]::FromMinutes(5)
|
||||
$client.DefaultRequestHeaders.Add('X-Admin-Token', $token)
|
||||
|
||||
$apk = [IO.File]::OpenRead((Resolve-Path $apkPath))
|
||||
$form = [Net.Http.MultipartFormDataContent]::new()
|
||||
$form.Add([Net.Http.StreamContent]::new($apk), 'apk', 'app-release.apk')
|
||||
$form.Add([Net.Http.StringContent]::new($notes), 'notes')
|
||||
$form.Add([Net.Http.StringContent]::new($mandatory), 'mandatory')
|
||||
|
||||
try {
|
||||
$response = $client.PostAsync("$updateServer/admin/releases", $form).GetAwaiter().GetResult()
|
||||
$body = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
|
||||
} finally {
|
||||
$form.Dispose()
|
||||
$apk.Dispose()
|
||||
$client.Dispose()
|
||||
}
|
||||
|
||||
if (-not $response.IsSuccessStatusCode) {
|
||||
Fail "Upload failed (HTTP $([int]$response.StatusCode)): $body`nThe APK is still at $apkPath"
|
||||
}
|
||||
|
||||
$release = $body | ConvertFrom-Json
|
||||
Write-Host "Published $($release.Version) (build $($release.VersionCode)), sha256 $($release.SHA256)" -ForegroundColor Green
|
||||
Write-Host "Don't forget to commit pubspec.yaml and tag the release: git tag v$version"
|
||||
@@ -2,7 +2,7 @@ name: kooltap
|
||||
description: "KoolTap: a tablet point-of-sale app for running bar tabs."
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.1.0
|
||||
version: 1.2.1+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.2
|
||||
|
||||
@@ -136,6 +136,9 @@ void main() {
|
||||
final snapshot = server.syncRequests.single['openTabs'] as List;
|
||||
expect(snapshot.single['customerName'], 'Alice');
|
||||
expect(snapshot.single['totalInCents'], 400);
|
||||
expect(snapshot.single['items'], [
|
||||
{'productName': 'Beer', 'quantity': 1, 'unitPriceInCents': 400},
|
||||
]);
|
||||
expect(eventTypes(), [RemoteSyncEventType.syncCompleted]);
|
||||
expect(await service.lastSuccessfulSyncAt, isNotNull);
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 1.0 KiB |