mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 09:03:25 +00:00
Merge pull request #8 from castdrian/feat-tmdb
feat: tmdb package & github action builds
This commit is contained in:
95
.github/workflows/build-mobile.yml
vendored
Normal file
95
.github/workflows/build-mobile.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
name: build mobile app
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, ready_for_review]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-android:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 21
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Build Android app
|
||||||
|
run: cd apps/expo && pnpm run apk
|
||||||
|
|
||||||
|
- name: Rename apk
|
||||||
|
run: cd apps/expo && mv android/app/build/outputs/apk/release/app-release.apk android/app/build/outputs/apk/release/movie-web.apk
|
||||||
|
|
||||||
|
- name: Upload movie-web.apk as artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: apk
|
||||||
|
path: ./apps/expo/android/app/build/outputs/apk/release/movie-web.apk
|
||||||
|
|
||||||
|
build-ios:
|
||||||
|
runs-on: macos-14
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Xcode Select Version
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: '15.1.0'
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 21
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Build iOS app
|
||||||
|
run: cd apps/expo && pnpm run ipa
|
||||||
|
|
||||||
|
- name: Export .ipa from .app
|
||||||
|
run: |
|
||||||
|
cd apps/expo
|
||||||
|
mkdir -p ios/build/Build/Products/Release-iphoneos/Payload
|
||||||
|
mv ios/build/Build/Products/Release-iphoneos/movieweb.app ios/build/Build/Products/Release-iphoneos/Payload/
|
||||||
|
cd ios/build/Build/Products/Release-iphoneos
|
||||||
|
zip -r ../../../movie-web.ipa Payload
|
||||||
|
|
||||||
|
- name: Upload movie-web.ipa as artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ipa
|
||||||
|
path: ./apps/expo/ios/build/movie-web.ipa
|
143
.github/workflows/release-mobile.yml
vendored
Normal file
143
.github/workflows/release-mobile.yml
vendored
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
name: release mobile app
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Automated Version Bump
|
||||||
|
uses: phips28/gh-action-bump-version@v10.1.1
|
||||||
|
with:
|
||||||
|
skip-tag: 'true'
|
||||||
|
commit-message: 'chore: bump mobile version to {{version}} [skip ci]'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
build-android:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [bump-version]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 21
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Build Android app
|
||||||
|
run: cd apps/expo && pnpm run apk
|
||||||
|
|
||||||
|
- name: Rename apk
|
||||||
|
run: cd apps/expo && mv android/app/build/outputs/apk/release/app-release.apk android/app/build/outputs/apk/release/movie-web.apk
|
||||||
|
|
||||||
|
- name: Upload movie-web.apk as artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: apk
|
||||||
|
path: ./apps/expo/android/app/build/outputs/apk/release/movie-web.apk
|
||||||
|
|
||||||
|
build-ios:
|
||||||
|
runs-on: macos-14
|
||||||
|
needs: [bump-version]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Xcode Select Version
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: '15.1.0'
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 21
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
name: Install pnpm
|
||||||
|
with:
|
||||||
|
version: 8
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Build iOS app
|
||||||
|
run: cd apps/expo && pnpm run ipa
|
||||||
|
|
||||||
|
- name: Export .ipa from .app
|
||||||
|
run: |
|
||||||
|
cd apps/expo
|
||||||
|
mkdir -p ios/build/Build/Products/Release-iphoneos/Payload
|
||||||
|
mv ios/build/Build/Products/Release-iphoneos/movieweb.app ios/build/Build/Products/Release-iphoneos/Payload/
|
||||||
|
cd ios/build/Build/Products/Release-iphoneos
|
||||||
|
zip -r ../../../movie-web.ipa Payload
|
||||||
|
|
||||||
|
- name: Upload movie-web.ipa as artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ipa
|
||||||
|
path: ./apps/expo/ios/build/movie-web.ipa
|
||||||
|
|
||||||
|
release-app:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build-android, build-ios]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Get package version
|
||||||
|
id: package-version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@v1.3.1
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: v${{ steps.package-version.outputs.current-version }}
|
||||||
|
files: |
|
||||||
|
movie-web.apk
|
||||||
|
movie-web.ipa
|
||||||
|
fail_on_unmatched_files: true
|
||||||
|
token: ${{ env.GITHUB_TOKEN }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,6 +17,8 @@ coverage
|
|||||||
dist/
|
dist/
|
||||||
expo-env.d.ts
|
expo-env.d.ts
|
||||||
apps/expo/.gitignore
|
apps/expo/.gitignore
|
||||||
|
ios/
|
||||||
|
android/
|
||||||
|
|
||||||
# production
|
# production
|
||||||
build
|
build
|
||||||
|
71
README.md
71
README.md
@@ -65,75 +65,6 @@ To add a new package, simply run `pnpm turbo gen init` in the monorepo root. Thi
|
|||||||
|
|
||||||
The generator sets up the `package.json`, `tsconfig.json` and a `index.ts`, as well as configures all the necessary configurations for tooling around your package such as formatting, linting and typechecking. When the package is created, you're ready to go build out the package.
|
The generator sets up the `package.json`, `tsconfig.json` and a `index.ts`, as well as configures all the necessary configurations for tooling around your package such as formatting, linting and typechecking. When the package is created, you're ready to go build out the package.
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
### Expo
|
|
||||||
|
|
||||||
Deploying your Expo application works slightly differently compared to Next.js on the web. Instead of "deploying" your app online, you need to submit production builds of your app to app stores, like [Apple App Store](https://www.apple.com/app-store) and [Google Play](https://play.google.com/store/apps). You can read the full [guide to distributing your app](https://docs.expo.dev/distribution/introduction), including best practices, in the Expo docs.
|
|
||||||
|
|
||||||
1. Make sure to modify the `getBaseUrl` function to point to your backend's production URL:
|
|
||||||
|
|
||||||
<https://github.com/t3-oss/create-t3-turbo/blob/656965aff7db271e5e080242c4a3ce4dad5d25f8/apps/expo/src/utils/api.tsx#L20-L37>
|
|
||||||
|
|
||||||
2. Let's start by setting up [EAS Build](https://docs.expo.dev/build/introduction), which is short for Expo Application Services. The build service helps you create builds of your app, without requiring a full native development setup. The commands below are a summary of [Creating your first build](https://docs.expo.dev/build/setup).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install the EAS CLI
|
|
||||||
pnpm add -g eas-cli
|
|
||||||
|
|
||||||
# Log in with your Expo account
|
|
||||||
eas login
|
|
||||||
|
|
||||||
# Configure your Expo app
|
|
||||||
cd apps/expo
|
|
||||||
eas build:configure
|
|
||||||
```
|
|
||||||
|
|
||||||
3. After the initial setup, you can create your first build. You can build for Android and iOS platforms and use different [`eas.json` build profiles](https://docs.expo.dev/build-reference/eas-json) to create production builds or development, or test builds. Let's make a production build for iOS.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
eas build --platform ios --profile production
|
|
||||||
```
|
|
||||||
|
|
||||||
> If you don't specify the `--profile` flag, EAS uses the `production` profile by default.
|
|
||||||
|
|
||||||
4. Now that you have your first production build, you can submit this to the stores. [EAS Submit](https://docs.expo.dev/submit/introduction) can help you send the build to the stores.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
eas submit --platform ios --latest
|
|
||||||
```
|
|
||||||
|
|
||||||
> You can also combine build and submit in a single command, using `eas build ... --auto-submit`.
|
|
||||||
|
|
||||||
5. Before you can get your app in the hands of your users, you'll have to provide additional information to the app stores. This includes screenshots, app information, privacy policies, etc. _While still in preview_, [EAS Metadata](https://docs.expo.dev/eas/metadata) can help you with most of this information.
|
|
||||||
|
|
||||||
6. Once everything is approved, your users can finally enjoy your app. Let's say you spotted a small typo; you'll have to create a new build, submit it to the stores, and wait for approval before you can resolve this issue. In these cases, you can use EAS Update to quickly send a small bugfix to your users without going through this long process. Let's start by setting up EAS Update.
|
|
||||||
|
|
||||||
The steps below summarize the [Getting started with EAS Update](https://docs.expo.dev/eas-update/getting-started/#configure-your-project) guide.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Add the `expo-updates` library to your Expo app
|
|
||||||
cd apps/expo
|
|
||||||
pnpm expo install expo-updates
|
|
||||||
|
|
||||||
# Configure EAS Update
|
|
||||||
eas update:configure
|
|
||||||
```
|
|
||||||
|
|
||||||
7. Before we can send out updates to your app, you have to create a new build and submit it to the app stores. For every change that includes native APIs, you have to rebuild the app and submit the update to the app stores. See steps 2 and 3.
|
|
||||||
|
|
||||||
8. Now that everything is ready for updates, let's create a new update for `production` builds. With the `--auto` flag, EAS Update uses your current git branch name and commit message for this update. See [How EAS Update works](https://docs.expo.dev/eas-update/how-eas-update-works/#publishing-an-update) for more information.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd apps/expo
|
|
||||||
eas update --auto
|
|
||||||
```
|
|
||||||
|
|
||||||
> Your OTA (Over The Air) updates must always follow the app store's rules. You can't change your app's primary functionality without getting app store approval. But this is a fast way to update your app for minor changes and bug fixes.
|
|
||||||
|
|
||||||
9. Done! Now that you have created your production build, submitted it to the stores, and installed EAS Update, you are ready for anything!
|
|
||||||
|
|
||||||
### References
|
### References
|
||||||
|
|
||||||
This app is based on [create-t3-turbo](https://github.com/t3-oss/create-t3-turbo) and [Turborepo](https://turborepo.org).
|
This app is based on [create-t3-turbo](https://github.com/t3-oss/create-t3-turbo) and [Turborepo](https://turborepo.org).
|
||||||
|
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ const defineConfig = (): ExpoConfig => ({
|
|||||||
name: "movie-web",
|
name: "movie-web",
|
||||||
slug: "mw-mobile",
|
slug: "mw-mobile",
|
||||||
scheme: "dev.movieweb.app",
|
scheme: "dev.movieweb.app",
|
||||||
version: "1.0.0",
|
version: "0.1.0",
|
||||||
orientation: "portrait",
|
orientation: "portrait",
|
||||||
icon: "./assets/images/icon.png",
|
icon: "./assets/images/icon.png",
|
||||||
userInterfaceStyle: "automatic",
|
userInterfaceStyle: "automatic",
|
||||||
|
@@ -6,16 +6,20 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "git clean -xdf .expo .turbo node_modules",
|
"clean": "git clean -xdf .expo .turbo node_modules",
|
||||||
"dev": "expo start",
|
"dev": "expo start",
|
||||||
"dev:android": "expo start --android",
|
"dev:android": "expo start -c --android",
|
||||||
"dev:ios": "expo start --ios",
|
"dev:ios": "expo start -c --ios",
|
||||||
"android": "expo run:android",
|
"android": "expo run:android",
|
||||||
"ios": "expo run:ios",
|
"ios": "expo run:ios",
|
||||||
|
"apk": "expo prebuild --platform=android && cd android && ./gradlew assembleRelease",
|
||||||
|
"ipa": "expo prebuild --platform=ios && cd ios && xcodebuild -workspace movieweb.xcworkspace -scheme movieweb -sdk iphoneos -configuration Release -derivedDataPath build -destination generic/platform=iOS CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO",
|
||||||
|
"ipa:sim": "expo prebuild --platform=ios && cd ios && xcodebuild -workspace movieweb.xcworkspace -scheme movieweb -sdk iphonesimulator -configuration Release -derivedDataPath build -destination \"generic/platform=iOS Simulator\" CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO",
|
||||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/metro-config": "^0.17.3",
|
"@expo/metro-config": "^0.17.3",
|
||||||
|
"@movie-web/tmdb": "*",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"expo": "~50.0.5",
|
"expo": "~50.0.5",
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { View } from "react-native";
|
||||||
import { Tabs } from "expo-router";
|
import { Tabs } from "expo-router";
|
||||||
|
|
||||||
import Colors from "@movie-web/tailwind-config/colors";
|
import Colors from "@movie-web/tailwind-config/colors";
|
||||||
@@ -55,11 +56,9 @@ export default function TabLayout() {
|
|||||||
title: "Search",
|
title: "Search",
|
||||||
tabBarLabel: "",
|
tabBarLabel: "",
|
||||||
tabBarIcon: () => (
|
tabBarIcon: () => (
|
||||||
<TabBarIcon
|
<View className="android:top-2 ios:top-2 flex h-14 w-14 items-center justify-center overflow-hidden rounded-full bg-primary-400 text-center align-middle text-2xl text-white">
|
||||||
className="flex aspect-[1/1] h-14 items-center justify-center rounded-full bg-primary-400 text-center align-middle text-2xl text-white"
|
<TabBarIcon name="search" color="#FFF" />
|
||||||
name="search"
|
</View>
|
||||||
color="#FFF"
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@@ -5,7 +5,11 @@ import { FontAwesome5 } from "@expo/vector-icons";
|
|||||||
|
|
||||||
import Colors from "@movie-web/tailwind-config/colors";
|
import Colors from "@movie-web/tailwind-config/colors";
|
||||||
|
|
||||||
export default function Searchbar() {
|
export default function Searchbar({
|
||||||
|
onSearchChange,
|
||||||
|
}: {
|
||||||
|
onSearchChange: (text: string) => void;
|
||||||
|
}) {
|
||||||
const [keyword, setKeyword] = useState("");
|
const [keyword, setKeyword] = useState("");
|
||||||
const inputRef = useRef<TextInput>(null);
|
const inputRef = useRef<TextInput>(null);
|
||||||
|
|
||||||
@@ -22,6 +26,11 @@ export default function Searchbar() {
|
|||||||
}, []),
|
}, []),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleChange = (text: string) => {
|
||||||
|
setKeyword(text);
|
||||||
|
onSearchChange(text);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="mb-6 mt-4 flex-row items-center rounded-full border border-primary-400 focus-within:border-primary-300">
|
<View className="mb-6 mt-4 flex-row items-center rounded-full border border-primary-400 focus-within:border-primary-300">
|
||||||
<View className="ml-1 w-12 items-center justify-center">
|
<View className="ml-1 w-12 items-center justify-center">
|
||||||
@@ -29,7 +38,7 @@ export default function Searchbar() {
|
|||||||
</View>
|
</View>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={keyword}
|
value={keyword}
|
||||||
onChangeText={(text) => setKeyword(text)}
|
onChangeText={handleChange}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
placeholder="What are you looking for?"
|
placeholder="What are you looking for?"
|
||||||
placeholderTextColor={Colors.secondary[200]}
|
placeholderTextColor={Colors.secondary[200]}
|
||||||
|
@@ -1,11 +1,26 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
import { ScrollView, View } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
|
|
||||||
|
import { getMediaPoster, searchTitle } from "@movie-web/tmdb";
|
||||||
|
|
||||||
|
import type { ItemData } from "~/components/item/item";
|
||||||
import Item from "~/components/item/item";
|
import Item from "~/components/item/item";
|
||||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||||
import { Text } from "~/components/ui/Text";
|
import { Text } from "~/components/ui/Text";
|
||||||
import Searchbar from "./Searchbar";
|
import Searchbar from "./Searchbar";
|
||||||
|
|
||||||
export default function SearchScreen() {
|
export default function SearchScreen() {
|
||||||
|
const [searchResults, setSearchResults] = useState<ItemData[]>([]);
|
||||||
|
|
||||||
|
const handleSearchChange = async (query: string) => {
|
||||||
|
if (query.length > 0) {
|
||||||
|
const results = await fetchSearchResults(query).catch(() => []);
|
||||||
|
setSearchResults(results);
|
||||||
|
} else {
|
||||||
|
setSearchResults([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<ScreenLayout
|
<ScreenLayout
|
||||||
@@ -16,19 +31,29 @@ export default function SearchScreen() {
|
|||||||
}
|
}
|
||||||
subtitle="Looking for something?"
|
subtitle="Looking for something?"
|
||||||
>
|
>
|
||||||
<Searchbar />
|
<Searchbar onSearchChange={handleSearchChange} />
|
||||||
<View className="flex w-full flex-1 flex-row flex-wrap justify-start">
|
<View className="flex w-full flex-1 flex-row flex-wrap justify-start">
|
||||||
<View className="basis-1/2 px-3 pb-3">
|
{searchResults.map((item, index) => (
|
||||||
<Item />
|
<View key={index} className="basis-1/2 px-3 pb-3">
|
||||||
</View>
|
<Item data={item} />
|
||||||
<View className="basis-1/2 px-3 pb-3">
|
</View>
|
||||||
<Item />
|
))}
|
||||||
</View>
|
|
||||||
<View className="basis-1/2 px-3 pb-3">
|
|
||||||
<Item />
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</ScreenLayout>
|
</ScreenLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchSearchResults(query: string): Promise<ItemData[]> {
|
||||||
|
const results = await searchTitle(query);
|
||||||
|
|
||||||
|
return results.map((result) => ({
|
||||||
|
id: result.id.toString(),
|
||||||
|
title: result.media_type === "tv" ? result.name : result.title,
|
||||||
|
posterUrl: getMediaPoster(result.poster_path),
|
||||||
|
year: new Date(
|
||||||
|
result.media_type === "tv" ? result.first_air_date : result.release_date,
|
||||||
|
).getFullYear(),
|
||||||
|
type: result.media_type,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
@@ -21,7 +21,7 @@ export {
|
|||||||
|
|
||||||
export const unstable_settings = {
|
export const unstable_settings = {
|
||||||
// Ensure that reloading on `/modal` keeps a back button present.
|
// Ensure that reloading on `/modal` keeps a back button present.
|
||||||
initialRouteName: "(tabs)/index",
|
initialRouteName: "(tabs)",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||||
|
@@ -1,25 +1,35 @@
|
|||||||
import { Image, View } from "react-native";
|
import { Image, View } from "react-native";
|
||||||
|
|
||||||
import { TMDB_POSTER_PATH } from "~/app/constants/General";
|
|
||||||
import { Text } from "~/components/ui/Text";
|
import { Text } from "~/components/ui/Text";
|
||||||
|
|
||||||
export default function Item() {
|
export interface ItemData {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
type: "movie" | "tv";
|
||||||
|
year: number;
|
||||||
|
posterUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Item({ data }: { data: ItemData }) {
|
||||||
|
const { title, type, year, posterUrl } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="w-full">
|
<View className="w-full">
|
||||||
<View className="mb-2 aspect-[9/14] w-full overflow-hidden rounded-2xl">
|
<View className="mb-2 aspect-[9/14] w-full overflow-hidden rounded-2xl">
|
||||||
<Image
|
<Image
|
||||||
source={{
|
source={{
|
||||||
uri: `${TMDB_POSTER_PATH}/w342//gdIrmf2DdY5mgN6ycVP0XlzKzbE.jpg`,
|
uri: posterUrl,
|
||||||
width: 200,
|
|
||||||
}}
|
}}
|
||||||
className="h-full w-full object-cover"
|
className="h-full w-full"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text className="font-bold">Hamilton</Text>
|
<Text className="font-bold">{title}</Text>
|
||||||
<View className="flex-row items-center gap-3">
|
<View className="flex-row items-center gap-3">
|
||||||
<Text className="text-xs text-gray-600">Movie</Text>
|
<Text className="text-xs text-gray-600">
|
||||||
|
{type === "tv" ? "Show" : "Movie"}
|
||||||
|
</Text>
|
||||||
<View className="h-1 w-1 rounded-3xl bg-gray-600" />
|
<View className="h-1 w-1 rounded-3xl bg-gray-600" />
|
||||||
<Text className="text-sm text-gray-600">2023</Text>
|
<Text className="text-sm text-gray-600">{year}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
0
apps/expo/src/app/constants/.gitkeep
Normal file
0
apps/expo/src/app/constants/.gitkeep
Normal file
@@ -1 +0,0 @@
|
|||||||
export const TMDB_POSTER_PATH = `https://image.tmdb.org/t/p`;
|
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "create-t3-turbo",
|
"name": "@movie-web/native",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.11.0"
|
"node": ">=20.11.0"
|
||||||
|
34
packages/tmdb/package.json
Normal file
34
packages/tmdb/package.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "@movie-web/tmdb",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rm -rf .turbo node_modules",
|
||||||
|
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@movie-web/eslint-config": "workspace:^0.2.0",
|
||||||
|
"@movie-web/prettier-config": "workspace:^0.1.0",
|
||||||
|
"@movie-web/tsconfig": "workspace:^0.1.0",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"prettier": "^3.1.1",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"@movie-web/eslint-config/base"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"prettier": "@movie-web/prettier-config",
|
||||||
|
"dependencies": {
|
||||||
|
"tmdb-ts": "^1.6.1"
|
||||||
|
}
|
||||||
|
}
|
24
packages/tmdb/src/details.ts
Normal file
24
packages/tmdb/src/details.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { MovieDetails, TvShowDetails } from "tmdb-ts";
|
||||||
|
|
||||||
|
import { tmdb } from "./util";
|
||||||
|
|
||||||
|
export async function fetchMediaDetails(
|
||||||
|
id: string,
|
||||||
|
type: "movie" | "tv",
|
||||||
|
): Promise<
|
||||||
|
{ type: "movie" | "tv"; result: TvShowDetails | MovieDetails } | undefined
|
||||||
|
> {
|
||||||
|
try {
|
||||||
|
const result =
|
||||||
|
type === "movie"
|
||||||
|
? await tmdb.movies.details(parseInt(id, 10))
|
||||||
|
: await tmdb.tvShows.details(parseInt(id, 10));
|
||||||
|
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
result,
|
||||||
|
};
|
||||||
|
} catch (ex) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
4
packages/tmdb/src/index.ts
Normal file
4
packages/tmdb/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const name = "tmdb";
|
||||||
|
export * from "./search";
|
||||||
|
export * from "./details";
|
||||||
|
export * from "./util";
|
22
packages/tmdb/src/search.ts
Normal file
22
packages/tmdb/src/search.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { MovieWithMediaType, TVWithMediaType } from "tmdb-ts";
|
||||||
|
|
||||||
|
import { tmdb } from "./util";
|
||||||
|
|
||||||
|
export async function searchTitle(query: string) {
|
||||||
|
try {
|
||||||
|
const rawResults = await tmdb.search.multi({
|
||||||
|
query,
|
||||||
|
page: 1,
|
||||||
|
include_adult: false,
|
||||||
|
});
|
||||||
|
const results = rawResults.results.filter(
|
||||||
|
(result) => result.media_type === "tv" || result.media_type === "movie",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!results.length) throw new Error("No results found");
|
||||||
|
|
||||||
|
return results as unknown as MovieWithMediaType[] | TVWithMediaType[];
|
||||||
|
} catch (ex) {
|
||||||
|
throw new Error(`Error searching for title: ${(ex as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
9
packages/tmdb/src/util.ts
Normal file
9
packages/tmdb/src/util.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { TMDB } from "tmdb-ts";
|
||||||
|
|
||||||
|
const TMDB_API_KEY =
|
||||||
|
"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYTM1ZTgyMzE4OTc0NTgxNDJmZjljZTE4ODExNWRlNiIsInN1YiI6IjY0OTM0ZDQ1ODliNTYxMDExYzliZDVhMiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.AzWnIcxPNgDwGdzeIZ_C3mRC_5_qy-Z-SRPglLjzlNc";
|
||||||
|
export const tmdb = new TMDB(TMDB_API_KEY);
|
||||||
|
|
||||||
|
export function getMediaPoster(posterPath: string): string {
|
||||||
|
return `https://image.tmdb.org/t/p/w185/${posterPath}`;
|
||||||
|
}
|
8
packages/tmdb/tsconfig.json
Normal file
8
packages/tmdb/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@movie-web/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
||||||
|
},
|
||||||
|
"include": ["*.ts", "src"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
}
|
36
pnpm-lock.yaml
generated
36
pnpm-lock.yaml
generated
@@ -34,6 +34,9 @@ importers:
|
|||||||
'@expo/metro-config':
|
'@expo/metro-config':
|
||||||
specifier: ^0.17.3
|
specifier: ^0.17.3
|
||||||
version: 0.17.3(@react-native/babel-preset@0.73.20)
|
version: 0.17.3(@react-native/babel-preset@0.73.20)
|
||||||
|
'@movie-web/tmdb':
|
||||||
|
specifier: '*'
|
||||||
|
version: link:../../packages/tmdb
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.0
|
version: 0.7.0
|
||||||
@@ -135,6 +138,31 @@ importers:
|
|||||||
specifier: ^5.3.3
|
specifier: ^5.3.3
|
||||||
version: 5.3.3
|
version: 5.3.3
|
||||||
|
|
||||||
|
packages/tmdb:
|
||||||
|
dependencies:
|
||||||
|
tmdb-ts:
|
||||||
|
specifier: ^1.6.1
|
||||||
|
version: 1.6.1
|
||||||
|
devDependencies:
|
||||||
|
'@movie-web/eslint-config':
|
||||||
|
specifier: workspace:^0.2.0
|
||||||
|
version: link:../../tooling/eslint
|
||||||
|
'@movie-web/prettier-config':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../../tooling/prettier
|
||||||
|
'@movie-web/tsconfig':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../../tooling/typescript
|
||||||
|
eslint:
|
||||||
|
specifier: ^8.56.0
|
||||||
|
version: 8.56.0
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.1.1
|
||||||
|
version: 3.2.4
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.3.3
|
||||||
|
version: 5.3.3
|
||||||
|
|
||||||
tooling/eslint:
|
tooling/eslint:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
@@ -9537,6 +9565,14 @@ packages:
|
|||||||
upper-case: 1.1.3
|
upper-case: 1.1.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/tmdb-ts@1.6.1:
|
||||||
|
resolution: {integrity: sha512-TJQYQctzky03z8bhlJtZ9ZjFHNvLhpow3qKHMMZj2LEOvlqcJ/Dyy33IyuBROrhzWLelkmGraAA718B0ENP1Fg==}
|
||||||
|
dependencies:
|
||||||
|
cross-fetch: 3.1.8
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tmp@0.0.33:
|
/tmp@0.0.33:
|
||||||
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
||||||
engines: {node: '>=0.6.0'}
|
engines: {node: '>=0.6.0'}
|
||||||
|
Reference in New Issue
Block a user