├── .gitignore
├── README.md
├── avatar-creator
├── README.md
└── react
│ └── avatar-builder
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ ├── context
│ │ └── avatarState.tsx
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
│ ├── components
│ ├── RiveAvatarComponent.tsx
│ ├── RiveIconButton.tsx
│ ├── RiveIconsContainer.tsx
│ ├── RiveMainEntry.tsx
│ ├── RiveOptionButton.tsx
│ └── RiveOptionsContainer.tsx
│ ├── json
│ └── avatarConfig.json
│ ├── lib
│ └── localData.ts
│ ├── next.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ ├── avatar_demo.riv
│ ├── next.svg
│ └── vercel.svg
│ ├── tailwind.config.js
│ └── tsconfig.json
├── hero-animation
├── README.md
└── react
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
│ ├── components
│ ├── RiveButton.tsx
│ └── RiveHero.tsx
│ ├── next.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ ├── hero_use_case.riv
│ ├── next.svg
│ └── vercel.svg
│ ├── tailwind.config.js
│ ├── tsconfig.json
│ └── utils
│ ├── throttle.ts
│ ├── useMediaBreakpoint.ts
│ └── usePrefersReducedMotion.ts
├── pull-to-refresh
├── .DS_Store
├── README.MD
├── android
│ ├── .gitignore
│ ├── .idea
│ │ ├── .gitignore
│ │ ├── .name
│ │ ├── compiler.xml
│ │ ├── deploymentTargetDropDown.xml
│ │ ├── gradle.xml
│ │ ├── inspectionProfiles
│ │ │ └── Project_Default.xml
│ │ ├── kotlinc.xml
│ │ ├── misc.xml
│ │ └── vcs.xml
│ ├── app
│ │ ├── .gitignore
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src
│ │ │ ├── androidTest
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── pulltorefreshrive
│ │ │ │ └── ExampleInstrumentedTest.kt
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── pulltorefreshrive
│ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ └── ui
│ │ │ │ │ └── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Shape.kt
│ │ │ │ │ ├── Theme.kt
│ │ │ │ │ └── Type.kt
│ │ │ └── res
│ │ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ │ ├── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ │ ├── mipmap-anydpi-v33
│ │ │ │ └── ic_launcher.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ │ ├── raw
│ │ │ │ └── pull_to_refresh_use_case.riv
│ │ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ │ └── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── pulltorefreshrive
│ │ │ └── ExampleUnitTest.kt
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── flutter
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── android
│ │ ├── .gitignore
│ │ ├── app
│ │ │ ├── build.gradle
│ │ │ └── src
│ │ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ │ ├── main
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── pull_to_refresh_rive_flutter
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── res
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── values-night
│ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values
│ │ │ │ │ └── styles.xml
│ │ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle
│ │ ├── gradle.properties
│ │ ├── gradle
│ │ │ └── wrapper
│ │ │ │ └── gradle-wrapper.properties
│ │ └── settings.gradle
│ ├── assets
│ │ └── pull_to_refresh_use_case.riv
│ ├── ios
│ │ ├── .gitignore
│ │ ├── Flutter
│ │ │ ├── AppFrameworkInfo.plist
│ │ │ ├── Debug.xcconfig
│ │ │ └── Release.xcconfig
│ │ ├── Podfile
│ │ ├── Podfile.lock
│ │ ├── Runner.xcodeproj
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata
│ │ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── Runner.xcscheme
│ │ ├── Runner.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ ├── Runner
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets
│ │ │ │ ├── AppIcon.appiconset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── LaunchImage.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── LaunchImage.png
│ │ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ │ └── README.md
│ │ │ ├── Base.lproj
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ └── Runner-Bridging-Header.h
│ │ └── RunnerTests
│ │ │ └── RunnerTests.swift
│ ├── lib
│ │ ├── main.dart
│ │ └── skeleton_item.dart
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── test
│ │ └── widget_test.dart
└── ios
│ ├── Rive Pull to Refresh.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ │ └── Package.resolved
│ │ └── xcuserdata
│ │ │ └── gordon.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata
│ │ └── gordon.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── Rive Pull to Refresh
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── ContentView.swift
│ ├── CustomRefreshView.swift
│ ├── MockListTile.swift
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ ├── RivePullToRefreshView.swift
│ ├── Rive_Pull_to_RefreshApp.swift
│ └── animations
│ └── pull_to_refresh_use_case.riv
└── rive-google-ads
├── README.md
└── examples
└── rive-google-ads-parcel
├── .gitignore
├── assets
├── bike_op.riv
├── disney.riv
├── rive.wasm
├── sniffr-googlead-v3.riv
├── wc__op.riv
├── wc_op.riv
└── world_creator.riv
├── package.json
└── src
├── basicRiveAd.ts
├── index.html
├── index.ts
├── riveAdWithDeviceOrientation.ts
└── utils
└── isDeviceOrientationSupported.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rive Use Cases
2 |
3 | This folder contains a number of different runtime use cases that demonstrate everything you can do with Rive, along with step-by-step tutorials on how to create these experiences.
4 |
5 | See the [Rive Use Cases page](https://rive.app/use-cases) for more examples. For more inspiration, see [awesome-rive](https://github.com/rive-app/awesome-rive), a repository dedicated to amazing things created with Rive.
6 |
7 | ## Google Ads
8 |
9 | - [Code](rive-google-ads)
10 |
11 | ## Pull To Refresh
12 |
13 | https://github.com/rive-app/rive-use-cases/assets/13705472/92de6f68-2c09-4df2-be23-dbb87ea1e8b6
14 |
15 | - [Code](pull-to-refresh)
16 | - [Tutorials](https://rive.app/use-cases/pull-to-refresh)
17 |
18 | ## Avatar Creator
19 |
20 | - [Code](avatar-creator)
21 | - [Tutorials](https://rive.app/use-cases/avatar-creator-tutorial)
22 |
23 | ## Hero Animation
24 |
25 | - [Code](hero-animation)
26 | - [Tutorials](https://rive.app/use-cases/hero-animations)
27 |
--------------------------------------------------------------------------------
/avatar-creator/README.md:
--------------------------------------------------------------------------------
1 | ## Rive: Avatar Creator
2 |
3 | This folder contains an example that demonstrates how to create an interactive avatar builder experience with Rive for applications on web, mobile, and more! With this demo project, you can select different avatar traits to change, ranging from the body color, the eyes, and the hair, to the color of the backdrop for the avatar, and see the changed traits reflect immediately.
4 |
5 | See this [video playlist](https://www.youtube.com/playlist?list=PLujDTZWVDSsFdHbrnBCqLZ5aAb5o2HrIP) for tutorials on how to build this animation and integrate it on the listed platforms.
6 |
7 | ### Platforms (Code Samples)
8 |
9 | - Web (React): [code](react/), [video tutorial](https://youtu.be/Qr7Ng6fpqnk?si=1subLGm1n1FQnY5B)
10 |
11 | See the [Rive documentation](https://rive.app/docs/runtimes/getting-started) for additional runtime information.
12 |
13 | ### Other Use Cases
14 |
15 | - See the [Rive Use Cases page](https://rive.app/use-cases) for more examples
16 | - Additional code resources can be found at the [root repository](https://github.com/rive-app/rive-use-cases/)
17 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/README.md:
--------------------------------------------------------------------------------
1 | # Avatar Builder
2 | This is a [Next.js](https://nextjs.org/) project with a single landing page displaying an interactive avatar builder experience to choose different avatar traits and change with a number of options.
3 |
4 | ## Getting Started
5 |
6 | First, install the dependencies locally, and run the development server:
7 |
8 | ```bash
9 | npm i
10 | npm run dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/app/context/avatarState.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | createContext,
5 | useCallback,
6 | useReducer,
7 | PropsWithChildren,
8 | Context,
9 | } from "react";
10 |
11 | /**
12 | * Initial state for Avatar Builder.
13 | * - `activeIcon` - Name correlates to the currently selected icon in the sidebar, and should match
14 | * a pattern in the artboard name from the `.riv` file
15 | * (i.e. `BodyColor` for `BodyColorIcon` artboard)
16 | * - `riveAvatarSelections` - Object that holds the current selection for each character feature. The
17 | * number correlates to the state machine input value for `numOption` input
18 | * for the option button state machines in the `.riv` file
19 | *
20 | */
21 | const initialState = {
22 | activeIcon: "BodyColor",
23 | riveAvatarSelections: {
24 | BodyColor: 0,
25 | BodySize: 0,
26 | BodyEyes: 0,
27 | BodyHair: 0,
28 | BodyFaceHair: 0,
29 | BackgroundColor: 0,
30 | },
31 | };
32 |
33 | const actions = {
34 | SET_ACTIVE_ICON: "SET_ACTIVE_ICON",
35 | SET_RIVE_AVATAR_SELECTION: "SET_RIVE_AVATAR_SELECTION",
36 | };
37 |
38 | export interface Action {
39 | readonly type: T;
40 | readonly payload?: P;
41 | }
42 |
43 | /**
44 | * Reducer function that updates state given a specific action and payload
45 | */
46 | const reducer = (state: any, action: any) => {
47 | switch (action.type) {
48 | case actions.SET_ACTIVE_ICON:
49 | return {
50 | ...state,
51 | activeIcon: action.activeIcon,
52 | };
53 | case actions.SET_RIVE_AVATAR_SELECTION:
54 | return {
55 | ...state,
56 | riveAvatarSelections: {
57 | ...state.riveAvatarSelections,
58 | [action.feature]: action.featureValue,
59 | },
60 | };
61 | default:
62 | return state;
63 | }
64 | };
65 |
66 | export const AvatarStateContext: Context = createContext({});
67 |
68 | /**
69 | * Wraps the app with the AvatarStateContext provider and exposes action functions
70 | * that child components can call to update the state indirectly
71 | */
72 | export const AvatarStateProvider = ({ children }: PropsWithChildren) => {
73 | const [state, dispatch] = useReducer(reducer, initialState);
74 |
75 | /**
76 | * Sets the active icon in the state when a user clicks a character feature icon
77 | */
78 | const setActiveIcon = useCallback(
79 | (activeIcon: string) => {
80 | let splicedIconName = activeIcon.replace("Icon", "");
81 | dispatch({
82 | type: actions.SET_ACTIVE_ICON,
83 | activeIcon: splicedIconName,
84 | });
85 | },
86 | [dispatch]
87 | );
88 |
89 | /**
90 | * Sets a selection of a character feature in the state when a user clicks on one of the
91 | * multiple options for that feature (i.e. Mustache for Facial hair)
92 | */
93 | const setRiveAvatarSelection = useCallback(
94 | (feature: string, featureValue: number) => {
95 | dispatch({
96 | type: actions.SET_RIVE_AVATAR_SELECTION,
97 | feature,
98 | featureValue,
99 | });
100 | },
101 | []
102 | );
103 |
104 | const value = {
105 | state,
106 | setActiveIcon,
107 | setRiveAvatarSelection,
108 | };
109 |
110 | return (
111 |
112 | {children}
113 |
114 | );
115 | };
116 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/avatar-creator/react/avatar-builder/app/favicon.ico
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | --font-sans: 'Inter', sans-serif;
10 | }
11 |
12 | @media (prefers-color-scheme: dark) {
13 | :root {
14 | --foreground-rgb: 255, 255, 255;
15 | --background-start-rgb: 0, 0, 0;
16 | --background-end-rgb: 0, 0, 0;
17 | }
18 | }
19 |
20 | body {
21 | color: rgb(var(--foreground-rgb));
22 | }
23 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "./globals.css";
2 | import { Inter } from "@next/font/google";
3 |
4 | const inter = Inter({
5 | subsets: ["latin"],
6 | // this will be the css variable
7 | variable: "--font-inter",
8 | });
9 |
10 | export const metadata = {
11 | title: "Avatar Creator",
12 | description: "Rive avatar creator",
13 | };
14 |
15 | export default function RootLayout({
16 | children,
17 | }: {
18 | children: React.ReactNode;
19 | }) {
20 | return (
21 |
22 |
28 | {children}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { Suspense } from "react";
2 | import RiveAvatar from "@/components/RiveAvatarComponent";
3 | import { RuntimeLoader } from "@rive-app/react-canvas";
4 | import RiveIconsContainer from "@/components/RiveIconsContainer";
5 | import { getLocalData } from "@/lib/localData";
6 | import { AvatarStateProvider } from "./context/avatarState";
7 |
8 | import data from "@/json/avatarConfig.json";
9 | import RiveOptionsContainer from "@/components/RiveOptionsContainer";
10 | import RiveMainEntry from "@/components/RiveMainEntry";
11 | export type JSONData = typeof data;
12 |
13 | export default async function Home() {
14 | const localData: JSONData = await getLocalData();
15 | return (
16 |
17 | loading
}>
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/components/RiveAvatarComponent.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useContext } from "react";
4 | import {
5 | useRive,
6 | useStateMachineInput,
7 | Layout,
8 | Alignment,
9 | } from "@rive-app/react-canvas";
10 | import { AvatarStateContext } from "@/app/context/avatarState";
11 |
12 | // @refresh reset
13 |
14 | const STATE_MACHINE_NAME = "State Machine 1";
15 |
16 | /**
17 | * Component for the actual Avatar character preview. It listens to the global state context
18 | * to listen to when users select new character feature options and updates the avatar state
19 | * machine accordingly
20 | */
21 | export default function RiveAvatarComponent() {
22 | const { rive, RiveComponent } = useRive({
23 | src: "./avatar_demo.riv",
24 | artboard: "Avatar",
25 | stateMachines: [STATE_MACHINE_NAME],
26 | layout: new Layout({
27 | alignment: Alignment.TopCenter,
28 | }),
29 | autoplay: true,
30 | });
31 |
32 | const {
33 | state: { riveAvatarSelections },
34 | } = useContext(AvatarStateContext);
35 |
36 | const numBodyColor = useStateMachineInput(
37 | rive,
38 | STATE_MACHINE_NAME,
39 | "numBodyColor"
40 | );
41 | const numBodySize = useStateMachineInput(
42 | rive,
43 | STATE_MACHINE_NAME,
44 | "numBodySize"
45 | );
46 | const numBodyEyes = useStateMachineInput(
47 | rive,
48 | STATE_MACHINE_NAME,
49 | "numBodyEyes"
50 | );
51 | const numBodyHair = useStateMachineInput(
52 | rive,
53 | STATE_MACHINE_NAME,
54 | "numBodyHair"
55 | );
56 | const numBodyFaceHair = useStateMachineInput(
57 | rive,
58 | STATE_MACHINE_NAME,
59 | "numBodyFaceHair"
60 | );
61 | const numBackgroundColor = useStateMachineInput(
62 | rive,
63 | STATE_MACHINE_NAME,
64 | "numBackgroundColor"
65 | );
66 |
67 | const changesTrigger = useStateMachineInput(
68 | rive,
69 | STATE_MACHINE_NAME,
70 | "changes"
71 | );
72 |
73 | /**
74 | * Set new user-selected character features on the state machine and fire a trigger input
75 | * to visually show the new selection on the avatar
76 | */
77 | useEffect(() => {
78 | if (
79 | rive &&
80 | numBodyColor &&
81 | numBodySize &&
82 | numBodyEyes &&
83 | numBodyHair &&
84 | numBodyFaceHair &&
85 | numBackgroundColor &&
86 | changesTrigger
87 | ) {
88 | numBodyColor!.value = riveAvatarSelections["BodyColor"];
89 | numBodySize!.value = riveAvatarSelections["BodySize"];
90 | numBodyEyes!.value = riveAvatarSelections["BodyEyes"];
91 | numBodyHair!.value = riveAvatarSelections["BodyHair"];
92 | numBodyFaceHair!.value = riveAvatarSelections["BodyFaceHair"];
93 | numBackgroundColor!.value = riveAvatarSelections["BackgroundColor"];
94 | changesTrigger!.fire();
95 | }
96 | }, [
97 | rive,
98 | numBodyColor,
99 | numBodySize,
100 | numBodyEyes,
101 | numBodyHair,
102 | numBodyFaceHair,
103 | riveAvatarSelections,
104 | numBackgroundColor,
105 | changesTrigger,
106 | ]);
107 |
108 | return (
109 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/components/RiveIconButton.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useContext, useCallback, useEffect } from "react";
4 | import { useRive, useStateMachineInput } from "@rive-app/react-canvas";
5 | import { AvatarStateContext } from "@/app/context/avatarState";
6 |
7 | // @refresh reset
8 |
9 | interface RiveIconButtonProps {
10 | artboardName: string;
11 | }
12 |
13 | const STATE_MACHINE_NAME = "State Machine 1";
14 |
15 | /**
16 | * Reusable component for the character feature icons that users can click to see
17 | * a list of options for that feature (i.e. Facial Hair, Body Color, etc.)
18 | */
19 | export default function RiveIconButton({ artboardName }: RiveIconButtonProps) {
20 | const {
21 | state: { activeIcon, riveAvatarSelections },
22 | setActiveIcon,
23 | } = useContext(AvatarStateContext);
24 |
25 | const { rive, RiveComponent } = useRive({
26 | src: "./avatar_demo.riv",
27 | artboard: artboardName,
28 | stateMachines: [STATE_MACHINE_NAME],
29 | autoplay: true,
30 | shouldDisableRiveListeners: true,
31 | });
32 |
33 | const strippedDownName = artboardName.replace("Icon", "");
34 |
35 | const isHovered = useStateMachineInput(rive, STATE_MACHINE_NAME, "isHover");
36 | const isIconActive = useStateMachineInput(
37 | rive,
38 | STATE_MACHINE_NAME,
39 | "isIconActive",
40 | activeIcon === strippedDownName
41 | );
42 |
43 | const numOption = useStateMachineInput(
44 | rive,
45 | STATE_MACHINE_NAME,
46 | `num${strippedDownName}`
47 | );
48 |
49 | /**
50 | * The icon graphic should update to reflect the chosen feature option for that icon
51 | * and so this listens for user selections in the options and updates accordingly
52 | */
53 | useEffect(() => {
54 | if (rive && numOption) {
55 | numOption.value = riveAvatarSelections[strippedDownName];
56 | }
57 | }, [rive, numOption, riveAvatarSelections, strippedDownName]);
58 |
59 | /**
60 | * When a user clicks on an icon, we want to set the isIconActive flag on the state machine input
61 | * to true for that icon, and false for all other icons
62 | */
63 | useEffect(() => {
64 | if (rive && isIconActive) {
65 | if (activeIcon === strippedDownName) {
66 | isIconActive.value = true;
67 | } else {
68 | isIconActive.value = false;
69 | }
70 | }
71 | }, [rive, activeIcon, isIconActive, strippedDownName]);
72 |
73 | const onFocus = useCallback(() => {
74 | if (rive && isHovered) {
75 | isHovered.value = true;
76 | }
77 | }, [rive, isHovered]);
78 |
79 | const onBlur = useCallback(() => {
80 | if (rive && isHovered) {
81 | isHovered.value = false;
82 | }
83 | }, [rive, isHovered]);
84 |
85 | const onClick = useCallback(() => {
86 | if (rive && isIconActive) {
87 | isIconActive.value = true;
88 | setActiveIcon(artboardName);
89 | }
90 | }, [rive, isIconActive, artboardName, setActiveIcon]);
91 |
92 | return (
93 |
101 | );
102 | }
103 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/components/RiveIconsContainer.tsx:
--------------------------------------------------------------------------------
1 | import RiveIconButton from "./RiveIconButton";
2 |
3 | const getArtboardName = (artboardName: String) => {
4 | if (artboardName === "BackgroundColor") {
5 | return `${artboardName}Icon`;
6 | }
7 | return `Body${artboardName}Icon`;
8 | };
9 |
10 | /**
11 | * List out all the character feature icon buttons
12 | */
13 | export default function RiveIconsContainer() {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/components/RiveMainEntry.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useContext } from "react";
4 | import RiveAvatar from "@/components/RiveAvatarComponent";
5 | import RiveIconsContainer from "@/components/RiveIconsContainer";
6 | import { AvatarStateContext } from "@/app/context/avatarState";
7 | import RiveOptionsContainer from "@/components/RiveOptionsContainer";
8 | import { JSONData } from "@/app/page";
9 |
10 | interface RiveMainEntryProps {
11 | localData: JSONData;
12 | }
13 |
14 | /**
15 | * Main component for the whole Avatar Builder page
16 | */
17 | export default function RiveMainEntry({ localData }: RiveMainEntryProps) {
18 | const {
19 | state: { activeIcon },
20 | } = useContext(AvatarStateContext);
21 | const trimmedActiveIcon: keyof JSONData =
22 | activeIcon === "BackgroundColor" ? activeIcon : activeIcon.split("Body")[1];
23 | return (
24 |
25 |
26 |
27 |
28 |
29 | Avatar Creator
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Avatar Creator
38 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/components/RiveOptionButton.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCallback, useContext, useEffect } from "react";
4 | import { useRive, useStateMachineInput } from "@rive-app/react-canvas";
5 | import { AvatarStateContext } from "@/app/context/avatarState";
6 |
7 | // @refresh reset
8 |
9 | interface RiveOptionButtonProps {
10 | artboardName: string;
11 | optionIdx: number;
12 | }
13 |
14 | const STATE_MACHINE_NAME = "State Machine 1";
15 |
16 | /**
17 | * Reusable component for a character feature option button (i.e. mustache and beard for facial hair feature)
18 | */
19 | export default function RiveOptionButton({
20 | artboardName,
21 | optionIdx,
22 | }: RiveOptionButtonProps) {
23 | const mainName = artboardName.replace("Button", "");
24 |
25 | const {
26 | state: { riveAvatarSelections },
27 | setRiveAvatarSelection,
28 | } = useContext(AvatarStateContext);
29 |
30 | const selectionValue = riveAvatarSelections[mainName];
31 |
32 | const { rive, RiveComponent } = useRive({
33 | src: "./avatar_demo.riv",
34 | artboard: artboardName,
35 | stateMachines: [STATE_MACHINE_NAME],
36 | autoplay: true,
37 | shouldDisableRiveListeners: true,
38 | });
39 |
40 | const isHovered = useStateMachineInput(
41 | rive,
42 | STATE_MACHINE_NAME,
43 | "isBoxHover"
44 | );
45 |
46 | const isIconActive = useStateMachineInput(
47 | rive,
48 | STATE_MACHINE_NAME,
49 | "isIconActive",
50 | selectionValue === optionIdx
51 | );
52 |
53 | const numOption = useStateMachineInput(
54 | rive,
55 | STATE_MACHINE_NAME,
56 | "numOption",
57 | optionIdx
58 | );
59 |
60 | useEffect(() => {
61 | if (rive && numOption && isIconActive) {
62 | isIconActive.value = selectionValue === numOption.value;
63 | }
64 | }, [selectionValue, rive, numOption, isIconActive]);
65 |
66 | const onFocus = useCallback(() => {
67 | if (rive && isHovered) {
68 | isHovered.value = true;
69 | }
70 | }, [rive, isHovered]);
71 |
72 | const onBlur = useCallback(() => {
73 | if (rive && isHovered) {
74 | isHovered.value = false;
75 | }
76 | }, [rive, isHovered]);
77 |
78 | const onClick = useCallback(() => {
79 | if (rive && numOption) {
80 | setRiveAvatarSelection(mainName, numOption.value);
81 | }
82 | }, [rive, numOption, setRiveAvatarSelection, mainName]);
83 |
84 | return (
85 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/components/RiveOptionsContainer.tsx:
--------------------------------------------------------------------------------
1 | import RiveOptionButton from "./RiveOptionButton";
2 |
3 | interface RiveOptionsContainerProps {
4 | buttonCollectionName: string;
5 | numOptions: number;
6 | }
7 |
8 | const getArtboardName = (artboardName: String) => {
9 | if (artboardName === "BackgroundColor") {
10 | return `${artboardName}Button`;
11 | }
12 | return `Body${artboardName}Button`;
13 | };
14 |
15 | /**
16 | * List out all the character feature option buttons
17 | */
18 | export default function RiveOptionsContainer({
19 | buttonCollectionName,
20 | numOptions,
21 | }: RiveOptionsContainerProps) {
22 | const optionButtons = [];
23 | for (let i = 0; i < numOptions; i++) {
24 | optionButtons.push(
25 |
30 | );
31 | }
32 | return (
33 |
34 |
35 | {optionButtons.map((buttonComp) => buttonComp)}
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/json/avatarConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "Color": {
3 | "numOptions": 5
4 | },
5 | "Size": {
6 | "numOptions": 3
7 | },
8 | "Eyes": {
9 | "numOptions": 4
10 | },
11 | "Hair": {
12 | "numOptions": 11
13 | },
14 | "FaceHair": {
15 | "numOptions": 3
16 | },
17 | "BackgroundColor": {
18 | "numOptions": 4
19 | }
20 | }
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/lib/localData.ts:
--------------------------------------------------------------------------------
1 | import fsPromises from "fs/promises";
2 | import path from "path";
3 |
4 | import data from "@/json/avatarConfig.json";
5 | export type JSONType = typeof data;
6 |
7 | export async function getLocalData() {
8 | // Get the path of the json file
9 | const filePath = path.join(process.cwd(), "json/avatarConfig.json");
10 | // Read the json file
11 | const jsonData = await fsPromises.readFile(filePath) as unknown as string;
12 | // Parse data as json
13 | const objectData = JSON.parse(jsonData);
14 |
15 | return objectData;
16 | }
17 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "avatar-builder",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@next/font": "^13.4.18",
13 | "@rive-app/canvas": "^1.2.1",
14 | "@rive-app/react-canvas": "^3.0.54",
15 | "@types/node": "20.4.0",
16 | "@types/react": "18.2.14",
17 | "@types/react-dom": "18.2.6",
18 | "autoprefixer": "10.4.14",
19 | "eslint": "8.44.0",
20 | "eslint-config-next": "13.4.9",
21 | "next": "14.2.26",
22 | "postcss": "8.4.25",
23 | "react": "18.2.0",
24 | "react-dom": "18.2.0",
25 | "tailwindcss": "3.3.2",
26 | "typescript": "5.1.6"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/public/avatar_demo.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/avatar-creator/react/avatar-builder/public/avatar_demo.riv
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
5 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
7 | ],
8 | theme: {
9 | extend: {
10 | fontFamily: {
11 | sans: ["var(--font-inter)"],
12 | },
13 | backgroundImage: {
14 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
15 | "gradient-conic":
16 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
17 | },
18 | },
19 | },
20 | purge: {
21 | safelist: ["text-[#ffffff]"],
22 | },
23 | plugins: [],
24 | };
25 |
--------------------------------------------------------------------------------
/avatar-creator/react/avatar-builder/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/hero-animation/README.md:
--------------------------------------------------------------------------------
1 | ## Rive: Hero Animation
2 |
3 | This folder contains an example that demonstrates how to create an interactive hero animation experience with Rive on web applications for screens such as a landing page.
4 |
5 | See this [video playlist](https://youtube.com/playlist?list=PLujDTZWVDSsFnqEdfIi6v238EVbdKV93L) for tutorials on how to build this animation and integrate it on the listed platforms.
6 |
7 | ### Platforms (Code Samples)
8 |
9 | - Web (React): [code](react/), [video tutorial](https://youtu.be/_s1zuX7hn_U)
10 |
11 | See the [Rive documentation](https://rive.app/docs/runtimes/getting-started) for additional runtime information.
12 |
13 | ### Other Use Cases
14 |
15 | - See the [Rive Use Cases page](https://rive.app/use-cases) for more examples
16 | - Additional code resources can be found at the [root repository](https://github.com/rive-app/rive-use-cases/)
17 |
--------------------------------------------------------------------------------
/hero-animation/react/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/hero-animation/react/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/hero-animation/react/README.md:
--------------------------------------------------------------------------------
1 | # Explore Hero Landing
2 | This is a [Next.js](https://nextjs.org/) project with a single landing page displaying an interactive hero animation with a call-to-action button.
3 |
4 | ## Getting Started
5 |
6 | First, install the dependencies locally, and run the development server:
7 |
8 | ```bash
9 | npm i
10 | npm run dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
--------------------------------------------------------------------------------
/hero-animation/react/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/hero-animation/react/app/favicon.ico
--------------------------------------------------------------------------------
/hero-animation/react/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | background: #09090E;
21 | }
22 |
--------------------------------------------------------------------------------
/hero-animation/react/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "./globals.css";
2 |
3 | export const metadata = {
4 | title: "Explore Page",
5 | description: "Rive hero demo",
6 | };
7 |
8 | export default function RootLayout({
9 | children,
10 | }: {
11 | children: React.ReactNode;
12 | }) {
13 | return (
14 |
15 |
21 | {children}
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/hero-animation/react/app/page.tsx:
--------------------------------------------------------------------------------
1 | import RiveHero from "@/components/RiveHero";
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/hero-animation/react/components/RiveButton.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react";
2 | import { useRive, useStateMachineInput } from "@rive-app/react-canvas";
3 |
4 | export default function RiveButton() {
5 | const { rive, RiveComponent } = useRive({
6 | src: "/hero_use_case.riv",
7 | artboard: "Button",
8 | stateMachines: "State Machine 1",
9 | autoplay: true,
10 | shouldDisableRiveListeners: true,
11 | });
12 |
13 | const isHoverInput = useStateMachineInput(rive, "State Machine 1", "isHover");
14 |
15 | const onButtonActivate = useCallback(() => {
16 | if (rive && isHoverInput) {
17 | isHoverInput.value = true;
18 | }
19 | }, [rive, isHoverInput]);
20 |
21 | const onButtonDeactivate = useCallback(() => {
22 | if (rive && isHoverInput) {
23 | isHoverInput.value = false;
24 | }
25 | }, [rive, isHoverInput]);
26 |
27 | return (
28 |
29 |
33 | Explore
34 |
35 |
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/hero-animation/react/components/RiveHero.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState, MouseEvent, MouseEventHandler } from "react";
4 | import {
5 | useRive,
6 | useStateMachineInput,
7 | Layout,
8 | Fit,
9 | Alignment,
10 | } from "@rive-app/react-canvas";
11 |
12 | import useMediaQuery from "@/utils/useMediaBreakpoint";
13 | import usePrefersReducedMotion from "@/utils/usePrefersReducedMotion";
14 | import { throttle } from "@/utils/throttle";
15 | import RiveButton from "./RiveButton";
16 |
17 | // @refresh reset
18 |
19 | export default function RiveHero() {
20 | const [lastWidth, setLastWidth] = useState(0);
21 | const [lastHeight, setLastHeight] = useState(0);
22 |
23 | const lgQuery = useMediaQuery("only screen and (min-width: 1025px)");
24 | const prefersReducedMotion = usePrefersReducedMotion();
25 |
26 | const {
27 | rive,
28 | setCanvasRef,
29 | setContainerRef,
30 | canvas: canvasRef,
31 | container: canvasContainerRef,
32 | } = useRive(
33 | {
34 | src: "/hero_use_case.riv",
35 | artboard: "Hero Demo Listeners Resize",
36 | stateMachines: "State Machine 1",
37 | layout: new Layout({
38 | fit: Fit.Cover,
39 | alignment: Alignment.Center,
40 | }),
41 | autoplay: true,
42 | },
43 | {
44 | // We disable the automatic resize logic in Rive React runtime so we can manipulate
45 | // sizing manually, which is necessary for our adaptive layout effect
46 | shouldResizeCanvasToContainer: false,
47 | }
48 | );
49 |
50 | // On larger viewports, display the entire artboard while maintaining aspect ratio
51 | // On smaller viewports, cover the viewport with the artboard while maintaining aspect ratio
52 | // which may crop certain parts of the artboard
53 | useEffect(() => {
54 | if (rive) {
55 | if (lgQuery) {
56 | rive!.layout = new Layout({
57 | fit: Fit.Contain,
58 | alignment: Alignment.Center,
59 | });
60 | } else {
61 | rive!.layout = new Layout({
62 | fit: Fit.Cover,
63 | alignment: Alignment.Center,
64 | });
65 | }
66 | }
67 | }, [rive, lgQuery]);
68 |
69 | const numX = useStateMachineInput(rive, "State Machine 1", "numX", 50);
70 | const numY = useStateMachineInput(rive, "State Machine 1", "numY", 50);
71 | const numSize = useStateMachineInput(rive, "State Machine 1", "numSize", 0);
72 |
73 | // Pause the animation when the user prefers reduced motion
74 | useEffect(() => {
75 | if (rive) {
76 | prefersReducedMotion ? rive.pause() : rive.play();
77 | }
78 | }, [rive, prefersReducedMotion]);
79 |
80 | // Resize the canvas to match its parent container size. Additionally, once the viewport gets below
81 | // our lg scale threshold, scale the `numSize` input to achieve an adaptive layout effect.
82 | useEffect(() => {
83 | if (rive && canvasRef && canvasContainerRef) {
84 | const resizeObserver = new ResizeObserver(
85 | throttle(() => {
86 | //Get the block size
87 | if (rive && canvasContainerRef) {
88 | const newWidth = canvasContainerRef.clientWidth;
89 | const newHeight = canvasContainerRef.clientHeight;
90 | // From 500px to 1200px, scale the numSize input on a scale of 0-100%
91 | if (newWidth <= 1200 && numSize) {
92 | const resizeRange = 1200 - 500;
93 | numSize!.value = ((1200 - newWidth) / resizeRange) * 100;
94 | }
95 | const dpr = window.devicePixelRatio;
96 | if (
97 | canvasRef &&
98 | (lastWidth !== newWidth || lastHeight !== newHeight)
99 | ) {
100 | const newCanvasWidth = dpr * newWidth;
101 | const newCanvasHeight = dpr * newHeight;
102 | canvasRef.width = newCanvasWidth;
103 | canvasRef.height = newCanvasHeight;
104 | setLastWidth(newCanvasWidth);
105 | setLastHeight(newCanvasHeight);
106 | canvasRef.style.width = `${newWidth}px`;
107 | canvasRef.style.height = `${newHeight}px`;
108 | rive!.resizeToCanvas();
109 | rive!.startRendering();
110 | }
111 | }
112 | }, 0)
113 | );
114 |
115 | resizeObserver.observe(canvasContainerRef);
116 |
117 | return () => resizeObserver.unobserve(canvasRef);
118 | }
119 | // numSize does not need to be added because we're simply setting its internal value
120 | // rather than using any reactive state
121 | // eslint-disable-next-line react-hooks/exhaustive-deps
122 | }, [rive, canvasRef, canvasContainerRef, lastWidth, lastHeight]);
123 |
124 | // Drive the mouse positon inputs for the state machine based on cursor mouse movement position
125 | const onMouseMove: MouseEventHandler = (
126 | e: MouseEvent
127 | ) => {
128 | if (!numX || !numY) {
129 | return;
130 | }
131 | const maxWidth = window.innerWidth;
132 | const maxHeight = window.innerHeight;
133 | numX.value = (e.clientX / maxWidth) * 100;
134 | numY.value = 100 - (e.clientY / maxHeight) * 100;
135 | };
136 |
137 | return (
138 |
144 |
150 |
151 |
152 | );
153 | }
154 |
--------------------------------------------------------------------------------
/hero-animation/react/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | headers: async () => [
4 | {
5 | // cache riv files in the public folder
6 | source: "/:all*(riv)",
7 | locale: false,
8 | headers: [
9 | {
10 | key: "Cache-Control",
11 | value: "public, max-age=31536000",
12 | },
13 | ],
14 | },
15 | ],
16 | webpack: (config, options) => {
17 | // config.experiments = {
18 | // ...config.experiments,
19 | // asyncWebAssembly: true,
20 | // futureDefaults: true,
21 | // };
22 | config.module.rules.push({
23 | test: /\.wasm$/,
24 | use: [
25 | {
26 | loader: "url-loader",
27 | },
28 | ],
29 | });
30 | // config.module.rules.push({
31 | // test: /\.wasm$/,
32 | // use: [
33 | // {
34 | // loader: "file-loader",
35 | // },
36 | // ],
37 | // });
38 |
39 | return config;
40 | },
41 | };
42 |
43 | const withBundleAnalyzer = require("@next/bundle-analyzer")({
44 | enabled: process.env.ANALYZE === "true",
45 | });
46 |
47 | module.exports = withBundleAnalyzer(nextConfig);
48 |
--------------------------------------------------------------------------------
/hero-animation/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "explore-hero-landing",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "analyze": "ANALYZE=true next build"
11 | },
12 | "dependencies": {
13 | "@rive-app/react-canvas": "^3.0.52",
14 | "@types/node": "20.1.0",
15 | "@types/react": "18.2.6",
16 | "@types/react-dom": "18.2.4",
17 | "autoprefixer": "10.4.14",
18 | "eslint": "8.40.0",
19 | "eslint-config-next": "13.4.1",
20 | "next": "14.2.26",
21 | "postcss": "8.4.23",
22 | "react": "18.2.0",
23 | "react-dom": "18.2.0",
24 | "tailwindcss": "3.3.2",
25 | "typescript": "5.0.4"
26 | },
27 | "devDependencies": {
28 | "@next/bundle-analyzer": "^13.4.2",
29 | "cache-loader": "^4.1.0",
30 | "cross-env": "^7.0.3",
31 | "file-loader": "^6.2.0",
32 | "url-loader": "^4.1.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/hero-animation/react/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/hero-animation/react/public/hero_use_case.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/hero-animation/react/public/hero_use_case.riv
--------------------------------------------------------------------------------
/hero-animation/react/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/hero-animation/react/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/hero-animation/react/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
5 | './components/**/*.{js,ts,jsx,tsx,mdx}',
6 | './app/**/*.{js,ts,jsx,tsx,mdx}',
7 | ],
8 | theme: {
9 | extend: {
10 | backgroundImage: {
11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
12 | 'gradient-conic':
13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
14 | },
15 | },
16 | },
17 | plugins: [],
18 | }
19 |
--------------------------------------------------------------------------------
/hero-animation/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/hero-animation/react/utils/throttle.ts:
--------------------------------------------------------------------------------
1 | export function throttle(f: Function, delay: number) {
2 | let timer = 0;
3 | return function (this: Function, ...args: any) {
4 | clearTimeout(timer);
5 | timer = window.setTimeout(() => f.apply(this, args), delay);
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/hero-animation/react/utils/useMediaBreakpoint.ts:
--------------------------------------------------------------------------------
1 | // Reference: https://usehooks-ts.com/react-hook/use-media-query
2 | import { useEffect, useState } from 'react'
3 |
4 | function useMediaQuery(query: string): boolean {
5 | const getMatches = (query: string): boolean => {
6 | // Prevents SSR issues
7 | if (typeof window !== 'undefined') {
8 | return window.matchMedia(query).matches
9 | }
10 | return false
11 | }
12 |
13 | const [matches, setMatches] = useState(getMatches(query))
14 |
15 | function handleChange() {
16 | setMatches(getMatches(query))
17 | }
18 |
19 | useEffect(() => {
20 | const matchMedia = window.matchMedia(query)
21 |
22 | // Triggered at the first client-side load and if query changes
23 | handleChange()
24 |
25 | // Listen matchMedia
26 | if (matchMedia.addListener) {
27 | matchMedia.addListener(handleChange)
28 | } else {
29 | matchMedia.addEventListener('change', handleChange)
30 | }
31 |
32 | return () => {
33 | if (matchMedia.removeListener) {
34 | matchMedia.removeListener(handleChange)
35 | } else {
36 | matchMedia.removeEventListener('change', handleChange)
37 | }
38 | }
39 | // eslint-disable-next-line react-hooks/exhaustive-deps
40 | }, [query])
41 |
42 | return matches
43 | }
44 |
45 | export default useMediaQuery;
46 |
--------------------------------------------------------------------------------
/hero-animation/react/utils/usePrefersReducedMotion.ts:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from 'react';
2 |
3 | // TODO: Remove this once its incorporated in rive-react
4 | export default function usePrefersReducedMotion(): boolean {
5 | const [prefersReducedMotion, setPrefersReducedMotion] =
6 | useState(false);
7 |
8 | useEffect(() => {
9 | const canListen = typeof window !== 'undefined' && 'matchMedia' in window;
10 | if (!canListen) {
11 | return;
12 | }
13 |
14 | const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
15 | function updatePrefersReducedMotion() {
16 | setPrefersReducedMotion(() => mediaQuery.matches);
17 | }
18 | mediaQuery.addEventListener('change', updatePrefersReducedMotion);
19 | updatePrefersReducedMotion();
20 | return () =>
21 | mediaQuery.removeEventListener('change', updatePrefersReducedMotion);
22 | }, []);
23 |
24 | return prefersReducedMotion;
25 | }
--------------------------------------------------------------------------------
/pull-to-refresh/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/.DS_Store
--------------------------------------------------------------------------------
/pull-to-refresh/README.MD:
--------------------------------------------------------------------------------
1 | ## Rive: Pull to Refresh
2 |
3 | This folder contains examples that demonstrate how to create a pull to refresh experience with Rive on various different platforms.
4 |
5 | See this [video playlist](https://www.youtube.com/playlist?list=PLujDTZWVDSsHff0U3Q5Wo7uImQpGn4tMd) for tutorials on how to build this animation and integrate it on the listed platforms.
6 |
7 | ### Platforms (Code Samples)
8 |
9 | - iOS (SwiftUI): [code](ios/), [video tutorial](https://www.youtube.com/watch?v=j5ISLdXhU6I)
10 | - Android (Jetpack Compose): [code](android/), [video tutorial](https://youtu.be/5JiBNXAR6sg)
11 | - Flutter: [code](flutter/), [video tutorial](https://youtu.be/IlZpFIKOI60)
12 |
13 | See the [Rive documentation](https://rive.app/docs/runtimes/getting-started) for additional runtime information.
14 |
15 | ### Other Use Cases
16 |
17 | - See the [Rive Use Cases page](https://rive.app/use-cases) for more examples
18 | - Additional code resources can be found at the [root repository](https://github.com/rive-app/rive-use-cases/)
19 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/.name:
--------------------------------------------------------------------------------
1 | Pull To Refresh Rive
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'com.example.pulltorefreshrive'
8 | compileSdk 33
9 |
10 | defaultConfig {
11 | applicationId "com.example.pulltorefreshrive"
12 | minSdk 26
13 | targetSdk 33
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | }
36 | buildFeatures {
37 | compose true
38 | }
39 | composeOptions {
40 | kotlinCompilerExtensionVersion '1.4.0'
41 | }
42 | packagingOptions {
43 | resources {
44 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
45 | }
46 | }
47 | }
48 |
49 | dependencies {
50 | implementation 'com.google.accompanist:accompanist-systemuicontroller:0.31.3-beta'
51 | implementation 'app.rive:rive-android:5.0.1'
52 | implementation "androidx.startup:startup-runtime:1.1.1"
53 | // During initialization, you may need to add a dependency
54 | // for Jetpack Startup
55 | implementation 'androidx.core:core-ktx:1.7.0'
56 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
57 | implementation 'androidx.activity:activity-compose:1.3.1'
58 | implementation "androidx.compose.ui:ui:$compose_ui_version"
59 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
60 | implementation 'androidx.compose.material:material:1.3.0'
61 | implementation 'androidx.compose.material3:material3:1.1.0'
62 | testImplementation 'junit:junit:4.13.2'
63 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
64 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
65 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
66 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
67 | debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
68 | }
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/androidTest/java/com/example/pulltorefreshrive/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.pulltorefreshrive
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.pulltorefreshrive", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/java/com/example/pulltorefreshrive/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.pulltorefreshrive
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.annotation.RawRes
7 | import androidx.compose.animation.core.Spring
8 | import androidx.compose.foundation.background
9 | import androidx.compose.foundation.layout.*
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.viewinterop.AndroidView
18 | import app.rive.runtime.kotlin.RiveAnimationView
19 |
20 | import androidx.compose.animation.core.animate
21 | import androidx.compose.animation.core.animateFloatAsState
22 | import androidx.compose.animation.core.spring
23 | import androidx.compose.foundation.isSystemInDarkTheme
24 | import androidx.compose.foundation.lazy.LazyColumn
25 | import androidx.compose.foundation.shape.RoundedCornerShape
26 | import androidx.compose.material.*
27 | import androidx.compose.runtime.*
28 | import kotlinx.coroutines.delay
29 | import kotlinx.coroutines.launch
30 |
31 | import androidx.compose.material.pullrefresh.pullRefresh
32 | import androidx.compose.runtime.getValue
33 | import androidx.compose.runtime.rememberCoroutineScope
34 | import androidx.compose.runtime.setValue
35 | import androidx.compose.ui.platform.LocalDensity
36 | import app.rive.runtime.kotlin.core.Fit
37 | import com.google.accompanist.systemuicontroller.rememberSystemUiController
38 |
39 | class MainActivity : ComponentActivity() {
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState)
42 |
43 |
44 | setContent {
45 | Surface(
46 | modifier = Modifier.fillMaxSize(),
47 | color = Color(0xFF001C1C)
48 | ) {
49 | CustomPullRefreshSample(height = 200f)
50 | }
51 | }
52 | }
53 | }
54 |
55 | @Composable
56 | fun ComposableRiveAnimationView(
57 | modifier: Modifier = Modifier,
58 | @RawRes animation: Int,
59 | stateMachineName: String? = null,
60 | alignment: app.rive.runtime.kotlin.core.Alignment = app.rive.runtime.kotlin.core.Alignment.CENTER,
61 | fit: Fit = Fit.CONTAIN,
62 | onInit: (RiveAnimationView) -> Unit
63 | ) {
64 | AndroidView(
65 | modifier = modifier,
66 | factory = { context ->
67 | RiveAnimationView(context).also {
68 | it.setRiveResource(
69 | resId = animation,
70 | stateMachineName = stateMachineName,
71 | alignment = alignment,
72 | fit = fit,
73 |
74 | )
75 | }
76 | },
77 | update = { view -> onInit(view) }
78 | )
79 | }
80 |
81 | @Composable
82 | @OptIn(ExperimentalMaterialApi::class)
83 | fun CustomPullRefreshSample(height: Float) {
84 | // Set system UI color
85 | val systemUiController = rememberSystemUiController()
86 | val useDarkIcons = !isSystemInDarkTheme()
87 | DisposableEffect(systemUiController, useDarkIcons) {
88 | systemUiController.setSystemBarsColor(
89 | color = Color(0xFF003C3D),
90 | )
91 | onDispose {}
92 | }
93 |
94 |
95 | // Variables
96 | val refreshScope = rememberCoroutineScope()
97 | val threshold = with(LocalDensity.current) { height.dp.toPx() }
98 |
99 | var refreshing by remember { mutableStateOf(false) }
100 | var itemCount by remember { mutableStateOf(15) }
101 | var currentDistance by remember { mutableStateOf(0f) }
102 | var animation: RiveAnimationView? = null
103 |
104 | val progress = currentDistance / threshold
105 |
106 | val scrollValue by animateFloatAsState(
107 | targetValue = height * progress,
108 | animationSpec = spring(
109 | dampingRatio = Spring.DampingRatioNoBouncy,
110 | stiffness = Spring.StiffnessHigh,
111 | ),
112 | finishedListener = { value ->
113 | if (value == 0f) {
114 | // Only reset the animation when not visible
115 | animation?.reset()
116 | animation?.pause()
117 | }
118 | }
119 | )
120 |
121 | // Methods
122 | fun refresh() = refreshScope.launch {
123 | refreshing = true
124 | // This simulates loading data with delays. The delays are added to ensure the different
125 | // states of the animation has time to play
126 | animation?.fireState("numberSimulation", "advance")
127 | delay(1500) // Some future to complete - loading data for example
128 | animation?.fireState("numberSimulation", "advance")
129 | delay(1500)
130 |
131 | // Once complete set the target value back to 0, and refreshing false.
132 | animate(initialValue = currentDistance, targetValue = 0f) { value, _ ->
133 | currentDistance = value
134 | }
135 | refreshing = false
136 | }
137 |
138 | fun onPull(pullDelta: Float): Float = when {
139 | refreshing -> 0f
140 | else -> {
141 | val newOffset = (currentDistance + pullDelta).coerceAtLeast(0f)
142 | val dragConsumed = newOffset - currentDistance
143 |
144 | currentDistance = newOffset
145 | animation?.setNumberState("numberSimulation", "pull", progress * 100)
146 | dragConsumed
147 | }
148 | }
149 |
150 | fun onRelease(velocity: Float): Float {
151 | if (refreshing) return 0f // Already refreshing - don't call refresh again.
152 | var targetValue = 0f;
153 | if (currentDistance > threshold) {
154 | targetValue = threshold
155 | refresh()
156 | }
157 |
158 | refreshScope.launch {
159 | animate(initialValue = currentDistance, targetValue = targetValue) { value, _ ->
160 | currentDistance = value
161 | }
162 | }
163 |
164 | // Only consume if the fling is downwards and the indicator is visible
165 | return if (velocity > 0f && currentDistance > 0f) {
166 | velocity
167 | } else {
168 | 0f
169 | }
170 | }
171 |
172 | Box(Modifier.pullRefresh(::onPull, ::onRelease)) {
173 | if (scrollValue > 0) {
174 | Box(
175 | Modifier
176 | .height(height.dp)
177 | .offset(
178 | 0.dp,
179 | (-(height / 2 - scrollValue / 2).coerceIn(
180 | maximumValue = height,
181 | minimumValue = 0f
182 | )).dp
183 | )
184 | .fillMaxWidth()
185 | .align(Alignment.TopCenter)
186 | ) {
187 | ComposableRiveAnimationView(
188 | animation = R.raw.pull_to_refresh_use_case,
189 | stateMachineName = "numberSimulation",
190 | fit = Fit.COVER,
191 | ) { view ->
192 | animation = view
193 | }
194 | }
195 | }
196 |
197 | LazyColumn(
198 | modifier = Modifier
199 | .offset(y = (scrollValue).dp)
200 | .fillMaxHeight()
201 | .background(Color(0xFF001C1C))
202 | ) {
203 | items(itemCount) {
204 | ListItemUI()
205 | }
206 |
207 | }
208 | }
209 | }
210 |
211 | @Composable
212 | fun ListItemUI() {
213 | Box(
214 | modifier = Modifier
215 | .padding(16.dp)
216 | .fillMaxWidth()
217 | .height(100.dp)
218 | .background(color = Color(0xFF003C3D), shape = RoundedCornerShape(8.dp))
219 | ) {
220 | Box(
221 | modifier = Modifier
222 | .padding(16.dp)
223 | .align(Alignment.CenterStart)
224 | .size(64.dp)
225 | .background(color = Color.DarkGray, shape = RoundedCornerShape(4.dp))
226 | )
227 |
228 | Box(
229 | modifier = Modifier
230 | .padding(start = 88.dp, top = 0.dp, end = 16.dp, bottom = 16.dp)
231 | .align(alignment = Alignment.Center)
232 | .fillMaxWidth()
233 | .height(16.dp)
234 | .background(color = Color.DarkGray, shape = RoundedCornerShape(4.dp))
235 | )
236 |
237 | Box(
238 | modifier = Modifier
239 | .padding(start = 88.dp, top = 40.dp, end = 16.dp)
240 | .align(Alignment.Center)
241 | .fillMaxWidth()
242 | .height(16.dp)
243 | .background(color = Color.DarkGray, shape = RoundedCornerShape(4.dp))
244 | )
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/java/com/example/pulltorefreshrive/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.example.pulltorefreshrive.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/java/com/example/pulltorefreshrive/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.example.pulltorefreshrive.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/java/com/example/pulltorefreshrive/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.example.pulltorefreshrive.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorPalette = darkColors(
10 | primary = Purple200,
11 | primaryVariant = Purple700,
12 | secondary = Teal200
13 | )
14 |
15 | private val LightColorPalette = lightColors(
16 | primary = Purple500,
17 | primaryVariant = Purple700,
18 | secondary = Teal200
19 |
20 | /* Other default colors to override
21 | background = Color.White,
22 | surface = Color.White,
23 | onPrimary = Color.White,
24 | onSecondary = Color.Black,
25 | onBackground = Color.Black,
26 | onSurface = Color.Black,
27 | */
28 | )
29 |
30 | @Composable
31 | fun PullToRefreshRiveTheme(
32 | darkTheme: Boolean = isSystemInDarkTheme(),
33 | content: @Composable () -> Unit
34 | ) {
35 | val colors = if (darkTheme) {
36 | DarkColorPalette
37 | } else {
38 | LightColorPalette
39 | }
40 |
41 | MaterialTheme(
42 | colors = colors,
43 | typography = Typography,
44 | shapes = Shapes,
45 | content = content
46 | )
47 | }
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/java/com/example/pulltorefreshrive/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.example.pulltorefreshrive.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/raw/pull_to_refresh_use_case.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/app/src/main/res/raw/pull_to_refresh_use_case.riv
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Pull To Refresh Rive
3 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/app/src/test/java/com/example/pulltorefreshrive/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.pulltorefreshrive
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/pull-to-refresh/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | compose_ui_version = '1.2.0'
4 | }
5 | }// Top-level build file where you can add configuration options common to all sub-projects/modules.
6 | plugins {
7 | id 'com.android.application' version '7.4.2' apply false
8 | id 'com.android.library' version '7.4.2' apply false
9 | id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
10 | }
--------------------------------------------------------------------------------
/pull-to-refresh/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/pull-to-refresh/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/pull-to-refresh/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed May 24 12:12:59 CEST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/pull-to-refresh/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Pull To Refresh Rive"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
17 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
18 | - platform: android
19 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
20 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
21 | - platform: ios
22 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
23 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
24 | - platform: linux
25 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
26 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
27 | - platform: macos
28 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
29 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
30 | - platform: web
31 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
32 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
33 | - platform: windows
34 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
35 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/README.md:
--------------------------------------------------------------------------------
1 | # pull_to_refresh_rive_flutter
2 |
3 | A new Flutter project.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 |
14 | For help getting started with Flutter development, view the
15 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | namespace "com.example.pull_to_refresh_rive_flutter"
30 | compileSdkVersion flutter.compileSdkVersion
31 | ndkVersion flutter.ndkVersion
32 |
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_1_8
35 | targetCompatibility JavaVersion.VERSION_1_8
36 | }
37 |
38 | kotlinOptions {
39 | jvmTarget = '1.8'
40 | }
41 |
42 | sourceSets {
43 | main.java.srcDirs += 'src/main/kotlin'
44 | }
45 |
46 | defaultConfig {
47 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
48 | applicationId "com.example.pull_to_refresh_rive_flutter"
49 | // You can update the following values to match your application needs.
50 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
51 | minSdkVersion flutter.minSdkVersion
52 | targetSdkVersion flutter.targetSdkVersion
53 | versionCode flutterVersionCode.toInteger()
54 | versionName flutterVersionName
55 | }
56 |
57 | buildTypes {
58 | release {
59 | // TODO: Add your own signing config for the release build.
60 | // Signing with the debug keys for now, so `flutter run --release` works.
61 | signingConfig signingConfigs.debug
62 | }
63 | }
64 | }
65 |
66 | flutter {
67 | source '../..'
68 | }
69 |
70 | dependencies {
71 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
72 | }
73 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
14 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/kotlin/com/example/pull_to_refresh_rive_flutter/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.pull_to_refresh_rive_flutter
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.3.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | tasks.register("clean", Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
6 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/assets/pull_to_refresh_use_case.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/assets/pull_to_refresh_use_case.riv
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '11.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - rive_common (0.0.1):
4 | - Flutter
5 |
6 | DEPENDENCIES:
7 | - Flutter (from `Flutter`)
8 | - rive_common (from `.symlinks/plugins/rive_common/ios`)
9 |
10 | EXTERNAL SOURCES:
11 | Flutter:
12 | :path: Flutter
13 | rive_common:
14 | :path: ".symlinks/plugins/rive_common/ios"
15 |
16 | SPEC CHECKSUMS:
17 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
18 | rive_common: 60ae7896ab40f9513974f36f015de33f70d2c5c5
19 |
20 | PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189
21 |
22 | COCOAPODS: 1.11.3
23 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Pull To Refresh Rive Flutter
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | pull_to_refresh_rive_flutter
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:easy_refresh/easy_refresh.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:pull_to_refresh_rive_flutter/skeleton_item.dart';
5 | import 'package:rive/rive.dart';
6 |
7 | const kDefaultArcheryTriggerOffset = 200.0;
8 |
9 | void main() => runApp(const MyApp());
10 |
11 | class MyApp extends StatelessWidget {
12 | const MyApp({super.key});
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return MaterialApp(
17 | title: 'Material App',
18 | themeMode: ThemeMode.dark,
19 | darkTheme: ThemeData.dark(useMaterial3: true).copyWith(
20 | colorScheme: ColorScheme.fromSeed(
21 | seedColor: Colors.green, brightness: Brightness.dark),
22 | ),
23 | home: const ArcheryPage(),
24 | );
25 | }
26 | }
27 |
28 | class ArcheryPage extends StatefulWidget {
29 | const ArcheryPage({Key? key}) : super(key: key);
30 |
31 | @override
32 | State createState() => _ArcheryPageState();
33 | }
34 |
35 | class _ArcheryPageState extends State {
36 | int _count = 10;
37 | late EasyRefreshController _controller;
38 |
39 | @override
40 | void initState() {
41 | super.initState();
42 | _controller = EasyRefreshController(
43 | controlFinishRefresh: true,
44 | controlFinishLoad: true,
45 | );
46 | }
47 |
48 | @override
49 | void dispose() {
50 | _controller.dispose();
51 | super.dispose();
52 | }
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | return Scaffold(
57 | body: EasyRefresh(
58 | controller: _controller,
59 | header: const ArcheryHeader(
60 | position: IndicatorPosition.locator,
61 | processedDuration: Duration(seconds: 1),
62 | ),
63 | onRefresh: () async {
64 | await Future.delayed(const Duration(seconds: 2));
65 | if (!mounted) {
66 | return;
67 | }
68 | setState(() {
69 | _count = 10;
70 | });
71 | _controller.finishRefresh();
72 | _controller.resetFooter();
73 | },
74 | child: CustomScrollView(
75 | slivers: [
76 | const SliverAppBar(
77 | title: Text('Shooting practice'),
78 | pinned: true,
79 | ),
80 | const HeaderLocator.sliver(),
81 | SliverList(
82 | delegate: SliverChildBuilderDelegate(
83 | (context, index) {
84 | return const SkeletonItem();
85 | },
86 | childCount: _count,
87 | ),
88 | ),
89 | ],
90 | ),
91 | ),
92 | );
93 | }
94 | }
95 |
96 | class ArcheryHeader extends Header {
97 | const ArcheryHeader({
98 | super.clamping = false,
99 | super.triggerOffset = kDefaultArcheryTriggerOffset,
100 | super.position = IndicatorPosition.above,
101 | super.processedDuration = Duration.zero,
102 | super.springRebound = false,
103 | super.hapticFeedback = false,
104 | super.safeArea = false,
105 | super.spring,
106 | super.readySpringBuilder,
107 | super.frictionFactor,
108 | super.infiniteOffset,
109 | super.hitOver,
110 | super.infiniteHitOver,
111 | });
112 |
113 | @override
114 | Widget build(BuildContext context, IndicatorState state) {
115 | return _ArcheryIndicator(
116 | state: state,
117 | reverse: state.reverse,
118 | );
119 | }
120 | }
121 |
122 | class _ArcheryIndicator extends StatefulWidget {
123 | /// Indicator properties and state.
124 | final IndicatorState state;
125 |
126 | /// True for up and left.
127 | /// False for down and right.
128 | final bool reverse;
129 |
130 | const _ArcheryIndicator({
131 | Key? key,
132 | required this.state,
133 | required this.reverse,
134 | }) : super(key: key);
135 |
136 | @override
137 | State<_ArcheryIndicator> createState() => _ArcheryIndicatorState();
138 | }
139 |
140 | class _ArcheryIndicatorState extends State<_ArcheryIndicator> {
141 | double get _offset => widget.state.offset;
142 | IndicatorMode get _mode => widget.state.mode;
143 | double get _actualTriggerOffset => widget.state.actualTriggerOffset;
144 |
145 | SMINumber? pull;
146 | SMITrigger? advance;
147 | StateMachineController? controller;
148 |
149 | @override
150 | void initState() {
151 | super.initState();
152 | widget.state.notifier.addModeChangeListener(_onModeChange);
153 | _loadRiveFile();
154 | }
155 |
156 | RiveFile? _riveFile;
157 | void _loadRiveFile() {
158 | rootBundle.load('assets/pull_to_refresh_use_case.riv').then(
159 | (data) async {
160 | // Load the RiveFile from the binary data.
161 | setState(() {
162 | _riveFile = RiveFile.import(data);
163 | });
164 | },
165 | );
166 | }
167 |
168 | @override
169 | void dispose() {
170 | widget.state.notifier.removeModeChangeListener(_onModeChange);
171 | controller?.dispose();
172 | // _riveFile = null;
173 | super.dispose();
174 | }
175 |
176 | /// Mode change listener.
177 | void _onModeChange(IndicatorMode mode, double offset) {
178 | // print(mode);
179 | switch (mode) {
180 | case IndicatorMode.drag:
181 | controller?.isActive = true;
182 | case IndicatorMode.ready:
183 | advance?.fire();
184 | case IndicatorMode.processed:
185 | advance?.fire();
186 | default:
187 | break;
188 | }
189 | }
190 |
191 | @override
192 | Widget build(BuildContext context) {
193 | if (_mode == IndicatorMode.drag || _mode == IndicatorMode.armed) {
194 | final percentage = (_offset / _actualTriggerOffset).clamp(0.0, 1.1) * 100;
195 | pull?.value = percentage;
196 | }
197 | return SizedBox(
198 | width: double.infinity,
199 | height: _offset,
200 | child: (_offset > 0 && _riveFile != null)
201 | ? RiveAnimation.direct(
202 | _riveFile!,
203 | artboard: 'Bullseye',
204 | fit: BoxFit.cover,
205 | onInit: (artboard) {
206 | controller = StateMachineController.fromArtboard(
207 | artboard, 'numberSimulation')!;
208 | controller?.isActive = false;
209 | if (controller == null) {
210 | throw Exception(
211 | 'Unable to initialize state machine controller');
212 | }
213 | artboard.addController(controller!);
214 | pull = controller!.findInput('pull') as SMINumber;
215 | advance = controller!.findInput('advance') as SMITrigger;
216 | },
217 | )
218 | : const SizedBox.shrink(),
219 | );
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/lib/skeleton_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// Skeleton list item.
4 | class SkeletonItem extends StatelessWidget {
5 | /// Scrollable direction.
6 | final Axis direction;
7 |
8 | const SkeletonItem({
9 | Key? key,
10 | this.direction = Axis.vertical,
11 | }) : super(key: key);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | final themeData = Theme.of(context);
16 | final backgroundColor = themeData.colorScheme.surfaceVariant;
17 | final foregroundColor = themeData.colorScheme.surface;
18 | if (direction == Axis.vertical) {
19 | return Card(
20 | elevation: 0,
21 | color: backgroundColor,
22 | child: Padding(
23 | padding: const EdgeInsets.all(16),
24 | child: Row(
25 | crossAxisAlignment: CrossAxisAlignment.start,
26 | children: [
27 | Container(
28 | margin: const EdgeInsets.only(right: 16),
29 | height: 80,
30 | width: 80,
31 | color: foregroundColor,
32 | ),
33 | Expanded(
34 | child: Column(
35 | crossAxisAlignment: CrossAxisAlignment.start,
36 | children: [
37 | Container(
38 | margin: const EdgeInsets.only(top: 8, right: 24),
39 | height: 12,
40 | width: double.infinity,
41 | constraints: const BoxConstraints(
42 | maxWidth: 200,
43 | ),
44 | color: foregroundColor,
45 | ),
46 | Container(
47 | margin: const EdgeInsets.only(top: 16),
48 | height: 12,
49 | width: 80,
50 | color: foregroundColor,
51 | ),
52 | Container(
53 | margin: const EdgeInsets.only(top: 8),
54 | height: 12,
55 | width: 80,
56 | color: foregroundColor,
57 | ),
58 | ],
59 | ),
60 | ),
61 | ],
62 | ),
63 | ),
64 | );
65 | }
66 | return Card(
67 | elevation: 0,
68 | color: backgroundColor,
69 | child: Padding(
70 | padding: const EdgeInsets.all(16),
71 | child: Column(
72 | crossAxisAlignment: CrossAxisAlignment.start,
73 | children: [
74 | Container(
75 | margin: const EdgeInsets.only(bottom: 16),
76 | height: 80,
77 | width: 80,
78 | color: foregroundColor,
79 | ),
80 | Expanded(
81 | child: Row(
82 | crossAxisAlignment: CrossAxisAlignment.start,
83 | children: [
84 | Container(
85 | margin: const EdgeInsets.only(left: 8, bottom: 24),
86 | width: 12,
87 | height: double.infinity,
88 | constraints: const BoxConstraints(
89 | maxHeight: 200,
90 | ),
91 | color: foregroundColor,
92 | ),
93 | Container(
94 | margin: const EdgeInsets.only(left: 16),
95 | width: 12,
96 | height: 80,
97 | color: foregroundColor,
98 | ),
99 | Container(
100 | margin: const EdgeInsets.only(left: 8),
101 | width: 12,
102 | height: 80,
103 | color: foregroundColor,
104 | ),
105 | ],
106 | ),
107 | ),
108 | ],
109 | ),
110 | ),
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: pull_to_refresh_rive_flutter
2 | description: A new Flutter project.
3 | # The following line prevents the package from being accidentally published to
4 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6 |
7 | # The following defines the version and build number for your application.
8 | # A version number is three numbers separated by dots, like 1.2.43
9 | # followed by an optional build number separated by a +.
10 | # Both the version and the builder number may be overridden in flutter
11 | # build by specifying --build-name and --build-number, respectively.
12 | # In Android, build-name is used as versionName while build-number used as versionCode.
13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15 | # Read more about iOS versioning at
16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 | # In Windows, build-name is used as the major, minor, and patch parts
18 | # of the product and file versions while build-number is used as the build suffix.
19 | version: 1.0.0+1
20 |
21 | environment:
22 | sdk: '>=3.0.2 <4.0.0'
23 |
24 | # Dependencies specify other packages that your package needs in order to work.
25 | # To automatically upgrade your package dependencies to the latest versions
26 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
27 | # dependencies can be manually updated by changing the version numbers below to
28 | # the latest version available on pub.dev. To see which dependencies have newer
29 | # versions available, run `flutter pub outdated`.
30 | dependencies:
31 | flutter:
32 | sdk: flutter
33 | easy_refresh: ^3.3.2+1
34 | rive: ^0.11.1
35 |
36 |
37 | # The following adds the Cupertino Icons font to your application.
38 | # Use with the CupertinoIcons class for iOS style icons.
39 | cupertino_icons: ^1.0.2
40 |
41 | dev_dependencies:
42 | flutter_test:
43 | sdk: flutter
44 |
45 | # The "flutter_lints" package below contains a set of recommended lints to
46 | # encourage good coding practices. The lint set provided by the package is
47 | # activated in the `analysis_options.yaml` file located at the root of your
48 | # package. See that file for information about deactivating specific lint
49 | # rules and activating additional ones.
50 | flutter_lints: ^2.0.0
51 |
52 | # For information on the generic Dart part of this file, see the
53 | # following page: https://dart.dev/tools/pub/pubspec
54 |
55 | # The following section is specific to Flutter packages.
56 | flutter:
57 |
58 | # The following line ensures that the Material Icons font is
59 | # included with your application, so that you can use the icons in
60 | # the material Icons class.
61 | uses-material-design: true
62 |
63 | # To add assets to your application, add an assets section, like this:
64 | assets:
65 | - assets/
66 | # - images/a_dot_burr.jpeg
67 | # - images/a_dot_ham.jpeg
68 |
69 | # An image asset can refer to one or more resolution-specific "variants", see
70 | # https://flutter.dev/assets-and-images/#resolution-aware
71 |
72 | # For details regarding adding assets from package dependencies, see
73 | # https://flutter.dev/assets-and-images/#from-packages
74 |
75 | # To add custom fonts to your application, add a fonts section here,
76 | # in this "flutter" section. Each entry in this list should have a
77 | # "family" key with the font family name, and a "fonts" key with a
78 | # list giving the asset and other descriptors for the font. For
79 | # example:
80 | # fonts:
81 | # - family: Schyler
82 | # fonts:
83 | # - asset: fonts/Schyler-Regular.ttf
84 | # - asset: fonts/Schyler-Italic.ttf
85 | # style: italic
86 | # - family: Trajan Pro
87 | # fonts:
88 | # - asset: fonts/TrajanPro.ttf
89 | # - asset: fonts/TrajanPro_Bold.ttf
90 | # weight: 700
91 | #
92 | # For details regarding fonts from package dependencies,
93 | # see https://flutter.dev/custom-fonts/#from-packages
94 |
--------------------------------------------------------------------------------
/pull-to-refresh/flutter/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:pull_to_refresh_rive_flutter/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "rive-ios",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/rive-app/rive-ios",
7 | "state" : {
8 | "revision" : "8c8d2b2081db05d7cfc04e04e9bfd44a756326fa",
9 | "version" : "4.0.3"
10 | }
11 | }
12 | ],
13 | "version" : 2
14 | }
15 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh.xcodeproj/project.xcworkspace/xcuserdata/gordon.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/ios/Rive Pull to Refresh.xcodeproj/project.xcworkspace/xcuserdata/gordon.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh.xcodeproj/xcuserdata/gordon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh.xcodeproj/xcuserdata/gordon.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Rive Pull to Refresh.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Rive Pull to Refresh
4 | //
5 | // Created by Peter G Hayes on 22/05/2023.
6 | //
7 |
8 | import SwiftUI
9 | import RiveRuntime
10 |
11 | struct ContentView: View {
12 | var body: some View {
13 | RivePullToRefreshView( content: {
14 | VStack {
15 | MockListTile()
16 | MockListTile()
17 | MockListTile()
18 | MockListTile()
19 | MockListTile()
20 | MockListTile()
21 | MockListTile()
22 | MockListTile()
23 | MockListTile()
24 | MockListTile()
25 | }
26 |
27 | }, onRefresh: {
28 | // TODO: complete future event
29 | try? await Task.sleep(nanoseconds: 3_000_000_000)
30 | })
31 | }
32 | }
33 |
34 | struct ContentView_Previews: PreviewProvider {
35 | static var previews: some View {
36 | ContentView()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/CustomRefreshView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomRefreshView.swift
3 | // Rive Pull to Refresh
4 | //
5 | // Created by Peter G Hayes on 22/05/2023.
6 | //
7 |
8 | import SwiftUI
9 | import RiveRuntime
10 |
11 | struct RivePullToRefreshView: View {
12 | var content: Content
13 | var showsIndicator: Bool
14 | var onRefresh: ()async->()
15 | var height: Double
16 |
17 |
18 | init(showsIndicator: Bool = false, height: Double = 200, @ViewBuilder content: @escaping ()-> Content, onRefresh: @escaping ()async->()) {
19 | self.showsIndicator = showsIndicator
20 | self.content = content()
21 | self.onRefresh = onRefresh
22 | self.height = height
23 | }
24 |
25 | @StateObject var riveViewModel = RiveViewModel(fileName: "pull_to_refresh_use_case", fit: .cover, autoPlay: false)
26 | @StateObject var scrollDelegate: ScrollViewModel = .init()
27 |
28 |
29 | var body: some View {
30 | ScrollView(.vertical, showsIndicators: showsIndicator) {
31 | VStack {
32 | if (scrollDelegate.progress > 0) {
33 | riveViewModel.view().background(.blue)
34 | .frame(height: 150 * scrollDelegate.progress)
35 | .offset(y: scrollDelegate.isEligible ? -(scrollDelegate.contentOffset < 0 ? 0 :scrollDelegate.contentOffset) : -(scrollDelegate.scrollOffset < 0 ? 0 : scrollDelegate.scrollOffset))
36 | }
37 |
38 | content
39 |
40 | }
41 | .offset(coordinateSpace: "SCROLL") { offset in
42 | // print(offset)
43 | // MARK: Storing content offset
44 | scrollDelegate.contentOffset = offset
45 |
46 | // MARK: Stopping the progress when it's eligible for refresh
47 | if !scrollDelegate.isEligible {
48 |
49 | var progress = offset / 150
50 | progress = (progress < 0 ? 0 : progress)
51 | progress = (progress > 1 ? 1 : progress)
52 | scrollDelegate.scrollOffset = offset
53 | scrollDelegate.progress = progress
54 | }
55 | let inputValue = offset / 150 * 100
56 | riveViewModel.setInput("pull", value: inputValue)
57 | // print(offset / 150 );
58 | // if (scrollDelegate.progress == 0) {
59 | // print("reseting")
60 | // riveViewModel.reset()
61 | // }
62 |
63 | /// Drag released and pull to refresh is not elibible
64 | if (scrollDelegate.progress == 0 && scrollDelegate.isEligible == false) {
65 | riveViewModel.reset()
66 | }
67 |
68 | if scrollDelegate.isEligible && !scrollDelegate.isRefreshing {
69 | scrollDelegate.isRefreshing = true
70 | // MARK: Haptic Feedback
71 |
72 | UIImpactFeedbackGenerator(style: .medium).impactOccurred()
73 | }
74 | }
75 | }
76 | .coordinateSpace(name: "SCROLL")
77 | .onAppear(perform: scrollDelegate.addGesture)
78 | .onDisappear(perform: scrollDelegate.removeGesture)
79 | .onChange(of: scrollDelegate.isRefreshing) { refreshing in
80 | // MARK: Calling Async method
81 | if refreshing{
82 | Task{
83 | riveViewModel.triggerInput("advance")
84 | let backTime = 0.25
85 | await onRefresh()
86 | // Let the animation finish
87 | riveViewModel.triggerInput("advance")
88 | try? await Task.sleep(for: .seconds(2))
89 | // MARK: After refresh done restting properties
90 | withAnimation(.easeInOut(duration: backTime)){
91 | scrollDelegate.progress = 0
92 | scrollDelegate.isEligible = false
93 | scrollDelegate.isRefreshing = false
94 | scrollDelegate.scrollOffset = 0
95 | }
96 |
97 | try? await Task.sleep(for: .seconds(backTime))
98 | riveViewModel.reset()
99 | }
100 | } else {
101 | print("not refreshing")
102 | }
103 | }
104 | }
105 | }
106 |
107 | struct CustomRefreshView_Previews: PreviewProvider {
108 | static var previews: some View {
109 | RivePullToRefreshView() {
110 | Rectangle()
111 | .fill(.green)
112 | .frame(height: 100)
113 | Rectangle()
114 | .fill(.blue)
115 | .frame(height: 100)
116 | Rectangle()
117 | .fill(.red)
118 | .frame(height: 100)
119 | } onRefresh: {
120 | try? await Task.sleep(nanoseconds: 3_000_000_000)
121 | }
122 | }
123 | }
124 |
125 | // MARK: For Simultanous Pan Gesture
126 | class ScrollViewModel: NSObject, ObservableObject, UIGestureRecognizerDelegate {
127 | // MARK: Properties
128 | @Published var isEligible: Bool = false
129 | @Published var isRefreshing: Bool = false
130 | // MARK: Offsets and Progress
131 | @Published var scrollOffset: CGFloat = 0
132 | @Published var contentOffset: CGFloat = 0
133 | @Published var progress: CGFloat = 0
134 |
135 | // MARK: Since we need to know when the user left the screen to start refresh
136 | // Adding pan gesture to UI Main Appliction Window
137 | // With simlutaneous Gesture
138 | // Thus it won't disturb SwiftUI Scroll's and Gesture's
139 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
140 | return true
141 | }
142 |
143 | // MARK: Adding Gesture
144 | func addGesture() {
145 | let panGesture = UIPanGestureRecognizer(target: self, action:
146 | #selector(onGestureChange(gesture:)))
147 | panGesture.delegate = self
148 |
149 | rootController().view.addGestureRecognizer(panGesture)
150 | }
151 |
152 | // MARK: Removing when leaving the view
153 | func removeGesture() {
154 | rootController().view.gestureRecognizers?.removeAll()
155 | }
156 |
157 | // MARK: Finding Root Controller
158 | func rootController() -> UIViewController{
159 | guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene
160 | else {
161 | return .init()
162 | }
163 |
164 | guard let root = screen.windows.first?.rootViewController else {
165 | return .init()
166 | }
167 |
168 | return root;
169 | }
170 |
171 | @objc
172 | func onGestureChange(gesture: UIPanGestureRecognizer) {
173 | if gesture.state == .cancelled || gesture.state == .ended {
174 | print("user released touch")
175 | // MARK: Your max scroll offset goes here
176 | if !isRefreshing{
177 | if scrollOffset > 150 {
178 | isEligible = true
179 | } else {
180 | isEligible = false
181 | }
182 | }
183 | }
184 | }
185 | }
186 |
187 |
188 | // MARK: Offset Modifier
189 | extension View {
190 | @ViewBuilder
191 | func offset(coordinateSpace: String, offset: @escaping (CGFloat)->()) ->some View {
192 | self
193 | .overlay {
194 | GeometryReader{geometry in
195 | let minY = geometry.frame(in: .named(coordinateSpace)).minY
196 | Color.clear
197 | .preference(key: OffsetKey.self, value: minY)
198 | .onPreferenceChange(OffsetKey.self) { value in
199 | offset(value)
200 | }
201 | }
202 | }
203 | }
204 | }
205 |
206 | // MARK: Offset Preference Key
207 | struct OffsetKey: PreferenceKey{
208 | static var defaultValue: CGFloat = 0
209 |
210 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
211 | value = nextValue()
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/MockListTile.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MockListTile.swift
3 | // Rive Pull to Refresh
4 | //
5 | // Created by Peter G Hayes on 23/05/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | import SwiftUI
11 |
12 | struct MockListTile: View {
13 | var body: some View {
14 | HStack {
15 | VStack(alignment: .leading) {
16 | Text("---")
17 | .font(.headline)
18 |
19 | Text("-------")
20 | .font(.subheadline)
21 | .foregroundColor(.white)
22 | }
23 |
24 | Spacer()
25 |
26 | Image(systemName: "chevron.right") // Replace with your icon/image
27 | .foregroundColor(.gray)
28 | }
29 | .padding()
30 | .background(Color.gray)
31 | .cornerRadius(10)
32 | .shadow(color: Color.gray.opacity(0.4), radius: 4, x: 0, y: 2)
33 | .padding(.horizontal)
34 | }
35 | }
36 |
37 | struct MockListTile_Previews: PreviewProvider {
38 | static var previews: some View {
39 | MockListTile()
40 | .previewLayout(.sizeThatFits)
41 | .padding()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/RivePullToRefreshView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomRefreshView.swift
3 | // Rive Pull to Refresh
4 | //
5 | // Created by Peter G Hayes on 22/05/2023.
6 | //
7 |
8 | import SwiftUI
9 | import RiveRuntime
10 |
11 | struct RivePullToRefreshView: View {
12 | var content: Content
13 | var showsIndicator: Bool
14 | var onRefresh: ()async->()
15 | var height: Double
16 |
17 |
18 | init(showsIndicator: Bool = false, height: Double = 200, @ViewBuilder content: @escaping ()-> Content, onRefresh: @escaping ()async->()) {
19 | self.showsIndicator = showsIndicator
20 | self.content = content()
21 | self.onRefresh = onRefresh
22 | self.height = height
23 | _scrollDelegate = StateObject(wrappedValue: ScrollViewModel(height: height))
24 | }
25 |
26 | @StateObject var riveViewModel = RiveViewModel(fileName: "pull_to_refresh_use_case", fit: .cover, autoPlay: false)
27 | @StateObject var scrollDelegate: ScrollViewModel
28 |
29 |
30 | var body: some View {
31 | ScrollView(.vertical, showsIndicators: showsIndicator) {
32 | VStack {
33 | if (scrollDelegate.progress > 0) {
34 | riveViewModel.view()
35 | .frame(height: height * scrollDelegate.progress)
36 | .offset(y: scrollDelegate.isEligible ? -(scrollDelegate.contentOffset < 0 ? 0 :scrollDelegate.contentOffset) : -(scrollDelegate.scrollOffset < 0 ? 0 : scrollDelegate.scrollOffset))
37 | }
38 |
39 | content
40 |
41 | }
42 | .offset(coordinateSpace: "SCROLL") { offset in
43 | // MARK: Storing content offset
44 | scrollDelegate.contentOffset = offset
45 |
46 | // MARK: Stopping the progress when it's eligible for refresh
47 | if !scrollDelegate.isEligible {
48 |
49 | var progress = offset / height
50 | progress = (progress < 0 ? 0 : progress)
51 | progress = (progress > 1 ? 1 : progress)
52 | scrollDelegate.scrollOffset = offset
53 | scrollDelegate.progress = progress
54 | }
55 | let inputValue = offset / height * 100
56 | riveViewModel.setInput("pull", value: inputValue)
57 |
58 | /// Drag is released and pull to refresh is not eligible - reset Rive view model when hidden
59 | if (scrollDelegate.progress == 0 && scrollDelegate.isEligible == false) {
60 | riveViewModel.reset()
61 | }
62 |
63 | if scrollDelegate.isEligible && !scrollDelegate.isRefreshing {
64 | scrollDelegate.isRefreshing = true
65 | // MARK: Haptic Feedback
66 |
67 | UIImpactFeedbackGenerator(style: .medium).impactOccurred()
68 | }
69 | }
70 | }
71 | .coordinateSpace(name: "SCROLL")
72 | .onAppear(perform: scrollDelegate.addGesture)
73 | .onDisappear(perform: scrollDelegate.removeGesture)
74 | .onChange(of: scrollDelegate.isRefreshing) { refreshing in
75 | // MARK: Calling Async method
76 | if refreshing{
77 | Task{
78 | riveViewModel.triggerInput("advance")
79 | let backTime = 0.25
80 | await onRefresh()
81 | // Let the animation finish
82 | riveViewModel.triggerInput("advance")
83 | try? await Task.sleep(for: .seconds(2))
84 | // MARK: After refresh done restting properties
85 | withAnimation(.easeInOut(duration: backTime)){
86 | scrollDelegate.progress = 0
87 | scrollDelegate.isEligible = false
88 | scrollDelegate.isRefreshing = false
89 | scrollDelegate.scrollOffset = 0
90 | }
91 |
92 | try? await Task.sleep(for: .seconds(backTime))
93 | riveViewModel.reset()
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 |
101 | // MARK: For simultaneous pan gesture
102 | class ScrollViewModel: NSObject, ObservableObject, UIGestureRecognizerDelegate {
103 | var height: Double
104 |
105 | // MARK: Properties
106 | @Published var isEligible: Bool = false
107 | @Published var isRefreshing: Bool = false
108 | // MARK: Offsets and Progress
109 | @Published var scrollOffset: CGFloat = 0
110 | @Published var contentOffset: CGFloat = 0
111 | @Published var progress: CGFloat = 0
112 |
113 | init(height: Double) {
114 | self.height = height
115 | }
116 |
117 | // MARK: Since we need to know when the user left the screen to start refresh
118 | // Adding pan gesture to UI Main Appliction Window
119 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
120 | return true
121 | }
122 |
123 | // MARK: Adding Gesture
124 | func addGesture() {
125 | let panGesture = UIPanGestureRecognizer(target: self, action:
126 | #selector(onGestureChange(gesture:)))
127 | panGesture.delegate = self
128 |
129 | rootController().view.addGestureRecognizer(panGesture)
130 | }
131 |
132 | // MARK: Removing when leaving the view
133 | func removeGesture() {
134 | rootController().view.gestureRecognizers?.removeAll()
135 | }
136 |
137 | // MARK: Finding root controller
138 | func rootController() -> UIViewController{
139 | guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene
140 | else {
141 | return .init()
142 | }
143 |
144 | guard let root = screen.windows.first?.rootViewController else {
145 | return .init()
146 | }
147 |
148 | return root;
149 | }
150 |
151 | @objc
152 | func onGestureChange(gesture: UIPanGestureRecognizer) {
153 | if gesture.state == .cancelled || gesture.state == .ended {
154 | // User let go of drag
155 | if !isRefreshing{
156 | if scrollOffset > height {
157 | isEligible = true
158 | } else {
159 | isEligible = false
160 | }
161 | }
162 | }
163 | }
164 | }
165 |
166 |
167 | // MARK: Offset Modifier
168 | extension View {
169 | @ViewBuilder
170 | func offset(coordinateSpace: String, offset: @escaping (CGFloat)->()) ->some View {
171 | self
172 | .overlay {
173 | GeometryReader{geometry in
174 | let minY = geometry.frame(in: .named(coordinateSpace)).minY
175 | Color.clear
176 | .preference(key: OffsetKey.self, value: minY)
177 | .onPreferenceChange(OffsetKey.self) { value in
178 | offset(value)
179 | }
180 | }
181 | }
182 | }
183 | }
184 |
185 | // MARK: Offset Preference Key
186 | struct OffsetKey: PreferenceKey{
187 | static var defaultValue: CGFloat = 0
188 |
189 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
190 | value = nextValue()
191 | }
192 | }
193 |
194 |
195 | struct CustomRefreshView_Previews: PreviewProvider {
196 | static var previews: some View {
197 | RivePullToRefreshView() {
198 | MockListTile()
199 | MockListTile()
200 | MockListTile()
201 | MockListTile()
202 | } onRefresh: {
203 | try? await Task.sleep(nanoseconds: 3_000_000_000)
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/Rive_Pull_to_RefreshApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Rive_Pull_to_RefreshApp.swift
3 | // Rive Pull to Refresh
4 | //
5 | // Created by Peter G Hayes on 22/05/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct Rive_Pull_to_RefreshApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/pull-to-refresh/ios/Rive Pull to Refresh/animations/pull_to_refresh_use_case.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/pull-to-refresh/ios/Rive Pull to Refresh/animations/pull_to_refresh_use_case.riv
--------------------------------------------------------------------------------
/rive-google-ads/README.md:
--------------------------------------------------------------------------------
1 | # Rive: Google Ads App Campaign Integration
2 |
3 | This folder contains examples and resources for integrating Rive graphics into Google Ads app campaigns.
4 |
5 | ## Examples
6 |
7 | ### Parcel Bundler Example
8 |
9 | **Location**: `rive-google-ads/examples/rive-google-ads-parcel/`
10 |
11 | A complete example demonstrating how to use [Parcel](https://parceljs.org/) to bundle Rive into a Google Ads-compatible format.
12 |
13 | **Features:**
14 |
15 | - Proper ad sizing with responsive layout
16 | - Runtime: Using the \*single variant of Rive's web runtime which bundles the Rive WASM library into a single JS package
17 | - Random graphic selection between multiple Rive files
18 | - Optimized bundle size for ad delivery
19 | - Google Ads meta tags and structure
20 |
21 | **Key Components:**
22 |
23 | - `src/index.html` - Ad container with proper meta tags
24 | - `src/index.ts` - Rive logic and setup
25 | - `assets/` - Sample Rive files
26 | - `dist/` - Built ad files ready for Google Ads
27 |
28 | **Quick Start:**
29 |
30 | ```bash
31 | cd rive-google-ads/examples/rive-google-ads-parcel
32 | npm install
33 | npm run build
34 | ```
35 |
36 | The built files in `dist/` can be zipped up and uploaded to Google as an HTML5 ad.
37 |
38 | **Testing Locally:**
39 |
40 | ```bash
41 | cd rive-google-ads/examples/rive-google-ads-parcel
42 | npm install
43 | npm run start
44 | ```
45 |
46 | #### Important Notes
47 |
48 | ⚠️ **No Source Maps**: To reduce the size of the `dist` folder we pass `--no-source-maps` to the build process.
49 |
50 | ⚠️ **Root relative assets**: Assets (`.js/.ts` and `.riv` files) need to be root relative to be discoverable. We pass `--public-url ./` to the build process to ensure this.
51 |
52 | ⚠️ **Performance and Feature Limitations:** Google App Ads are displayed inside a WebView in Android or iOS apps. WebViews aren't as powerful as modern browsers, and Google may limit permissions that are normally available elsewhere. To ensure compatibility:
53 |
54 | - Follow Rive's [best practices guidelines](https://rive.app/docs/getting-started/best-practices)
55 | - Avoid excessive blend modes
56 | - Avoid clipping masks that clip the artboard
57 | - Test your ad on a real device, preferably using a WebView
58 | - Keep your total ad size (including .wasm, .js, .riv, and .html) under 1 MB
59 | - Google does not allow you to reference external assets or code. Everything should be included in the uploaded zip file to Google, and a root relative path should be used to reference assets.
60 |
61 | ## Resources
62 |
63 | - [Rive Documentation](https://rive.app/docs/runtimes/getting-started) - Complete runtime guides
64 | - [Rive Best Practices](https://rive.app/docs/getting-started/best-practices) - Best practices to optimize Rive graphics
65 | - [Rive Use Cases](https://rive.app/use-cases) - More examples and inspiration
66 | - [Rive Community](https://community.rive.app/) - Get help and share projects
67 |
68 | ## Contributing
69 |
70 | Found an issue or have a suggestion? Please [open an issue](https://github.com/rive-app/rive-use-cases/issues) or submit a pull request to help improve these examples.
71 |
72 | ---
73 |
74 | For more Rive examples and use cases, visit the [main repository](https://github.com/rive-app/rive-use-cases/).
75 |
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/.gitignore:
--------------------------------------------------------------------------------
1 | .parcel-cache/
2 | dist/
3 | dist.zip
4 | node_modules/
5 | package-lock.json
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/assets/bike_op.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/rive-google-ads/examples/rive-google-ads-parcel/assets/bike_op.riv
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/assets/disney.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/rive-google-ads/examples/rive-google-ads-parcel/assets/disney.riv
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/assets/rive.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/rive-google-ads/examples/rive-google-ads-parcel/assets/rive.wasm
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/assets/sniffr-googlead-v3.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/rive-google-ads/examples/rive-google-ads-parcel/assets/sniffr-googlead-v3.riv
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/assets/wc__op.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/rive-google-ads/examples/rive-google-ads-parcel/assets/wc__op.riv
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/assets/wc_op.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/rive-google-ads/examples/rive-google-ads-parcel/assets/wc_op.riv
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/assets/world_creator.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rive-app/rive-use-cases/29360a87ea1634ec9f7e87a2974a1802d8084d83/rive-google-ads/examples/rive-google-ads-parcel/assets/world_creator.riv
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rive-google-ads-parcel",
3 | "private": true,
4 | "version": "0.1.0",
5 | "source": "src/index.html",
6 | "scripts": {
7 | "start": "rm -rf dist .parcel-cache && parcel",
8 | "build": "rm -rf dist .parcel-cache && parcel build --no-source-maps --public-url ./"
9 | },
10 | "dependencies": {
11 | "@rive-app/webgl2": "^2.30.1"
12 | },
13 | "devDependencies": {
14 | "parcel": "^2.14.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/src/basicRiveAd.ts:
--------------------------------------------------------------------------------
1 | import { Rive, Fit, Layout, RuntimeLoader } from '@rive-app/webgl2'
2 |
3 | // Add your .riv file to the assets folder and update the name below
4 | const AdFile = new URL('/assets/wc_op.riv', import.meta.url)
5 | // const AdFile = new URL('/assets/sniffr-googlead-v3.riv', import.meta.url)
6 | // This is the name of the state machine in the Rive file that we want to control
7 | const stateMachineName = 'State Machine 1'
8 | // This is how the Rive artboard should fit in the canvas (Fit.Layout is recommended for ads)
9 | const fitType = Fit.Layout
10 | // If you're using Fit.Layout, you can set a scale factor to adjust the size of the Rive artboard in the canvas
11 | const layoutScaleFactor = 2
12 |
13 | // -----------------------------------------------------
14 | // You shouldn't need to change anything below this line
15 | // -----------------------------------------------------
16 |
17 | // Google Ads doesn't allow external resources, so we need to use the Rive WASM file bundled with the package
18 | const riveWASMResource = `${new URL('/assets/rive.wasm', import.meta.url)}`
19 | RuntimeLoader.setWasmUrl(riveWASMResource)
20 |
21 | // HTML Canvas element to render to
22 | const riveCanvas = document.getElementById('riveCanvas') as HTMLCanvasElement
23 |
24 | // When the window is resized, resize the Rive canvas
25 | function resizeRiveCanvas(riveInstance: Rive) {
26 | riveInstance.resizeDrawingSurfaceToCanvas()
27 |
28 | window.addEventListener(
29 | 'resize',
30 | () => {
31 | riveInstance.resizeDrawingSurfaceToCanvas()
32 | },
33 | false
34 | )
35 | }
36 |
37 | async function setup() {
38 | // Load the Rive file as an ArrayBuffer
39 | const bytes = await (await fetch(new Request(AdFile))).arrayBuffer()
40 |
41 | // Create a new Rive instance
42 | const riveInstance = new Rive({
43 | buffer: bytes,
44 | autoplay: true,
45 | autoBind: true,
46 | canvas: riveCanvas,
47 | layout: new Layout({
48 | fit: fitType,
49 | layoutScaleFactor: layoutScaleFactor,
50 | }),
51 | stateMachines: [stateMachineName],
52 | onLoad: () => {
53 | // Prevent a blurry canvas by using the device pixel ratio
54 | resizeRiveCanvas(riveInstance)
55 | },
56 | })
57 | }
58 |
59 | setup()
60 |
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Rive Canvas Ad Single
8 |
28 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Rive, Fit, Layout, Alignment } from "@rive-app/canvas-single";
2 |
3 | const WorldCreatorFile = new URL("/assets/world_creator.riv", import.meta.url);
4 | const DisneyFile = new URL("/assets/disney.riv", import.meta.url);
5 |
6 | // ---------------------------------
7 | // The layout the graphic will adhere to
8 | const layout = new Layout({
9 | fit: Fit.Layout, // Change to: rive.Fit.Contain, or Cover
10 | // alignment: Alignment.Center,
11 | // layoutScaleFactor: 2,
12 | });
13 |
14 | // ---------------------------------
15 | // Resize on widow
16 | function resizeRiveCanvas(riveInstance: Rive) {
17 | window.addEventListener(
18 | "resize",
19 | () => {
20 | riveInstance.resizeDrawingSurfaceToCanvas();
21 | },
22 | false
23 | );
24 | }
25 |
26 | // ---------------------------------
27 | // Randomly select a Rive file
28 | function getRandomRiveFile(): URL {
29 | const files = [WorldCreatorFile, DisneyFile];
30 | const randomIndex = Math.floor(Math.random() * files.length);
31 | return files[randomIndex];
32 | }
33 |
34 | // ---------------------------------
35 | // HTML Canvas element to render to
36 | const riveCanvas = document.getElementById("riveCanvas") as HTMLCanvasElement;
37 |
38 | async function loadRiveFile() {
39 | const RiveFileToLoad = getRandomRiveFile();
40 | return await (await fetch(new Request(RiveFileToLoad))).arrayBuffer();
41 | }
42 |
43 | async function setup() {
44 | const bytes = await loadRiveFile();
45 |
46 | const riveInstance = new Rive({
47 | buffer: bytes,
48 | autoplay: true,
49 | autoBind: true,
50 | canvas: riveCanvas,
51 | layout: layout,
52 | stateMachines: ["State Machine 1"],
53 | onLoad: () => {
54 | resizeRiveCanvas(riveInstance);
55 | // Prevent a blurry canvas by using the device pixel ratio
56 | riveInstance.resizeDrawingSurfaceToCanvas();
57 | },
58 | });
59 | }
60 |
61 | setup();
62 |
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/src/riveAdWithDeviceOrientation.ts:
--------------------------------------------------------------------------------
1 | // IMPORTANT: Device Orientation is NOT supported for iOS ads!
2 | // It may work on certain Android devices, but it is not guaranteed so include fallback functionality.
3 |
4 | import { Rive, Fit, Layout, RuntimeLoader } from '@rive-app/webgl2'
5 | import { isDeviceOrientationSupported } from './utils/isDeviceOrientationSupported'
6 |
7 | // Add your .riv file to the assets folder and update the name below
8 | const AdFile = new URL('/assets/bike_op.riv', import.meta.url)
9 | // This is the name of the state machine in the Rive file that we want to control
10 | const stateMachineName = 'State Machine 1'
11 | // This is how the Rive artboard should fit in the canvas (Fit.Layout is recommended for ads)
12 | const fitType = Fit.Layout
13 | // If you're using Fit.Layout, you can set a scale factor to adjust the size of the Rive artboard in the canvas
14 | const layoutScaleFactor = 2
15 |
16 | // Google Ads doesn't allow external resources, so we need to use the Rive WASM file bundled with the package
17 | const riveWASMResource = `${new URL('/assets/rive.wasm', import.meta.url)}`
18 | RuntimeLoader.setWasmUrl(riveWASMResource)
19 |
20 | // HTML Canvas element to render to
21 | const riveCanvas = document.getElementById('riveCanvas') as HTMLCanvasElement
22 |
23 | // Resize on widow
24 | function resizeRiveCanvas(riveInstance: Rive) {
25 | riveInstance.resizeDrawingSurfaceToCanvas()
26 |
27 | window.addEventListener(
28 | 'resize',
29 | () => {
30 | riveInstance.resizeDrawingSurfaceToCanvas()
31 | },
32 | false
33 | )
34 | }
35 |
36 | async function setup() {
37 | // Load the Rive file as an ArrayBuffer
38 | const bytes = await (await fetch(new Request(AdFile))).arrayBuffer()
39 |
40 | // Check if device orientation is supported (Android only)
41 | const deviceOrientationSupported = await isDeviceOrientationSupported()
42 |
43 | const riveInstance = new Rive({
44 | buffer: bytes,
45 | autoplay: true,
46 | autoBind: true,
47 | canvas: riveCanvas,
48 | layout: new Layout({
49 | fit: fitType,
50 | layoutScaleFactor: layoutScaleFactor,
51 | }),
52 | stateMachines: [stateMachineName],
53 | onLoad: () => {
54 | // Prevent a blurry canvas by using the device pixel ratio
55 | resizeRiveCanvas(riveInstance)
56 |
57 | // Get the ViewModel instance from the Rive instance
58 | const vmi = riveInstance.viewModelInstance
59 | // Get the nested ViewModel called 'property of Bike'
60 | const bikeVmi = vmi?.viewModel('property of Bike')
61 | // Get the rotation property from the Bike ViewModel
62 | // This will be used to control the rotation of the bike based on device orientation
63 | const rotation = bikeVmi?.number('Rotation')
64 | // Set the hoverEnabled property to false if using gyroscope
65 | const hoverEnabled = bikeVmi?.boolean('hoverEnabled')
66 |
67 | if (!hoverEnabled || !rotation) {
68 | console.error('Failed to get ViewModel properties')
69 | return
70 | }
71 |
72 | hoverEnabled.value = !deviceOrientationSupported
73 |
74 | function handleOrientation(event) {
75 | const gamma = event.gamma // Get the phone's left/right tilt, -90 to +90
76 | // @ts-ignore
77 | rotation.value = gamma
78 | }
79 |
80 | if (deviceOrientationSupported) {
81 | // If device orientation is supported, listen for device orientation events
82 | window.addEventListener('deviceorientation', handleOrientation)
83 | }
84 | },
85 | })
86 | }
87 |
88 | setup()
89 |
--------------------------------------------------------------------------------
/rive-google-ads/examples/rive-google-ads-parcel/src/utils/isDeviceOrientationSupported.ts:
--------------------------------------------------------------------------------
1 | async function isDeviceOrientationSupported() {
2 | if (
3 | typeof DeviceOrientationEvent !== 'undefined' &&
4 | typeof (DeviceOrientationEvent as any).requestPermission === 'function'
5 | ) {
6 | // For Safari on iOS or WebView on iOS
7 | try {
8 | const response = await (DeviceOrientationEvent as any).requestPermission()
9 | if (response === 'granted') {
10 | console.log('Device Orientation permission granted')
11 | return true
12 | } else {
13 | console.log('Device Orientation permission denied - iOS')
14 | return false
15 | }
16 | } catch (err) {
17 | console.error(err)
18 | }
19 | } else {
20 | // Android or desktop (no prompt needed)
21 | console.log(
22 | 'Device Orientation permission granted by default - Android or desktop'
23 | )
24 | return true
25 | }
26 | }
27 |
28 | export { isDeviceOrientationSupported }
29 |
--------------------------------------------------------------------------------