3 Commits

Author SHA1 Message Date
github-actions[bot]
2545e421de chore(master): release 0.2.1 2026-04-12 00:22:54 +03:00
a5c8e5b479 fix: sorting prefs persistence & error wrapping 2026-04-12 00:21:11 +03:00
e69625e8af build: only upload changed graphics, fix changelog slicing 2026-04-12 00:01:30 +03:00
7 changed files with 110 additions and 9 deletions

1
.gitignore vendored
View File

@@ -55,6 +55,7 @@ android/app/*.keystore
# Fastlane
android/fastlane/play-store-key.json
android/fastlane/.image_hashes.json
ios/fastlane/report.xml
android/fastlane/report.xml
/.envrc

View File

@@ -1,5 +1,12 @@
# Changelog
## [0.2.1](https://github.com/chenasraf/pantry-flutter/compare/v0.2.0...v0.2.1) (2026-04-11)
### Bug Fixes
* sorting prefs persistence & error wrapping ([a5c8e5b](https://github.com/chenasraf/pantry-flutter/commit/a5c8e5b479e92f87ea910b5af19ca24711ce7b16))
## [0.2.0](https://github.com/chenasraf/pantry-flutter/compare/v0.1.0...v0.2.0) (2026-04-11)

View File

@@ -1,3 +1,6 @@
require "digest"
require "json"
default_platform(:android)
platform :android do
@@ -13,17 +16,24 @@ platform :android do
"#{v[:build]} (#{v[:name]})"
end
# Google Play enforces a 500-char limit on release notes per language.
PLAY_NOTES_MAX = 500
PLAY_NOTES_TRAILER = "\n\n… see full notes on GitHub."
def changelog_notes
version = version_info[:name]
changelog_path = File.expand_path("../../CHANGELOG.md", __dir__)
return "Release #{version}" unless File.exist?(changelog_path)
changelog = File.read(changelog_path)
pattern = /^## \[#{Regexp.escape(version)}\].*?\n(.*?)(?=^## \[|\z)/m
# Match the release heading (both `## [x.y.z]...` and `## x.y.z ...` forms),
# capture until the next `## ` heading (any form) or EOF.
escaped = Regexp.escape(version)
pattern = /^## (?:\[#{escaped}\]|#{escaped}[\s(]).*?\n(.*?)(?=^## |\z)/m
match = changelog.match(pattern)
return "Release #{version}" unless match
match[1].strip
notes = match[1].strip
.gsub(/\s*\(\[[\da-f]+\]\([^)]+\)\)/, "")
.gsub(/^\*\s+\*\*[^*]+:\*\*\s*/m, "- ")
.gsub(/^\*\s+/, "- ")
@@ -31,6 +41,56 @@ platform :android do
.gsub(/\n{3,}/, "\n\n")
.strip
.then { |n| n.empty? ? "Release #{version}" : n }
truncate_for_play(notes)
end
# Truncate release notes to fit within the Play Store limit. If the
# notes exceed the limit, trim to a line boundary and append a trailer
# so the total stays under [PLAY_NOTES_MAX].
def truncate_for_play(notes)
return notes if notes.length <= PLAY_NOTES_MAX
budget = PLAY_NOTES_MAX - PLAY_NOTES_TRAILER.length
truncated = notes[0, budget]
# Cut at the last newline so we don't chop a bullet mid-word.
last_newline = truncated.rindex("\n")
truncated = truncated[0, last_newline] if last_newline && last_newline > budget / 2
truncated.rstrip + PLAY_NOTES_TRAILER
end
# -- Image change detection --
# We hash every file under metadata/android/*/images/ and compare to a
# cache file. If nothing changed, we skip image uploads entirely.
IMAGE_HASH_CACHE = File.expand_path(".image_hashes.json", __dir__)
def current_image_hashes
root = File.expand_path("metadata/android", __dir__)
return {} unless Dir.exist?(root)
files = Dir.glob(File.join(root, "**/images/**/*")).select { |f| File.file?(f) }
files.sort.each_with_object({}) do |path, acc|
rel = path.sub("#{root}/", "")
acc[rel] = Digest::SHA256.file(path).hexdigest
end
end
def cached_image_hashes
return {} unless File.exist?(IMAGE_HASH_CACHE)
JSON.parse(File.read(IMAGE_HASH_CACHE))
rescue StandardError
{}
end
def images_changed?
current_image_hashes != cached_image_hashes
end
def save_image_hashes
File.write(IMAGE_HASH_CACHE, JSON.pretty_generate(current_image_hashes))
end
desc "Upload AAB to Google Play"
@@ -42,22 +102,36 @@ platform :android do
FileUtils.mkdir_p(changelog_dir)
File.write(File.join(changelog_dir, "#{version_code}.txt"), changelog_notes)
changed = images_changed?
UI.message(changed ? "Images changed — uploading." : "Images unchanged — skipping.")
upload_to_play_store(
aab: File.expand_path("../../build/app/outputs/bundle/release/app-release.aab", __dir__),
track: options[:track] || "internal",
release_status: options[:status] || "draft",
version_name: version_name,
metadata_path: File.expand_path("metadata/android", __dir__),
skip_upload_images: !changed,
skip_upload_screenshots: !changed,
)
save_image_hashes if changed
end
desc "Sync metadata and screenshots only (no AAB upload)"
lane :metadata do
changed = images_changed?
UI.message(changed ? "Images changed — uploading." : "Images unchanged — skipping.")
upload_to_play_store(
skip_upload_aab: true,
skip_upload_apk: true,
metadata_path: File.expand_path("metadata/android", __dir__),
skip_upload_images: !changed,
skip_upload_screenshots: !changed,
)
save_image_hashes if changed
end
desc "Promote a release from one track to another"

View File

@@ -0,0 +1,8 @@
Features
- add sorting by category for checklist
- notifications support
Bug Fixes
- add bottom padding to accomodate fab

View File

@@ -69,15 +69,15 @@ class ChecklistService {
Future<String> getItemSortPref(int houseId) async {
return ApiClient.instance.get<Map<String, dynamic>, String>(
'/houses/$houseId/prefs/checklist-item-sort',
fromJson: (data) => data['sort'] as String? ?? 'custom',
'/houses/$houseId/prefs',
fromJson: (data) => data['checklistItemSort'] as String? ?? 'custom',
);
}
Future<void> setItemSortPref(int houseId, String sort) async {
await ApiClient.instance.put<Map<String, dynamic>, void>(
'/houses/$houseId/prefs/checklist-item-sort',
body: {'sort': sort},
'/houses/$houseId/prefs',
body: {'checklistItemSort': sort},
fromJson: (_) {},
);
}

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:pantry/i18n.dart';
import 'package:pantry/models/category.dart' as models;
@@ -167,8 +169,9 @@ class ChecklistsController extends ChangeNotifier {
_checklistService.cache.set('sortBy', sort);
notifyListeners();
// Persist to server
_checklistService.setItemSortPref(houseId, sort);
// Fire-and-forget the server persist so a slow or failing pref write
// never blocks the item reload.
unawaited(_persistSortPref(sort));
// Reload items with new sort
if (_currentList != null) {
@@ -177,6 +180,14 @@ class ChecklistsController extends ChangeNotifier {
}
}
Future<void> _persistSortPref(String sort) async {
try {
await _checklistService.setItemSortPref(houseId, sort);
} catch (e) {
debugPrint('[ChecklistsController] Failed to persist sort pref: $e');
}
}
Future<void> reorderItems(
List<ListItem> partition,
int oldIndex,

View File

@@ -16,7 +16,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
# 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.
version: 0.2.0+3
version: 0.2.1+4
environment:
sdk: ^3.11.1