The Android app updates itself from our Gitea releases; feed cards fade in instead of bouncing

- API: GET /app/android/latest reads the latest Gitea release (ANDROID_RELEASES_URL, optional token for a
  private repo) with the APK's SHA-256 from SHA256SUMS; GET /app/android/apk/:name streams that APK.
- App (modules/updater): downloads in the background, checks the SHA-256, and installs through
  PackageInstaller: quietly once when leaving the app (Android 12+), otherwise from the feed banner or
  Settings > App version. F-Droid builds leave it out and drop the install permissions.
- CI: the release notes come from the fastlane changelog; the app shows the part above ---.
- Settings: Create-account and Link Spotify buttons for Last.fm and ListenBrainz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 12:34:04 -04:00
co-authored by Claude Opus 5.5
parent 804493aee0
commit 5cbde5e7f9
29 changed files with 901 additions and 11 deletions
+7 -5
View File
@@ -1,7 +1,7 @@
import { router } from 'expo-router';
import { useEffect, useState } from 'react';
import { Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
import Animated, { FadeInDown, LinearTransition } from 'react-native-reanimated';
import Animated, { FadeIn, LinearTransition } from 'react-native-reanimated';
import { SafeAreaView } from 'react-native-safe-area-context';
import useSWR, { useSWRConfig } from 'swr';
import { sortFeed, type ConnectionDTO, type FeedItemDTO, type FollowListDTO, type ProfileDTO } from '@musicshare/shared';
@@ -10,6 +10,7 @@ import { ActivityBell } from '@/components/ActivityBell';
import { Avatar } from '@/components/Avatar';
import { NowPlayingCard } from '@/components/NowPlayingCard';
import { RelinkReminder } from '@/components/RelinkReminder';
import { UpdateBanner } from '@/components/UpdateBanner';
import { SharingControls } from '@/components/SharingControls';
import { Body, Button, Card, Title } from '@/components/ui';
import { api } from '@/lib/api';
@@ -84,9 +85,9 @@ export default function FeedScreen() {
return (
<SafeAreaView style={styles.safe} edges={['top']}>
<AmbientBackground />
{/* Cards rise in on load and glide to new positions when someone pins or starts playing. */}
{/* Cards fade in and slide to new positions when someone pins or starts playing. */}
<Animated.FlatList
itemLayoutAnimation={LinearTransition.springify().damping(18)}
itemLayoutAnimation={LinearTransition.duration(200)}
data={feed ?? []}
keyExtractor={(item) => item.user.id}
contentContainerStyle={styles.list}
@@ -97,6 +98,7 @@ export default function FeedScreen() {
<Text style={styles.brand}>musicshare</Text>
<ActivityBell />
</View>
<UpdateBanner />
<SharingControls compact />
<RelinkReminder />
{offerAppleMusic && (
@@ -160,8 +162,8 @@ export default function FeedScreen() {
</View>
) : null
}
renderItem={({ item, index }) => (
<Animated.View entering={FadeInDown.delay(Math.min(index, 8) * 60).springify().damping(16)}>
renderItem={({ item }) => (
<Animated.View entering={FadeIn.duration(180)}>
<NowPlayingCard
np={item}
preferred={me?.preferredService}