Merge pull request #47 from anultravioletaurora/9-implement-playlist-crud

9 implement playlist crud
This commit is contained in:
Violet Caulfield
2025-02-04 18:55:43 -06:00
committed by GitHub
18 changed files with 274 additions and 52 deletions

View File

@@ -1,20 +0,0 @@
name: build-ios-app
on:
pull_request:
push:
branches:
- 'main'
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm run init
- run: fastlane match
- run: fastlane beta
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
working-directory: ./ios

28
.github/workflows/build-ios.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: build-ios
on:
push:
branches-ignore:
- "main"
jobs:
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Echo package.json version to Github ENV
run: echo VERISON_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
- run: npm run init
- run: fastlane build
working-directory: ./ios
env:
# FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
APPSTORE_CONNECT_API_KEY_JSON: ${{ secrets.APPSTORE_CONNECT_API_KEY_JSON }}
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_REPO_PAT: "anultravioletaurora:${{ secrets.SIGNING_REPO_PAT }}"

40
.github/workflows/publish-ios-beta.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: publish-ios-beta
on:
pull_request:
push:
branches:
- 'main'
jobs:
publish-ios-beta:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.SIGNING_REPO_PAT }}
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Echo package.json version to Github ENV
run: echo VERISON_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
- run: npm run init
- name: Output App Store Connect API Key JSON to Fastlane
run: echo -e '${{ secrets.APPSTORE_CONNECT_API_KEY_JSON }}' > appstore_connect_api_key.json
working-directory: ./ios/fastlane
- run: fastlane beta
working-directory: ./ios
env:
APPSTORE_CONNECT_API_KEY_JSON: ${{ secrets.APPSTORE_CONNECT_API_KEY_JSON }}
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_REPO_PAT: "anultravioletaurora:${{ secrets.SIGNING_REPO_PAT }}"
# Commit Fastlane Xcode build number increment
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "[skip actions]"
file_pattern: "ios/Jellify.xcodeproj/project.pbxproj"

13
App.tsx
View File

@@ -8,6 +8,7 @@ import { useColorScheme } from 'react-native';
import jellifyConfig from './tamagui.config';
import { clientPersister } from './constants/storage';
import { queryClient } from './constants/query-client';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App(): React.JSX.Element {
@@ -19,11 +20,13 @@ export default function App(): React.JSX.Element {
persistOptions={{
persister: clientPersister
}}>
<TamaguiProvider config={jellifyConfig}>
<Theme name={isDarkMode ? 'dark' : 'light'}>
<Jellify />
</Theme>
</TamaguiProvider>
<GestureHandlerRootView>
<TamaguiProvider config={jellifyConfig}>
<Theme name={isDarkMode ? 'dark' : 'light'}>
<Jellify />
</Theme>
</TamaguiProvider>
</GestureHandlerRootView>
</PersistQueryClientProvider>
);
}

View File

@@ -1,4 +1,4 @@
import Client from "@/api/client";
import Client from "../../../api/client";
import { getPlaylistsApi } from "@jellyfin/sdk/lib/utils/api";

View File

@@ -1,11 +1,26 @@
import { Dirs, FileSystem } from "react-native-file-access";
import Client from "../../../api/client";
import { getLibraryApi } from "@jellyfin/sdk/lib/utils/api";
export async function downloadTrack(itemId: string) : Promise<void> {
// Make sure downloads folder exists, create if it doesn't
if (!(await FileSystem.exists(`${Dirs.DocumentDir}/downloads`)))
await FileSystem.mkdir(`${Dirs.DocumentDir}/downloads`)
getLibraryApi(Client.api!)
.getDownload({
itemId
}, {
'responseType': 'blob'
})
.then(async (response) => {
if (response.status < 300) {
await FileSystem.writeFile(getTrackFilePath(itemId), response.data)
}
})
}
function getTrackFilePath(itemId: string) {
return `${Dirs.DocumentDir}/downloads/${itemId}`
}

View File

@@ -18,7 +18,7 @@ export function ItemCard(props: CardProps) {
const dimensions = props.width && typeof(props.width) === "number" ? { width: props.width, height: props.width } : { width: 150, height: 150 };
const logoDimensions = props.width && typeof(props.width) === "number" ? { width: props.width / 2, height: props.width / 3 }: { width: 100, height: 30 };
const logoDimensions = props.width && typeof(props.width) === "number" ? { width: props.width / 2, height: props.width / 6 }: { width: 100, height: 30 };
return (
<View

View File

@@ -31,7 +31,7 @@ export default function PlayerScreen({ navigation }: { navigation: NativeStackNa
} = usePlayerContext();
const [seeking, setSeeking] = useState<boolean>(false);
const [progressState, setProgressState] = useState<number>(progress!.position);
const [progressState, setProgressState] = useState<number>(progress?.position ?? 0);
const { width } = useSafeAreaFrame();
@@ -45,7 +45,7 @@ export default function PlayerScreen({ navigation }: { navigation: NativeStackNa
useEffect(() => {
if (!seeking)
setProgressState(Math.round(progress!.position))
setProgressState(Math.round(progress?.position ?? 0))
}, [
progress
]);
@@ -147,7 +147,7 @@ export default function PlayerScreen({ navigation }: { navigation: NativeStackNa
{/* playback progress goes here */}
<HorizontalSlider
value={progressState}
max={progress!.duration}
max={progress?.duration ?? 1}
width={width / 1.1}
props={{
// If user swipes off of the slider we should seek to the spot
@@ -187,7 +187,7 @@ export default function PlayerScreen({ navigation }: { navigation: NativeStackNa
</XStack>
<XStack flex={1} justifyContent="flex-end">
<RunTimeSeconds>{progress!.duration}</RunTimeSeconds>
<RunTimeSeconds>{progress?.duration ?? 0}</RunTimeSeconds>
</XStack>
</XStack>

View File

@@ -30,6 +30,7 @@ export default function Queue({ navigation }: { navigation: NativeStackNavigatio
<DraggableFlatList
contentInsetAdjustmentBehavior="automatic"
data={queue}
dragHitSlop={{ left: -50 }} // https://github.com/computerjazz/react-native-draggable-flatlist/issues/336
extraData={nowPlaying}
// enableLayoutAnimationExperimental
getItemLayout={(data, index) => (

View File

@@ -1,13 +1,16 @@
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { StackParamList } from "../types";
import { XStack, YStack } from "tamagui";
import { getTokens, XStack, YStack } from "tamagui";
import { useItemTracks } from "../../api/queries/tracks";
import { RunTimeTicks } from "../Global/helpers/time-codes";
import { H4, H5, Text } from "../Global/helpers/text";
import Track from "../Global/components/track";
import { FlatList } from "react-native";
import BlurhashedImage from "../Global/components/blurhashed-image";
import DraggableFlatList from "react-native-draggable-flatlist";
import { reorderPlaylist } from "../../api/mutations/functions/playlists";
import { useState } from "react";
import Icon from "../Global/helpers/icon";
interface PlaylistProps {
playlist: BaseItemDto;
@@ -19,13 +22,33 @@ export default function Playlist({
navigation
}: PlaylistProps): React.JSX.Element {
const { data: tracks, isLoading } = useItemTracks(playlist.Id!);
const [editing, setEditing] = useState<boolean>(false);
const { data: tracks, isLoading, refetch } = useItemTracks(playlist.Id!);
navigation.setOptions({
headerRight: () => {
return (
<Icon
color={editing
? getTokens().color.telemagenta.val
: getTokens().color.white.val
}
name={editing ? 'check' : 'pencil'}
onPress={() => setEditing(!editing)}
/>
)
}
})
return (
<FlatList
<DraggableFlatList
contentInsetAdjustmentBehavior="automatic"
data={tracks}
ListHeaderComponent={() => (
data={tracks ?? []}
dragHitSlop={{ left: -50 }} // https://github.com/computerjazz/react-native-draggable-flatlist/issues/336
keyExtractor={({ Id }, index) => {
return `${index}-${Id}`
}}
ListHeaderComponent={(
<YStack alignItems="center">
<BlurhashedImage
item={playlist}
@@ -37,7 +60,13 @@ export default function Playlist({
</YStack>
)}
numColumns={1}
renderItem={({ item: track, index }) => {
onDragEnd={({ data, from, to }) => {
reorderPlaylist(playlist.Id!, data[to].Id!, to)
refetch();
}}
renderItem={({ item: track, getIndex, drag }) => {
const index = getIndex();
return (
<Track
@@ -47,10 +76,11 @@ export default function Playlist({
index={index}
queueName={playlist.Name ?? "Untitled Playlist"}
showArtwork
onLongPress={editing ? drag : undefined}
/>
)
}}
ListFooterComponent={() => (
ListFooterComponent={(
<XStack justifyContent="flex-end">
<Text
color={"$borderColor"}

View File

@@ -602,8 +602,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Jellify/Jellify.entitlements;
CURRENT_PROJECT_VERSION = 3;
CODE_SIGN_IDENTITY = "Apple Development: Jack Caulfield (66Z9J9NX2X)";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development: Jack Caulfield (66Z9J9NX2X)";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Jellify/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
@@ -611,16 +615,17 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = "1.0 ";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
OTHER_SWIFT_FLAGS = "$(inherited)";
"OTHER_SWIFT_FLAGS[arch=*]" = "DEBUG FB_SONARKIT_ENABLED";
PRODUCT_BUNDLE_IDENTIFIER = com.cosmonautical.jellify;
PRODUCT_NAME = Jellify;
PROVISIONING_PROFILE_SPECIFIER = "match Development com.cosmonautical.jellify";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.cosmonautical.jellify";
SWIFT_OBJC_BRIDGING_HEADER = "Jellify-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -635,15 +640,19 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Jellify/Jellify.entitlements;
CURRENT_PROJECT_VERSION = 3;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = WAH9CZ8BPG;
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
INFOPLIST_FILE = Jellify/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = "1.0 ";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -652,6 +661,8 @@
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = com.cosmonautical.jellify;
PRODUCT_NAME = Jellify;
PROVISIONING_PROFILE_SPECIFIER = "match Development com.cosmonautical.jellify";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.cosmonautical.jellify";
SWIFT_OBJC_BRIDGING_HEADER = "Jellify-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "Jellify.app"
BlueprintName = "Jellify"
ReferencedContainer = "container:Jellify.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "Jellify.app"
BlueprintName = "Jellify"
ReferencedContainer = "container:Jellify.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "Jellify.app"
BlueprintName = "Jellify"
ReferencedContainer = "container:Jellify.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -41,7 +41,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View File

@@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>3</string>
<string>7</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>3</string>
<string>7</string>
</dict>
</plist>

View File

@@ -17,9 +17,43 @@ default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :build do
setup_ci
match(
type: "appstore",
app_identifier: "com.cosmonautical.jellify",
readonly: true
)
build_app(
scheme: "Jellify",
workspace: "Jellify.xcworkspace"
)
end
lane :beta do
increment_build_number(xcodeproj: "Jellify.xcodeproj")
build_app(workspace: "Jellify.xcworkspace", scheme: "Jellify")
upload_to_testflight
setup_ci
match(
type: "appstore",
app_identifier: "com.cosmonautical.jellify",
readonly: true
)
increment_version_number(
version_number: ENV['VERISON_NUMBER'],
xcodeproj: "Jellify.xcodeproj"
)
increment_build_number(
xcodeproj: "Jellify.xcodeproj"
)
build_app(
scheme: "Jellify - Release",
workspace: "Jellify.xcworkspace",
)
# http://docs.fastlane.tools/actions/upload_to_testflight/#upload_to_testflight
upload_to_testflight(
api_key_path: "fastlane/appstore_connect_api_key.json"
)
end
end

View File

@@ -1,4 +1,4 @@
git_url("git@github.com:anultravioletaurora/jellify-signing.git")
git_url("https://github.com/anultravioletaurora/jellify-signing.git")
storage_mode("git")
@@ -11,3 +11,5 @@ username("violet@cosmonautical.cloud") # Your Apple Developer Portal username
# Remove the # in the beginning of the line to enable the other options
# The docs are available on https://docs.fastlane.tools/actions/match
git_basic_authorization(Base64.strict_encode64(ENV["MATCH_REPO_PAT"]))

View File

@@ -1,6 +1,6 @@
{
"name": "jellify",
"version": "0.1.5",
"version": "0.1.7",
"private": true,
"scripts": {
"init": "npm i && npm run pod:install",