Compare commits

...

4 Commits

Author SHA1 Message Date
af37e25692 fix saving draft with shared user, display user shared to/from in expanded leg card
All checks were successful
Release / meta (push) Successful in 6s
Release / linux-build (push) Successful in 58s
Release / web-build (push) Successful in 2m0s
Release / android-build (push) Successful in 9m58s
Release / release-master (push) Successful in 37s
Release / release-dev (push) Successful in 44s
2026-01-03 23:26:33 +00:00
Jack
1689869ce5 Update release.yml
Some checks failed
Release / android-build (push) Blocked by required conditions
Release / meta (push) Successful in 7s
Release / linux-build (push) Successful in 58s
Release / web-build (push) Successful in 1m18s
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled
2026-01-03 22:58:02 +00:00
Jack
425ab46656 Update release.yml
Some checks failed
Release / meta (push) Successful in 7s
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled
Release / android-build (push) Has been cancelled
Release / web-build (push) Has been cancelled
Release / linux-build (push) Has been cancelled
2026-01-03 22:57:32 +00:00
196511dfab unify pipeline, load friends leaderboard from homepage data
Some checks failed
Release / meta (push) Successful in 10s
Release / linux-build (push) Successful in 57s
Release / web-build (push) Failing after 1m11s
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled
Release / android-build (push) Has been cancelled
2026-01-03 22:55:03 +00:00
10 changed files with 190 additions and 192 deletions

View File

@@ -3,7 +3,7 @@ name: Release
on:
push:
branches:
- dev
- dev-other
env:
JAVA_VERSION: "17"
@@ -85,16 +85,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
#
# - name: Install OS deps (Android)
# run: |
# if command -v sudo >/dev/null 2>&1; then
# SUDO="sudo"
# else
# SUDO=""
# fi
# $SUDO apt-get update
# $SUDO apt-get install -y unzip xz-utils zip libstdc++6 liblzma-dev curl jq
#
# - name: Install OS deps (Android)
# run: |
# if command -v sudo >/dev/null 2>&1; then
# SUDO="sudo"
# else
# SUDO=""
# fi
# $SUDO apt-get update
# $SUDO apt-get install -y unzip xz-utils zip libstdc++6 liblzma-dev curl jq
- name: Setup Java
uses: actions/setup-java@v4
@@ -120,19 +120,19 @@ jobs:
echo "$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_PATH"
echo "$ANDROID_SDK_ROOT/build-tools/33.0.2" >> "$GITHUB_PATH"
# - name: Install Flutter SDK
# run: |
# set -euo pipefail
# FLUTTER_HOME="$HOME/flutter"
# # Avoid git ownership issues when Flutter checks out deps.
# git config --global --add safe.directory "$FLUTTER_HOME" || true
# if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
# rm -rf "$FLUTTER_HOME"
# curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
# tar -C "$HOME" -xf /tmp/flutter.tar.xz
# fi
# echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
# "$FLUTTER_HOME/bin/flutter" --version
# - name: Install Flutter SDK
# run: |
# set -euo pipefail
# FLUTTER_HOME="$HOME/flutter"
# # Avoid git ownership issues when Flutter checks out deps.
# git config --global --add safe.directory "$FLUTTER_HOME" || true
# if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
# rm -rf "$FLUTTER_HOME"
# curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
# tar -C "$HOME" -xf /tmp/flutter.tar.xz
# fi
# echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
# "$FLUTTER_HOME/bin/flutter" --version
- name: Allow all git directories (CI)
run: git config --global --add safe.directory '*'

View File

@@ -4,6 +4,7 @@ on:
push:
branches:
- master
- dev
env:
JAVA_VERSION: "17"
@@ -12,6 +13,7 @@ env:
BUILD_WINDOWS: "false" # Windows build disabled (no runner available)
GITEA_BASE_URL: https://git.tgj.services
WEB_IMAGE: "git.tgj.services/petegregoryy/mileograph-web"
REGISTRY: git.tgj.services
jobs:
meta:
@@ -79,21 +81,21 @@ jobs:
android-build:
runs-on:
- mileograph
- tgj-arc
needs: meta
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install OS deps (Android)
run: |
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
SUDO=""
fi
$SUDO apt-get update
$SUDO apt-get install -y unzip xz-utils zip libstdc++6 liblzma-dev curl jq
#
# - name: Install OS deps (Android)
# run: |
# if command -v sudo >/dev/null 2>&1; then
# SUDO="sudo"
# else
# SUDO=""
# fi
# $SUDO apt-get update
# $SUDO apt-get install -y unzip xz-utils zip libstdc++6 liblzma-dev curl jq
- name: Setup Java
uses: actions/setup-java@v4
@@ -119,19 +121,19 @@ jobs:
echo "$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_PATH"
echo "$ANDROID_SDK_ROOT/build-tools/33.0.2" >> "$GITHUB_PATH"
- name: Install Flutter SDK
run: |
set -euo pipefail
FLUTTER_HOME="$HOME/flutter"
# Avoid git ownership issues when Flutter checks out deps.
git config --global --add safe.directory "$FLUTTER_HOME" || true
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
tar -C "$HOME" -xf /tmp/flutter.tar.xz
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
"$FLUTTER_HOME/bin/flutter" --version
# - name: Install Flutter SDK
# run: |
# set -euo pipefail
# FLUTTER_HOME="$HOME/flutter"
# # Avoid git ownership issues when Flutter checks out deps.
# git config --global --add safe.directory "$FLUTTER_HOME" || true
# if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
# rm -rf "$FLUTTER_HOME"
# curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
# tar -C "$HOME" -xf /tmp/flutter.tar.xz
# fi
# echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
# "$FLUTTER_HOME/bin/flutter" --version
- name: Allow all git directories (CI)
run: git config --global --add safe.directory '*'
@@ -220,36 +222,12 @@ jobs:
linux-build:
runs-on:
- mileograph
- tgj-arc
needs: meta
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install OS deps (Linux desktop)
run: |
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
SUDO=""
fi
$SUDO apt-get update
$SUDO apt-get install -y unzip xz-utils zip libstdc++6 libglu1-mesa clang cmake ninja-build pkg-config libgtk-3-dev libsecret-1-dev liblzma-dev curl jq
- name: Install Flutter SDK
run: |
set -euo pipefail
FLUTTER_HOME="$HOME/flutter"
# Avoid git ownership issues when Flutter checks out deps.
git config --global --add safe.directory "$FLUTTER_HOME" || true
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
tar -C "$HOME" -xf /tmp/flutter.tar.xz
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
"$FLUTTER_HOME/bin/flutter" --version
- name: Allow all git directories (CI)
run: git config --global --add safe.directory '*'
@@ -275,38 +253,12 @@ jobs:
web-build:
runs-on:
- mileograph
- tgj-arc
needs: meta
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install OS deps (Web)
run: |
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
SUDO=""
fi
$SUDO apt-get update
$SUDO apt-get install -y unzip xz-utils zip libstdc++6 liblzma-dev curl jq docker.io
if ! docker info >/dev/null 2>&1; then
$SUDO systemctl start docker 2>/dev/null || $SUDO service docker start 2>/dev/null || true
fi
- name: Install Flutter SDK
run: |
set -euo pipefail
FLUTTER_HOME="$HOME/flutter"
git config --global --add safe.directory "$FLUTTER_HOME" || true
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
curl -fsSL -o /tmp/flutter.tar.xz "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz"
tar -C "$HOME" -xf /tmp/flutter.tar.xz
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
"$FLUTTER_HOME/bin/flutter" --version
- name: Allow all git directories (CI)
run: git config --global --add safe.directory '*'
@@ -330,50 +282,38 @@ jobs:
name: web-build
path: app-web.tar.gz
- name: Compute web image tags
id: web_meta
env:
BASE_VERSION: ${{ needs.meta.outputs.base_version }}
DEV_SUFFIX: ${{ needs.meta.outputs.dev_suffix }}
run: |
IMAGE="${WEB_IMAGE}"
TAG=""
ALIAS=""
if [ "${GITHUB_REF}" = "refs/heads/dev" ]; then
TAG="${BASE_VERSION}${DEV_SUFFIX}"
ALIAS="dev"
elif [ "${GITHUB_REF}" = "refs/heads/master" ]; then
TAG="${BASE_VERSION}"
ALIAS="latest"
fi
- name: Set up docker buildx
uses: docker/setup-buildx-action@v3
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "alias=${ALIAS}" >> "$GITHUB_OUTPUT"
- name: Docker meta
id: dmeta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/petegregoryy/railframe-web
flavor: latest=false
tags: |
type=sha,prefix=
type=raw,value=${{ needs.meta.outputs.base_version }}${{ needs.meta.outputs.dev_suffix }}
type=raw,value=dev
- name: Login to registry
if: ${{ secrets.DOCKERHUB_TOKEN != '' && steps.web_meta.outputs.tag != '' }}
env:
REGISTRY_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
echo "$REGISTRY_TOKEN" | docker login git.tgj.services -u petegregoryy --password-stdin
- name: Login to the docker registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: petegregoryy
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push web image
if: ${{ secrets.DOCKERHUB_TOKEN != '' && steps.web_meta.outputs.tag != '' }}
env:
IMAGE: ${{ steps.web_meta.outputs.image }}
TAG: ${{ steps.web_meta.outputs.tag }}
ALIAS: ${{ steps.web_meta.outputs.alias }}
run: |
docker buildx create --name buildx --driver=docker-container --use || docker buildx use buildx
TAG_ARGS=(-t "${IMAGE}:${TAG}")
if [ -n "$ALIAS" ]; then
TAG_ARGS+=(-t "${IMAGE}:${ALIAS}")
fi
docker buildx build --builder buildx --platform linux/amd64 \
-f Dockerfile.web \
--push \
"${TAG_ARGS[@]}" .
- name: Build and push
id: docker_build
uses: docker/build-push-action@v6
with:
file: Dockerfile.web
context: .
push: true
tags: ${{ steps.dmeta.outputs.tags }}
labels: ${{ steps.dmeta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/petegregoryy/railframe-web:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/petegregoryy/railframe-web:buildcache,mode=max
release-dev:
runs-on:

View File

@@ -234,34 +234,62 @@ class _LegCardState extends State<LegCard> {
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (leg.notes.isNotEmpty) ...[
Text('Notes', style: textTheme.titleSmall),
const SizedBox(height: 4),
Text(leg.notes),
const SizedBox(height: 12),
],
if (leg.locos.isNotEmpty) ...[
Text('Locos', style: textTheme.titleSmall),
const SizedBox(height: 6),
Wrap(
spacing: 8,
children: [
if (leg.notes.isNotEmpty) ...[
Text('Notes', style: textTheme.titleSmall),
const SizedBox(height: 4),
Text(leg.notes),
const SizedBox(height: 12),
],
if (leg.locos.isNotEmpty) ...[
Text('Locos', style: textTheme.titleSmall),
const SizedBox(height: 6),
Wrap(
spacing: 8,
runSpacing: 8,
children: _buildLocoChips(context, leg),
),
const SizedBox(height: 12),
],
if (_hasTrainDetails(leg)) ...[
Text('Train', style: textTheme.titleSmall),
const SizedBox(height: 6),
..._buildTrainDetails(leg, textTheme),
const SizedBox(height: 12),
],
if (routeSegments.isNotEmpty) ...[
Text('Route', style: textTheme.titleSmall),
const SizedBox(height: 6),
_buildRouteList(routeSegments),
],
if (_hasTrainDetails(leg)) ...[
Text('Train', style: textTheme.titleSmall),
const SizedBox(height: 6),
..._buildTrainDetails(leg, textTheme),
const SizedBox(height: 12),
],
if (sharedFrom != null)
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
'Shared from ${sharedFrom.sharedFromDisplay.isNotEmpty ? sharedFrom.sharedFromDisplay : 'another user'}.',
style: textTheme.bodyMedium,
),
),
if (sharedTo.isNotEmpty) ...[
Text(
'Shared to:',
style: textTheme.bodyMedium,
),
const SizedBox(height: 4),
Wrap(
spacing: 8,
runSpacing: 4,
children: sharedTo
.map((s) => Chip(
label: Text(s.sharedToDisplay.isNotEmpty
? s.sharedToDisplay
: s.sharedToUserId),
visualDensity: VisualDensity.compact,
))
.toList(),
),
const SizedBox(height: 12),
],
if (routeSegments.isNotEmpty) ...[
Text('Route', style: textTheme.titleSmall),
const SizedBox(height: 6),
_buildRouteList(routeSegments),
],
],
),
),

View File

@@ -324,6 +324,14 @@ extension _NewEntryDraftLogic on _NewEntryPageState {
"distance": _routeResult!.distance,
},
"tractionItems": _serializeTractionItems(),
"shareUserIds": _shareUserIds.toList(),
"shareUsers": _shareUsers
.map((u) => {
"user_id": u.userId,
"username": u.username,
"full_name": u.fullName,
})
.toList(),
};
}

View File

@@ -707,7 +707,7 @@ class _NewEntryPageState extends State<NewEntryPage> {
final enabled = value ?? false;
setState(() {
_matchDestinationToEntry = enabled;
if (enabled) _hasDestinationTime = true;
if (enabled && _hasEndTime) _hasDestinationTime = true;
});
_scheduleMatchUpdate();
_saveDraft();
@@ -1127,7 +1127,7 @@ class _NewEntryPageState extends State<NewEntryPage> {
if (_destinationController.text != endVal) {
_destinationController.text = endVal;
}
if (_hasDestinationTime) {
if (_hasDestinationTime && _hasEndTime) {
final endTime = _legEndDateTime ?? _legDateTime;
_selectedDestinationDate = DateTime(
endTime.year,
@@ -1283,9 +1283,9 @@ class _NewEntryPageState extends State<NewEntryPage> {
const SizedBox(height: 8),
_buildSharedBanner(),
],
_buildTripSelector(context),
const SizedBox(height: 12),
if (_activeLegShare == null) _buildShareSection(context),
_buildTripSelector(context),
const SizedBox(height: 8),
if (_activeLegShare == null) _buildShareSection(context),
_dateTimeGroup(
context,
title: 'Departure time',
@@ -1366,6 +1366,7 @@ class _NewEntryPageState extends State<NewEntryPage> {
border: OutlineInputBorder(),
),
),
const Divider(height: 24),
_trainLocationBlock(
label: 'Origin',
controller: _originController,
@@ -1384,6 +1385,7 @@ class _NewEntryPageState extends State<NewEntryPage> {
singleColumn: true,
),
),
const Divider(height: 24),
_trainLocationBlock(
label: 'Destination',
controller: _destinationController,
@@ -1403,6 +1405,7 @@ class _NewEntryPageState extends State<NewEntryPage> {
singleColumn: true,
),
),
const Divider(height: 24),
TextFormField(
controller: _networkController,
textCapitalization: TextCapitalization.characters,

View File

@@ -7,6 +7,7 @@ import 'package:mileograph_flutter/components/traction/traction_card.dart';
import 'package:mileograph_flutter/objects/objects.dart';
import 'package:mileograph_flutter/services/data_service.dart';
import 'package:mileograph_flutter/services/distance_unit_service.dart';
import 'package:mileograph_flutter/services/authservice.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';

View File

@@ -569,6 +569,7 @@ class _TractionPageState extends State<TractionPage> {
}
Widget _buildHeaderActions(BuildContext context, bool isMobile) {
final isElevated = context.read<AuthService>().isElevated;
final refreshButton = IconButton(
tooltip: 'Refresh',
onPressed: _refreshTraction,
@@ -587,32 +588,34 @@ class _TractionPageState extends State<TractionPage> {
),
);
final newTractionButton = FilledButton.icon(
onPressed: () async {
final createdClass = await context.push<String>(
'/traction/new',
);
if (!mounted) return;
if (createdClass != null && createdClass.isNotEmpty) {
_classController.text = createdClass;
_selectedClass = createdClass;
_refreshTraction();
} else if (createdClass == '') {
_refreshTraction();
}
},
icon: const Icon(Icons.add),
label: const Text('New Traction'),
);
final newTractionButton = !isElevated
? null
: FilledButton.icon(
onPressed: () async {
final createdClass = await context.push<String>(
'/traction/new',
);
if (!mounted) return;
if (createdClass != null && createdClass.isNotEmpty) {
_classController.text = createdClass;
_selectedClass = createdClass;
_refreshTraction();
} else if (createdClass == '') {
_refreshTraction();
}
},
icon: const Icon(Icons.add),
label: const Text('New Traction'),
);
final desktopActions = [
refreshButton,
if (classStatsButton != null) classStatsButton,
newTractionButton,
if (newTractionButton != null) newTractionButton,
];
final mobileActions = [
newTractionButton,
if (newTractionButton != null) newTractionButton,
if (classStatsButton != null) classStatsButton,
refreshButton,
];

View File

@@ -350,6 +350,7 @@ class HomepageStats {
final List<YearlyMileage> yearlyMileage;
final List<LocoSummary> topLocos;
final List<LeaderboardEntry> leaderboard;
final List<LeaderboardEntry> friendsLeaderboard;
final List<TripSummary> trips;
final int legCount;
final UserData? user;
@@ -359,6 +360,7 @@ class HomepageStats {
required this.yearlyMileage,
required this.topLocos,
required this.leaderboard,
required this.friendsLeaderboard,
required this.trips,
required this.legCount,
this.user,
@@ -370,6 +372,17 @@ class HomepageStats {
final totalMileage = mileageData is Map && mileageData['mileage'] != null
? (mileageData['mileage'] as num).toDouble()
: 0.0;
List<LeaderboardEntry> parseLeaderboard(dynamic raw) {
if (raw is List) {
return raw
.whereType<Map>()
.map((e) => LeaderboardEntry.fromJson(
e.map((k, v) => MapEntry(k.toString(), v)),
))
.toList();
}
return const [];
}
return HomepageStats(
totalMileage: totalMileage,
yearlyMileage: (json['yearly_mileage'] as List? ?? [])
@@ -378,9 +391,9 @@ class HomepageStats {
topLocos: (json['top_locos'] as List? ?? [])
.map((e) => LocoSummary.fromJson(e))
.toList(),
leaderboard: (json['leaderboard_data'] as List? ?? [])
.map((e) => LeaderboardEntry.fromJson(e))
.toList(),
leaderboard: parseLeaderboard(json['leaderboard_data'] ?? json['leaderboard']),
friendsLeaderboard:
parseLeaderboard(json['friends_leaderboard'] ?? json['friendsLeaderboard']),
trips: (json['trip_data'] as List? ?? [])
.map((e) => TripSummary.fromJson(e))
.toList(),

View File

@@ -169,10 +169,12 @@ class DataService extends ChangeNotifier {
_homepageStats = HomepageStats.fromJson(json);
_trips = [...(_homepageStats?.trips ?? const [])]
..sort(TripSummary.compareByDateDesc);
_friendsLeaderboard = _homepageStats?.friendsLeaderboard ?? [];
} catch (e) {
debugPrint('Failed to fetch homepage stats: $e');
_homepageStats = null;
_trips = [];
_friendsLeaderboard = [];
} finally {
_isHomepageLoading = false;
_notifyAsync();

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.6.2+5
version: 0.6.3+6
environment:
sdk: ^3.8.1