Compare commits

...

7 Commits

Author SHA1 Message Date
bvandeusen e63370bf0a Merge pull request 'v26.03.12.1' (#3) from dev into main
Reviewed-on: #3
2026-03-12 00:41:40 -04:00
bvandeusen f39b8ddb30 chore: bump version to 26.03.12+1 2026-03-12 00:40:10 -04:00
bvandeusen def7519feb fix: resolve all flutter analyze warnings and infos
- app.dart: add braces to single-statement if body
- briefing_api.dart: escape <id> in doc comment (unintended HTML)
- notes_api.dart, tasks_api.dart, projects_api.dart: use null-aware
  map elements (?'key': value) instead of if-null guards
- task_edit_screen.dart: remove unused tasks_api.dart import
- task_edit_screen.dart, project_selector.dart: suppress
  deprecated_member_use on DropdownButtonFormField.value (value is
  the controlled-widget param; switching to initialValue would break
  current-selection display)
- project_selector.dart: use _ instead of __ for ignored error params
2026-03-12 00:36:05 -04:00
bvandeusen e86d1a59af fix: redirect to /briefing after auth (was /notes which no longer exists) 2026-03-12 00:32:38 -04:00
bvandeusen c8f3861eb5 ci: only trigger on v* release tags, never on branch pushes 2026-03-12 00:24:59 -04:00
bvandeusen a2e734c498 Merge pull request 'ci: replace JS actions with shell steps — Flutter container has no Node' (#2) from dev into main
Reviewed-on: #2
2026-03-12 00:23:06 -04:00
bvandeusen 3a336ddf88 ci: replace JS actions with shell steps — Flutter container has no Node
actions/checkout and actions/upload-artifact are JavaScript actions.
When container: is set, act runs them inside the container (not on the
host runner), but ghcr.io/cirruslabs/flutter:stable has no Node.js.

Replace both with equivalent shell commands:
- checkout: git clone + git checkout SHA
- artifact upload: curl to Forgejo API (best-effort, non-blocking)
The release APK attach step was already a pure shell step and is unchanged.
2026-03-12 00:21:47 -04:00
11 changed files with 44 additions and 57 deletions
+29 -44
View File
@@ -1,12 +1,14 @@
# CI runs first; build only proceeds if analyze and test pass. # CI runs only on release tags.
# #
# Push to dev: analyze + test → build APK → upload artifact (fabledapp-dev-<sha>) # Tag v*: analyze + test → build APK → attach to Forgejo Release
# Push to main: analyze + test only (no build — wait for release tag)
# Tag v26.03.11: analyze + test → build APK → upload artifact + create Forgejo Release
# Pull request: analyze + test only
# #
# To cut a release: # To cut a release:
# git tag v26.03.11 && git push origin v26.03.11 # Create a release via the Forgejo UI on main with a v* tag name.
# The tag push triggers this workflow; the build job attaches the APK.
#
# Note: JavaScript actions (actions/checkout, actions/upload-artifact) cannot run
# inside the Flutter container because it has no Node.js. All steps use shell
# commands directly instead.
# #
# Required secrets (repo → Settings → Secrets → Actions): # Required secrets (repo → Settings → Secrets → Actions):
# RELEASE_TOKEN — Forgejo PAT with write:repository scope # RELEASE_TOKEN — Forgejo PAT with write:repository scope
@@ -14,25 +16,7 @@ name: CI & Build
on: on:
push: push:
branches: [main, dev]
tags: ["v*"] tags: ["v*"]
paths:
- "lib/**"
- "test/**"
- "pubspec.yaml"
- "pubspec.lock"
- "analysis_options.yaml"
- "android/**"
- "assets/**"
- ".forgejo/workflows/ci.yml"
pull_request:
paths:
- "lib/**"
- "test/**"
- "pubspec.yaml"
- "pubspec.lock"
- "analysis_options.yaml"
- ".forgejo/workflows/ci.yml"
jobs: jobs:
analyze: analyze:
@@ -41,7 +25,10 @@ jobs:
container: container:
image: ghcr.io/cirruslabs/flutter:stable image: ghcr.io/cirruslabs/flutter:stable
steps: steps:
- uses: actions/checkout@v6 - name: Checkout
run: |
git clone "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" .
git checkout "$GITHUB_SHA"
- name: Install dependencies - name: Install dependencies
run: flutter pub get run: flutter pub get
@@ -55,17 +42,14 @@ jobs:
build: build:
name: Build release APK name: Build release APK
needs: [analyze] needs: [analyze]
# Build on dev branch pushes and version tag pushes only.
# main branch pushes run CI for safety but do not build —
# the release tag (v*) is the sole trigger for a production APK.
if: |
github.event_name == 'push' &&
(github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/'))
runs-on: py3.12-node22 runs-on: py3.12-node22
container: container:
image: ghcr.io/cirruslabs/flutter:stable image: ghcr.io/cirruslabs/flutter:stable
steps: steps:
- uses: actions/checkout@v6 - name: Checkout
run: |
git clone "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" .
git checkout "$GITHUB_SHA"
- name: Install dependencies - name: Install dependencies
run: flutter pub get run: flutter pub get
@@ -76,21 +60,22 @@ jobs:
- name: Set artifact name - name: Set artifact name
id: artifact id: artifact
run: | run: |
if [[ "${{ github.ref }}" == "refs/heads/dev" ]]; then echo "name=fabledapp-${{ github.ref_name }}-${{ github.sha }}" >> $GITHUB_OUTPUT
echo "name=fabledapp-dev-${{ github.sha }}" >> $GITHUB_OUTPUT
else
echo "name=fabledapp-${{ github.sha }}" >> $GITHUB_OUTPUT
fi
- name: Upload artifact - name: Upload artifact to Forgejo
uses: actions/upload-artifact@v6 env:
with: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
name: ${{ steps.artifact.outputs.name }} APK: build/app/outputs/flutter-apk/app-release.apk
path: build/app/outputs/flutter-apk/app-release.apk API: https://git.fabledsword.com/api/v1/repos/bvandeusen/FabledApp
retention-days: 30 ARTIFACT_NAME: ${{ steps.artifact.outputs.name }}
run: |
# Upload the APK as a workflow artifact via the Forgejo API.
curl -s -X POST "$API/actions/artifacts" \
-H "Authorization: token $RELEASE_TOKEN" \
-F "name=$ARTIFACT_NAME" \
-F "file=@$APK" || echo "Artifact upload skipped (API may not support this endpoint)."
- name: Publish Forgejo release - name: Publish Forgejo release
if: startsWith(github.ref, 'refs/tags/')
env: env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
TAG: ${{ github.ref_name }} TAG: ${{ github.ref_name }}
+1 -1
View File
@@ -371,7 +371,7 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> {
String _hintForLocation(String location) { String _hintForLocation(String location) {
if (location.startsWith(Routes.library) && if (location.startsWith(Routes.library) &&
location.contains('tasks')) return 'Add a task…'; location.contains('tasks')) { return 'Add a task…'; }
if (location.startsWith(Routes.conversations)) return 'Ask Fabled…'; if (location.startsWith(Routes.conversations)) return 'Ask Fabled…';
return 'Capture a note…'; return 'Capture a note…';
} }
+1 -1
View File
@@ -35,7 +35,7 @@ class BriefingApi {
} }
} }
/// GET /api/briefing/conversations/<id>/messages /// GET /api/briefing/conversations/`<id>`/messages
Future<List<Message>> getMessages(int convId) async { Future<List<Message>> getMessages(int convId) async {
try { try {
final response = final response =
+2 -2
View File
@@ -38,7 +38,7 @@ class NotesApi {
'title': title, 'title': title,
'body': body, 'body': body,
'tags': tags, 'tags': tags,
if (projectId != null) 'project_id': projectId, ?'project_id': projectId,
}); });
return Note.fromJson(response.data as Map<String, dynamic>); return Note.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) { } on DioException catch (e) {
@@ -59,7 +59,7 @@ class NotesApi {
'title': title, 'title': title,
'body': body, 'body': body,
'tags': tags, 'tags': tags,
if (clearProject) 'project_id': null else if (projectId != null) 'project_id': projectId, if (clearProject) 'project_id': null else ?'project_id': projectId,
}); });
return Note.fromJson(response.data as Map<String, dynamic>); return Note.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) { } on DioException catch (e) {
+1 -1
View File
@@ -44,7 +44,7 @@ class ProjectsApi {
if (description != null && description.isNotEmpty) if (description != null && description.isNotEmpty)
'description': description, 'description': description,
if (goal != null && goal.isNotEmpty) 'goal': goal, if (goal != null && goal.isNotEmpty) 'goal': goal,
if (color != null) 'color': color, ?'color': color,
}); });
return Project.fromJson(response.data as Map<String, dynamic>); return Project.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) { } on DioException catch (e) {
+2 -2
View File
@@ -43,8 +43,8 @@ class TasksApi {
'status': status.value, 'status': status.value,
'priority': priority.value, 'priority': priority.value,
'due_date': dueDate?.toIso8601String(), 'due_date': dueDate?.toIso8601String(),
if (projectId != null) 'project_id': projectId, ?'project_id': projectId,
if (parentId != null) 'parent_id': parentId, ?'parent_id': parentId,
}); });
return Task.fromJson(response.data as Map<String, dynamic>); return Task.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) { } on DioException catch (e) {
+2 -2
View File
@@ -72,7 +72,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
_usernameController.text.trim(), _usernameController.text.trim(),
_passwordController.text, _passwordController.text,
); );
if (mounted) context.go(Routes.notes); if (mounted) context.go(Routes.briefing);
} on AuthException catch (e) { } on AuthException catch (e) {
setState(() => _error = e.message); setState(() => _error = e.message);
} on AppException catch (e) { } on AppException catch (e) {
@@ -90,7 +90,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
cookieJar: ref.read(cookieJarProvider), cookieJar: ref.read(cookieJarProvider),
onSuccess: () async { onSuccess: () async {
await ref.read(authProvider.notifier).verify(); await ref.read(authProvider.notifier).verify();
if (mounted) context.go(Routes.notes); if (mounted) context.go(Routes.briefing);
}, },
), ),
)); ));
+1 -1
View File
@@ -30,7 +30,7 @@ class _SplashScreenState extends ConsumerState<SplashScreen> {
if (!mounted) return; if (!mounted) return;
final status = ref.read(authProvider); final status = ref.read(authProvider);
if (status == AuthStatus.authenticated) { if (status == AuthStatus.authenticated) {
context.go(Routes.notes); context.go(Routes.briefing);
} else { } else {
context.go(Routes.login); context.go(Routes.login);
} }
+2 -1
View File
@@ -4,7 +4,6 @@ import 'package:go_router/go_router.dart';
import '../../core/constants.dart'; import '../../core/constants.dart';
import '../../core/exceptions.dart'; import '../../core/exceptions.dart';
import '../../data/api/tasks_api.dart';
import '../../data/models/task.dart'; import '../../data/models/task.dart';
import '../../providers/api_client_provider.dart'; import '../../providers/api_client_provider.dart';
import '../../providers/tasks_provider.dart'; import '../../providers/tasks_provider.dart';
@@ -239,6 +238,7 @@ class _TaskEditScreenState extends ConsumerState<TaskEditScreen> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<TaskStatus>( DropdownButtonFormField<TaskStatus>(
// ignore: deprecated_member_use
value: _status, value: _status,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Status', labelText: 'Status',
@@ -253,6 +253,7 @@ class _TaskEditScreenState extends ConsumerState<TaskEditScreen> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<TaskPriority>( DropdownButtonFormField<TaskPriority>(
// ignore: deprecated_member_use
value: _priority, value: _priority,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Priority', labelText: 'Priority',
+2 -1
View File
@@ -23,12 +23,13 @@ class ProjectSelector extends ConsumerWidget {
return projectsAsync.when( return projectsAsync.when(
loading: () => const LinearProgressIndicator(), loading: () => const LinearProgressIndicator(),
error: (_, __) => const SizedBox.shrink(), error: (_, _) => const SizedBox.shrink(),
data: (projects) { data: (projects) {
final active = final active =
projects.where((p) => p.status == 'active').toList(); projects.where((p) => p.status == 'active').toList();
return DropdownButtonFormField<int?>( return DropdownButtonFormField<int?>(
// ignore: deprecated_member_use
value: value, value: value,
decoration: decoration ?? decoration: decoration ??
const InputDecoration( const InputDecoration(
+1 -1
View File
@@ -2,7 +2,7 @@ name: fabled_app
description: "FabledAssistant mobile client for Android." description: "FabledAssistant mobile client for Android."
publish_to: 'none' publish_to: 'none'
version: 26.03.02+1 version: 26.03.12+1
environment: environment:
sdk: ^3.11.0 sdk: ^3.11.0