mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-09 15:38:39 +01:00
Compare commits
5 Commits
fix/save-a
...
fix/show-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1ea2830a8 | ||
|
|
64105a8bac | ||
|
|
4d0a571ad0 | ||
|
|
02ffac167b | ||
|
|
4eb734c99f |
14
.github/workflows/build-apps.yml
vendored
14
.github/workflows/build-apps.yml
vendored
@@ -11,6 +11,12 @@ on:
|
||||
push:
|
||||
branches: [develop, master]
|
||||
|
||||
# Exposed to `expo prebuild` (app.config.js → extra.build) so Settings can show the
|
||||
# branch + commit a CI build was made from. EAS cloud builds use EAS_BUILD_* instead.
|
||||
env:
|
||||
EXPO_PUBLIC_GIT_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
EXPO_PUBLIC_GIT_COMMIT: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
build-android-phone:
|
||||
if: (!contains(github.event.head_commit.message, '[skip ci]'))
|
||||
@@ -219,7 +225,7 @@ jobs:
|
||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
||||
with:
|
||||
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
||||
xcode-version: "26.4"
|
||||
xcode-version: "26.5"
|
||||
|
||||
- name: 🏗️ Setup EAS
|
||||
uses: expo/expo-github-action@b184ff86a3c926240f1b6db41764c83a01c02eef # main
|
||||
@@ -284,7 +290,7 @@ jobs:
|
||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
||||
with:
|
||||
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
||||
xcode-version: "26.4"
|
||||
xcode-version: "26.5"
|
||||
|
||||
- name: 🚀 Build iOS app
|
||||
env:
|
||||
@@ -344,7 +350,7 @@ jobs:
|
||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
||||
with:
|
||||
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
||||
xcode-version: "26.4"
|
||||
xcode-version: "26.5"
|
||||
|
||||
- name: 🏗️ Setup EAS
|
||||
uses: expo/expo-github-action@b184ff86a3c926240f1b6db41764c83a01c02eef # main
|
||||
@@ -412,7 +418,7 @@ jobs:
|
||||
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1
|
||||
with:
|
||||
# renovate: datasource=custom.xcode depName=xcode versioning=loose
|
||||
xcode-version: "26.4"
|
||||
xcode-version: "26.5"
|
||||
|
||||
- name: 🚀 Build iOS app
|
||||
env:
|
||||
|
||||
@@ -1,3 +1,41 @@
|
||||
const { execFileSync } = require("node:child_process");
|
||||
|
||||
// Build metadata, injected into `extra.build` and read at runtime via
|
||||
// expo-constants (see utils/version.ts). Sources in priority order:
|
||||
// EAS cloud build → GitHub Actions → explicit EXPO_PUBLIC_* → local git → null.
|
||||
const git = (args) => {
|
||||
try {
|
||||
return execFileSync("git", args, { stdio: ["ignore", "pipe", "ignore"] })
|
||||
.toString()
|
||||
.trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const buildMeta = {
|
||||
commit:
|
||||
(
|
||||
process.env.EAS_BUILD_GIT_COMMIT_HASH ||
|
||||
process.env.GITHUB_SHA ||
|
||||
process.env.EXPO_PUBLIC_GIT_COMMIT ||
|
||||
git(["rev-parse", "HEAD"]) ||
|
||||
""
|
||||
).slice(0, 7) || null,
|
||||
branch:
|
||||
process.env.EAS_BUILD_GIT_BRANCH ||
|
||||
process.env.GITHUB_HEAD_REF ||
|
||||
process.env.GITHUB_REF_NAME ||
|
||||
process.env.EXPO_PUBLIC_GIT_BRANCH ||
|
||||
git(["rev-parse", "--abbrev-ref", "HEAD"]) ||
|
||||
null,
|
||||
profile:
|
||||
process.env.EAS_BUILD_PROFILE ||
|
||||
process.env.EXPO_PUBLIC_BUILD_PROFILE ||
|
||||
null,
|
||||
builtAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
module.exports = ({ config }) => {
|
||||
if (process.env.EXPO_TV !== "1") {
|
||||
config.plugins.push("expo-background-task");
|
||||
@@ -22,6 +60,8 @@ module.exports = ({ config }) => {
|
||||
androidConfig.googleServicesFile = process.env.GOOGLE_SERVICES_JSON;
|
||||
}
|
||||
|
||||
config.extra = { ...config.extra, build: buildMeta };
|
||||
|
||||
return {
|
||||
...(Object.keys(androidConfig).length > 0 && { android: androidConfig }),
|
||||
...config,
|
||||
|
||||
6
bun.lock
6
bun.lock
@@ -108,7 +108,7 @@
|
||||
"@types/react": "~19.2.10",
|
||||
"@types/react-test-renderer": "19.1.0",
|
||||
"cross-env": "10.1.0",
|
||||
"expo-doctor": "1.19.7",
|
||||
"expo-doctor": "1.19.9",
|
||||
"husky": "9.1.7",
|
||||
"lint-staged": "17.0.5",
|
||||
"react-test-renderer": "19.2.3",
|
||||
@@ -969,7 +969,7 @@
|
||||
|
||||
"expo-device": ["expo-device@56.0.4", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-ucVcGPkvBrl2QHuy7XcYex2Y6BETvJ6TREutZrwLGUDnlvbpKS8KfQoNZOpvkyo5Nmm9RrasYQ0CrXmBHho2mg=="],
|
||||
|
||||
"expo-doctor": ["expo-doctor@1.19.7", "", { "bin": { "expo-doctor": "bin/expo-doctor.js" } }, "sha512-pzn7QtCifRlvGIQz8k7kszeYFaI5Yn81WTHlk/20tmd3jwnXxPjlcdyhFSkuRtO2v4a9gA/6aUWVBOosfffj9w=="],
|
||||
"expo-doctor": ["expo-doctor@1.19.9", "", { "bin": { "expo-doctor": "bin/expo-doctor.js" } }, "sha512-SJW5HxEDQ9f5QdFvrUwfbdJZ4HI0EAAxsrJqrHBFjKBum+uSOcEIZPLRibwNQLTHOwTO1TWNLiMlF9sDUBWeYw=="],
|
||||
|
||||
"expo-file-system": ["expo-file-system@56.0.7", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-dcKzo8ShPloM7jgfnMcJStgQebhP8owVjCkNI/aX6NMFV1CYB8bxKGMdnzJ3mXk5nfaiW+F/lSKr2UIJ02WAUA=="],
|
||||
|
||||
@@ -1599,7 +1599,7 @@
|
||||
|
||||
"react-native-text-ticker": ["react-native-text-ticker@1.15.0", "", {}, "sha512-d/uK+PIOhsYMy1r8h825iq/nADiHsabz3WMbRJSnkpQYn+K9aykUAXRRhu8ZbTAzk4CgnUWajJEFxS5ZDygsdg=="],
|
||||
|
||||
"react-native-track-player": ["react-native-track-player@github:lovegaoshi/react-native-track-player#33a3ecd", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-windows": "*", "shaka-player": "^4.7.9" }, "optionalPeers": ["react-native-windows", "shaka-player"] }, "lovegaoshi-react-native-track-player-33a3ecd"],
|
||||
"react-native-track-player": ["react-native-track-player@github:lovegaoshi/react-native-track-player#33a3ecd", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-windows": "*", "shaka-player": "^4.7.9" }, "optionalPeers": ["react-native-windows", "shaka-player"] }, "lovegaoshi-react-native-track-player-33a3ecd", "sha512-vfkld2jUj7EPkAjIc/Vbx4Q4MtOOLmYtCYCE2dWJsyLnPqgj1f0xVzBxbeVP7dfT+eSh4KIXfdxESXaHgrXIlw=="],
|
||||
|
||||
"react-native-udp": ["react-native-udp@4.1.7", "", { "dependencies": { "buffer": "^5.6.0", "events": "^3.1.0" } }, "sha512-NUE3zewu61NCdSsLlj+l0ad6qojcVEZPT4hVG/x6DU9U4iCzwtfZSASh9vm7teAcVzLkdD+cO3411LHshAi/wA=="],
|
||||
|
||||
|
||||
@@ -69,23 +69,17 @@ export const SaveAccountModal: React.FC<SaveAccountModalProps> = ({
|
||||
[isAndroid],
|
||||
);
|
||||
|
||||
const isPresentedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
bottomSheetModalRef.current?.present();
|
||||
} else if (isPresentedRef.current) {
|
||||
} else {
|
||||
bottomSheetModalRef.current?.dismiss();
|
||||
isPresentedRef.current = false;
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const handleSheetChanges = useCallback(
|
||||
(index: number) => {
|
||||
if (index >= 0) {
|
||||
isPresentedRef.current = true;
|
||||
} else if (index === -1 && isPresentedRef.current) {
|
||||
isPresentedRef.current = false;
|
||||
if (index === -1) {
|
||||
resetState();
|
||||
onClose();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as Application from "expo-application";
|
||||
import { useAtom } from "jotai";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { View, type ViewProps } from "react-native";
|
||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
||||
import { getVersionInfo } from "@/utils/version";
|
||||
import { ListGroup } from "../list/ListGroup";
|
||||
import { ListItem } from "../list/ListItem";
|
||||
|
||||
@@ -13,10 +13,9 @@ export const UserInfo: React.FC<Props> = ({ ...props }) => {
|
||||
const [user] = useAtom(userAtom);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const version =
|
||||
Application?.nativeApplicationVersion ||
|
||||
Application?.nativeBuildVersion ||
|
||||
"N/A";
|
||||
// Graduated build identifier — see utils/version.ts:
|
||||
// dev → "0.54.1 · branch · commit", develop/CI → "0.54.1 · commit", production → "0.54.1 (42)".
|
||||
const { display: version } = getVersionInfo();
|
||||
|
||||
return (
|
||||
<View {...props}>
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
"@types/react": "~19.2.10",
|
||||
"@types/react-test-renderer": "19.1.0",
|
||||
"cross-env": "10.1.0",
|
||||
"expo-doctor": "1.19.7",
|
||||
"expo-doctor": "1.19.9",
|
||||
"husky": "9.1.7",
|
||||
"lint-staged": "17.0.5",
|
||||
"react-test-renderer": "19.2.3",
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
} from "@/utils/secureCredentials";
|
||||
import { store } from "@/utils/store";
|
||||
import { clearTVDiscoverySafely } from "@/utils/tvDiscovery/sync";
|
||||
import { APP_VERSION } from "@/utils/version";
|
||||
|
||||
interface Server {
|
||||
address: string;
|
||||
@@ -53,7 +54,7 @@ const initialApi = (() => {
|
||||
const id = getOrSetDeviceId();
|
||||
const deviceName = getDeviceNameSync();
|
||||
const jellyfinInstance = new Jellyfin({
|
||||
clientInfo: { name: "Streamyfin", version: "0.54.1" },
|
||||
clientInfo: { name: "Streamyfin", version: APP_VERSION },
|
||||
deviceInfo: {
|
||||
name: deviceName,
|
||||
id,
|
||||
@@ -135,7 +136,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
||||
const id = getOrSetDeviceId();
|
||||
const deviceName = getDeviceNameSync();
|
||||
return new Jellyfin({
|
||||
clientInfo: { name: "Streamyfin", version: "0.54.1" },
|
||||
clientInfo: { name: "Streamyfin", version: APP_VERSION },
|
||||
deviceInfo: {
|
||||
name: deviceName,
|
||||
id,
|
||||
@@ -169,7 +170,7 @@ export const JellyfinProvider: React.FC<{ children: ReactNode }> = ({
|
||||
return {
|
||||
authorization: `MediaBrowser Client="Streamyfin", Device=${
|
||||
Platform.OS === "android" ? "Android" : "iOS"
|
||||
}, DeviceId="${deviceId}", Version="0.54.1"`,
|
||||
}, DeviceId="${deviceId}", Version="${APP_VERSION}"`,
|
||||
};
|
||||
}, [deviceId]);
|
||||
|
||||
|
||||
87
utils/version.ts
Normal file
87
utils/version.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as Application from "expo-application";
|
||||
import Constants from "expo-constants";
|
||||
|
||||
/** Raw marketing version (app.json `version`), e.g. "0.54.1". Exposed so the Jellyfin
|
||||
* clientInfo auto-tracks the app version instead of a hardcoded string. */
|
||||
export const APP_VERSION = Application.nativeApplicationVersion ?? "unknown";
|
||||
|
||||
/** Build metadata injected at build time by `app.config.js` into `extra.build`. */
|
||||
export interface BuildMeta {
|
||||
commit?: string | null;
|
||||
branch?: string | null;
|
||||
profile?: string | null;
|
||||
builtAt?: string | null;
|
||||
}
|
||||
|
||||
export interface VersionInfo {
|
||||
/** Marketing version (CFBundleShortVersionString / android versionName), e.g. "0.54.1". */
|
||||
version: string | null;
|
||||
/** Build number (CFBundleVersion / versionCode), e.g. "42". */
|
||||
build: string | null;
|
||||
/** Short git commit the build was made from, e.g. "a1b2c3d". */
|
||||
commit: string | null;
|
||||
/** Git branch the build was made from, e.g. "develop". */
|
||||
branch: string | null;
|
||||
/** EAS build profile, e.g. "production", "preview", or null for local. */
|
||||
profile: string | null;
|
||||
isDev: boolean;
|
||||
isProduction: boolean;
|
||||
/** Graduated label for the Settings "App version" row (see tiering below). */
|
||||
display: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a graduated version string for Settings.
|
||||
*
|
||||
* Tiering (most → least detailed):
|
||||
* - dev / local build → `version · branch · commit` (full context for debugging)
|
||||
* - develop / CI / preview → `version · commit` (pin the exact source)
|
||||
* - production (store / TestFlight) → `version (build)` (store-correlatable; the
|
||||
* build number lets TestFlight reports pin a build whose version isn't a
|
||||
* published release. Note: TestFlight and the public App Store ship the same
|
||||
* binary — telling them apart needs a runtime iOS receipt check, intentionally
|
||||
* not done here.)
|
||||
*/
|
||||
export function getVersionInfo(): VersionInfo {
|
||||
// Read native/config values defensively — a version string must never crash Settings
|
||||
// (e.g. a dev build whose native expo-constants is out of sync with the JS).
|
||||
const read = <T>(fn: () => T): T | null => {
|
||||
try {
|
||||
return fn() ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const version = read(() => Application.nativeApplicationVersion);
|
||||
const build = read(() => Application.nativeBuildVersion);
|
||||
const meta = (read(() => Constants.expoConfig?.extra?.build) ??
|
||||
{}) as BuildMeta;
|
||||
const commit = meta.commit ?? null;
|
||||
const branch = meta.branch ?? null;
|
||||
const profile = meta.profile ?? null;
|
||||
const isDev = __DEV__ === true;
|
||||
const isProduction =
|
||||
typeof profile === "string" && profile.startsWith("production");
|
||||
|
||||
let display: string;
|
||||
if (isDev) {
|
||||
display = [version ?? "dev", branch, commit].filter(Boolean).join(" · ");
|
||||
} else if (isProduction) {
|
||||
display =
|
||||
version && build ? `${version} (${build})` : (version ?? build ?? "N/A");
|
||||
} else {
|
||||
display = [version, commit].filter(Boolean).join(" · ") || version || "N/A";
|
||||
}
|
||||
|
||||
return {
|
||||
version,
|
||||
build,
|
||||
commit,
|
||||
branch,
|
||||
profile,
|
||||
isDev,
|
||||
isProduction,
|
||||
display,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user