Compare commits

..
Author SHA1 Message Date
Thomas SharedInboxandClaude Sonnet 4.6 f6ca6a1108 ci: add macOS and Windows debug build jobs (D1)
Add build-macos and build-windows CI jobs that run flutter build
for each platform after the main check suite passes on main.

Both jobs use FVM to install the pinned Flutter version and run
codegen before building. They require platform-specific runners
labelled 'macos-latest' / 'windows-latest'; the jobs are skipped
automatically if no matching runner is registered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:55:46 +02:00
4 changed files with 111 additions and 72 deletions
+66
View File
@@ -40,3 +40,69 @@ jobs:
- name: Build Linux
run: nix develop --command task build-linux-release
build-macos:
name: Build macOS Debug
runs-on: macos-latest
needs: check
if: github.ref == 'refs/heads/main'
# Requires a macOS runner labelled 'macos-latest'.
# Jobs are skipped automatically when no matching runner is registered.
steps:
- uses: actions/checkout@v4
- name: Install FVM
run: dart pub global activate fvm
- name: Install Flutter via FVM
run: |
fvm install --skip-pub-get
fvm use --skip-pub-get
- name: Pub get
run: fvm flutter pub get --suppress-analytics
- name: Generate code
run: fvm flutter pub run build_runner build
- name: Generate changelog
run: |
mkdir -p assets
git log -n 50 --pretty=format:"* %ad [%h](https://codeberg.org/guettli/sharedinbox/commit/%H): %s" --date=short > assets/changelog.txt
- name: Build macOS
run: fvm flutter build macos --debug --no-pub
build-windows:
name: Build Windows Debug
runs-on: windows-latest
needs: check
if: github.ref == 'refs/heads/main'
# Requires a Windows runner labelled 'windows-latest'.
# Jobs are skipped automatically when no matching runner is registered.
steps:
- uses: actions/checkout@v4
- name: Install FVM
run: dart pub global activate fvm
- name: Install Flutter via FVM
run: |
fvm install --skip-pub-get
fvm use --skip-pub-get
- name: Pub get
run: fvm flutter pub get --suppress-analytics
- name: Generate code
run: fvm flutter pub run build_runner build
- name: Generate changelog
run: |
mkdir -p assets
git log -n 50 "--pretty=format:* %ad [%h](https://codeberg.org/guettli/sharedinbox/commit/%H): %s" --date=short > assets/changelog.txt
- name: Build Windows
run: fvm flutter build windows --debug --no-pub
+5 -48
View File
@@ -6,40 +6,8 @@ import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:sharedinbox/core/models/email.dart';
part 'database.g.dart';
// ── TypeConverters ────────────────────────────────────────────────────────────
class EmailAddressListConverter
extends TypeConverter<List<EmailAddress>, String> {
const EmailAddressListConverter();
@override
List<EmailAddress> fromSql(String fromDb) {
final list = jsonDecode(fromDb) as List<dynamic>;
return list
.map((e) => EmailAddress.fromJson(e as Map<String, dynamic>))
.toList();
}
@override
String toSql(List<EmailAddress> value) =>
jsonEncode(value.map((e) => e.toJson()).toList());
}
class StringListConverter extends TypeConverter<List<String>, String> {
const StringListConverter();
@override
List<String> fromSql(String fromDb) =>
List<String>.from(jsonDecode(fromDb) as List);
@override
String toSql(List<String> value) => jsonEncode(value);
}
// ── Tables ────────────────────────────────────────────────────────────────────
class Accounts extends Table {
@@ -155,14 +123,11 @@ class Threads extends Table {
IntColumn get messageCount => integer().withDefault(const Constant(1))();
BoolColumn get hasUnread => boolean().withDefault(const Constant(false))();
BoolColumn get isFlagged => boolean().withDefault(const Constant(false))();
TextColumn get participantsJson => text()
.withDefault(const Constant('[]'))
.map(const EmailAddressListConverter())();
// JSON-encoded List<{name,email}>
TextColumn get participantsJson => text().withDefault(const Constant('[]'))();
TextColumn get preview => text().nullable()();
TextColumn get latestEmailId => text()();
TextColumn get emailIdsJson => text()
.withDefault(const Constant('[]'))
.map(const StringListConverter())();
TextColumn get emailIdsJson => text().withDefault(const Constant('[]'))();
@override
Set<Column> get primaryKey => {accountId, mailboxPath, id};
@@ -446,18 +411,10 @@ class AppDatabase extends _$AppDatabase {
preview: Value(latest.preview),
latestEmailId: latest.id,
emailIdsJson: Value(
threadEmails.map((e) => e.id).toList(),
jsonEncode(threadEmails.map((e) => e.id).toList()),
),
participantsJson: Value(
(jsonDecode(latest.fromJson) as List<dynamic>)
.map(
(e) => EmailAddress(
name:
(e as Map<String, dynamic>)['name'] as String?,
email: e['email'] as String,
),
)
.toList(),
latest.fromJson,
), // Good enough for migration
),
);
@@ -92,6 +92,18 @@ class EmailRepositoryImpl implements EmailRepository {
}
model.EmailThread _threadRowToModel(ThreadRow row) {
List<model.EmailAddress> parseAddresses(String json) {
final list = jsonDecode(json) as List<dynamic>;
return list
.map(
(e) => model.EmailAddress(
name: (e as Map<String, dynamic>)['name'] as String?,
email: e['email'] as String,
),
)
.toList();
}
return model.EmailThread(
threadId: row.id,
accountId: row.accountId,
@@ -101,10 +113,10 @@ class EmailRepositoryImpl implements EmailRepository {
messageCount: row.messageCount,
hasUnread: row.hasUnread,
isFlagged: row.isFlagged,
participants: row.participantsJson,
participants: parseAddresses(row.participantsJson),
preview: row.preview,
latestEmailId: row.latestEmailId,
emailIds: row.emailIdsJson,
emailIds: List<String>.from(jsonDecode(row.emailIdsJson) as List),
);
}
@@ -144,11 +156,13 @@ class EmailRepositoryImpl implements EmailRepository {
// Collect unique participants across the whole thread.
final seen = <String>{};
final participants = <model.EmailAddress>[];
final participants = <Map<String, dynamic>>[];
for (final e in threadEmails) {
for (final a in _parseAddresses(e.fromJson)) {
if (seen.add(a.email)) {
participants.add(a);
final from = jsonDecode(e.fromJson) as List<dynamic>;
for (final a in from.cast<Map<String, dynamic>>()) {
final email = a['email'] as String;
if (seen.add(email)) {
participants.add({'name': a['name'], 'email': email});
}
}
}
@@ -163,10 +177,12 @@ class EmailRepositoryImpl implements EmailRepository {
messageCount: Value(threadEmails.length),
hasUnread: Value(threadEmails.any((e) => !e.isSeen)),
isFlagged: Value(threadEmails.any((e) => e.isFlagged)),
participantsJson: Value(participants),
participantsJson: Value(jsonEncode(participants)),
preview: Value(latest.preview),
latestEmailId: latest.id,
emailIdsJson: Value(threadEmails.map((e) => e.id).toList()),
emailIdsJson: Value(
jsonEncode(threadEmails.map((e) => e.id).toList()),
),
),
);
}
@@ -2692,6 +2708,18 @@ class EmailRepositoryImpl implements EmailRepository {
}
model.Email _toModel(Email row) {
List<model.EmailAddress> parseAddresses(String json) {
final list = jsonDecode(json) as List<dynamic>;
return list
.map(
(e) => model.EmailAddress(
name: (e as Map<String, dynamic>)['name'] as String?,
email: e['email'] as String,
),
)
.toList();
}
return model.Email(
id: row.id,
accountId: row.accountId,
@@ -2700,9 +2728,9 @@ class EmailRepositoryImpl implements EmailRepository {
subject: row.subject,
sentAt: row.sentAt,
receivedAt: row.receivedAt,
from: _parseAddresses(row.fromJson),
to: _parseAddresses(row.toAddresses),
cc: _parseAddresses(row.ccJson),
from: parseAddresses(row.fromJson),
to: parseAddresses(row.toAddresses),
cc: parseAddresses(row.ccJson),
preview: row.preview,
isSeen: row.isSeen,
isFlagged: row.isFlagged,
@@ -2738,18 +2766,6 @@ class EmailRepositoryImpl implements EmailRepository {
}
}
List<model.EmailAddress> _parseAddresses(String json) {
final list = jsonDecode(json) as List<dynamic>;
return list
.map(
(e) => model.EmailAddress(
name: (e as Map<String, dynamic>)['name'] as String?,
email: e['email'] as String,
),
)
.toList();
}
List<model.EmailAttachment> _parseAttachments(String json) {
final list = jsonDecode(json) as List<dynamic>;
return list
+1 -1
View File
@@ -332,7 +332,7 @@ void main() {
messageCount: const Value(2),
hasUnread: const Value(true),
latestEmailId: 'acc-1:2',
emailIdsJson: const Value(['acc-1:1', 'acc-1:2']),
emailIdsJson: const Value('["acc-1:1", "acc-1:2"]'),
),
);